brainspy.processors package#
Module contents#
Main package for handling all related simulations and hardware measurements of dopant-networks. Check https://github.com/BraiNEdarwin/brains-py/wiki/C.-Package-Description
Subpackages#
- brainspy.processors.hardware package
- brainspy.processors.modules package
- brainspy.processors.simulation package
- Module contents
- Subpackages
- Submodules
- brainspy.processors.simulation.model module
- brainspy.processors.simulation.processor module
SurrogateModelSurrogateModel.close()SurrogateModel.forward()SurrogateModel.forward_numpy()SurrogateModel.get_clipping_value()SurrogateModel.get_key()SurrogateModel.get_voltage_ranges()SurrogateModel.is_hardware()SurrogateModel.set_amplification()SurrogateModel.set_effects()SurrogateModel.set_effects_from_dict()SurrogateModel.set_output_clipping()SurrogateModel.set_voltage_ranges()SurrogateModel.training
Submodules#
brainspy.processors.dnpu module#
It is a very similar class to that of Processor, but it contains the con- trol voltages, declared as learnable parameters. Therefore, it only has the same input dimensions as the number of available data input electrodes. It is also a child of torch.nn.Module, and it allows for representing a layer of DNPUs in a time- multiplexing fashion (with the same Processor instance). It also enables applying linear transformations to the inputs before passing them to the processor.
- class brainspy.processors.dnpu.DNPU(processor: Processor, data_input_indices: list, forward_pass_type: str = 'vec')[source]#
Bases:
ModuleThis class enables to declare the control voltages as learnable parameters in order to train a surrogate model to find optimal control voltage values for a particular problem. It is a child of an nn.Module class that contains a processor. Therefore, the solutions found for control voltages using a surrogate model, can be seamlessly applied to hardware DNPUs. The class also enables to declare more than one internal DNPU node, that will act as a single layer of the specified node number, in time-multiplexing of the same model.
- add_input_transform(input_range: list, strict: bool = True)[source]#
Adds a linear transformation required to convert the input into the input electrode voltage ranges. It automatically calculates the input electrode voltage ranges for a particular DNPU according to the voltage ranges it was trained with. It is used typically to perform a current to voltage transformation, but it can also be applied for transforming the raw values from a dataset into voltages. The application of the input transformation occurs when the data has been reshaped into [Batch_size, window_no, in_chanels, node_no, input_electrode_no]. This function has to be called from outside the module, after its initialisation.
- Parameters:
input_range (list) – The range that the original raw input data is going to have. It can be specified with two values [min, max], representing the minimum and maximum values that the input data is expected to have. In this case, the linear transformation will be adapted to the length of the input dimension automatically. E.g. input_range = [0,1]. It can also be specified for different minimum and maximum value ranges per electrode. In this case, the list has to be specified with the same shape as the input_range variable of the class. This can be obtained by calling the get_input_ranges method.
strict (boolean) – Defines if the input is going to be clipped before doing the linear transformation in order to ensure that the transformation is correct.
- clip_input_for(x)[source]#
Clips the input. To be used only during the for forward pass type.
- Parameters:
x (torch.Tensor) – Input data that might contain values above or below the specified minimum and maximum ranges.
- Returns:
Clipped data that is assured to be within the specified minimum and maximum ranges.
- Return type:
torch.Tensor
- clip_input_vec(x)[source]#
Clips the input. To be used only during the vectorised forward pass type.
- Parameters:
x (torch.Tensor) – Input data that might contain values above or below the specified minimum and maximum ranges.
- Returns:
Clipped data that is assured to be within the specified minimum and maximum ranges.
- Return type:
torch.Tensor
- close()[source]#
Close the processor. For simulation models, it does nothing. For hardware models it closes the drivers.
- constraint_control_voltages()[source]#
Allows to constraint the control voltages to their corresponding maximum and minimum values. Can be used after loss.backward() and optimizer.step() to clip out those control voltages that are outside their ranges.
Example
[…] loss.backward() optimizer.step() model.constraint_weights() […]
- forward(x: Tensor) Tensor[source]#
Run a forward pass through the processor after merging the input data voltages with the control voltages.
- Parameters:
x (torch.Tensor) – Input data.
- Returns:
Output data.
- Return type:
torch.Tensor
- forward_for(x)[source]#
Run a forward pass through all of the DNPU nodes using a for loop.
- Parameters:
x (torch.Tensor) – Input data in the shape of (batch_size, total_data_input_electrode_no), where total_data_input_electrode_no is the number of all data input electrodes from all the nodes used in the time-multiplexing layer.
- Returns:
Output data in the shape of (batch_size, readout_electrode_no)
- Return type:
torch.Tensor
- forward_single(x: Tensor, control_voltages: Tensor, data_input_indices: Tensor, control_indices: Tensor)[source]#
Run a forward pass through the processor for a single DNPU node.
- Parameters:
x (torch.Tensor) – Input data in the shape of (batch_size, data_input_electrode_no), where data_input_electrode_no is the number of data input electrodes for a single DNPU inside the time-multiplexing layer.
control_voltages (torch.Tensor) – Control voltage values that will be sent to the control electrodes.
data_input_indices (torch.Tensor [int64]) – Indices of the data input electrodes.
control_indices (torch.Tensor [int64]) – Indices of the control electrodes.
- Returns:
Output data in the shape of (batch_size, readout_electrode_no)
- Return type:
torch.Tensor
- forward_vec(x)[source]#
Run a forward pass through all of the DNPU nodes using by vectorising the input into (batch_size, dnpu_node_no, activation_electrode_no).
- Parameters:
x (torch.Tensor) – Input data in the shape of (batch_size, total_data_input_electrode_no), where total_data_input_electrode_no is the number of all data input electrodes from all the nodes used in the time-multiplexing layer.
- Returns:
Output data in the shape of (batch_size, readout_electrode_no)
- Return type:
torch.Tensor
- get_clipping_value() Tensor[source]#
Get the output clipping/clipping value.
- Returns:
The output clipping of the processor.
- Return type:
torch.Tensor
- get_control_electrode_no()[source]#
Returns how many control electrodes are used per DNPU node inside the module.
- Returns:
Number of control electrodes.
- Return type:
control_electrode_no - int
- get_control_ranges() Tensor[source]#
Get the voltage ranges of the control electrodes. It has a shape of (dnpu_no, electrode_no, 2), where the last dimension is 0 for the minimum value of the range and 1 for the maximum value of the range. :returns: Control voltage ranges. :rtype: torch.Tensor
- get_control_voltages() Tensor[source]#
Get the (next) control_voltages of the network, detach it from the computational graph.
- Returns:
Value of the control_voltages.
- Return type:
torch.Tensor
- get_data_input_electrode_no()[source]#
Returns how many data input electrodes are used per DNPU node inside the module.
- Returns:
Number of data input electrodes.
- Return type:
data_input_electrode_no - int
- get_info_dict() dict[source]#
Get the info dictionary of the processor.
- Returns:
The info dictionary.
- Return type:
dict
- get_input_ranges() Tensor[source]#
Get the voltage ranges of the input electrodes. It has a shape of (dnpu_no, electrode_no, 2), where the last dimension is 0 for the minimum value of the range and 1 for the maximum value of the range. :returns: Input voltage ranges. :rtype: torch.Tensor
- get_node_input_data(x)[source]#
Returns the input data from a particular DNPU node, one by one, from the input data of all DNPU nodes.
- Parameters:
x (torch.Tensor) – Input data tensor containing the input data per electrode for all DNPU nodes.
- Returns:
Input data for a particular DNPU node.
- Return type:
torch.Tensor
- get_node_no()[source]#
It counts how many control and data input electrodes are going to be declared using the data_input_indices variable.
- hw_eval(configs: dict, data_input_indices: list | None = None)[source]#
It helps setting the DNPU to evaluation mode. While training happens in simulation, evaluation happens in hardware. This function sets the nn.Module to evaluation mode (meaning no training) and swaps the processor (typically to hardware, although it also supports to do it for hardware_debug or simulation). Checks if the voltage ranges of the new processor are the same as the ones of the old.
- Parameters:
arg (Processor or dict) – Either a processor or configs dictionary to be applied to the existing one.
- Raises:
AssertionError – If control voltages of the new processor are different than the ones used for training the DNPU.
- init_activation_electrode_no()[source]#
It counts how many control and data input electrodes are going to be declared using the data_input_indices variable.
- Parameters:
data_input_indices (Sequence[int]) – Specifies which electrodes are going to be used for inputing data. The reminder of the activation electrodes will be automatically selected as control electrodes. The list should have the following shape (dnpu_node_no,data_input_electrode_no). The minimum dnpu_node_no should be 1, e.g., data_input_indices = [[1,2]]. When specifying more than one dnpu node in the list, the module will simulate, in time-multiplexing, as if there was a layer of DNPU devices. Fore example, for an 8 electrode DNPU device with a single readout electrode and 7 activation electrodes, when data_input_indices = [[1,2],[1,3],[3,4]], it will be considered that there are 3 DNPU devices, where the first DNPU device will use the data input electrodes 1 and 2, the second DNPU device will use data input electrodes 1 and 3 and the third DNPU device will use data input electrodes 3 and 4. Also, the first DNPU device will have electrodes 0, 3, 4, 5, and 6 defined as control electrodes. The second DNPU device will have electrodes 0,2,4,5, and 6 defined as control electrodes. The third DNPU device will have electrodes 0,1,2,5, and 6 defined as control electrodes. More information about what activation, readout, data input and control electrodes are can be found at the wiki: https://github.com/BraiNEdarwin/brains-py/wiki/A.-Introduction
- Returns:
input_data_electrode_no - int – Number of data input electrodes.
control_electrode_no - int – Number of control electrodes.
- init_electrode_info(data_input_indices: Sequence[int])[source]#
Set the input data electrode indices and control electrode indices, as well as the data input and control voltage ranges allowed according to the assigned processor electrodes. The voltage ranges are defined with the following shape: (node_no, electrode_no, 2), where the last dimension correspond to the minimum and maximum of the range.
Method is called by the constructor.
Example
>>> dnpu.get_activation_electrode_no() 7 >>> input_indices = [[0, 2]] >>> dnpu._init_electrode_info(input_indices) >>> dnpu.data_input_indices torch.Tensor([0, 2]) >>> dnpu.control_indices torch.Tensor([1, 3, 4, 5, 6])
Here we have a DNPU with 7 activation electrodes, where the two with indices 0 and 2 are set as input electrodes, and the rest become control electrodes.
- Parameters:
data_input_indices (Sequence[int]) – Indices of the input electrodes.
- is_hardware() bool[source]#
Check if processor is a hardware processor or a simulation processor.
- Returns:
True if hardware, False if simulation.
- Return type:
bool
- regularizer() Tensor[source]#
Return a penalty term if the value of the control_voltages is outside of the interval for the control voltages.
Example
>>> dnpu.control_low torch.Tensor([1.0, 5.0]) >>> dnpu.control_high torch.Tensor([3.0, 7.0]) >>> dnpu.control_voltages torch.Tensor([2.5, 8.0]) >>> dnpu.regularizer() torch.Tensor([1.0])
In this example we have two control electrodes, the first with voltage range 1 to 3 and the second 5 to 7. The control_voltages of the network is 2.5 for the first electrode and 8 for the second. The first is within the boundaries so no penalty is generated from it but the second is outside, which means a penalty will be generated, which is equal to how far outside the interval the value is. In this case 8 - 7 = 1, so the penalty will be 0 + 1 = 1.
- Returns:
Penalty term >=0.
- Return type:
torch.Tensor
- remove_input_transform()[source]#
Removes the usage of a input transfomration before sending the data to the DNPUs.
- sample_controls()[source]#
Returns a random single value of a control voltage, in the shape of (node_no, control_electrode_no), within a specified control voltage range. The control voltage ranges are automatically taken from the electrode position. Each electrode might have a different voltage range, and this is defined during the data gathering process of the surrogate model generator.
Example
[…] loss.backward() optimizer.step() model.constraint_weights() […]
- set_control_voltages(control_voltages: Tensor)[source]#
Change the control_voltages of the network.
Example
>>> dnpu.set_control_voltages(torch.tensor([1.0, 2.0, 3.0, 4.0]))
- Parameters:
control_voltages (torch.Tensor) – New value of the control_voltages. One dimensional tensor.
- set_forward_pass(forward_pass_type: str)[source]#
Sets the type of forward pass that is going to be used.
- Parameters:
forward_pass_type (str) – It indicates if the forward pass for more than one DNPU devices on time-multiplexing will be executed using vectorisation or a for loop. The available options are ‘vec’ or ‘for’. By default it uses the vectorised version.
- sw_train(configs: dict, info: dict, model_state_dict: OrderedDict | None = None)[source]#
It helps swap the DNPU to training mode. While evaluation happens in hardware, training happens in software. This function sets the nn.Module to training mode and swaps the processor (typically to software). Checks if the voltage ranges of the new processor are the same as the ones of the old.
- Parameters:
arg (Processor or dict) – Either a processor or configs dictionary to be applied to the existing one.
- Raises:
AssertionError – If control voltages of the new processor are different than the ones used for training the DNPU.
- training: bool#
- brainspy.processors.dnpu.merge_electrode_data(input_data, control_data, input_data_indices: Tensor, control_indices) array | Tensor[source]#
Merge data from two electrodes with the specified indices for each. Need to indicate whether numpy or torch is used. The result will have the same type as the input.
Example
>>> inputs = np.array([[1.0, 3.0], [2.0, 4.0]]) >>> control_voltages = np.array([[5.0, 7.0], [6.0, 8.0]]) >>> input_indices = [0, 2] >>> control_voltage_indices = [3, 1] >>> electrodes.merge_electrode_data( ... inputs=inputs, ... control_voltages=control_voltages, ... input_indices=input_indices, ... control_voltage_indices=control_voltage_indices, ... use_torch=False, ... ) np.array([[1.0, 7.0, 3.0, 5.0], [2.0, 8.0, 4.0, 6.0]])
Merging two arrays of size 2x2, resulting in an array of size 2x4.
- Parameters:
inputs (np.array or torch.tensor) – Data for the input electrodes.
control_voltages (np.array or torch.tensor) – Data for the control electrodes.
input_indices (iterable of int) – Indices of the input electrodes.
control_voltage_indices (iterable of int) – Indices of the control electrodes.
- Returns:
result – Array or tensor with merged data.
- Return type:
np.array or torch.tensor
- brainspy.processors.dnpu.random() x in the interval [0, 1).#
brainspy.processors.processor module#
For providing seamless validation on hardware, the brains-py library leverages on the concept of Processor, which allows to change the internal behaviour of the class to measure on simulations or hardware measurements, while maintaining an equivalent behaviour for the public methods. Internally, the Processor class also deals with the differences between input/output signals that are inherent to measuring in hardware or simulations (e.g., noise or length of signal). This greatly facilitates the reuse of the same code for simulations and hardware measurements. Without this feature, the research on these materials becomes difficult and time-consuming to reproduce in hardware.
- class brainspy.processors.processor.Processor(configs: dict, info: dict | None = None, model_state_dict: OrderedDict | None = None, average_plateaus: bool = True)[source]#
Bases:
ModuleA class for handling the usage and swapping of hardware and software processors.
- Parameters:
configs (dict) – Configs dictionary, documented in init method.
info (dict) – Info dictionary, documented in init method.
model_state_dict (collections.OrderedDict, optional. Documented in) –
method. (init) –
average_plateaus (bool) – When there are plateaus that are higher than 1, wether if average the whole plateau or not.
- close()[source]#
Closes the driver related to the NI Tasks if the main processor is hardware. If the main processor is a simulation model, this does nothing.
- format_targets(x: Tensor) Tensor[source]#
The hardware processor uses a waveform to represent points (see 5.1 in Introduction of the Wiki). Each point is represented with some slope and some plateau points. When passing through the hardware, there will be a difference between the output from the device and the input (in points). This function is used for the targets to have the same length in shape as the outputs. It simply repeats each point in the input as many times as there are points in the plateau. In this way, targets can then be compared against hardware outputs in the loss function.
- Parameters:
x (torch.Tensor) – Targets of the supervised learning problem, that will be extended to have the same length shape as the outputs from the processor.
- forward(x: Tensor) Tensor[source]#
Run a forward pass through the processor. It creates plateaus from data points before sending the data to the simulation or hardware processor. The hardware processor will internally create the slopes to the plateaus. The simulation processor does not need slopes.
- Parameters:
x (torch.Tensor) – Input data. It is expected to have a shape of [batch_size, activation_electrode_no].
- Returns:
Output data.
- Return type:
torch.Tensor
- get_activation_electrode_no()[source]#
Get the number of activation electrodes of the processor.
- Returns:
The number of activation electrodes of the processor.
- Return type:
int
- get_clipping_value()[source]#
Get the output clipping. For hardware, take it from the info dictionary. For simulation, use the existing method.
- Returns:
The output clipping of the processor.
- Return type:
torch.Tensor or list
- get_readout_electrode_no()[source]#
Get the number of readout electrodes of the processor.
- Returns:
The number of readout electrodes of the processor.
- Return type:
int
- get_voltage_ranges() Tensor[source]#
Get the voltage ranges of input of the the processor, whether it is hardware or simulation.
- Returns:
Voltage ranges.
- Return type:
torch.Tensor
- is_hardware() bool[source]#
Method to indicate whether this is a hardware processor.
- Returns:
True if hardware, False if software.
- Return type:
bool
- load_processor(configs: dict, info: dict | None = None, model_state_dict: OrderedDict | None = None)[source]#
Create a processor depending on the provided settings.
Info dictionary will override existing one if not None, otherwise existing one will be used.
For simulation, simply create the processor and set the effects (as is required).
For hardware, first check if the activation voltage ranges are in configs. If not, take them from electrode_info in the info dictionary. Create a warning with the values of the electrode effects. Make sure the amplification is set.
Method is called by constructor but can also be called externally.
- Parameters:
configs (dict) – Configs dictionary, documented in init method.
info (dict, optional) – Info dictionary, documented in init method, by default None.
model_state_dict (collections.OrderedDict, optional) – State dictionary for the simulation model, by default None. Documented in init method.
- Raises:
NotImplementedError – In case the processor type is not recognized.
UserWarning – When a hardware processor is created; contains the values of the electrode effects.
- swap(configs: dict, info: dict, model_state_dict: OrderedDict | None = None)[source]#
Re-initialize: load the processor again from the given dictionaries.
- Parameters:
configs (dict) – Configs dictionary, documented in init method of this class.
info (dict) – Info dictionary, documented in init method of this class.
model_state_dict (collections.OrderedDict, optional) – State dictionary for the simulation model, by default None. Documented in init method of this class.
- training: bool#
- brainspy.processors.processor.get_electrode_info(configs)[source]#
Retrieve electrode information from the data sampling configurations.
- Parameters:
configs (1.) – Refer to HardwareProcessor for a description of the keys.
- Returns:
electrode_info – Configuration dictionary containing all the keys related to the electrode information:
1. electrode_no: int Total number of electrodes in the device
activation_electrodes: dict
2.1 electrode_no: int Number of activation electrodes used for gathering the data
2.2 voltage_ranges: list Voltage ranges used for gathering the data. It contains the ranges per electrode, where the shape is (electrode_no,2). Being 2 the minimum and maximum of the ranges, respectively.
output_electrodes: dict
3.1 electrode_no : int Number of output electrodes used for gathering the data
3.2 clipping_value: list[float,float] Value used to apply a clipping to the sampling data within the specified values.
3.3 amplification: float Amplification correction factor used in the device to correct the amplification applied to the output current in order to convert it into voltage before its readout.
- Return type:
dict