Skip to content

API

1. SNN Simulator

This is the main module that orchestrates the simulation process of SNN. It handles the creation of input images, netlist generation, parameter substitution, and execution of the simulation, along with the collection of results.

This module
  1. Accepts user-specificatied parameter combinations for different SNN designs.
  2. Loads the generated input images, flattens them, and uses frequency coding proportional to pixel intensity
  3. Generates files and process-specific directory for each SNN simulation named with the date, process ID, and parameter details
  4. Creates the netlist for each simulation using the net_generator module.
  5. Substitutes parameters into the OCEAN script template and launches the SPICE simulation using the subst_run module.
  6. Collects and saves the waveforms results of the selected signals.
  7. Executes the simulations of different SNN designs in parallel.

main()

Main function to run all simulations using multiprocessing.

Steps
  1. Distribute parameter combinations across multiple processes.
  2. Run simulations in parallel.
  3. Measure and print the total simulation time.
Source code in src/snn_simulator.py
def main():
    """
    Main function to run all simulations using multiprocessing.

    Steps:
        1. Distribute parameter combinations across multiple processes.
        2. Run simulations in parallel.
        3. Measure and print the total simulation time.
    """
    with Pool() as p:
        p.map(run_simulation, param_combinations)  # Distribute combinations sets and run
        simul_t = datetime.timedelta(seconds=tm.time() - start_time)
    print(f" --- The simulation finished after {simul_t} - at {datetime.datetime.now()} ---")

run_simulation(params)

Runs a single SNN simulation with the given parameters.

Parameters:

Name Type Description Default
params dict

A dictionary containing global and local simulation parameters.

required
Steps
  1. Load and flatten input image.
  2. Calculate the number of spikes for each input neuron based on pixel intensity.
  3. Generate and prepare directories and files for the simulation.
  4. Create the netlist for the simulation.
  5. Substitute parameters into the OCEAN script and run the simulation.
  6. Clean up temporary files.
Source code in src/snn_simulator.py
def run_simulation(params):
    """
    Runs a single SNN simulation with the given parameters.

    Args:
        params (dict): A dictionary containing global and local simulation parameters.

    Steps:
        1. Load and flatten input image.
        2. Calculate the number of spikes for each input neuron based on pixel intensity.
        3. Generate and prepare directories and files for the simulation.
        4. Create the netlist for the simulation.
        5. Substitute parameters into the OCEAN script and run the simulation.
        6. Clean up temporary files.
    """
    # Load and flatten spiking neurons intensity
    input_data = np.load(f'./input_images/letter_imgs/generated_{params["inp_img"]}.npy')
    flat_input_data = input_data.flatten()
    # Use frequency coding proportional to pixel intensity + baseline
    n_spik_vec = params['cod_base'] + params['cod_max'] * (flat_input_data / 255.0)    
    params['num_input'] = len(flat_input_data)

    # Create a string that contains result signals, to insert in .ocn file 
    save_states = "" 
    for i in range(1, params['num_input'] * params['num_output'] + 1):
        input_index = (i - 1) % params['num_input'] + 1
        output_index = (i - 1) // params['num_input'] + 1
        for j in range(1, params['num_cells'] + 1):
            save_states += f'v("synapse{input_index}_{output_index}.cell{j}.I:ix") '
            if j < params['num_cells']:
                save_states += '+'

    base_dir = os.path.abspath("../../snn_sim_folders/")
    # Format date and time as a string in the format 'MMDD_HHMM'
    now = datetime.datetime.now()
    date = now.strftime("%m%d_%H%M")     
    #process_dir = f"dat_{date}_pross_{os.getpid()}"          # name the folder with date,time & process
    process_dir = f"dat_{date}_pross_{os.getpid()}_let{params['inp_img']}_dev{params['dev']}_cells{params['num_cells']}" #include the paramters with date & process

    abs_process_dir = os.path.join(base_dir, process_dir)

    # Create a process-specific directory for simulation files and results
    if not os.path.exists(abs_process_dir):   
        os.mkdir(abs_process_dir)             
        os.mkdir(abs_process_dir + "/netlist_ocn")
    copy_tree("./netlist_ocn", f"{abs_process_dir}/netlist_ocn")   
    netlist = os.path.join(abs_process_dir, "netlist_ocn", "netlist")
    results_file = os.path.join(abs_process_dir, "results.txt") 

    # Params to include in .ocn file
    params["netlist"] = netlist  # Path to the complete netlist file 
    params["process_dir"] = abs_process_dir  # Path to simulation process dir
    params["results_file"] = results_file  # Path to the file where results are written 
    params["save_states"] = save_states  # A string containing signals to be written in results file

    random.seed(10)
    network_generator = NetworkGenerator(netlist, params['num_input'], params['num_output'], params['num_cells'], n_spik_vec)
    network_generator.generate_netlist_file()  # The complete netlist file is created 

    updated_template_file = os.path.join(abs_process_dir, "updated_template.ocn")
    log_file = os.path.join(abs_process_dir, "oceanScript.log") 
    subst_run.substitute_templ("./oceanScript.ocn", updated_template_file, params)
    subst_run.exec_cmd(f"ocean -nograph < {updated_template_file} > {log_file}") 
    shutil.rmtree(f"{abs_process_dir}/psf")  # Remove the psf directory

2. Network Generation

Spice simulation with Spectre simulator requires a netlist file that includes the description of all the components of the circuit, and describes how these componenents are wired. The framework allows flexible geneartion of the netlist according to the desired network to be simulated. Only some network parameters should be given, for the corresponding netlist to be automatically generated. The follwoing module net_generator.py which contains diffrent classes is responsible of generating the netlist. Circuit componenents are mangaged by the follwing classes : Synapse, Synapse_subskt, Input_neuron, Output_neuron, each contains a generate_netlist_bloc method that sets a string template bloc specific to that componenent and which should be added to the netlist file. The class Netlist concatinates the instances of the componenets in a list, and generates a string bloc of a single componenent ready to be inserted in the final netlist. The class NetworkGenerator is the main class that assembles all the components by iterating the appending method of Netlist class. and trigers the netlist generation method of Netlist. Finally Separotor is a class to add a separation between a group of similar componenets for a bette rformatting.

Input_neuron

A class to specify a netlist bloc that describes an input neuron of the SNN, a method takes a template, adds information of the current neuron (neuron index, number of spikes associated to that input neuron, the duration of a single input spike, and the duration of presenting an input example to the netowrk). That sting bloc will then be included in the final netlist.

Attributes:

Name Type Description
input_index [int]

The index of this input neuron.

n_spikes [int]

The number of spikes this neuron will generate.

Methods

generate_netlist_bloc(): Generates a string bloc specefic to that input neuron in the final netlist file.

Source code in src/net_generator.py
class Input_neuron:
    """
    A class to specify a netlist bloc that describes an input neuron of the SNN, 
    a method takes a template, adds information of the current neuron 
    (neuron index, number of spikes associated to that input neuron, the duration of a single input spike, 
    and the duration of presenting an input example to the netowrk). That sting bloc will then be included
    in the final netlist.

    Attributes:
        input_index [int]: The index of this input neuron.
        n_spikes [int]: The number of spikes this neuron will generate.

    Methods
    -------
    generate_netlist_bloc():
        Generates a string bloc specefic to that input neuron in the final netlist file. 
    """

    def __init__(self, input_index, n_spikes):
        self.input_index = input_index
        self.n_spikes = n_spikes

    def generate_netlist_bloc(self):

        template = ( "input_neuron{} (input{} 0) Input_neuron r=0 n_spikes={} spike_duration=spike_duration presenting_time=sim_time \n") 
        return template.format(self.input_index, self.input_index, self.n_spikes)

Netlist

A class which assemles the instances of all the components, it then generates the netlist file add_component is a method that appends the instances of the componenets in one list called components. generate_netlist_file is a method that generates the diffrent parts of the netlist by using the generate_netlist_bloc() which is commun to all the components classes.

Attributes:

Name Type Description
file_path [str]

The path to the file where the netlist will be written.

components [list]

The list of components instances (synapses, neurons, etc.).

Methods

add_component(component): appends the instance of each omponenent to the components list. generate_netlist_file(): Writes the netlist to a file.

Source code in src/net_generator.py
class Netlist:
    """
    A class which assemles the instances of all the components, it then generates the netlist file
    add_component is a method that appends the instances of the componenets in one list called components.
    generate_netlist_file is a method that generates the diffrent parts of the netlist by using the generate_netlist_bloc()
    which is commun to all the components classes. 

    Attributes:
        file_path [str]: The path to the file where the netlist will be written.
        components [list]: The list of components instances (synapses, neurons, etc.).

    Methods
    -------
    add_component(component):
        appends the instance of each omponenent to the components list.
    generate_netlist_file():
        Writes the netlist to a file.
    """

    def __init__(self, file_path):
        self.file_path = file_path
        self.components = [] # The netlist will be built inside this list

    def add_component(self, component):
        self.components.append(component)                       

    def generate_netlist_file(self):
        content = ""
        with open("netlist_ocn/netlist", "r") as file0:
            content += file0.read()

        for component in self.components:
            content += component.generate_netlist_bloc()        # use the class instances of componenets stroed in components to generate template and add it to content

        with open(self.file_path, "w") as file1:
            file1.write(content)

NetworkGenerator

The main class that generates all the netlist file, it is based on the Netlist class, It iterates over all the components by group of similar ones, it appends instances of each component to the components list, while adding separation formatting between groups of similar components.

Attributes:

Name Type Description
file_path [str]

The path to the file where the netlist will be written.

num_input [int]

The number of input neurons in the network.

num_output [int]

The number of output neurons in the network.

num_cells [int]

The number of MTJ cells in each synapse.

netlist [Netlist]

The netlist object that will be written to a file.

n_spik_vec [list of int]

A list containing the number of spikes for each input neuron.

Methods

generate_netlist_file(): similar to the generate_netlist_file of the Netlist class, but instead of generating a single component, it operates globally, ie: it generates the whole netlist by iterating through the method of Netlist class.

Source code in src/net_generator.py
class NetworkGenerator:
    """
    The main class that generates all the netlist file, it is based on the Netlist class,
    It iterates over all the components by group of similar ones, it appends instances of 
    each component to the components list, while adding separation formatting between groups of similar components.

    Attributes:
        file_path [str]: The path to the file where the netlist will be written.
        num_input [int]: The number of input neurons in the network.
        num_output [int]: The number of output neurons in the network.
        num_cells [int]: The number of MTJ cells in each synapse.
        netlist [Netlist]: The netlist object that will be written to a file.
        n_spik_vec [list of int]: A list containing the number of spikes for each input neuron.

    Methods
    -------
    generate_netlist_file():
        similar to the generate_netlist_file of the Netlist class, but instead of generating a single component, 
        it operates globally, ie: it generates the whole netlist by iterating through the method of Netlist class.
    """

    def __init__(self, file_path, num_input, num_output, num_cells, n_spik_vec):
        self.file_path = file_path
        self.num_input = num_input
        self.num_output = num_output
        self.num_cells = num_cells
        self.netlist = Netlist(file_path)
        self.n_spik_vec = n_spik_vec # a list of a flattned array, containing n_spikes for each input neuron

    def generate_netlist_file(self):
        self.netlist.add_component(Synapse_subskt(self.num_cells))
        self.netlist.add_component(Separator())

        num_synapses = self.num_input * self.num_output

        # add the synapses 
        for i in range(1, num_synapses + 1):
            input_index = (i - 1) % self.num_input + 1
            output_index = (i - 1) // self.num_input + 1
            synapse = Synapse(input_index, output_index, self.num_cells)
            self.netlist.add_component(synapse)

        self.netlist.add_component(Separator())

        # add input neurons
        for i in range(1, self.num_input + 1):
            input_neuron = Input_neuron(i, self.n_spik_vec[i-1])
            self.netlist.add_component(input_neuron)

        self.netlist.add_component(Separator())

        # add output neurons 
        for i in range(1, self.num_output + 1):
            output_neuron = Output_neuron(i)
            self.netlist.add_component(output_neuron)

        self.netlist.generate_netlist_file()

Output_neuron

A class to specify a netlist bloc of an output neuron of the SNN, a method takes a template, adds information of the current neuron (neuron index, membrane threshold). That sting bloc will then be included in the final netlist.

Attributes:

Name Type Description
output_index [int]

The index of this output neuron.

Methods

generate_netlist_bloc(): Generates a string bloc specefic to that output neuron in the final netlist file.

Source code in src/net_generator.py
class Output_neuron:
    """
    A class to specify a netlist bloc of an output neuron of the SNN, 
    a method takes a template, adds information of the current neuron 
    (neuron index, membrane threshold). That sting bloc will then be included
    in the final netlist.

    Attributes:
        output_index [int]: The index of this output neuron.


    Methods
    -------
    generate_netlist_bloc():
        Generates a string bloc specefic to that output neuron in the final netlist file. 
    """

    def __init__(self, output_index):
        self.output_index = output_index 

    def generate_netlist_bloc(self):
        template = ( "output_neuron{} (output{}) LIF_neuron mem_vth=mem_vth\n" )

        return template.format(self.output_index, self.output_index)

Separator

A class to seperate between componenets in the netlist for a better formatting.

Methods

generate_netlist_bloc(): Generates a separation as a string.

Source code in src/net_generator.py
class Separator:
    """
    A class to seperate between componenets in the netlist for a better formatting.

    Methods
    -------
    generate_netlist_bloc():
        Generates a separation as a string.
    """

    def generate_netlist_bloc(self):
        return "\n//===================================================\n"     

Synapse

A class to specify a netlist bloc that describes an MTJ-based synapse of the SNN, generate_netlist_bloc is a method that returns as string which represents a template bloc of the synapse to be included in the netlist later. It personalizes the synapse at the current iteration by adding its specific informatio (connected neurons, number of MTJs per synapse, initialized states, seeds,... ). The synapse template calls the compound_synapse subcircuit which should be included once in the head of the netlist.

Attributes:

Name Type Description
input_index [int]

The index of the input neuron connected to this synapse.

output_index [int]

The index of the output neuron connected to this synapse.

paps [list of int]

The list of initial states of free layers for each MTJ in the synapse. (0 parallel, 1 anti-parallel).

seeds [list of int]

The list of seed values used for stochasticity for each MTJ in the synapse.

Methods

generate_netlist_bloc(): Generates a string bloc specefic to that synapse in the final netlist file.

Source code in src/net_generator.py
class Synapse:
    """
    A class to specify a netlist bloc that describes an MTJ-based synapse of the SNN, 
    generate_netlist_bloc is a method that returns as string which represents a template 
    bloc of the synapse to be included in the netlist later. It personalizes the synapse
    at the current iteration by adding its specific informatio (connected neurons, number 
    of MTJs per synapse, initialized states, seeds,... ). The synapse template calls the 
    compound_synapse subcircuit which should be included once in the head of the netlist.

    Attributes:
        input_index [int]: The index of the input neuron connected to this synapse.
        output_index [int]: The index of the output neuron connected to this synapse.
        paps [list of int]: The list of initial states of free layers for each MTJ in the synapse. (0 parallel, 1 anti-parallel).
        seeds [list of int]: The list of seed values used for stochasticity for each MTJ in the synapse.

    Methods
    -------
    generate_netlist_bloc():
        Generates a string bloc specefic to that synapse in the final netlist file. 

    """

    def __init__(self, input_index, output_index, num_cells):
        self.input_index = input_index
        self.output_index = output_index
        self.seeds = [random.randint(0, 9999) for _ in range(num_cells)]
        #self.paps = [i%2 for i in range(num_cells)]                 # alternate 0&1 in the MTJs of the synapse
        self.paps = [random.randint(0, 1) for _ in range(num_cells)] #initialize randamely

    def generate_netlist_bloc(self):                 
        template = "synapse{}_{} (input{} output{}) compound_synapse "
        paps_str = " ".join("PAP{}={}".format(i + 1, pap) for i, pap in enumerate(self.paps))
        seeds_str = " ".join("seed{}={}".format(i + 1, seed) for i, seed in enumerate(self.seeds))
        return template.format(
            self.input_index, self.output_index, self.input_index, self.output_index
        ) + paps_str + " \\\n\t\t" + seeds_str + "\n"

Synapse_subskt

A class to specify a netlist bloc of the synapse subcircuit named compound_synapse, which will be called by all the synapses. It is composed of multiple MTJs (the number is given as attribute), and it makes call of the cellPMAMTJ subcircuit which is predefined in the initial netlist file. generate_netlist_bloc is a method that returns the subcircuit template to be included in the netlist. It takes an initial template, adds parameters to it : either to set stochasticity, variability, temperature and its variation or not, and sets as parameters the initial state of the MTJ, and the seed.

Attributes:

Name Type Description
num_cells [int]

The number of MTJ cells in each synapse.

Methods

generate_netlist_bloc(): Generates a string bloc specefic to the compound_synapse subcircuit in the final netlist file.

Source code in src/net_generator.py
class Synapse_subskt:
    """
    A class to specify a netlist bloc of the synapse subcircuit named compound_synapse, 
    which will be called by all the synapses. It is composed of multiple MTJs (the number is given as attribute), 
    and it makes call of the cellPMAMTJ subcircuit which is predefined in the initial netlist file.
    generate_netlist_bloc is a method that returns the subcircuit template to be included in the netlist.
    It takes an initial template, adds parameters to it : either to set stochasticity, variability,
    temperature and its variation or not, and sets as parameters the initial state of the MTJ, and the seed. 

    Attributes:
        num_cells [int]: The number of MTJ cells in each synapse.


    Methods
    -------
    generate_netlist_bloc():
        Generates a string bloc specefic to the compound_synapse subcircuit in the final netlist file. 
    """

    def __init__(self, num_cells):
        self.num_cells = num_cells

    def generate_netlist_bloc(self):
        template = ("subckt compound_synapse in_ter out_ter \n"
                    "parameters {} \n")

        parameters = " ".join(["seed{}".format(i+1) for i in range(self.num_cells)] +
                              ["PAP{}".format(i+1) for i in range(self.num_cells)])

        cells = ""
        for i in range(self.num_cells): #Here we define synapse terminals in a way to let MTJs T2 terminal node same as synapse's input node
            cell_line = ("\tcell{} (out_ter in_ter) cellPMAMTJ   param1=gl_STO   param2=gl_RV   param3=gl_T   param4=gl_Temp_var param7=RV_dev   "
                         "param5=PAP{}   param6=seed{}\n".format(i+1, i+1, i+1))
            cells += cell_line

        netlist_bloc = template.format( parameters) + cells + "ends compound_synapse\n"
        return netlist_bloc

3. Substitution Run

This module provides utility functions for substituting values into templates and executing shell commands. It is designed to facilitate the automation of script generation and execution processes. The substitution method used here is inspired by the approach in the monaco project by @servinagrero: https://github.com/servinagrero/monaco/tree/develop

Functions:

Name Description
substitute_templ

Reads a template from a file, substitutes values from

exec_cmd

Executes a given shell command, optionally displaying the output.

Example Usage:

substitute_templ('input_template.txt', 'output_file.txt', {'key1': 'value1'}, {'key2': 'value2'})
exec_cmd('ls -la', verbose=True)

exec_cmd(command, *, verbose=False)

Execute a given shell command using subprocess.run.

Parameters:

Name Type Description Default
command str

Shell command to execute.

required
verbose bool

Whether to display the output of the command.

False
Source code in src/subst_run.py
def exec_cmd(command: str, *, verbose: bool = False) -> None:
    """Execute a given shell command using subprocess.run.

    Args:
      command: Shell command to execute.
      verbose: Whether to display the output of the command.
    """
    run_args = {
        'args': command,  # Corrected this line
        'shell': True,
        'check': True
    }

    if not verbose:
        run_args['stdout'] = DEVNULL
        run_args['stderr'] = DEVNULL

    run(**run_args)

substitute_templ(template_file, output_file, *substitution_dicts)

Substitute values into a template read from a file.

Parameters:

Name Type Description Default
template_file str

Path to the input file containing the template.

required
output_file str

Path to the output file to write the substituted content.

required
*substitution_dicts Dict[str, str]

Dictionaries containing values to substitute into the template.

()
Source code in src/subst_run.py
def substitute_templ(template_file: str, output_file: str, *substitution_dicts: Dict[str, str]):
    """Substitute values into a template read from a file.

    Args:
      template_file: Path to the input file containing the template.
      output_file: Path to the output file to write the substituted content.
      *substitution_dicts: Dictionaries containing values to substitute into the template.
    """
    with open(template_file, 'r') as tmpl_file:
        tmpl = Template(tmpl_file.read())
        substitutions = {k: v for d in substitution_dicts for k, v in d.items()}
        substituted_content = tmpl.safe_substitute(substitutions)

        with open(output_file, 'w+') as result_file:
            result_file.write(substituted_content)

4. Plot Euclidean Distance

This module calculates and plots the Euclidean distance between the final weights of trained SNNs and the input images. The module provides functions to compute the distances for individual letters as well as average distances across multiple letters.

Functions:

Name Description
eucl_dist

Calculate and plot Euclidean distances for a specific letter.

avg_eucl_dist

Calculate and plot average Euclidean distances across multiple letters.

calculate_dist

Helper function to calculate Euclidean distances for a given letter.

plot_distances

Helper function to plot the Euclidean distances.

Example Usage:

eucl_dist(trained_letters_dict, 'I')
avg_eucl_dist(trained_letters_dict)

avg_eucl_dist(trained_letters_dict)

Calculate and plot average Euclidean distances across multiple letters.

Parameters:

Name Type Description Default
trained_letters_dict dict

Dictionary containing base directories for trained letters.

required
Source code in src/plot_eucl_dist.py
def avg_eucl_dist(trained_letters_dict):
    """
    Calculate and plot average Euclidean distances across multiple letters.

    Args:
        trained_letters_dict (dict): Dictionary containing base directories for trained letters.
    """
    # Load the all_images dictionary from the pickle file
    all_images = "input_images/letter_imgs/all_images.pkl"
    with open(all_images, 'rb') as pickle_file:
        images = pickle.load(pickle_file)

    # Initialize a structure to hold the distances
    average_distances = {nc: {dev: [] for dev in VR_std} for nc in mtjs}

    for letter, base_dir in trained_letters_dict.items():
        specific_letter_image = images[letter]
        euclidean_distances = calculate_dist(base_dir, specific_letter_image)

        # Accumulate the distances for averaging
        for num_cells in mtjs:
            for dev in VR_std:
                average_distances[num_cells][dev] += euclidean_distances[num_cells][dev]

    # Average the distances across letters
    for num_cells in mtjs:
        for dev in VR_std:
            if average_distances[num_cells][dev]:
                average_distances[num_cells][dev] = np.mean(average_distances[num_cells][dev])
            else:
                average_distances[num_cells][dev] = None

    plot_distances(average_distances, 'Average Euclidean Distance vs VR_std Across Letters')

calculate_dist(base_dir, letter_image)

Helper function to calculate Euclidean distances for a given letter.

Parameters:

Name Type Description Default
base_dir str

Base directory containing the trained folders.

required
letter_image ndarray

The image of the specific letter.

required

Returns:

Name Type Description
dict

Dictionary containing Euclidean distances for each combination of num_cells and VR_std.

Source code in src/plot_eucl_dist.py
def calculate_dist(base_dir, letter_image):
    """
    Helper function to calculate Euclidean distances for a given letter.

    Args:
        base_dir (str): Base directory containing the trained folders.
        letter_image (numpy.ndarray): The image of the specific letter.

    Returns:
        dict: Dictionary containing Euclidean distances for each combination of num_cells and VR_std.
    """
    trained_folders = sorted(glob.glob(base_dir))
    euclidean_distances = {nc: {dev: [] for dev in VR_std} for nc in mtjs}

    for trained_folder in trained_folders:
        folder_name = os.path.basename(trained_folder)
        parts = folder_name.split('_')
        dev = float(parts[-2].replace('dev', ''))
        num_cells = int(parts[-1].replace('cells', ''))

        data_file = os.path.join(trained_folder, "results.txt")
        if os.path.exists(data_file):
            data = np.genfromtxt(data_file, delimiter="", skip_header=4)
            final_weights_flat = data[-1, 1:-1]
            euclidean_distance = np.linalg.norm(final_weights_flat/num_cells - (letter_image/255.0))
            euclidean_distances[num_cells][dev].append(euclidean_distance)

    return euclidean_distances

eucl_dist(trained_letters_dict, specific_letter)

Calculate and plot Euclidean distances for a specific letter.

Parameters:

Name Type Description Default
trained_letters_dict dict

Dictionary containing base directories for trained letters.

required
specific_letter str

The letter to calculate distances for.

required
Source code in src/plot_eucl_dist.py
def eucl_dist(trained_letters_dict, specific_letter):
    """
    Calculate and plot Euclidean distances for a specific letter.

    Args:
        trained_letters_dict (dict): Dictionary containing base directories for trained letters.
        specific_letter (str): The letter to calculate distances for.
    """
    # Load the all_images dictionary from the pickle file
    all_images = "input_images/letter_imgs/all_images.pkl"
    with open(all_images, 'rb') as pickle_file:
        images = pickle.load(pickle_file)

    specific_letter_image = images[specific_letter]
    base_dir = trained_letters_dict.get(specific_letter)

    if base_dir:
        euclidean_distances = calculate_dist(base_dir, specific_letter_image)
        plot_distances(euclidean_distances, f'Euclidean Distance vs VR_std for Letter "{specific_letter}"')
    else:
        print(f"Base directory for letter '{specific_letter}' not found in the provided dictionary.")

plot_distances(euclidean_distances, title)

Helper function to plot the Euclidean distances.

Parameters:

Name Type Description Default
euclidean_distances dict

Dictionary containing Euclidean distances to plot.

required
title str

Title of the plot.

required
Source code in src/plot_eucl_dist.py
def plot_distances(euclidean_distances, title):
    """
    Helper function to plot the Euclidean distances.

    Args:
        euclidean_distances (dict): Dictionary containing Euclidean distances to plot.
        title (str): Title of the plot.
    """
    fig, axe1 = plt.subplots()
    for num_cells, dev_distances in euclidean_distances.items():
        devs = sorted(dev_distances.keys())
        distances = [np.mean(dev_distances[dev]) for dev in devs]
        axe1.plot(devs, distances, label=f'num_cells={num_cells}', marker='o')

    axe1.set_xlabel('VR_std')
    axe1.set_ylabel('Euclidean Distance')
    axe1.set_title(title)
    axe1.legend()

5. Plot Weight Membrane

This script processes and plots simulation results for an SNN. It loads data from a specified results file, plots the weights history, the output neuron membrane potential, and compares initial and final weights.

Functions:

Name Description
parse_arguments

Parse command-line arguments.

load_data

from results.txt, organized as : time | synapse1 | synapse2 | ... | membrane_out_neuron.

plot_weights_history

Plot the weights history.

plot_membrane_potential

Plot the output neuron membrane potential.

plot_weights_comparison

Plot initial and final weights comparison.

main

Main function to orchestrate loading data and plotting results.

Example Usage:

>> python plot_weig_membr.py simulations_folder_name

load_data(folder_name)

Load data from the results file.

Source code in src/plot_weig_membr.py
def load_data(folder_name):
    """Load data from the results file."""
    file_path = os.path.join(folder_name, "results.txt")
    if not os.path.isfile(file_path):
        print(f"Error: File {file_path} does not exist.")
        sys.exit(1)
    return np.genfromtxt(file_path, delimiter="", skip_header=4)

main()

Main function to orchestrate loading data and plotting results.

Source code in src/plot_weig_membr.py
def main():
    """Main function to orchestrate loading data and plotting results."""
    args = parse_arguments()
    data = load_data(args.folder_name)

    init_weights_flat = data[0, 1:-1]
    init_weights_img = init_weights_flat.reshape((5, 5))

    final_weights_flat = data[-1, 1:-1]
    final_weights_img = final_weights_flat.reshape((5, 5))

    # Update matplotlib global parameters for font
    plt.rcParams.update({'font.size': 14, 'font.family': 'serif'})

    plot_weights_history(data)
    plot_membrane_potential(data)
    fig = plot_weights_comparison(init_weights_img, final_weights_img)

    # Save figure to home directory if needed
    # plt.savefig(os.path.join(os.path.expanduser("~"), os.path.basename(args.folder_name) + ".pdf"), format='pdf')

    plt.show()

parse_arguments()

Parse command-line arguments.

Source code in src/plot_weig_membr.py
def parse_arguments():
    """Parse command-line arguments."""
    parser = argparse.ArgumentParser(description="Plot simulation results.")
    parser.add_argument("folder_name", help="Folder containing the simulation results.")
    return parser.parse_args()

plot_membrane_potential(data)

Plot the output neuron membrane potential.

Source code in src/plot_weig_membr.py
def plot_membrane_potential(data):
    """Plot the output neuron membrane potential."""
    plt.figure(figsize=(5, 5))
    plt.plot(1e3 * data[:, 0], 1e3 * data[:, -1], color="green")
    plt.title("Output neuron membrane potential")
    plt.xlabel("Time (ms)")
    plt.ylabel("Mem potential (mV)")
    plt.grid(True)

plot_weights_comparison(init_weights_img, final_weights_img)

Plot initial and final weights comparison.

Source code in src/plot_weig_membr.py
def plot_weights_comparison(init_weights_img, final_weights_img):
    """Plot initial and final weights comparison."""
    fig, (axe1, axe2) = plt.subplots(1, 2, figsize=(8, 4))

    im1 = axe1.imshow(init_weights_img, cmap='gray')
    axe1.set_title("Initial Weights")
    axe1.axis('off')
    cbar1 = plt.colorbar(im1, ax=axe1)
    cbar1.locator = MaxNLocator(integer=True)
    cbar1.update_ticks()

    im2 = axe2.imshow(final_weights_img, cmap='gray')
    axe2.set_title("Trained Weights")
    axe2.axis('off')
    cbar2 = plt.colorbar(im2, ax=axe2)
    cbar2.locator = MaxNLocator(integer=True)
    cbar2.update_ticks()

    plt.tight_layout()
    return fig

plot_weights_history(data)

Plot the weights history.

Source code in src/plot_weig_membr.py
def plot_weights_history(data):
    """Plot the weights history."""
    plt.figure(figsize=(5, 5))
    plt.plot(1e3 * data[:, 0], data[:, 1:-1])
    plt.title("Weights history")
    plt.xlabel("Time (ms)")
    plt.ylabel("Weight state")
    plt.grid(True)

6. Generate Letters

This script generates binary images of letters, saves them in both .npy and .pkl formats, and stores them in a specified directory. Each letter is represented by specific pixel positions in a 5x5 image grid. Images are saved individually as .npy arrays, and collectively as .pkl dict. all images are generated at once.

create_letter_image(letter, positions)

Create and save an image of a specified letter.

Parameters:

Name Type Description Default
letter str

The letter to create an image for.

required
positions list of tuples

The pixel positions of the letter in the image.

required
Source code in src/input_images/gen_letters.py
def create_letter_image(letter, positions):
    """
    Create and save an image of a specified letter.

    Args:
        letter (str): The letter to create an image for.
        positions (list of tuples): The pixel positions of the letter in the image.
    """
    image = np.full((image_size[0], image_size[1]), background_color, dtype=np.uint8)

    for x, y in positions:
        image[y, x] = letter_color
        letter_pixel_values[letter] = image.flatten()

    # Save the image
    # plt.imsave(f'{save_dir}/generated_{letter}.png', image, format="png") # PNG 
    np.save(f'{save_dir}/generated_{letter}.npy', image)                  # npy

7. View Images

This script loads and displays an image stored in a .npy file. It uses matplotlib to visualize the image and print its size and shape.

load_and_display_image(input_path)

Load an image from a .npy file and display it using matplotlib.

Parameters:

Name Type Description Default
input_path str

Path to the .npy file containing the image.

required
Source code in src/input_images/see_img.py
def load_and_display_image(input_path):
    """
    Load an image from a .npy file and display it using matplotlib.

    Args:
        input_path (str): Path to the .npy file containing the image.
    """
    image = np.load(input_path)
    print(f"size is: {np.size(image)} and shape is {np.shape(image)} ")

    plt.imshow(image, cmap='gray')
    plt.colorbar()
    plt.show()

8. Extract from MNIST

This script extracts specific images from the MNIST dataset and saves them as .npy files. The script loads MNIST images from the idx3-ubyte format and saves selected images in a specified directory.

Functions:

Name Description
load_idx3_ubyte

Loads MNIST images from a given idx3-ubyte file.

load_idx3_ubyte(idx3_ubyte_file)

Load MNIST data from the given file path.

Parameters:

Name Type Description Default
idx3_ubyte_file str

Path to the idx3-ubyte file containing MNIST images.

required

Returns:

Type Description

np.ndarray: Array of shape (num_images, rows, cols) containing the MNIST images.

Source code in src/input_images/extract_mnist.py
def load_idx3_ubyte(idx3_ubyte_file):
    """
    Load MNIST data from the given file path.

    Args:
        idx3_ubyte_file (str): Path to the idx3-ubyte file containing MNIST images.

    Returns:
        np.ndarray: Array of shape (num_images, rows, cols) containing the MNIST images.
    """
    with open(idx3_ubyte_file, 'rb') as f:
        magic, num_images, rows, cols = np.frombuffer(f.read(16), dtype=np.dtype('>i4'))
        assert magic == 2051, 'Invalid magic number'
        images = np.frombuffer(f.read(), dtype=np.uint8).reshape(num_images, rows, cols)
    return images