def test_similarity_transform(): sys = Alpha(0.1) TA, TB, TC, TD = sys.transform(np.eye(2), np.eye(2)).ss A, B, C, D = sys2ss(sys) assert np.allclose(A, TA) assert np.allclose(B, TB) assert np.allclose(C, TC) assert np.allclose(D, TD) T = [[1, 1], [-0.5, 0]] rsys = sys.transform(T) assert ss_equal(rsys, sys.transform(T, inv(T))) TA, TB, TC, TD = rsys.ss assert not np.allclose(A, TA) assert not np.allclose(B, TB) assert not np.allclose(C, TC) assert np.allclose(D, TD) assert sys_equal(sys, (TA, TB, TC, TD)) length = 1000 dt = 0.001 x_old = np.asarray( [sub.impulse(length=length, dt=dt) for sub in sys]) x_new = np.asarray( [sub.impulse(length=length, dt=dt) for sub in rsys]) # dot(T, x_new(t)) = x_old(t) assert np.allclose(np.dot(T, x_new), x_old)
def test_identity(radii): sys = Alpha(0.1) identity = Identity() assert repr(identity) == "Identity()" I = np.eye(len(sys)) realize_result = identity(sys, radii) assert realize_result.sys is sys assert np.allclose(realize_result.T, I * radii) assert np.allclose(realize_result.Tinv, inv(I * radii)) rsys = realize_result.realization assert ss_equal(rsys, sys.transform(realize_result.T)) # Check that it's still the same system, even though different matrices assert sys_equal(sys, rsys) if radii == 1: assert ss_equal(sys, rsys) else: assert not np.allclose(sys.B, rsys.B) assert not np.allclose(sys.C, rsys.C) # Check that the state vectors have scaled power assert np.allclose(state_norm(sys) / radii, state_norm(rsys))
def test_impulse_dt(): length = 1000 sys = Alpha(0.1) # the dt should not alter the magnitude of the response assert np.allclose( max(sys.impulse(length, dt=0.001)), max(sys.impulse(length, dt=0.0005)), atol=1e-4)
def test_mapping(Simulator, plt, seed): sys = Alpha(0.1) syn = Lowpass(0.01) gsyn = 2*syn # scaled lowpass isyn = 2/s # scaled integrator dt = 0.001 ss = ss2sim(sys, syn, None) # normal lowpass, continuous dss = ss2sim(sys, syn, dt) # normal lowpass, discrete gss = ss2sim(sys, gsyn, None) # scaled lowpass, continuous gdss = ss2sim(sys, gsyn, dt) # scaled lowpass, discrete iss = ss2sim(sys, isyn, None) # scaled integrator, continuous idss = ss2sim(sys, isyn, dt) # scaled integrator, discrete assert ss.analog and gss.analog and iss.analog assert not (dss.analog or gdss.analog or idss.analog) with Network(seed=seed) as model: stim = nengo.Node(output=lambda t: np.sin(20*np.pi*t)) probes = [] for mapped, synapse in ((ss, syn), (dss, syn), (gss, gsyn), (gdss, gsyn), (iss, isyn), (idss, isyn)): A, B, C, D = mapped.ss x = nengo.Node(size_in=2) y = nengo.Node(size_in=1) nengo.Connection(stim, x, transform=B, synapse=synapse) nengo.Connection(x, x, transform=A, synapse=synapse) nengo.Connection(x, y, transform=C, synapse=None) nengo.Connection(stim, y, transform=D, synapse=None) probes.append(nengo.Probe(y)) p_stim = nengo.Probe(stim) pss, pdss, pgss, pgdss, piss, pidss = probes with Simulator(model, dt=dt) as sim: sim.run(1.0) expected = shift(sys.filt(sim.data[p_stim], dt)) plt.plot(sim.trange(), sim.data[pss], label="Continuous", alpha=0.5) plt.plot(sim.trange(), sim.data[pdss], label="Discrete", alpha=0.5) plt.plot(sim.trange(), sim.data[pgss], label="Gain Cont.", alpha=0.5) plt.plot(sim.trange(), sim.data[pgdss], label="Gain Disc.", alpha=0.5) plt.plot(sim.trange(), sim.data[piss], label="Integ Cont.", alpha=0.5) plt.plot(sim.trange(), sim.data[pidss], label="Integ Disc.", alpha=0.5) plt.plot(sim.trange(), expected, label="Expected", linestyle='--') plt.legend() assert np.allclose(sim.data[pss], expected, atol=0.01) assert np.allclose(sim.data[pdss], expected) assert np.allclose(sim.data[pgss], expected, atol=0.01) assert np.allclose(sim.data[pgdss], expected) assert np.allclose(sim.data[piss], expected, atol=0.01) assert np.allclose(sim.data[pidss], expected)
def test_modred(rng): dt = 0.001 isys = Lowpass(0.05) noise = 0.5 * Lowpass(0.01) + 0.5 * Alpha(0.005) p = 0.999 sys = p * isys + (1 - p) * noise T, Tinv, S = balanced_transformation(sys) balsys = sys.transform(T, Tinv) # Keeping just the best state should remove the 3 noise dimensions # Discarding the lowest state should do at least as well for keep_states in (S.argmax(), list(set(range(len(sys))) - set((S.argmin(), )))): delsys = modred(balsys, keep_states) assert delsys.order_den == np.asarray(keep_states).size u = rng.normal(size=2000) expected = sys.filt(u, dt) actual = delsys.filt(u, dt) assert rmse(expected, actual) < 1e-4 step = np.zeros(2000) step[50:] = 1.0 dcsys = modred(balsys, keep_states, method='dc') assert np.allclose(dcsys.dcgain, balsys.dcgain) # use of shift related to nengo issue #938 assert not sys.has_passthrough assert dcsys.has_passthrough expected = shift(sys.filt(step, dt)) actual = dcsys.filt(step, dt) assert rmse(expected, actual) < 1e-4
def test_non_siso_manipulation(): sys = Alpha(0.1) A, B, C, D = sys.ss SIMO = LinearSystem((A, B, np.eye(len(A)), [[0], [0]])) assert not SIMO.is_SISO assert SIMO.size_in == 1 assert SIMO.size_out == 2 assert SIMO.shape == (2, 1) assert not SIMO.has_passthrough assert ss_equal(_eval(SIMO), SIMO) assert isinstance(str(SIMO), str) assert ss_equal(canonical(SIMO), SIMO) for sub1, sub2 in zip(sys, SIMO): assert ss_equal(sub1, sub2) MISO = LinearSystem((A, [[1, 1]], C, [[0, 1]])) assert not MISO.is_SISO assert MISO.size_in == 2 assert MISO.size_out == 1 assert MISO.shape == (1, 2) assert MISO.has_passthrough assert ss_equal(_eval(MISO), MISO) assert isinstance(str(MISO), str) MIMO = LinearSystem((A, [[1, 1]], np.eye(len(A)), np.zeros((2, 2)))) assert not MIMO.is_SISO assert MIMO.size_in == MIMO.size_out == 2 assert MIMO.shape == (2, 2) assert not MIMO.has_passthrough assert ss_equal(_eval(MIMO), MIMO) assert isinstance(str(MIMO), str) for sub1, sub2 in zip(MISO, MIMO): assert ss_equal(sub1, sub2)
def test_balreal(): isys = Lowpass(0.05) noise = 0.5 * Lowpass(0.01) + 0.5 * Alpha(0.005) p = 0.8 sys = p * isys + (1 - p) * noise T, Tinv, S = balanced_transformation(sys) assert np.allclose(inv(T), Tinv) assert np.allclose(S, hsvd(sys)) balsys = sys.transform(T, Tinv) assert balsys == sys assert np.all(S >= 0) assert np.all(S[0] > 0.3) assert np.all(S[1:] < 0.05) assert np.allclose(sorted(S, reverse=True), S) P = control_gram(balsys) Q = observe_gram(balsys) diag = np.diag_indices(len(P)) offdiag = np.ones_like(P, dtype=bool) offdiag[diag] = False offdiag = np.where(offdiag) assert np.allclose(P[diag], S) assert np.allclose(P[offdiag], 0) assert np.allclose(Q[diag], S) assert np.allclose(Q[offdiag], 0)
def test_balred(rng): dt = 0.001 sys = Alpha(0.01) + Lowpass(0.001) u = rng.normal(size=2000) expected = sys.filt(u, dt) def check(order, within, tol, method='del'): redsys = balred(sys, order, method=method) assert redsys.order_den <= order actual = redsys.filt(u, dt) assert abs(rmse(expected, actual) - within) < tol with warns(UserWarning): check(4, 0, 1e-13) with warns(UserWarning): check(3, 0, 1e-13) check(2, 0.03, 0.01) check(1, 0.3, 0.1)
def test_grams(): sys = 0.6 * Alpha(0.01) + 0.4 * Lowpass(0.05) A, B, C, D = sys2ss(sys) P = control_gram(sys) assert np.allclose(np.dot(A, P) + np.dot(P, A.T), -np.dot(B, B.T)) assert matrix_rank(P) == len(P) # controllable Q = observe_gram(sys) assert np.allclose(np.dot(A.T, Q) + np.dot(Q, A), -np.dot(C.T, C)) assert matrix_rank(Q) == len(Q) # observable
def test_func_realize(radii): sys = Alpha(0.1) T = np.asarray([[1., 2.], [0, -1.]]) for Tinv in (None, inv(T)): realize_result = _realize(sys, radii, T, Tinv) assert realize_result.sys is sys assert np.allclose(inv(realize_result.T), realize_result.Tinv) rsys = realize_result.realization assert ss_equal(rsys, sys.transform(realize_result.T)) # Check that the state vector are related by T length = 1000 dt = 0.001 x_old = np.asarray([sub.impulse(length, dt) for sub in sys]) x_new = np.asarray([sub.impulse(length, dt) for sub in rsys]) r = np.atleast_2d(np.asarray(radii).T).T assert np.allclose(np.dot(T, x_new * r), x_old)
def test_l1_norm_known(): # Check that Lowpass has a norm of exactly 1 l1, rtol = l1_norm(Lowpass(0.1)) assert np.allclose(l1, 1) assert np.allclose(rtol, 0) # Check that passthrough is handled properly assert np.allclose(l1_norm(Lowpass(0.1) + 5)[0], 6) assert np.allclose(l1_norm(Lowpass(0.1) - 5)[0], 6) # Check that Alpha scaled by a has a norm of approximately abs(a) for a in (-2, 3): for desired_rtol in (1e-1, 1e-2, 1e-4, 1e-8): l1, rtol = l1_norm(a * Alpha(0.1), rtol=desired_rtol) assert np.allclose(l1, abs(a), rtol=rtol) assert rtol <= desired_rtol
def test_state_norm(plt): # Choose a filter, timestep, and number of simulation timesteps sys = Alpha(0.1) dt = 0.000001 length = 2000000 assert np.allclose(dt * length, 2.0) # Check that the power of each state equals the H2-norm of each state # The analog case is the same after scaling since dt is approx 0. response = sys.X.impulse(length, dt) actual = norm(response, axis=0) * dt assert np.allclose(actual, state_norm(cont2discrete(sys, dt))) assert np.allclose(actual, state_norm(sys) * np.sqrt(dt)) step = int(0.002 / dt) plt.figure() plt.plot(response[::step, 0], label="$x_0$") plt.plot(response[::step, 1], label="$x_1$") plt.plot(np.dot(response[::step], sys.C.T), label="$y$") plt.legend()
def test_sys_conversions(): sys = Alpha(0.1) tf = sys2tf(sys) ss = sys2ss(sys) zpk = sys2zpk(sys) assert sys_equal(sys2ss(tf), ss) assert sys_equal(sys2ss(ss), ss) # unchanged assert sys_equal(sys2tf(tf), tf) # unchanged assert sys_equal(sys2tf(ss), tf) assert sys_equal(sys2zpk(zpk), zpk) # sanity check assert sys_equal(sys2zpk(tf), zpk) # sanity check assert sys_equal(sys2zpk(ss), zpk) assert sys_equal(sys2tf(zpk), tf) assert sys_equal(sys2ss(zpk), ss) # should also work with nengo's synapse types assert sys_equal(sys2zpk(nengo.Alpha(0.1)), zpk) assert sys_equal(sys2tf(nengo.Alpha(0.1)), tf) assert sys_equal(sys2ss(nengo.Alpha(0.1)), ss) # system can also be just a scalar assert sys_equal(sys2tf(2.0), (1, 0.5)) assert np.allclose(sys2ss(5)[3], 5) assert sys_equal(sys2zpk(5), 5) with pytest.raises(ValueError): sys2ss(np.zeros(5)) with pytest.raises(ValueError): sys2zpk(np.zeros(5)) with pytest.raises(ValueError): sys2tf(np.zeros(5)) with pytest.raises(ValueError): # _ss2tf(...): passthrough must be single element sys2tf(([], [], [], [1, 2]))
sub2 = LinearSystem((sys.A, B, I[i:i+1], np.zeros((1, 3)))) _transclose(sub1.filt(u), sub2.filt(u), y[:, i]) def test_bad_filt(): sys = PadeDelay(0.1, order=4).X with pytest.raises(ValueError): sys.filt(np.ones((4, 4))) with pytest.raises(ValueError): sys.filt(np.ones((4, 1)), filtfilt=True) with pytest.raises(ValueError): sys.filt(np.ones((4,)), copy=False) @pytest.mark.parametrize("sys", [ Lowpass(0.01), Alpha(0.2), LinearSystem(([1, 1], [0.01, 1]))]) def test_simulation(sys, Simulator, plt, seed): assert isinstance(sys, LinearSystem) old_sys = nengo.LinearFilter(sys.num, sys.den) assert sys == old_sys with Network() as model: stim = nengo.Node(output=nengo.processes.WhiteSignal( 1.0, high=10, seed=seed)) out_new = nengo.Node(size_in=2) out_old = nengo.Node(size_in=2) nengo.Connection(stim, out_new, transform=[[1], [-1]], synapse=sys) nengo.Connection(stim, out_old, transform=[[1], [-1]], synapse=old_sys) p_new = nengo.Probe(out_new) p_old = nengo.Probe(out_old)
def test_given_dt(): process = WhiteSignal(1.0, high=10) assert EvalPoints(Alpha(0.1), process, dt=0.1).dt == 0.1
def test_invalid_process(): with pytest.raises(ValidationError): assert EvalPoints(Alpha(0.1), process=1)
def test_alpha_whitesignal(Simulator, seed, rng, plt): # Pick test LinearSystem sys = Alpha(0.1) state = sys.X assert state.shape == (2, 1) # Pick test process n_steps = 1000 dt = 0.01 process = WhiteSignal(5.0, high=10, default_dt=dt, seed=seed) # Sample evaluation points dist = EvalPoints(state, process, n_steps=n_steps) assert dist.n_steps == n_steps assert dist.dt == dt # taken from process assert isinstance(repr(dist), str) n_eval_points = 500 eval_points = dist.sample(n_eval_points, 2, rng=rng) assert eval_points.shape == (n_eval_points, 2) # Sample encoders encoders = Encoders(state, process).sample(n_eval_points, 2, rng=rng) assert encoders.shape == (n_eval_points, 2) plt.figure() # plt.scatter(*encoders.T, label="Encoders") plt.scatter(*eval_points.T, s=2, marker='*', label="Eval Points") # Check that most evaluation points fall within radii x_m, zero, x_p = sorted(np.unique(encoders[:, 0])) assert np.allclose(-x_m, x_p) assert np.allclose(zero, 0) sl = (1 / x_m < eval_points[:, 0]) & (eval_points[:, 0] < 1 / x_p) assert np.count_nonzero(sl) / float(n_eval_points) >= 0.99 y_m, zero, y_p = sorted(np.unique(encoders[:, 1])) assert np.allclose(zero, 0) assert np.allclose(-y_m, y_p) sl = (1 / y_m < eval_points[:, 1]) & (eval_points[:, 1] < 1 / y_p) assert np.count_nonzero(sl) / float(n_eval_points) >= 0.99 # Simulate same process / system in nengo network with Network() as model: output = nengo.Node(output=process) probes = [nengo.Probe(output, synapse=sub) for sub in sys] with Simulator(model, dt=dt) as sim: sim.run(n_steps * dt) plt.scatter(sim.data[probes[0]], sim.data[probes[1]], s=1, alpha=0.5, label="Simulated") plt.legend() # Check that each eval_point is a subset / sample from the ideal ideal = np.asarray( [sim.data[probes[0]].squeeze(), sim.data[probes[1]].squeeze()]).T assert ideal.shape == (n_steps, 2) for pt in eval_points: dists = np.linalg.norm(ideal - pt[None, :], axis=1) assert dists.shape == (n_steps, ) assert np.allclose(np.min(dists), 0)
diag = np.diag_indices(len(P)) offdiag = np.ones_like(P, dtype=bool) offdiag[diag] = False offdiag = np.where(offdiag) assert np.allclose(P[diag], S) assert np.allclose(P[offdiag], 0) assert np.allclose(Q[diag], S) assert np.allclose(Q[offdiag], 0) @pytest.mark.parametrize( "sys", [PadeDelay(0.1, 4), PadeDelay(0.2, 5, 5), Alpha(0.2)]) def test_hankel(sys): assert np.allclose(hsvd(sys), balanced_transformation(sys)[2]) def test_l1_norm_known(): # Check that Lowpass has a norm of exactly 1 l1, rtol = l1_norm(Lowpass(0.1)) assert np.allclose(l1, 1) assert np.allclose(rtol, 0) # Check that passthrough is handled properly assert np.allclose(l1_norm(Lowpass(0.1) + 5)[0], 6) assert np.allclose(l1_norm(Lowpass(0.1) - 5)[0], 6) # Check that Alpha scaled by a has a norm of approximately abs(a)
def test_sys_multiplication(): # Check that alpha is just two lowpass multiplied together assert Lowpass(0.1) * Lowpass(0.1) == Alpha(0.1)
sim.run(T) # lower bound includes both approximation error and the gap between # random {-1, 1} flip-flop inputs and the true worst-case input worst_x = np.max(abs(sim.data[p]), axis=0) assert (lower <= worst_x + eps).all() assert (worst_x <= 1 + eps).all() def test_l1_repr(): assert (repr(L1Norm(rtol=.1, max_length=10)) == "L1Norm(rtol=0.1, max_length=10)") @pytest.mark.parametrize("sys,lower", [(Lowpass(0.005), 1.0), (Alpha(0.01), 0.3), (Bandpass(50, 5), 0.05), (Highpass(0.01, 4), 0.1), (PadeDelay(0.1, 2, 2), 0.3)]) def test_hankel_normalization(Simulator, rng, sys, lower): _test_normalization(Simulator, sys, rng, Hankel(), l1_lower=0.5, lower=lower) @pytest.mark.parametrize("radius", [0.5, 5, 10]) @pytest.mark.parametrize("sys", [Lowpass(0.005)]) def test_normalization_radius(Simulator, rng, sys, radius):