From ed52fc5d1be4d7e9945dd701c1152030b496c4f9 Mon Sep 17 00:00:00 2001 From: Chris Earl <55409142+C-Earl@users.noreply.github.com> Date: Thu, 29 Jan 2026 16:48:52 +0900 Subject: [PATCH 1/3] Added new feature to topology features --- bindsnet/network/topology_features.py | 194 +++++++++++++++++++++++++- 1 file changed, 193 insertions(+), 1 deletion(-) diff --git a/bindsnet/network/topology_features.py b/bindsnet/network/topology_features.py index 43ccc138..e685c6a0 100644 --- a/bindsnet/network/topology_features.py +++ b/bindsnet/network/topology_features.py @@ -281,7 +281,7 @@ def link(self, parent_feature) -> None: Allow two features to share tensor values """ - valid_features = (Probability, Weight, Bias, Intensity) + valid_features = (Probability, Delay, Weight, Bias, Intensity) assert isinstance(self, valid_features), f"A {self} cannot use feature linking" assert isinstance( @@ -464,6 +464,198 @@ def assert_valid_range(self): ), f"Invalid range for feature {self.name}: the min value must be of type torch.Tensor, float, or int" +class Delay(AbstractFeature): + def __init__( + self, + name: str, + value: Union[torch.Tensor, float, int] = None, + range: Optional[Sequence[float]] = None, + norm: Optional[Union[torch.Tensor, float, int]] = None, + learning_rule: Optional[bindsnet.learning.LearningRule] = None, + nu: Optional[Union[list, tuple]] = None, + reduction: Optional[callable] = None, + decay: float = 0.0, + max_delay: Optional[int] = 32, + delay_decay: Optional[float] = 0, # TODO: Make this global + lambda + drop_late_spikes: Optional[bool] = False, + refractory: Optional[bool] = False, # TODO: Change this name + normalize_delays: Optional[ + bool + ] = False, # force normalize delays instead of clipping + ) -> None: + # language=rst + """ + Delays outgoing signals based on the values of :code:`value` and :code:`max_delay`. Delays are calculated as + being :code:`value` * :code:`max_delay`, where :code: `value` is in range [0, 1] + :param name: Name of the feature + :param value: Unscaled delays. Unscaled implies that these values are in [0, 1], and will be multiplied by :code:`max_delay` to determine delay time + :param range: Range of acceptable values for the :code:`value` parameter + :param norm: Value which all values in :code:`value` will sum to. Normalization of values occurs after each sample + and after the value has been updated by the learning rule (if there is one) + :param learning_rule: Rule which will modify the :code:`value` after each sample + :param nu: Learning rate for the learning rule + :param reduction: Method for reducing parameter updates along the minibatch + dimension + :param decay: Constant multiple to decay weights by on each iteration + :param max_delay: Maximum possible delay + :param delay_decay: Decay :code:`value` by this amount every time step + :param drop_late_spikes: Surpress spikes when delay is at maximum + :param refractory: Block spikes in synapse until earlier ones pass + :param normalize: Force normalize delay every run instead of clipping values + """ + + ### Assertions ### + super().__init__( + name=name, + value=value, + range=[0, 1], # note: Value isn't used, not 'None' to avoid errors + norm=norm, + learning_rule=learning_rule, + nu=nu, + reduction=reduction, + decay=decay, + ) + self.max_delay = max_delay + self.delay_decay = delay_decay + self.drop_late_spikes = drop_late_spikes + self.refractory = refractory + self.normalize_delays = normalize_delays + + def compute(self, conn_spikes) -> Union[torch.Tensor, float, int]: + value = self.value.clone().detach().flatten() + if self.normalize_delays: + # force normalize delay values + tmp_min, tmp_max = torch.min(value), torch.max(value) + if tmp_max > 1 or tmp_min < 0: + value = (value - tmp_min) / (tmp_max - tmp_min) + else: + # force clip delay values + value = torch.clamp(value, 0, 1) + + # Generate new delays for insertion into buffers + delays = ((1 - value) * (self.max_delay - 1)).long() + + if self.refractory: + # TODO: Is there a reason why this is in here? + if self.drop_late_spikes: + conn_spikes[delays == self.max_delay] = 0 + + # Prevent additional spikes if one is already on the synapse + conn_spikes &= self.refrac_count <= 0 + self.refrac_count -= 1 + bool_spikes = conn_spikes.bool() + self.refrac_count[bool_spikes] = delays[bool_spikes] + + # add circular time index to delays + # TODO: Dead spikes of delay = self.dmax don't properly die if self.time_idx > 0 + delays = (delays + self.time_idx) % self.max_delay + + # Fill the delay buffer, according to connection delays + # |delay_buffer| = [source.n * target.n, max_delay] + # TODO: Can we remove .float() for performance? (Change delay buffer type?) + flattened_conn_spikes = conn_spikes.flatten().float() + self.delay_buffer[self.delays_idx, delays] = flattened_conn_spikes # .bool() + + # Outgoing signal is spikes scheduled to fire at time_idx + # TODO: Detach + Clone likely not efficient as passing reference to buffer at current time index; efficiency + out_signal = ( + self.delay_buffer[:, self.time_idx] + .view(self.source_n, self.target_n) + .detach() + .clone() + ) + + # Clear transmitted spikes + self.delay_buffer[:, self.time_idx] = 0.0 + + # Suppress max delays + if self.drop_late_spikes and not self.refractory: + late_spikes_time = (self.time_idx - 1) % self.max_delay + self.delay_buffer[:, late_spikes_time] = 0.0 + + # Increment circular time pointer + self.time_idx = (self.time_idx + 1) % self.max_delay + + # TODO: Remember to move this to global + # Decay + if self.delay_decay: + self.delay_buffer = self.delay_buffer - self.delay_decay.to("cuda") + self.delay_buffer[ + self.delay_decay < 0 + ] = 0 # TODO: Determine if this is faster than clamp(min=0) + + return out_signal + + def reset_state_variables(self) -> None: + super().reset_state_variables() + + # Reset time index and empty buffer + self.time_idx = 0 + self.delay_buffer.zero_() + + def prime_feature(self, connection, device, **kwargs) -> None: + #### Initialize value #### + if self.value is None: + self.initialize_value = lambda: torch.clamp( + torch.rand( + (connection.source.n, connection.target.n), + dtype=torch.float32, + device=device, + ), + self.range[0], + self.range[1], + ) + else: + self.value = self.value.to(torch.float32).to(device) + + super().prime_feature(connection, device, **kwargs) + + #### Initialize additional class variables #### + self.delays_idx = torch.arange( + 0, connection.source.n * connection.target.n, dtype=torch.int32 + ).to(device) + self.delay_buffer = torch.zeros( + connection.source.n * connection.target.n, + self.max_delay, + dtype=torch.float32, + ).to(device) + self.time_idx = 0 + self.source_n = connection.source.n + self.target_n = connection.target.n + + # Tensor necessary for interaction with delay buffer + if self.delay_decay: + self.delay_decay = torch.tensor([self.delay_decay]) + + if self.refractory: + self.refrac_count = torch.zeros( + connection.source.n * connection.target.n, + dtype=torch.long, + device=device, + ) + + def assert_valid_range(self): + super().assert_valid_range() + + r = self.range + + ## Check min greater than 0 ## + if isinstance(r[0], torch.Tensor): + assert ( + r[0] >= 0 + ).all(), ( + f"Invalid range for feature {self.name}: a min value is less than 0" + ) + elif isinstance(r[0], (float, int)): + assert ( + r[0] >= 0 + ), f"Invalid range for feature {self.name}: the min value is less than 0" + else: + assert ( + False + ), f"Invalid range for feature {self.name}: the min value must be of type torch.Tensor, float, or int" + + class Mask(AbstractFeature): def __init__( self, From 118d4f2f2b6f1bbcf017b395cb0483f4638063bc Mon Sep 17 00:00:00 2001 From: Chris Earl <55409142+C-Earl@users.noreply.github.com> Date: Fri, 30 Jan 2026 18:12:43 +0900 Subject: [PATCH 2/3] Added new feature tester --- examples/mnist/reservoir_delays.py | 362 +++++++++++++++++++++++++++++ 1 file changed, 362 insertions(+) create mode 100644 examples/mnist/reservoir_delays.py diff --git a/examples/mnist/reservoir_delays.py b/examples/mnist/reservoir_delays.py new file mode 100644 index 00000000..a46ac3cc --- /dev/null +++ b/examples/mnist/reservoir_delays.py @@ -0,0 +1,362 @@ +import os +import numpy as np +import torch +import torch.nn as nn +import argparse +import matplotlib.pyplot as plt +from bindsnet import network + +from torchvision import transforms +from tqdm import tqdm + +from bindsnet.analysis.plotting import ( + plot_input, + plot_spikes, + plot_voltages, + plot_weights, +) +from bindsnet.datasets import MNIST +from bindsnet.encoding import PoissonEncoder +from bindsnet.network import Network +from bindsnet.network.nodes import Input + +# Build a simple two-layer, input-output network. +from bindsnet.network.monitors import Monitor +from bindsnet.network.nodes import LIFNodes +from bindsnet.network.topology import MulticompartmentConnection + +from bindsnet.network.topology_features import Delay, Mask, Probability, Weight +from bindsnet.learning.MCC_learning import PostPre, MSTDP, NoOp + + +parser = argparse.ArgumentParser() +parser.add_argument("--seed", type=int, default=0) +parser.add_argument("--n_neurons", type=int, default=500) +parser.add_argument("--n_epochs", type=int, default=500) +parser.add_argument("--examples", type=int, default=500) +parser.add_argument("--n_workers", type=int, default=-1) +parser.add_argument("--time", type=int, default=250) +parser.add_argument("--dt", type=int, default=1.0) +parser.add_argument("--intensity", type=float, default=64) +parser.add_argument("--progress_interval", type=int, default=10) +parser.add_argument("--update_interval", type=int, default=250) +parser.add_argument("--plot", dest="plot", action="store_true") +parser.add_argument("--gpu", dest="gpu", action="store_true") +parser.set_defaults(plot=False, gpu=True, train=True) + +args = parser.parse_args() + +seed = args.seed +n_neurons = args.n_neurons +n_epochs = args.n_epochs +examples = args.examples +n_workers = args.n_workers +time = args.time +dt = args.dt +intensity = args.intensity +progress_interval = args.progress_interval +update_interval = args.update_interval +train = args.train +plot = args.plot +gpu = args.gpu + +np.random.seed(seed) +torch.cuda.manual_seed_all(seed) +torch.manual_seed(seed) + +# Sets up Gpu use +device = torch.device("cuda" if torch.cuda.is_available() else "cpu") +if gpu and torch.cuda.is_available(): + torch.cuda.manual_seed_all(seed) +else: + torch.manual_seed(seed) + device = "cpu" + if gpu: + gpu = False +torch.set_num_threads(os.cpu_count() - 1) +print("Running on Device = ", device) + + +### Base model ### +model = Network() +model.to(device) + + +### Layers ### +input_l = Input(n=784, shape=(1, 28, 28), traces=True) +output_l = LIFNodes( + n=n_neurons, thresh=-52 + np.random.randn(n_neurons).astype(float), traces=True +) + +model.add_layer(input_l, name="X") +model.add_layer(output_l, name="Y") + + +## Connections ### +# Initialize features +weight_feature = Weight(name="my_weights", value=torch.rand(input_l.n, output_l.n)) +delay_feature = Delay(name="my_delay", value=torch.rand(input_l.n, output_l.n)) + +# Construct pipeline +pl_in = [weight_feature, delay_feature] + +# Add pipeline to a new connection +input_con = MulticompartmentConnection( + source=input_l, target=output_l, device=device, pipeline=pl_in +) + + +# Initialize features +weight_feature = Weight( + name="my_weights2", + value=torch.randn(output_l.n, output_l.n), + norm=1, + nu=[0.001, 0.002], + learning_rule=PostPre, +) + +# Construct pipeline +pl_rec = [weight_feature] + +# Add pipeline to a new connection +recurrent_con = MulticompartmentConnection( + source=output_l, target=output_l, device=device, pipeline=pl_rec +) + +model.add_connection(input_con, source="X", target="Y") +model.add_connection(recurrent_con, source="Y", target="Y") + +# Directs network to GPU +if gpu: + model.to("cuda") + +### MNIST ### +dataset = MNIST( + PoissonEncoder(time=time, dt=dt), + None, + root=os.path.join("../../test", "..", "data", "MNIST"), + download=True, + transform=transforms.Compose( + [transforms.ToTensor(), transforms.Lambda(lambda x: x * intensity)] + ), +) + + +### Monitor setup ### +inpt_axes = None +inpt_ims = None +spike_axes = None +spike_ims = None +weights_im = None +weights_im2 = None +voltage_ims = None +voltage_axes = None +spikes = {} +voltages = {} +for l in model.layers: + spikes[l] = Monitor(model.layers[l], ["s"], time=time, device=device) + model.add_monitor(spikes[l], name="%s_spikes" % l) + + if type(model.layers[l]) != Input: + voltages[l] = Monitor(model.layers[l], ["v"], time=time, device=device) + model.add_monitor(voltages[l], name="%s_voltages" % l) + + +### Running model on MNIST ### + +# Create a dataloader to iterate and batch data +dataloader = torch.utils.data.DataLoader( + dataset, batch_size=1, shuffle=True, num_workers=0, pin_memory=True +) + +n_iters = examples + +# Connection tunning +pbar = tqdm(enumerate(dataloader)) +model.train(True) +for i, dataPoint in pbar: + if i > n_iters: + break + + # Extract & resize the MNIST samples image data for training + # int(time / dt) -> length of spike train + # 28 x 28 -> size of sample + datum = dataPoint["encoded_image"].view(int(time / dt), 1, 1, 28, 28).to(device) + label = dataPoint["label"] + pbar.set_description_str("Train progress: (%d / %d)" % (i, n_iters)) + + # Run network on sample image + model.run(inputs={"X": datum}, time=time, input_time_dim=1, reward=1.0) + + # Plot spiking activity using monitors + if plot: + # inpt_axes, inpt_ims = plot_input( + # dataPoint["image"].view(28, 28), + # datum.view(int(time / dt), 784).sum(0).view(28, 28), + # label=label, + # axes=inpt_axes, + # ims=inpt_ims, + # ) + spike_ims, spike_axes = plot_spikes( + {layer: spikes[layer].get("s").view(time, -1) for layer in spikes}, + axes=spike_axes, + ims=spike_ims, + ) + voltage_ims, voltage_axes = plot_voltages( + {layer: voltages[layer].get("v").view(time, -1) for layer in voltages}, + ims=voltage_ims, + axes=voltage_axes, + ) + + plt.pause(1e-8) + model.reset_state_variables() + +# Run the model on the data for training the detactor. +training_pairs = [] +pbar = tqdm(enumerate(dataloader)) +model.train(False) +for i, dataPoint in pbar: + if i > n_iters: + break + + # Extract & resize the MNIST samples image data for training + # int(time / dt) -> length of spike train + # 28 x 28 -> size of sample + datum = dataPoint["encoded_image"].view(int(time / dt), 1, 1, 28, 28).to(device) + label = dataPoint["label"] + pbar.set_description_str("Data extraction progress: (%d / %d)" % (i, n_iters)) + + # Run network on sample image + model.run(inputs={"X": datum}, time=time, input_time_dim=1, reward=1.0) + training_pairs.append([spikes["Y"].get("s").sum(0), label]) + + # Plot spiking activity using monitors + if plot: + # inpt_axes, inpt_ims = plot_input( + # dataPoint["image"].view(28, 28), + # datum.view(int(time / dt), 784).sum(0).view(28, 28), + # label=label, + # axes=inpt_axes, + # ims=inpt_ims, + # ) + spike_ims, spike_axes = plot_spikes( + {layer: spikes[layer].get("s").view(time, -1) for layer in spikes}, + axes=spike_axes, + ims=spike_ims, + ) + voltage_ims, voltage_axes = plot_voltages( + {layer: voltages[layer].get("v").view(time, -1) for layer in voltages}, + ims=voltage_ims, + axes=voltage_axes, + ) + + plt.pause(1e-8) + model.reset_state_variables() + + +# TODO: Delete this portion for fully delay/prob-conn dependent learning +### Classification ### + + +# Define logistic regression model using PyTorch. +# These neurons will take the reservoirs output as its input, and be trained to classify the images. +class NN(nn.Module): + def __init__(self, input_size, num_classes): + super(NN, self).__init__() + # h = int(input_size/2) + self.linear_1 = nn.Linear(input_size, num_classes) + # self.linear_1 = nn.Linear(input_size, h) + # self.linear_2 = nn.Linear(h, num_classes) + + def forward(self, x): + out = torch.sigmoid(self.linear_1(x.float().view(-1))) + # out = torch.sigmoid(self.linear_2(out)) + return out + + +# Create and train logistic regression model on reservoir outputs. +learning_model = NN(n_neurons, 10).to(device) +criterion = torch.nn.MSELoss(reduction="sum") +optimizer = torch.optim.SGD(learning_model.parameters(), lr=1e-4, momentum=0.9) + +# Training the Model +print("\n Training the read out") +pbar = tqdm(enumerate(range(n_epochs))) +for epoch, _ in pbar: + avg_loss = 0 + + # Extract spike outputs from reservoir for a training sample + # i -> Loop index + # s -> Reservoir output spikes + # l -> Image label + for i, (s, l) in enumerate(training_pairs): + # Reset gradients to 0 + optimizer.zero_grad() + + # Run spikes through logistic regression model + outputs = learning_model(s) + + # Calculate MSE + label = torch.zeros(1, 1, 10).float().to(device) + label[0, 0, l] = 1.0 + loss = criterion(outputs.view(1, 1, -1), label) + avg_loss += loss.data + + # Optimize parameters + loss.backward() + optimizer.step() + + pbar.set_description_str( + "Epoch: %d/%d, Loss: %.4f" + % (epoch + 1, n_epochs, avg_loss / len(training_pairs)) + ) + +# Run same simulation on reservoir with testing data instead of training data +# (see training section for intuition) +n_iters = examples +test_pairs = [] +pbar = tqdm(enumerate(dataloader)) +for i, dataPoint in pbar: + if i > n_iters: + break + datum = dataPoint["encoded_image"].view(int(time / dt), 1, 1, 28, 28).to(device) + label = dataPoint["label"] + pbar.set_description_str("Testing progress: (%d / %d)" % (i, n_iters)) + + model.run(inputs={"X": datum}, time=time, input_time_dim=1) + test_pairs.append([spikes["Y"].get("s").sum(0), label]) + + if plot: + # inpt_axes, inpt_ims = plot_input( + # dataPoint["image"].view(28, 28), + # datum.view(time, 784).sum(0).view(28, 28), + # label=label, + # axes=inpt_axes, + # ims=inpt_ims, + # ) + spike_ims, spike_axes = plot_spikes( + {layer: spikes[layer].get("s").view(time, -1) for layer in spikes}, + axes=spike_axes, + ims=spike_ims, + ) + voltage_ims, voltage_axes = plot_voltages( + {layer: voltages[layer].get("v").view(time, -1) for layer in voltages}, + ims=voltage_ims, + axes=voltage_axes, + ) + + plt.pause(1e-8) + model.reset_state_variables() + +# Test learning model with previously trained logistic regression classifier +correct, total = 0, 0 +for s, label in test_pairs: + outputs = learning_model(s) + _, predicted = torch.max(outputs.data.unsqueeze(0), 1) + total += 1 + correct += int(predicted == label.long().to(device)) + +print( + "\n Accuracy of the model on %d test images: %.2f %%" + % (n_iters, 100 * correct / total) +) From b9c188a8c60ce7a634f0046dd2ff8b94b5158427 Mon Sep 17 00:00:00 2001 From: Chris Earl <55409142+C-Earl@users.noreply.github.com> Date: Fri, 30 Jan 2026 18:20:38 +0900 Subject: [PATCH 3/3] Reformatted with Black --- bindsnet/datasets/preprocess.py | 2 +- bindsnet/datasets/torchvision_wrapper.py | 9 ++------- bindsnet/learning/learning.py | 2 +- bindsnet/network/topology_features.py | 8 ++++---- examples/benchmark/sparse_vs_dense_tensors.py | 1 - examples/mnist/MCC_reservoir.py | 1 - examples/mnist/reservoir_delays.py | 1 - 7 files changed, 8 insertions(+), 16 deletions(-) diff --git a/bindsnet/datasets/preprocess.py b/bindsnet/datasets/preprocess.py index 9948ac18..37ce1dc5 100644 --- a/bindsnet/datasets/preprocess.py +++ b/bindsnet/datasets/preprocess.py @@ -151,7 +151,7 @@ def crop_sample(sample): opts = {} image, bb = sample["image"], sample["bb"] orig_bbox = BoundingBox(bb[0], bb[1], bb[2], bb[3]) - (output_image, pad_image_location, edge_spacing_x, edge_spacing_y) = cropPadImage( + output_image, pad_image_location, edge_spacing_x, edge_spacing_y = cropPadImage( orig_bbox, image ) new_bbox = BoundingBox(0, 0, 0, 0) diff --git a/bindsnet/datasets/torchvision_wrapper.py b/bindsnet/datasets/torchvision_wrapper.py index 23960a04..b1e36519 100644 --- a/bindsnet/datasets/torchvision_wrapper.py +++ b/bindsnet/datasets/torchvision_wrapper.py @@ -16,18 +16,13 @@ def create_torchvision_dataset_wrapper(ds_type): ds_type = getattr(torchDB, ds_type) class TorchvisionDatasetWrapper(ds_type): - __doc__ = ( - """BindsNET torchvision dataset wrapper for: + __doc__ = """BindsNET torchvision dataset wrapper for: The core difference is the output of __getitem__ is no longer (image, label) rather a dictionary containing the image, label, and their encoded versions if encoders were provided. - \n\n""" - + str(ds_type) - if ds_type.__doc__ is None - else ds_type.__doc__ - ) + \n\n""" + str(ds_type) if ds_type.__doc__ is None else ds_type.__doc__ def __init__( self, diff --git a/bindsnet/learning/learning.py b/bindsnet/learning/learning.py index b7ad0927..0ffad89c 100644 --- a/bindsnet/learning/learning.py +++ b/bindsnet/learning/learning.py @@ -877,7 +877,7 @@ def _conv1d_connection_update(self, **kwargs) -> None: ``AbstractConnection`` class. """ # Get convolutional layer parameters. - (out_channels, in_channels, kernel_size) = self.connection.w.size() + out_channels, in_channels, kernel_size = self.connection.w.size() padding, stride = self.connection.padding, self.connection.stride batch_size = self.source.batch_size diff --git a/bindsnet/network/topology_features.py b/bindsnet/network/topology_features.py index e685c6a0..6e3d6d6a 100644 --- a/bindsnet/network/topology_features.py +++ b/bindsnet/network/topology_features.py @@ -508,7 +508,7 @@ def __init__( super().__init__( name=name, value=value, - range=[0, 1], # note: Value isn't used, not 'None' to avoid errors + range=[0, 1], # note: Value isn't used, not 'None' to avoid errors norm=norm, learning_rule=learning_rule, nu=nu, @@ -580,9 +580,9 @@ def compute(self, conn_spikes) -> Union[torch.Tensor, float, int]: # Decay if self.delay_decay: self.delay_buffer = self.delay_buffer - self.delay_decay.to("cuda") - self.delay_buffer[ - self.delay_decay < 0 - ] = 0 # TODO: Determine if this is faster than clamp(min=0) + self.delay_buffer[self.delay_decay < 0] = ( + 0 # TODO: Determine if this is faster than clamp(min=0) + ) return out_signal diff --git a/examples/benchmark/sparse_vs_dense_tensors.py b/examples/benchmark/sparse_vs_dense_tensors.py index db5a34b7..228fdc4a 100644 --- a/examples/benchmark/sparse_vs_dense_tensors.py +++ b/examples/benchmark/sparse_vs_dense_tensors.py @@ -4,7 +4,6 @@ from bindsnet.evaluation import all_activity, assign_labels, proportion_weighting - parser = argparse.ArgumentParser() parser.add_argument("--benchmark_type", choices=["memory", "runtime"], default="memory") args = parser.parse_args() diff --git a/examples/mnist/MCC_reservoir.py b/examples/mnist/MCC_reservoir.py index 89145d6f..ff91876f 100644 --- a/examples/mnist/MCC_reservoir.py +++ b/examples/mnist/MCC_reservoir.py @@ -26,7 +26,6 @@ from bindsnet.network.topology import MulticompartmentConnection from bindsnet.utils import get_square_weights - parser = argparse.ArgumentParser() parser.add_argument("--seed", type=int, default=0) parser.add_argument("--n_neurons", type=int, default=500) diff --git a/examples/mnist/reservoir_delays.py b/examples/mnist/reservoir_delays.py index a46ac3cc..720e263c 100644 --- a/examples/mnist/reservoir_delays.py +++ b/examples/mnist/reservoir_delays.py @@ -28,7 +28,6 @@ from bindsnet.network.topology_features import Delay, Mask, Probability, Weight from bindsnet.learning.MCC_learning import PostPre, MSTDP, NoOp - parser = argparse.ArgumentParser() parser.add_argument("--seed", type=int, default=0) parser.add_argument("--n_neurons", type=int, default=500)