def plot_performance_LF(M_lf, M_hf, Su, sh_l, sh_m, title=""): T = sg.az_el() Y_test_dirs = rsh.real_sph_harm_transform(sh_l, sh_m, T.az, T.el) P, rVxyz, _, _ = compute_rVrE_fast(M_lf, Su, Y_test_dirs) rVaz, rVel, rVr, rVu = xyz2aeru(rVxyz) _, _, E, rExyz = compute_rVrE_fast(M_hf, Su, Y_test_dirs) rEaz, rEel, rEr, rEu = xyz2aeru(rExyz) out_figs=[] fig = plot_rX(rVr.reshape(T.shape), title=f"{title}\nMagnitude of rV vs. test direction", clim=(0.7, 1.1), show=False) out_figs.append(fig) plt.show() ev_dot = np.sum(rEu * rVu, axis=0) dir_diff = np.arccos(np.clip(ev_dot, -1, 1)) * 180/np.pi fig = plot_rX(dir_diff.reshape(T.shape), title=f"{title}\nrE vs. rV direction difference (degrees)", clim=(0, 20), show=False) out_figs.append(fig) plt.show() return out_figs
def projection(degree, order, speakers_azimuth, speakers_elevation): """Compute basic decoder matrix by projection method. Parameters ---------- degree: int or array-like collection of ints degree (l) of each spherical harmonic. order: int or array-like collection of ints order (m) of each spherical harmonic. speakers_azimuth, speakers_elevation: array-like collection of floats: speaker azimuths and elevations in radians. Returns ------- numpy.ndarray Basic decoder matrix Note ---- Optimal for platonic solids, more generally spherical designs only. This is here mostly for comparison to other methods. """ if np.isscalar(degree): sh_l, sh_m, *_ = zip(*channel_spec(degree, order)) else: sh_l, sh_m = degree, order M = rsh.real_sph_harm_transform(sh_l, sh_m, np.array(speakers_azimuth).ravel(), np.array(speakers_elevation).ravel()) return M.transpose()
def compute_rVrE(sh_l, sh_m, M, Su, test_dirs=sg.az_el()): """Compute rV and rE, single call interface.""" Y_test_dirs = rsh.real_sph_harm_transform(sh_l, sh_m, test_dirs.az.ravel(), test_dirs.el.ravel()) P, rVxyz, E, rExyz = compute_rVrE_fast(M, Su, Y_test_dirs) rVaz, rVel, rVr, rVu = xyz2aeru(rVxyz) rEaz, rEel, rEr, rEu = xyz2aeru(rExyz) return rVr.reshape(test_dirs.shape), rEr.reshape(test_dirs.shape)
def compute_rVrE_dict(sh_l, sh_m, M, Su, test_dirs=sg.az_el()): """Compute rV and rE, single call interface.""" Y_test_dirs = rsh.real_sph_harm_transform(sh_l, sh_m, test_dirs.az.ravel(), test_dirs.el.ravel()) P, rVxyz, E, rExyz = compute_rVrE_fast(M, Su, Y_test_dirs) P = P.reshape(test_dirs.shape) #rVxyz = rVxyz.reshape(3, *test_dirs.shape) E = E.reshape(test_dirs.shape) #rExyz = rExyz.reshape(3, *test_dirs.shape) rVaz, rVel, rVr, rVu = xyz2aeru(rVxyz) rEaz, rEel, rEr, rEu = xyz2aeru(rExyz) rEaz = rEaz.reshape(test_dirs.shape) rEel = rEel.reshape(test_dirs.shape) return dict(P=P, rVxyz=rVxyz, E=E, rExyz=rExyz, rVaz=rVaz, rVel=rVel, rVr=rVr, rVu=rVu, rEaz=rEaz, rEel=rEel, rEr=rEr, rEu=rEu, M=M, sh_l=sh_l, sh_m=sh_m, test_dirs=test_dirs)
def plot_performance(M, Su, sh_l, sh_m, # /, # / instroduced in 3.8 mask_matrix=None, title="", plot_spkrs=True, test_dirs=None): """Compute and plot basic performance metrics of a decoder matrix.""" # fill in defaults if test_dirs is None: test_dirs = sg.az_el() ambisonic_order = np.max(sh_l) # FIXME: is this correct for mixed orders? # we want to return a list of all the figures to make the HTML report. # accumulate them in out_figs out_figs = [] Y_test_dirs = rsh.real_sph_harm_transform(sh_l, sh_m, test_dirs.az.ravel(), test_dirs.el.ravel()) if mask_matrix is not None: Y_test_dirs = mask_matrix @ Y_test_dirs P, rVxyz, E, rExyz = compute_rVrE_fast(M, Su, Y_test_dirs) rVaz, rVel, rVr, rVu = xyz2aeru(rVxyz) rEaz, rEel, rEr, rEu = xyz2aeru(rExyz) # the arg to arccos can get epsilon larger than 1 due to round off, # which produces NaNs, so clip to [-1, 1] rE_dir_err = np.arccos(np.clip(np.sum(rEu * test_dirs.u, axis=0), -1, 1)) * 180 / π # magnitude of rE if True: fig = plot_rX(rEr.reshape(test_dirs.shape), title=(f'{title}\n' + 'magnitude of rE vs. test direction'), clim=(0.5, 1), show=False) out_figs.append(fig) plt.show() # plot of ambisonic order if True: fig = plot_rX( (shelf.rE_to_ambisonic_order_3d( rEr.reshape(test_dirs.shape)) - ambisonic_order).round(), title=(f'{title}\n' + f'relative ambisonic order ({ambisonic_order}) vs. test direction'), clim=(-3, +3), cmap='Spectral_r', show=False) if plot_spkrs: plot_loudspeakers(Su) out_figs.append(fig) plt.show() # E vs td if True: E_dB = 10 * np.log10(E.reshape(test_dirs.shape)) E_dB_ceil = np.ceil(E_dB.max()) fig = plot_rX(E_dB, title=(f'{title}\n' + 'E (dB) vs. test_direction'), clim=(E_dB_ceil - 20, E_dB_ceil), show=False, ) out_figs.append(fig) plt.show() # direction error if True: fig = plot_rX(rE_dir_err.reshape(test_dirs.shape), title=f'{title}\n' + 'direction error (deg)', clim=(0, 20), show=False) out_figs.append(fig) plt.show() fig = plot_rX(((rE_dir_err.reshape(test_dirs.shape) / 3).round()) * 3, title=f'{title}\n' + 'direction error (deg)', clim=(0, 20), show=False) # overlay loudspeaker positions if plot_spkrs: plot_loudspeakers(Su) out_figs.append(fig) plt.show() if True: fig = plt.figure(figsize=(10,4)) plot_az_el_grid(sh_l, sh_m, M, Su, title=f"{title}\nrE directions", show=False) if plot_spkrs: plot_loudspeakers(Su) out_figs.append(fig) plt.show() return out_figs
def optimize_LF(M, Su, sh_l, sh_m, W=1, raise_error_on_failure=False): M_shape = M.shape g_spkr, g_total = lm.diffuse_field_gain(M) # the test directions T = sg.t_design5200() cap = sg.spherical_cap(T.u, (0, 0, 1), 5 * np.pi / 6)[0] W = np.where(cap, 1, 1) Y_test = rsh.real_sph_harm_transform(sh_l, sh_m, T.az, T.el) rExyz, E = rE(M, Su, Y_test) rEu, rEr = xyz2ur(rExyz) # define the loss function def o(x): M = x.reshape(M_shape) rVxyz, P = rV(M, Su, Y_test) df_gain = np.sum(M * M) df_gain_loss = (g_total - df_gain)**2 # Tikhonov regularization term - typical value = 1e-3 tikhonov_regularization_term = np.sum(M**2) * 1e-2 #tikhonov_lambda # dir loss mag(rVxyz) should be 1 direction_loss = np.sum(W * ((rVxyz - rEu)**2)) P_loss = np.sum(W * ((P - 1)**2)) return (direction_loss + df_gain_loss + P_loss / 100000 + tikhonov_regularization_term) val_and_grad_fn = jax.value_and_grad(o) val_and_grad_fn = jax.jit(val_and_grad_fn) def objective_and_gradient(x): v, g = val_and_grad_fn(x) g = onp.array(g, order='F') return v, g x0 = M.ravel() with Timer() as t: res = opt.minimize( objective_and_gradient, x0, bounds=opt.Bounds(-1, 1), method='L-BFGS-B', jac=True, options=dict(disp=50, # maxls=50, # maxcor=30, # gtol=1e-8, # ftol=1e-12 ), # callback=callback, ) if True: print() print(f"Execution time: {t.interval:0.3f} sec.") print(res.message) print(res) print() if res.status != 0 and raise_error_on_failure: print('bummer:', res.message) raise RuntimeError(res.message) M_opt = res.x.reshape(M_shape) return M_opt, res
def optimize( M, Su, sh_l, sh_m, E_goal=None, iprint=50, tikhonov_lambda=1.0e-3, sparseness_penalty=1, uniform_loudness_penalty=0.1, #0.01 rE_goal=1.0, maxcor=100, # how accurate is the Hessian, more is better but slower raise_error_on_failure=True): """Optimize psychoacoustic criteria.""" # # handle defaults if E_goal is None: E_goal = 1 if rE_goal == 'auto' or rE_goal is None: #FIXME This assumes 3D arrays rE_goal = shelf.max_rE_3d(np.max(sh_l) + 2) print(f"rE_goal min={np.min(rE_goal)} max={np.max(rE_goal)}") # the test directions T = sg.t_design5200() Y_test = rsh.real_sph_harm_transform(sh_l, sh_m, T.az, T.el) if M is None: # infer M_shape from Su and Y M_shape = ( Su.shape[1], # number of loudspeakers Y_test.shape[0], # number of program channels ) M = random.uniform(key, shape=M_shape, minval=-1.0, maxval=1.0) else: M_shape = M.shape # the loss function def o(x) -> float: M = x.reshape(M_shape) rExyz, E = rE(M, Su, Y_test) # truncation loss due to finite order truncation_loss = np.sum((rExyz - T.u * rE_goal)**2) # uniform loudness loss uniform_loudness_loss = (np.sum( (E - E_goal)**2) * uniform_loudness_penalty) # was 10 # Tikhonov regularization term - typical value = 1e-3 tikhonov_regularization_term = np.sum(M**2) * tikhonov_lambda # don't turn off speakers # pull diffuse-field gain for each speaker away from zero sparsness_term = (np.sum(np.abs(1 - np.sum(M**2, axis=1))) * 100 * sparseness_penalty) # the entire loss function f = (truncation_loss + uniform_loudness_loss + tikhonov_regularization_term + sparsness_term) return f # consult the automatic differentiation oracle val_and_grad_fn = jax.value_and_grad(o) val_and_grad_fn = jax.jit(val_and_grad_fn) def objective_and_gradient(x, *args): v, g = val_and_grad_fn(x, *args) # I'm not to happy about having to copy g but L-BGFS needs it in # Fortran order and JAX only does C order. Check with g.flags # NOTE: asarray() and asfortranarray() don't work correctly here g = onp.array(g, order='F') return v, g x0 = M.ravel() # initial guess with Timer() as t: # https://docs.scipy.org/doc/scipy/reference/optimize.minimize-lbfgsb.html res = opt.minimize( objective_and_gradient, x0, bounds=opt.Bounds(-1, 1), method='L-BFGS-B', jac=True, options=dict( maxcor=maxcor, disp=iprint, maxls=500, # maxcor=30, gtol=1e-8, #ftol=1e-12 ), # callback=callback, ) if True: print() print(f"Execution time: {t.interval:0.3f} sec.") print(res.message) print(res) print() if res.status != 0 and raise_error_on_failure: print('bummer:', res.message) raise RuntimeError(res.message) M_opt = res.x.reshape(M_shape) return M_opt, res