%23%20%2F%2F%2F%20script%0A%23%20requires-python%20%3D%20%22%3E%3D3.10%2C%3C3.15%22%0A%23%0A%23%20%5Btool.marimo-studio%5D%0A%23%20default%20%3D%20%22animation%22%0A%23%20runtimes%20%3D%20%5B%22server%22%5D%0A%23%20%2F%2F%2F%0A%0Aimport%20marimo%0A%0A__generated_with%20%3D%20%220.25.1%22%0Aapp%20%3D%20marimo.App(width%3D%22medium%22%2C%20app_title%3D%22Fourier%20drawing%22)%0A%0A%0A%40app.cell%0Adef%20_()%3A%0A%20%20%20%20import%20marimo%20as%20mo%0A%20%20%20%20import%20matplotlib.pyplot%20as%20plt%0A%20%20%20%20import%20numpy%20as%20np%0A%20%20%20%20from%20matplotlib.font_manager%20import%20FontProperties%0A%20%20%20%20from%20matplotlib.textpath%20import%20TextPath%0A%0A%20%20%20%20return%20FontProperties%2C%20TextPath%2C%20mo%2C%20np%2C%20plt%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20introduction(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%20Fourier%20drawing%0A%0A%20%20%20%20Any%20closed%20path%20is%20a%20sum%20of%20rotating%20vectors.%20Sample%20the%20path%20as%20complex%0A%20%20%20%20numbers%20%24z_j%24%2C%20and%20its%20Fourier%20coefficients%20give%20each%20vector%20its%20length%0A%20%20%20%20and%20starting%20angle%3A%0A%0A%20%20%20%20%5C%5B%0A%20%20%20%20c_k%20%3D%20%5Cfrac%7B1%7D%7BN%7D%5Csum_%7Bj%3D0%7D%5E%7BN-1%7Dz_j%20e%5E%7B-2%5Cpi%20i%20k%20j%2FN%7D%2C%0A%20%20%20%20%5Cqquad%20z(t)%20%5Capprox%20%5Csum_k%20c_k%20e%5E%7B2%5Cpi%20i%20k%20t%7D.%0A%20%20%20%20%5C%5D%0A%0A%20%20%20%20The%20notebook%20keeps%20the%20frequencies%20%24%5Cpm1%2C%20%5Cpm2%2C%20%5Cdots%24%20up%20to%20the%20slider's%0A%20%20%20%20value%20and%20reconstructs%20the%20path%20from%20them.%20The%20Animation%20view's%0A%20%20%20%20%60scene.py%60%20turns%20the%20vectors%20and%20traces%20their%20tip.%20Inspired%20by%0A%20%20%20%203Blue1Brown's%20*But%20what%20is%20a%20Fourier%20series%3F*%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20controls(mo)%3A%0A%20%20%20%20path%20%3D%20mo.ui.dropdown(%0A%20%20%20%20%20%20%20%20%5B%22manim-marimo%20mark%22%2C%20%22TeX's%20italic%20m%22%5D%2C%20value%3D%22manim-marimo%20mark%22%2C%20label%3D%22Path%22%0A%20%20%20%20)%0A%20%20%20%20pairs%20%3D%20mo.ui.slider(%0A%20%20%20%20%20%20%20%20steps%3D%5B2%2C%204%2C%208%2C%2016%2C%2032%2C%2048%5D%2C%20value%3D48%2C%20label%3D%22Frequency%20pairs%22%2C%20show_value%3DTrue%0A%20%20%20%20)%0A%20%20%20%20mo.hstack(%5Bpath%2C%20pairs%5D%2C%20justify%3D%22start%22%2C%20gap%3D2)%0A%20%20%20%20return%20pairs%2C%20path%0A%0A%0A%40app.cell%0Adef%20mark(np)%3A%0A%20%20%20%20%23%20manim-marimo's%20mark%20is%20marimo's%20looped%20circle%2C%20drawn%20by%20five%20rotating%0A%20%20%20%20%23%20vectors%3A%20one%20turns%20four%20times%20while%20four%20short%20ones%20let%20the%20loops%20drift.%0A%20%20%20%20%23%20Each%20row%20is%20a%20frequency%2C%20a%20length%2C%20and%20a%20starting%20angle.%0A%20%20%20%20mark_vectors%20%3D%20%5B%0A%20%20%20%20%20%20%20%20(4%2C%201.0%2C%20np.pi%20%2F%204)%2C%0A%20%20%20%20%20%20%20%20(-4%2C%200.05%2C%201.2)%2C%0A%20%20%20%20%20%20%20%20(1%2C%200.06%2C%202.8)%2C%0A%20%20%20%20%20%20%20%20(2%2C%200.045%2C%200.0)%2C%0A%20%20%20%20%20%20%20%20(3%2C%200.032%2C%201.7)%2C%0A%20%20%20%20%5D%0A%20%20%20%20return%20(mark_vectors%2C)%0A%0A%0A%40app.cell%0Adef%20outline(FontProperties%2C%20TextPath%2C%20mark_vectors%2C%20np%2C%20path)%3A%0A%20%20%20%20sample_count%20%3D%201024%0A%20%20%20%20phase%20%3D%20np.arange(sample_count)%20%2F%20sample_count%0A%20%20%20%20if%20path.value%20%3D%3D%20%22manim-marimo%20mark%22%3A%0A%20%20%20%20%20%20%20%20signal%20%3D%20sum(%0A%20%20%20%20%20%20%20%20%20%20%20%20length%20*%20np.exp(1j%20*%20(2%20*%20np.pi%20*%20frequency%20*%20phase%20%2B%20angle))%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20frequency%2C%20length%2C%20angle%20in%20mark_vectors%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%23%20The%20glyph's%20outline%20is%20one%20closed%20curve.%20Resampling%20it%20at%20equal%20arc%0A%20%20%20%20%20%20%20%20%23%20length%20gives%20every%20stretch%20of%20the%20path%20the%20same%20weight.%0A%20%20%20%20%20%20%20%20glyph%20%3D%20TextPath(%0A%20%20%20%20%20%20%20%20%20%20%20%20(0%2C%200)%2C%20%22%24m%24%22%2C%20size%3D1%2C%20prop%3DFontProperties(math_fontfamily%3D%22cm%22)%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20polygon%20%3D%20glyph.to_polygons(closed_only%3DTrue)%5B0%5D%0A%20%20%20%20%20%20%20%20arc%20%3D%20np.r_%5B0%2C%20np.cumsum(np.hypot(*np.diff(polygon%2C%20axis%3D0).T))%5D%0A%20%20%20%20%20%20%20%20along%20%3D%20phase%20*%20arc%5B-1%5D%0A%20%20%20%20%20%20%20%20signal%20%3D%20np.interp(along%2C%20arc%2C%20polygon%5B%3A%2C%200%5D)%20%2B%201j%20*%20np.interp(%0A%20%20%20%20%20%20%20%20%20%20%20%20along%2C%20arc%2C%20polygon%5B%3A%2C%201%5D%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20signal%20%3D%20(signal%20-%20signal.mean())%20%2F%20np.ptp(signal.real)%0A%20%20%20%20return%20phase%2C%20sample_count%2C%20signal%0A%0A%0A%40app.cell%0Adef%20spectrum(np%2C%20pairs%2C%20sample_count%2C%20signal)%3A%0A%20%20%20%20all_coefficients%20%3D%20np.fft.fft(signal)%20%2F%20sample_count%0A%20%20%20%20frequencies%20%3D%20np.array(%5Bk%20for%20n%20in%20range(1%2C%20pairs.value%20%2B%201)%20for%20k%20in%20(n%2C%20-n)%5D)%0A%20%20%20%20%23%20Chaining%20the%20longest%20vectors%20first%20keeps%20the%20chain's%20tip%20steady.%0A%20%20%20%20frequencies%20%3D%20frequencies%5B%0A%20%20%20%20%20%20%20%20np.argsort(-np.abs(all_coefficients%5Bfrequencies%20%25%20sample_count%5D)%2C%20kind%3D%22stable%22)%0A%20%20%20%20%5D%0A%20%20%20%20coefficients%20%3D%20all_coefficients%5Bfrequencies%20%25%20sample_count%5D%0A%20%20%20%20radii%20%3D%20np.abs(coefficients)%0A%20%20%20%20terms%20%3D%20np.column_stack((frequencies%2C%20coefficients.real%2C%20coefficients.imag))%0A%20%20%20%20return%20coefficients%2C%20frequencies%0A%0A%0A%40app.cell%0Adef%20reconstruction(coefficients%2C%20frequencies%2C%20np%2C%20phase%2C%20signal)%3A%0A%20%20%20%20approximation%20%3D%20(%0A%20%20%20%20%20%20%20%20np.exp(2j%20*%20np.pi%20*%20phase%5B%3A%2C%20None%5D%20*%20frequencies)%20*%20coefficients%0A%20%20%20%20).sum(axis%3D1)%0A%20%20%20%20rms_error%20%3D%20float(np.sqrt(np.mean(np.abs(signal%20-%20approximation)%20**%202)))%0A%20%20%20%20%23%20The%20scene%20traces%20this%20path%2C%20one%20point%20per%20frame%20of%20its%20single%20turn.%0A%20%20%20%20traced%20%3D%20np.column_stack((approximation.real%2C%20approximation.imag))%5B%3A%3A2%5D%0A%20%20%20%20return%20approximation%2C%20rms_error%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20comparison(approximation%2C%20mo%2C%20pairs%2C%20plt%2C%20rms_error%2C%20signal)%3A%0A%20%20%20%20figure%2C%20axes%20%3D%20plt.subplots(figsize%3D(7%2C%203.6))%0A%20%20%20%20axes.plot(%0A%20%20%20%20%20%20%20%20signal.real%2C%20signal.imag%2C%20color%3D%22%239aa1a9%22%2C%20linewidth%3D3%2C%20label%3D%22Sampled%20path%22%0A%20%20%20%20)%0A%20%20%20%20axes.plot(%0A%20%20%20%20%20%20%20%20approximation.real%2C%0A%20%20%20%20%20%20%20%20approximation.imag%2C%0A%20%20%20%20%20%20%20%20color%3D%22%231c758a%22%2C%0A%20%20%20%20%20%20%20%20linewidth%3D1.5%2C%0A%20%20%20%20%20%20%20%20label%3Df%22%7B2%20*%20pairs.value%7D%20rotating%20vectors%22%2C%0A%20%20%20%20)%0A%20%20%20%20axes.set_aspect(%22equal%22)%0A%20%20%20%20axes.axis(%22off%22)%0A%20%20%20%20axes.legend(frameon%3DFalse%2C%20loc%3D%22upper%20right%22%2C%20bbox_to_anchor%3D(1.32%2C%201))%0A%20%20%20%20mo.vstack(%5Bfigure%2C%20mo.md(f%22Root%20mean%20square%20error%3A%20**%7Brms_error%3A.4f%7D**%22)%5D)%0A%20%20%20%20return%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20app.run()%0A
07a57f1729e8e6c28be24a48236eac3c