%23%20gallery_category%3A%20FDN%20Design%20%26%20Analysis%0A%0Aimport%20marimo%0A%0A__generated_with%20%3D%20%220.23.13%22%0Aapp%20%3D%20marimo.App()%0A%0A%0A%40app.cell%0Adef%20_()%3A%0A%20%20%20%20import%20marimo%20as%20mo%0A%0A%20%20%20%20return%20(mo%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%20Nearest%20Sign-Agnostic%20Orthogonal%20Matrix%0A%0A%20%20%20%20Given%20a%20non-negative%20matrix%20**B**%20(e.g.%2C%20measured%20energy%20flow%20between%20delay%0A%20%20%20%20lines)%2C%20find%20the%20orthogonal%20matrix%20**U**%20that%20minimises%20%60%60%E2%80%96B%20%E2%88%92%20%7CU%7C%E2%80%96_F%60%60%20where%0A%20%20%20%20%60%60%7C%C2%B7%7C%60%60%20is%20element-wise%20absolute%20value.%0A%0A%20%20%20%20The%20challenge%20is%20assigning%20the%20right%20%C2%B11%20sign%20to%20each%20element.%20%20A%20naive%0A%20%20%20%20approach%20(just%20solving%20the%20ordinary%20Procrustes%20problem%20with%20%60nearest_orthogonal%60)%0A%20%20%20%20ignores%20the%20freedom%20in%20signs.%20%20The%20sign-agnostic%20algorithm%20does%3A%0A%0A%20%20%20%201.%20Normalise%20**B**%20to%20doubly%20stochastic%20via%20Sinkhorn-Knopp.%0A%20%20%20%202.%20Initialise%20with%20a%20random%20sign%20pattern.%0A%20%20%20%203.%20Alternate%3A%20(a)%20solve%20nearest-orthogonal%20via%20SVD%2C%20(b)%20update%20signs.%0A%20%20%20%204.%20Repeat%20from%202%20with%20new%20random%20initialisations%3B%20keep%20best.%0A%0A%20%20%20%20Reference%3A%20*Schlecht%20and%20Habets%2C%20%22Sign-Agnostic%20Matrix%20Design%20for%20Spatial%0A%20%20%20%20Artificial%20Reverberation%20with%20Feedback%20Delay%20Networks%2C%22%20AES%20Conf.%20on%20Spatial%0A%20%20%20%20Reproduction%2C%202018.*%0A%0A%20%20%20%20Original%20MATLAB%3A%20Sebastian%20J.%20Schlecht%2C%2029.%20January%202020.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_()%3A%0A%20%20%20%20import%20numpy%20as%20np%0A%20%20%20%20import%20plotly.io%20as%20pio%0A%0A%20%20%20%20import%20pyFDN%0A%0A%20%20%20%20pio.renderers.default%20%3D%20%22sphinx_gallery%22%0A%20%20%20%20return%20np%2C%20pyFDN%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20Setup%0A%0A%20%20%20%20Generate%20a%20random%20orthogonal%20matrix%20**A**%20and%20strip%20its%20signs%20to%20get%20**B%20%3D%20%7CA%7C**.%0A%20%20%20%20The%20goal%20is%20to%20recover%20a%20matrix%20close%20to%20**A**%20from%20**B**%20alone.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(np%2C%20pyFDN)%3A%0A%20%20%20%20np.random.seed(42)%0A%20%20%20%20N%20%3D%208%0A%0A%20%20%20%20%23%20Ground-truth%20orthogonal%20matrix%0A%20%20%20%20A_original%20%3D%20pyFDN.random_orthogonal(N)%0A%20%20%20%20%23%20Target%3A%20absolute%20values%20only%20(signs%20removed)%0A%20%20%20%20B%20%3D%20np.abs(A_original)%0A%20%20%20%20return%20A_original%2C%20B%2C%20N%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20Solve%0A%0A%20%20%20%20Compare%20the%20naive%20**nearest_orthogonal**%20(ignores%20sign%20freedom)%20against%20the%0A%20%20%20%20**sign-agnostic**%20solution.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(B%2C%20N%2C%20np%2C%20pyFDN)%3A%0A%20%20%20%20classic%20%3D%20pyFDN.nearest_orthogonal(B)%0A%20%20%20%20sign_agnostic%20%3D%20pyFDN.nearest_sign_agnostic_orthogonal(B%2C%20max_trials%3D10_000)%0A%0A%20%20%20%20error_classic%20%3D%20np.linalg.norm(np.abs(classic)%20-%20B%2C%20%22fro%22)%20%2F%20N**2%0A%20%20%20%20error_sign_agnostic%20%3D%20np.linalg.norm(np.abs(sign_agnostic)%20-%20B%2C%20%22fro%22)%20%2F%20N**2%0A%0A%20%20%20%20print(f%22Classic%20Procrustes%20error%3A%20%20%20%20%20%20%7Berror_classic%3A.6f%7D%22)%0A%20%20%20%20print(f%22Sign-agnostic%20Procrustes%20error%3A%20%7Berror_sign_agnostic%3A.6f%7D%22)%0A%20%20%20%20print(f%22Improvement%20factor%3A%20%7Berror_classic%20%2F%20error_sign_agnostic%3A.1f%7D%C3%97%22)%0A%0A%20%20%20%20assert%20pyFDN.is_orthogonal(classic%2C%20tol%3D1e-8)%2C%20%22classic%20solution%20not%20orthogonal%22%0A%20%20%20%20assert%20pyFDN.is_orthogonal(sign_agnostic%2C%20tol%3D1e-8)%2C%20%22sign-agnostic%20not%20orthogonal%22%0A%20%20%20%20assert%20error_sign_agnostic%20%3C%3D%20error_classic%20%2B%201e-10%2C%20(%0A%20%20%20%20%20%20%20%20%22sign-agnostic%20should%20not%20be%20worse%22%0A%20%20%20%20)%0A%20%20%20%20return%20classic%2C%20sign_agnostic%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20Visualise%0A%0A%20%20%20%20Plot%20the%20three%20matrices%20side%20by%20side%3A%20original%20**A**%2C%20input%20**%7CA%7C**%2C%20and%20the%20two%20solutions.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(A_original%2C%20B%2C%20classic%2C%20np%2C%20pyFDN%2C%20sign_agnostic)%3A%0A%20%20%20%20def%20align_signs(M%2C%20ref)%3A%0A%20%20%20%20%20%20%20%20M%20%3D%20M%20*%20np.sign(np.sum(M%20*%20ref%2C%20axis%3D1%2C%20keepdims%3DTrue))%20%20%23%20rows%0A%20%20%20%20%20%20%20%20M%20%3D%20M%20*%20np.sign(np.sum(M%20*%20ref%2C%20axis%3D0%2C%20keepdims%3DTrue))%20%20%23%20cols%0A%20%20%20%20%20%20%20%20return%20M%0A%0A%20%20%20%20classic_aligned%20%3D%20align_signs(classic%2C%20A_original)%0A%20%20%20%20sign_agnostic_aligned%20%3D%20align_signs(sign_agnostic%2C%20A_original)%0A%0A%20%20%20%20def%20orth_label(M)%3A%0A%20%20%20%20%20%20%20%20return%20%22orth%20%E2%9C%93%22%20if%20pyFDN.is_orthogonal(M%2C%20tol%3D1e-8)%20else%20%22orth%20%E2%9C%97%22%0A%0A%20%20%20%20matrices%20%3D%20%5B%0A%20%20%20%20%20%20%20%20A_original%2C%0A%20%20%20%20%20%20%20%20B%2C%0A%20%20%20%20%20%20%20%20sign_agnostic_aligned%2C%0A%20%20%20%20%20%20%20%20np.abs(sign_agnostic_aligned)%2C%0A%20%20%20%20%20%20%20%20classic_aligned%2C%0A%20%20%20%20%20%20%20%20np.abs(classic_aligned)%2C%0A%20%20%20%20%5D%0A%20%20%20%20titles%20%3D%20%5B%0A%20%20%20%20%20%20%20%20f%22Original%20A%20(%7Borth_label(A_original)%7D)%22%2C%0A%20%20%20%20%20%20%20%20%22%7CA%7C%20(input)%22%2C%0A%20%20%20%20%20%20%20%20f%22Sign-agnostic%20aligned%3Cbr%3E(%7Borth_label(sign_agnostic_aligned)%7D)%22%2C%0A%20%20%20%20%20%20%20%20%22%7CSign-agnostic%20aligned%7C%22%2C%0A%20%20%20%20%20%20%20%20f%22Nearest%20orthogonal%20aligned%3Cbr%3E(%7Borth_label(classic_aligned)%7D)%22%2C%0A%20%20%20%20%20%20%20%20%22%7CNearest%20orthogonal%20aligned%7C%22%2C%0A%20%20%20%20%5D%0A%0A%20%20%20%20fig%20%3D%20pyFDN.plot_matrix_grid(%0A%20%20%20%20%20%20%20%20matrices%2C%0A%20%20%20%20%20%20%20%20titles%3Dtitles%2C%0A%20%20%20%20%20%20%20%20ncols%3D2%2C%0A%20%20%20%20%20%20%20%20zmin%3D-1%2C%0A%20%20%20%20%20%20%20%20zmax%3D1%2C%0A%20%20%20%20%20%20%20%20show_ticks%3DTrue%2C%0A%20%20%20%20%20%20%20%20title%3D%22Sign-agnostic%20vs.%20classic%20nearest%20orthogonal%22%2C%0A%20%20%20%20%20%20%20%20height%3D1000%2C%0A%20%20%20%20)%0A%20%20%20%20fig.show()%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
0333d3d6446aeb4195eb3b702e84bd1e