# Lecture 1B: Trace Variables and Filtering Traces represent one very important component tool in ngc-learn as these are often, in biophysical model simulations, used to produce real-valued representations of often discrete-valued patterns, e.g., spike vectors within a spike train, that can facilitate mechanisms such as online biological credit assignment. In this lesson, we will observe how one of ngc-learn's core trace components -- the `VarTrace` -- operates. ## Setting Up a Variable Trace for a Poisson Spike Train To observe the value of a variable trace, we will pair it to another in-built ngc-component; the `PoissonCell`, which will be configured to emit spikes approximately at `63.75` Hertz (yielding a fairly sparse spike train). This means we will construct a two-component dynamical system, where the input compartment `outputs` of the `PoissonCell` will be wired directly into the `inputs` compartment of the `VarTrace`. Note that a `VarTrace` has an `inputs` compartment -- which is where raw signals typically go into -- and a `trace` output compartment -- which is where filtered signal values/by-products are emitted from. The code below will instantiate the paired Poisson cell and corresponding variable trace: ```python from jax import numpy as jnp, random, jit from ngclearn import Context, MethodProcess ## import model-specific mechanisms from ngclearn.components.input_encoders.poissonCell import PoissonCell from ngclearn.components.other.varTrace import VarTrace ## create seeding keys (JAX-style) dkey = random.PRNGKey(231) dkey, *subkeys = random.split(dkey, 2) with Context("Model") as model: cell = PoissonCell("z0", n_units=1, target_freq=63.75, key=subkeys[0]) trace = VarTrace("tr0", n_units=1, tau_tr=30., a_delta=0.5) ## wire up cell z0 to trace tr0 cell.outputs >> trace.inputs advance_process = (MethodProcess("advance") >> cell.advance_state >> trace.advance_state) reset_process = (MethodProcess("reset") >> cell.reset >> trace.reset) ## set up some utility functions for the model context def clamp(x): cell.inputs.set(x) ``` ## Running the Paired Cell-Trace System We can then run the above two-component dynamical system by injecting a fixed (valid) probability value into the Poisson input encoder and then record the resulting spikes and trace values. We will do this for `T = 200` milliseconds (ms) with the code below: ```python dt = 1. # ms # integration time constant T = 200 ## number time steps to simulate probs = jnp.asarray([[0.35]],dtype=jnp.float32) time_span = [] spikes = [] traceVals = [] reset_process.run() for ts in range(T): clamp(probs) advance_process.run(t=ts*1., dt=dt) print(f"\r{cell.outputs.get()} {trace.trace.get()}", end="") spikes.append( cell.outputs.get() ) traceVals.append( trace.trace.get() ) time_span.append(ts * dt) print() spikes = jnp.concatenate(spikes,axis=0) traceVals = jnp.concatenate(traceVals,axis=0) ``` We can plot the above simulation's trace outputs with the discrete spikes super-imposed at their times of occurrence with the code below: ```python import matplotlib #.pyplot as plt matplotlib.use('Agg') import matplotlib.pyplot as plt cmap = plt.cm.jet fig, ax = plt.subplots() zTr = ax.plot(time_span, traceVals, '-.', color='tab:red') stat = jnp.where(spikes > 0.) indx = (stat[0] * 1. - 1.).tolist() spk = ax.vlines(x=indx, ymin=0.985, ymax=1.05, colors='black', ls='-', lw=5) ax.set(xlabel='Time (ms)', ylabel='Trace Output', title='Variable Trace of Poisson Spikes') #ax.legend([zTr[0],spk[0]],['z','phi(z)']) ax.grid() fig.savefig("poisson_trace.jpg") ``` to get the following output saved to disk: Notice that every time a spike is produced by the Poisson encoding cell, the trace increments by `0.5` -- the result of the `a_delta` hyper-parameter we set when crafting the model and simulation object -- and then exponentially decays in the absence of a spike (with the time constant of `tau_tr = 30` milliseconds). The variable trace can be further configured to filter signals in different ways if desired; specifically by manipulating its `decay_type` and `a_delta` arguments. Notably, if a piecewise-gated variable trace is desired (a very common choice in some neuronal circuit models), then all one would have to do is set `a_delta = 0`, yielding the following line in the model creation code earlier in this tutorial: ```python trace = VarTrace("tr0", n_units=1, tau_tr=30., a_delta=0., decay_type="exp") ``` Running the same code from before but with the above alteration would yield the plot below: Notice that, this time, when a spike is emitted from the Poisson cell, the trace is "clamped" to the value of one and then exponentially decays. Such a trace configuration is useful if one requires the maintained trace to never increase beyond a value of one, preventing divergence or run-away values if a spike train is particularly dense and yielding friendlier values for biological learning rules.