How can I update the counter value only once in each run, but share it among all the methods in the class in nengo?

Hello all,

Problem: I have four methods (sepal_length, sepal_width, petal_length and petal_width) in DataFeeder class. I want to update the counter value only once in each run, but share this updated counter value with all 4 methods.

Background

I am working on Iris dataset which looks like:

df = pd.read_csv(dataset)
features = df.iloc[:, 1:5].values
print(df.head())

Output

    ID SepalLengthCm  SepalWidthCm  PetalLengthCm  PetalWidthCm      Species
    1        5.1        3.5            1.4              0.2         Iris-setosa
    2        4.9        3.0            1.4              0.2         Iris-setosa
    3        4.7        3.2            1.3              0.2         Iris-setosa

I would like to retain each features for a particular time period (let’s say 50 ms) separately using a function (project requirement). To give you an example:

features[0,0] = 5.1 |
features[0,1] = 3.5 |
features[0,2] = 1.4 | -> from 0 to 0.049 (that's 50 ms, since python indexing starts from 0)
features[0,3] = 0.2 |

in the next run

features[1,0] = 4.9 |
features[1,1] = 3.0 |
features[1,2] = 1.4 | -> from 0.05 to 0.099
features[1,3] = 0.2 |

and so on for all 150 rows. I created a class called Datafeeder. I have two variable called window and counter which is explained in the code below.

class DataFeeder:

    def __init__(self, window=50, time_step=1e-3, counter=0):
        self.t = 0
        self.idx = 0 # in this case, row index. For iris dataset it will be from 0 to 149, since there are 150 rows.
        self.window = window # I want to keep each feature for 50 ms
        self.time_step = time_step
        self.counter = counter

    def index(self):

        self.idx = int(self.t / self.time_step)

        # Example, run 1: self.idx = 0. Counter value is immediately updated, which is okay.
        # Now, self.counter is 1.
        # Run 2: self.idx = 1. The if condition is not satisfied, until self.idx = 50.
        # The counter is updated to 2 after 50 ms

        if self.idx % self.window == 0:
            self.counter += 1

        return self.counter

    def sepal_length(self, t):
        self.t = t
        self.counter = self.index()

        print(self.counter)

        return features[self.counter % features.shape[0], 0]

    def sepal_width(self, t):
        self.t = t
        self.counter = self.index()
        return features[self.counter % features.shape[0], 1]

    def petal_length(self, t):
        self.t = t
        self.counter = self.index()
        return features[self.counter % features.shape[0], 2]

    def petal_width(self, t):
        self.t = t
        self.counter = self.index()
        return features[self.counter % features.shape[0], 3]

model = nengo.Network()

with model:
    data_feeder = DataFeeder()
    feature_1 = nengo.Node(output=data_feeder.sepal_length, label="Features 1")
    feature_2 = nengo.Node(output=data_feeder.sepal_width, label="Features 2")
    feature_3 = nengo.Node(output=data_feeder.petal_length, label="Features 3")
    feature_4 = nengo.Node(output=data_feeder.petal_width, label="Features 4")

    feature_1_probe = nengo.Probe(feature_1)
    feature_2_probe = nengo.Probe(feature_2)
    feature_3_probe = nengo.Probe(feature_3)
    feature_4_probe = nengo.Probe(feature_4)

    ens = nengo.Ensemble(n_neurons=10, dimensions=1, label="Encoder", neuron_type=nengo.LIF(amplitude=10))
    ens_probe = nengo.Probe(ens)
    ens_spikes = nengo.Probe(ens.neurons)

    nengo.Connection(feature_1, ens)
    nengo.Connection(feature_2, ens)
    nengo.Connection(feature_3, ens)
    nengo.Connection(feature_4, ens)

dt = 1e-3
with nengo.Simulator(model, dt=dt) as sim:
    sim.run(dt * 1000) #14900
    spike_count = np.sum(sim.data[ens_spikes] > 0, axis=0)

print(f'spike counts are {spike_count}')

The above code works as expected. But, the problem is

  1. index function is called only in sepal_length function. If, for some reason, I decide not to use sepal_length feature in my code, the counter value wont be updated. Hence, not the efficient way of coding

  2. If index method is called in all the method (i.e. sepal_length, sepal_width, petal_length and petal_width), the counter value is updated 4 times. So, run 1: features[1,0] for 50 ms. After 50 ms, features[4,0] etc which is not what I want.

Tried so far: In order to eliminate calling index method only in sepal_length method, I tried

  1. Updating the counter variable in init
  2. Calling index method in init
  3. Counter variable is made a class variable and then updating it in init
  4. Using everything, that’s in init, as a class variable

None of the above solutions worked for me. The counter value doesn’t update after 50 ms, unless it is done in one of the methods (i.e. sepal_length, sepal_width, petal_length and petal_width). self.t is updated inside the method (I checked), but not in init

Does anyone know how can I update the counter value only once in each run and without calling (index method) in any of the other methods, but share it among all the methods in the class?

I believe the solution I proposed in your other thread (i.e., using multi-dimensional outputs for nodes) will solve this issue.

However, if you really want to use the approach you’ve taken above, then I suggest making another class function which serves only to increment the counter variable. You should also remove the counter logic from the other functions (sepal_length, sepal_width, etc.)

class Datafeeder:
    ...
    def incr_count(t):
        self.t = t
        self.counter = self.index()
    ...

Then, in your Nengo model, you’ll need to create a node to call this function. Whether or not you call the other function does not matter, just as long as this function is called:

with model:
    ...
    count_node = nengo.Node(data_feeder.incr_count)
    feature_1 = nengo.Node(data_feeder.sepal_length)
    ...