def test_cont2discrete_zoh(dt, allclose): """test the function cont2discrete with zero-order hold""" taus = np.logspace(-np.log10(dt) - 1, np.log10(dt) + 3, 30) # test with lowpass filter, using analytic solution for tau in taus: num, den = [1], [tau, 1] d = -np.expm1(-dt / tau) num0, den0 = [0, d], [1, d - 1] num1, den1, _ = cont2discrete((num, den), dt) assert allclose(num0, num1) assert allclose(den0, den1) # test with alpha filter, using analytic solution for tau in taus: num, den = [1], [tau**2, 2 * tau, 1] a = dt / tau ea = np.exp(-a) num0 = [0, -a * ea + (1 - ea), ea * (a + ea - 1)] den0 = [1, -2 * ea, ea**2] num1, den1, _ = cont2discrete((num, den), dt) assert allclose(num0, num1) assert allclose(den0, den1) # test integrative filter, using analytic solution num, den = [1], [1, 0] num0, den0 = [0, dt], [1, -1] num1, den1, _ = cont2discrete((num, den), dt) assert allclose(num0, num1) assert allclose(den0, den1)
def test_cont2discrete_zoh(dt): taus = np.logspace(-np.log10(dt) - 1, np.log10(dt) + 3, 30) # test with lowpass filter, using analytic solution for tau in taus: num, den = [1], [tau, 1] d = -np.expm1(-dt / tau) num0, den0 = [0, d], [1, d - 1] num1, den1, _ = cont2discrete((num, den), dt) assert np.allclose(num0, num1) assert np.allclose(den0, den1) # test with alpha filter, using analytic solution for tau in taus: num, den = [1], [tau**2, 2*tau, 1] a = dt / tau ea = np.exp(-a) num0 = [0, -a*ea + (1 - ea), ea*(a + ea - 1)] den0 = [1, -2 * ea, ea**2] num1, den1, _ = cont2discrete((num, den), dt) assert np.allclose(num0, num1) assert np.allclose(den0, den1) # test integrative filter, using analytic solution num, den = [1], [1, 0] num0, den0 = [0, dt], [1, -1] num1, den1, _ = cont2discrete((num, den), dt) assert np.allclose(num0, num1) assert np.allclose(den0, den1)
def build_filter(model, synapse, owner, input_signal): num, den, _ = cont2discrete( (synapse.num, synapse.den), model.dt, method='zoh') num = num.flatten() num = num[1:] if num[0] == 0 else num den = den[1:] # drop first element (equal to 1) build_discrete_filter(model, synapse, owner, input_signal, num, den)
def make_step(self, shape_in, shape_out, dt, rng, y0=None, dtype=np.float64, method='zoh'): """Returns a `.Step` instance that implements the linear filter.""" assert shape_in == shape_out num, den = self.num, self.den if self.analog: num, den, _ = cont2discrete((num, den), dt, method=method) num = num.flatten() if den[0] != 1.: raise ValidationError("First element of the denominator must be 1", attr='den', obj=self) num = num[1:] if num[0] == 0 else num den = den[1:] # drop first element (equal to 1) num, den = num.astype(dtype), den.astype(dtype) output = np.zeros(shape_out, dtype=dtype) if len(num) == 1 and len(den) == 0: return LinearFilter.NoDen(num, den, output) elif len(num) == 1 and len(den) == 1: return LinearFilter.Simple(num, den, output, y0=y0) return LinearFilter.General(num, den, output, y0=y0)
def test_combined_delay(Simulator, allclose): # ensure that two sequential filters has the same output # as combining their individual discrete transfer functions nt = 50 tau = 0.005 dt = 0.001 sys1 = nengo.Lowpass(tau) (num,), den, _ = cont2discrete((sys1.num, sys1.den), dt=dt) sys2 = nengo.LinearFilter(np.poly1d(num) ** 2, np.poly1d(den) ** 2, analog=False) with nengo.Network() as model: u = nengo.Node(1) x = nengo.Node(size_in=1) nengo.Connection(u, x, synapse=sys1) p1 = nengo.Probe(x, synapse=sys1) p2 = nengo.Probe(u, synapse=sys2) with Simulator(model, dt=dt) as sim: sim.run_steps(nt) assert allclose(sim.data[p1], sim.data[p2]) # Both have two time-step delays: # for sys1, this comes from two levels of filtering # for sys2, this comes from one level of filtering + delay in sys2 assert allclose(sim.data[p1][:2], 0) assert not allclose(sim.data[p1][2], 0, record_rmse=False)
def _get_ss(self, dt): A, B, C, D = tf2ss(self.num, self.den) # discretize (if len(A) == 0, filter is stateless and already discrete) if self.analog and len(A) > 0: A, B, C, D, _ = cont2discrete((A, B, C, D), dt, method=self.method) return A, B, C, D
def __init__(self, vocab, gain=5, deriv_synapse=.1, theta=.05, order=30, dt=.001, **kwargs): super().__init__(**kwargs) self.vocab = vocab # parameters of LMU theta = theta # length of window order = order # number of Legendre polynomials dt = dt # simulation timestep Q = np.arange(order, dtype=np.float64) R = (2 * Q + 1)[:, None] / theta j, i = np.meshgrid(Q, Q) A = np.where(i < j, -1, (-1.0)**(i - j + 1)) * R B = (-1.0)**Q[:, None] * R C = np.ones((1, order)) D = np.zeros((1, )) A, B, C, D, _ = cont2discrete((A, B, C, D), dt=dt, method="zoh") with self: self.input = nengo.Node(size_in=1) self.deriv = nengo.Ensemble(100, 1, intercepts=nengo.dists.Uniform( 0.1, .9), encoders=[[1]] * 100) gain = 5 nengo.Connection(self.input, self.deriv, transform=gain) nengo.Connection(self.input, self.deriv, transform=-gain, synapse=deriv_synapse) self.lmu = nengo.Node(size_in=order) nengo.Connection(self.deriv, self.lmu, transform=B, synapse=None) nengo.Connection(self.lmu, self.lmu, transform=A, synapse=0) self.output = nengo.Node(size_in=2) nengo.Connection(self.deriv, self.output[0], synapse=None) nengo.Connection(self.lmu, self.output[1], transform=C, synapse=None) self.declare_input(self.input, None) self.declare_output(self.output, None)
def test_cont2discrete(): import scipy.signal dt = 1e-3 tau = 0.03 num, den = [1], [tau**2, 2 * tau, 1] num0, den0, _ = scipy.signal.cont2discrete((num, den), dt) num1, den1, _ = cont2discrete((num, den), dt) assert np.allclose(num0, num1) assert np.allclose(den0, den1)
def test_cont2discrete(): import scipy.signal dt = 1e-3 tau = 0.03 num, den = [1], [tau**2, 2*tau, 1] num0, den0, _ = scipy.signal.cont2discrete((num, den), dt) num1, den1, _ = cont2discrete((num, den), dt) assert np.allclose(num0, num1) assert np.allclose(den0, den1)
def pack_data(self, dt, buffer, offset=0): """Pack the struct describing the filter into the buffer.""" # Compute the filter coefficients b, a, _ = cont2discrete((self.num, self.den), dt) b = b.flatten() # Strip out the first values # `a` is negated so that it can be used with a multiply-accumulate # instruction on chip. assert b[0] == 0.0 # Oops! ab = np.vstack((-a[1:], b[1:])).T.flatten() # Convert the values to fixpoint and write into a data buffer struct.pack_into("<I{}s".format(self.order * 2 * 4), buffer, offset, self.order, tp.np_to_fix(ab).tostring())
def pack_data(self, dt, buffer, offset=0): """Pack the struct describing the filter into the buffer.""" # Compute the filter coefficients b, a, _ = cont2discrete((self.num, self.den), dt) b = b.flatten() # Strip out the first values # `a` is negated so that it can be used with a multiply-accumulate # instruction on chip. assert b[0] == 0.0 # Oops! ab = np.vstack((-a[1:], b[1:])).T.flatten() # Convert the values to fixpoint and write into a data buffer struct.pack_into("<I", buffer, offset, self.order) buffer[offset + 4:4+self.order*8] = tp.np_to_fix(ab).tostring()
def make_step(self, dt, output, method='zoh'): num, den = self.num, self.den if self.analog: num, den, _ = cont2discrete((num, den), dt, method=method) num = num.flatten() if den[0] != 1.: raise ValueError("First element of the denominator must be 1") num = num[1:] if num[0] == 0 else num den = den[1:] # drop first element (equal to 1) if len(num) == 1 and len(den) == 0: return LinearFilter.NoDen(num, den, output) elif len(num) == 1 and len(den) == 1: return LinearFilter.Simple(num, den, output) return LinearFilter.General(num, den, output)
def make_step(self, dt, output, method='zoh'): num, den, _ = cont2discrete((self.num, self.den), dt, method=method) num = num.flatten() num = num[1:] if num[0] == 0 else num den = den[1:] # drop first element (equal to 1) if len(num) == 1 and len(den) == 0: return functools.partial( LinearFilter.no_den_step, output=output, b=num[0]) elif len(num) == 1 and len(den) == 1: return functools.partial( LinearFilter.simple_step, output=output, a=den[0], b=num[0]) else: x = collections.deque(maxlen=len(num)) y = collections.deque(maxlen=len(den)) return functools.partial(LinearFilter.general_step, output=output, x=x, y=y, num=num, den=den)
def lti(u, system, state=lambda x: x, dt=0.001, method="zoh"): r"""Operator that solves \dot{x} = A.state(x) + B.u. where A, B = system. The state parameter can be any callable function that consumes a Stimulus operator and produces a Gyrus operator that consumes said operator as an input. For instance, nonlinear dynamical systems may be implemented by specifying a nonlinear function for the ``state``. """ if not is_iterable(system) or len(system) != 2: raise NotImplementedError( f"lti currently only supports systems as two-tuples: (A, B); not {system}" ) # Reshape and validate A, B matrices. A, B = system A = np.asarray(A) if A.ndim != 2 or A.shape[0] != A.shape[1]: raise ValueError(f"A ({A}) must be a square matrix") size_out = A.shape[0] B = np.asarray(B) if B.ndim == 1: B = B[:, None] if B.ndim != 2: raise ValueError(f"B ({B}) must be 1D or 2D, but is {B.ndim}") if B.shape[0] != size_out: raise ValueError( f"B ({B}) must be an array of length {size_out}, not {B.shape[0]}") C = np.zeros_like(B).T D = 0 # Discretize the dynamical system, \dot{x} = Ax + Bu, to # x[t + dt] = Ax[t] + Bu[t], using some method (ZOH recommended for stability). Abar, Bbar, _, _, _ = cont2discrete((A, B, C, D), dt=dt, method=method) # Apply Voelker (2019) equation 5.30 with H(z) = dt / (z - 1). # This compensates for the discretized integrator such that the resulting # system is the one that was requested. In this particular case (with the # synapse being the discretized integrator) this reduces to the inverse # of Euler's method. Amap = (Abar - np.eye(len(Abar))) / dt Bmap = Bbar / dt # Finally express the Amap, Bmap system using vectorized Gyrus operators. return u.transform(Bmap).integrate( integrand=lambda x: state(x).transform(Amap))
def write_spec(self, spec, dt, width): BasicFilterImpl.write_basic_spec(self, spec, width) """Pack the struct describing the filter into the buffer.""" # Compute the filter coefficients b, a, _ = cont2discrete((self.num, self.den), dt) b = b.flatten() # Strip out the first values # `a` is negated so that it can be used with a multiply-accumulate # instruction on chip. assert b[0] == 0.0 # Oops! ab = numpy.vstack((-a[1:], b[1:])).T.flatten() # Convert the values to fixpoint and write into a data buffer print "other {}".format(self._other) spec.write_value(self._order) print "ab {}".format(ab) spec.write_array(helpful_functions.convert_numpy_array_to_s16_15(ab))
def __init__(self, ops, signals, config): super().__init__(ops, signals, config) # the main difference between this and the general linearfilter # OneX implementation is that this version allows us to merge # synapses with different input dimensionality (by duplicating # the synapse parameters for every input, rather than using # broadcasting) self.input_data = signals.combine([op.input for op in ops]) self.output_data = signals.combine([op.output for op in ops]) nums = [] dens = [] for op in ops: if op.process.tau <= 0.03 * signals.dt_val: num = 1 den = 0 else: num, den, _ = cont2discrete((op.process.num, op.process.den), signals.dt_val, method="zoh") num = num.flatten() num = num[1:] if num[0] == 0 else num assert len(num) == 1 num = num[0] assert len(den) == 2 den = den[1] nums += [num] * op.input.shape[0] dens += [den] * op.input.shape[0] if self.input_data.minibatched: # add batch dimension for broadcasting nums = np.expand_dims(nums, 0) dens = np.expand_dims(dens, 0) # apply the negative here dens = -np.asarray(dens) self.nums = tf.constant(nums, dtype=self.output_data.dtype) self.dens = tf.constant(dens, dtype=self.output_data.dtype)
def __init__(self, units, order, theta, **kwargs): super().__init__(**kwargs) self.units = units self.order = order self.theta = theta Q = np.arange(order, dtype=np.float64) R = (2 * Q + 1)[:, None] / theta j, i = np.meshgrid(Q, Q) A = np.where(i < j, -1, (-1.0) ** (i - j + 1)) * R B = (-1.0) ** Q[:, None] * R C = np.ones((1, order)) D = np.zeros((1,)) self._A, self._B, _, _, _ = cont2discrete( (A, B, C, D), dt=1.0, method="zoh" )
def __init__(self, ops, signals, config): super(LowpassBuilder, self).__init__(ops, signals, config) self.input_data = signals.combine([op.input for op in ops]) self.output_data = signals.combine([op.output for op in ops]) nums = [] dens = [] for op in ops: if op.process.tau <= 0.03 * signals.dt_val: num = 1 den = 0 else: num, den, _ = cont2discrete((op.process.num, op.process.den), signals.dt_val, method="zoh") num = num.flatten() num = num[1:] if num[0] == 0 else num assert len(num) == 1 num = num[0] assert len(den) == 2 den = den[1] nums += [num] * op.input.shape[0] dens += [den] * op.input.shape[0] nums = np.asarray(nums) while nums.ndim < len(self.input_data.full_shape): nums = np.expand_dims(nums, -1) # note: applying the negative here dens = -np.asarray(dens) while dens.ndim < len(self.input_data.full_shape): dens = np.expand_dims(dens, -1) # need to manually broadcast for scatter_mul # dens = np.tile(dens, (1, signals.minibatch_size)) self.nums = signals.constant(nums, dtype=self.output_data.dtype) self.dens = signals.constant(dens, dtype=self.output_data.dtype)
def make_step(self, shape_in, shape_out, dt, rng, y0=None, dtype=np.float64, method="zoh"): """Returns a `.Step` instance that implements the linear filter.""" assert shape_in == shape_out num, den = self.num, self.den if self.analog: num, den, _ = cont2discrete((num, den), dt, method=method) num = num.flatten() if den[0] != 1.0: raise ValidationError("First element of the denominator must be 1", attr="den", obj=self) num = num[1:] if num[0] == 0 else num den = den[1:] # drop first element (equal to 1) num, den = num.astype(dtype), den.astype(dtype) output = np.zeros(shape_out, dtype=dtype) if len(num) == 1 and len(den) == 0: return LinearFilter.NoDen(num, den, output) elif len(num) == 1 and len(den) == 1: return LinearFilter.Simple(num, den, output, y0=y0) return LinearFilter.General(num, den, output, y0=y0)
def test_pack_data(self, num, den, dt, order): # Create the filter lf = LinearFilter(0, False, num, den) # Create a buffer to pack data into data = bytearray((order * 2 + 1) * 4) # Pack the parameters lf.pack_data(dt, data, 0) # Generate what we expect the data to look like numd, dend, _ = cont2discrete((num, den), dt) numd = numd.flatten() exp = list() for a, b in zip(dend[1:], numd[1:]): exp.append(-a) exp.append(b) expected_data = tp.np_to_fix(np.array(exp)).tostring() # Check that's what we get assert struct.unpack_from("<I", data, 0)[0] == order assert data[4:] == expected_data
def __init__(self, ops, signals): # TODO: implement general linear filter (using queues?) self.input_data = (None if ops[0].input is None else signals.combine( [op.input for op in ops])) self.output_data = signals.combine([op.output for op in ops]) nums = [] dens = [] for op in ops: if op.process.tau <= 0.03 * signals.dt_val: num = 1 den = 0 else: num, den, _ = cont2discrete((op.process.num, op.process.den), signals.dt_val, method="zoh") num = num.flatten() num = num[1:] if num[0] == 0 else num assert len(num) == 1 num = num[0] assert len(den) == 2 den = den[1] nums += [num] * op.input.shape[0] dens += [den] * op.input.shape[0] nums = np.asarray(nums)[:, None] # note: applying the negative here dens = -np.asarray(dens)[:, None] # need to manually broadcast for scatter_mul # dens = np.tile(dens, (1, signals.minibatch_size)) self.nums = tf.constant(nums, dtype=self.output_data.dtype) self.dens = tf.constant(dens, dtype=self.output_data.dtype)
def test_pack_data(self, num, den, dt, order): # Create the filter lf = LinearFilter(0, False, num, den) # Create a buffer to pack data into data = bytearray((order*2 + 1)*4) # Pack the parameters lf.pack_data(dt, data, 0) # Generate what we expect the data to look like numd, dend, _ = cont2discrete((num, den), dt) numd = numd.flatten() exp = list() for a, b in zip(dend[1:], numd[1:]): exp.append(-a) exp.append(b) expected_data = tp.np_to_fix(np.array(exp)).tostring() # Check that's what we get assert struct.unpack_from("<I", data, 0)[0] == order assert data[4:] == expected_data
def test_cont2discrete_other_methods(): dt = 1e-3 # test with len(sys) == 3 assert (repr(cont2discrete( ([1], [1], [1]), dt)) == "(array([1.0010005]), array([1.0010005]), 1.0, 0.001)") # test with len(sys) == 5 with pytest.raises(ValueError): cont2discrete(([1], [1], [1], [1], [1]), dt) # test method gbt and alpha None with pytest.raises(ValueError): cont2discrete(([1], [1], [1]), dt, method="gbt", alpha=None) # test method gbt and alpha invalid with pytest.raises(ValueError): cont2discrete(([1], [1], [1]), dt, method="gbt", alpha=2) # test method bilinear assert (repr(cont2discrete(([1], [1], [1]), dt, method="bilinear")) == "(array([1.0010005]), array([1.0010005]), 1.0, 0.001)") # test method backward_diff assert (repr(cont2discrete(([1], [1], [1]), dt, method="backward_diff")) == "(array([1.001001]), array([1.001001]), 1.0, 0.001)") # test bad method with pytest.raises(ValueError): cont2discrete(([1], [1], [1]), dt, method="not_a_method")
def __init__(self, ops, signals, config): super(LinearFilterBuilder, self).__init__(ops, signals, config) self.input_data = signals.combine([op.input for op in ops]) self.output_data = signals.combine([op.output for op in ops]) self.n_ops = len(ops) self.signal_d = ops[0].input.shape[0] As = [] Cs = [] Ds = [] # compute the A/C/D matrices for each operator for op in ops: A, B, C, D = tf2ss(op.process.num, op.process.den) if op.process.analog: # convert to discrete system A, B, C, D, _ = cont2discrete((A, B, C, D), signals.dt_val, method="zoh") # convert to controllable form num, den = ss2tf(A, B, C, D) if op.process.analog: # add shift num = np.concatenate((num, [[0]]), axis=1) with warnings.catch_warnings(): # ignore the warning about B, since we aren't using it anyway warnings.simplefilter("ignore", BadCoefficients) A, _, C, D = tf2ss(num, den) As.append(A) Cs.append(C[0]) Ds.append(D.item()) self.state_d = sum(x.shape[0] for x in Cs) # build a sparse matrix containing the A matrices as blocks # along the diagonal sparse_indices = [] corner = np.zeros(2, dtype=np.int64) for A in As: idxs = np.reshape(np.dstack(np.meshgrid( np.arange(A.shape[0]), np.arange(A.shape[1]), indexing="ij")), (-1, 2)) idxs += corner corner += A.shape sparse_indices += [idxs] sparse_indices = np.concatenate(sparse_indices, axis=0) self.A = signals.constant(np.concatenate(As, axis=0).flatten(), dtype=signals.dtype) self.A_indices = signals.constant(sparse_indices, dtype=( tf.int32 if np.all(sparse_indices < np.iinfo(np.int32).max) else tf.int64)) self.A_shape = tf.constant(corner, dtype=tf.int64) if np.allclose(Cs, 0): self.C = None else: # add empty dimension for broadcasting self.C = signals.constant(np.concatenate(Cs)[:, None], dtype=signals.dtype) if np.allclose(Ds, 0): self.D = None else: # add empty dimension for broadcasting self.D = signals.constant(np.asarray(Ds)[:, None], dtype=signals.dtype) self.offsets = tf.expand_dims( tf.range(0, len(ops) * As[0].shape[0], As[0].shape[0]), axis=1) # create a variable to represent the internal state of the filter self.state_sig = signals.make_internal( "state", (self.state_d, signals.minibatch_size * self.signal_d), minibatched=False)
def __init__(self, ops, signals, config): super(LinearFilterBuilder, self).__init__(ops, signals, config) self.input_data = signals.combine([op.input for op in ops]) self.output_data = signals.combine([op.output for op in ops]) self.n_ops = len(ops) self.signal_d = ops[0].input.shape[0] As = [] Cs = [] Ds = [] # compute the A/C/D matrices for each operator for op in ops: A, B, C, D = tf2ss(op.process.num, op.process.den) if op.process.analog: # convert to discrete system A, B, C, D, _ = cont2discrete((A, B, C, D), signals.dt_val, method="zoh") # convert to controllable form num, den = ss2tf(A, B, C, D) if op.process.analog: # add shift num = np.concatenate((num, [[0]]), axis=1) with warnings.catch_warnings(): # ignore the warning about B, since we aren't using it anyway warnings.simplefilter("ignore", BadCoefficients) A, _, C, D = tf2ss(num, den) As.append(A) Cs.append(C[0]) Ds.append(D.item()) self.state_d = sum(x.shape[0] for x in Cs) # build a sparse matrix containing the A matrices as blocks # along the diagonal sparse_indices = [] corner = np.zeros(2, dtype=np.int64) for A in As: idxs = np.reshape( np.dstack( np.meshgrid(np.arange(A.shape[0]), np.arange(A.shape[1]), indexing="ij")), (-1, 2)) idxs += corner corner += A.shape sparse_indices += [idxs] sparse_indices = np.concatenate(sparse_indices, axis=0) self.A = signals.constant(np.concatenate(As, axis=0).flatten(), dtype=signals.dtype) self.A_indices = signals.constant( sparse_indices, dtype=(tf.int32 if np.all( sparse_indices < np.iinfo(np.int32).max) else tf.int64)) self.A_shape = tf.constant(corner, dtype=tf.int64) if np.allclose(Cs, 0): self.C = None else: # add empty dimension for broadcasting self.C = signals.constant(np.concatenate(Cs)[:, None], dtype=signals.dtype) if np.allclose(Ds, 0): self.D = None else: # add empty dimension for broadcasting self.D = signals.constant(np.asarray(Ds)[:, None], dtype=signals.dtype) self.offsets = tf.expand_dims(tf.range(0, len(ops) * As[0].shape[0], As[0].shape[0]), axis=1) # create a variable to represent the internal state of the filter self.state_sig = signals.make_internal( "state", (self.state_d, signals.minibatch_size * self.signal_d), minibatched=False)
def __init__(self, units, order, theta, input_d, **kwargs): super().__init__(**kwargs) # compute the A and B matrices according to the LMU's mathematical # derivation (see the paper for details) Q = np.arange(order, dtype=np.float64) R = (2 * Q + 1)[:, None] / theta j, i = np.meshgrid(Q, Q) A = np.where(i < j, -1, (-1.0) ** (i - j + 1)) * R B = (-1.0) ** Q[:, None] * R C = np.ones((1, order)) D = np.zeros((1,)) A, B, _, _, _ = cont2discrete((A, B, C, D), dt=1.0, method="zoh") with self: nengo_dl.configure_settings(trainable=None) # create objects corresponding to the x/u/m/h variables in LMU self.x = nengo.Node(size_in=input_d) self.u = nengo.Node(size_in=1) self.m = nengo.Node(size_in=order) self.h = nengo_dl.TensorNode( tf.nn.tanh, shape_in=(units,), pass_time=False ) # compute u_t # note that setting synapse=0 (versus synapse=None) adds a # one-timestep delay, so we can think of any connections with # synapse=0 as representing value_{t-1} nengo.Connection( self.x, self.u, transform=np.ones((1, input_d)), synapse=None ) nengo.Connection( self.h, self.u, transform=np.zeros((1, units)), synapse=0 ) nengo.Connection( self.m, self.u, transform=np.zeros((1, order)), synapse=0 ) # compute m_t # in this implementation we'll make A and B non-trainable, but they # could also be optimized in the same way as the other parameters conn = nengo.Connection(self.m, self.m, transform=A, synapse=0) self.config[conn].trainable = False conn = nengo.Connection(self.u, self.m, transform=B, synapse=None) self.config[conn].trainable = False # compute h_t nengo.Connection( self.x, self.h, transform=np.zeros((units, input_d)), synapse=None, ) nengo.Connection( self.h, self.h, transform=np.zeros((units, units)), synapse=0 ) nengo.Connection( self.m, self.h, transform=nengo_dl.dists.Glorot(distribution="normal"), synapse=None, )