From 1a907eac3c5d0ba2f6d5341399a05bc41dd50154 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Thu, 2 May 2024 14:32:58 -0400 Subject: [PATCH 001/316] [CORE] Added base neuron model cython module --- farms_network/core/__init__.py | 18 +++++ farms_network/core/neuron.pxd | 71 +++++++++++++++++ farms_network/core/neuron.pyx | 141 +++++++++++++++++++++++++++++++++ 3 files changed, 230 insertions(+) create mode 100644 farms_network/core/__init__.py create mode 100644 farms_network/core/neuron.pxd create mode 100644 farms_network/core/neuron.pyx diff --git a/farms_network/core/__init__.py b/farms_network/core/__init__.py new file mode 100644 index 0000000..f411717 --- /dev/null +++ b/farms_network/core/__init__.py @@ -0,0 +1,18 @@ +""" +---------------------------------------------------------------------- +Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty +Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +----------------------------------------------------------------------- +""" diff --git a/farms_network/core/neuron.pxd b/farms_network/core/neuron.pxd new file mode 100644 index 0000000..81bad54 --- /dev/null +++ b/farms_network/core/neuron.pxd @@ -0,0 +1,71 @@ +""" + +----------------------------------------------------------------------- +Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty +Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +----------------------------------------------------------------------- + +Header for Neuron Base Struture. + +""" + + +cdef packed struct Neuron: + # Generic parameters + unsigned int nstates + unsigned int nparameters + unsigned int ninputs + + char* model_type + char* name + + # Parameters + void* parameters + + # Functions + void ode_rhs_c( + double time, + double[:] states, + double[:] dstates, + double[:] inputs, + double[:] weights, + double[:] noise, + double drive, + Neuron neuron + ) + double output_c(double time, double[:] states, Neuron neuron) + + +cdef: + void ode_rhs_c( + double time, + double[:] states, + double[:] dstates, + double[:] inputs, + double[:] weights, + double[:] noise, + double drive, + Neuron neuron + ) + double output_c(double time, double[:] states, Neuron neuron) + + +cdef class PyNeuron: + """ Python interface to Neuron C-Structure""" + + cdef: + Neuron* _neuron + char* _name + char* _model_type diff --git a/farms_network/core/neuron.pyx b/farms_network/core/neuron.pyx new file mode 100644 index 0000000..b90de1a --- /dev/null +++ b/farms_network/core/neuron.pyx @@ -0,0 +1,141 @@ +""" +----------------------------------------------------------------------- +Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty +Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +----------------------------------------------------------------------- +""" + +from libc.stdio cimport printf +from libc.stdlib cimport free, malloc +from libc.string cimport strcpy, strdup + + +cdef void ode_rhs_c( + double time, + double[:] states, + double[:] dstates, + double[:] inputs, + double[:] weights, + double[:] noise, + double drive, + Neuron neuron +): + """ Neuron ODE """ + printf("Base implementation of ODE C function \n") + + +cdef double output_c(double time, double[:] states, Neuron neuron): + """ Neuron output """ + printf("Base implementation of neuron output C function \n") + return 0.0 + + +cdef class PyNeuron: + + def __cinit__(self): + self._neuron = malloc(sizeof(Neuron)) + if self._neuron is NULL: + raise MemoryError("Failed to allocate memory for Neuron") + self._neuron.name = NULL + self._neuron.model_type = NULL + self._neuron.ode_rhs_c = ode_rhs_c + self._neuron.output_c = output_c + + def __dealloc__(self): + if self._neuron.name is not NULL: + free(self._neuron.name) + if self._neuron.model_type is not NULL: + free(self._neuron.model_type) + if self._neuron is not NULL: + free(self._neuron) + + def __init__(self, name, model_type, nstates, nparameters, ninputs): + self.name = name + self.model_type = model_type + self.nstates = nstates + self.nparameters = nparameters + self.ninputs = ninputs + + # Property methods for name + @property + def name(self): + if self._neuron.name is NULL: + return None + return self._neuron.name.decode('UTF-8') + + @name.setter + def name(self, value): + if self._neuron.name is not NULL: + free(self._neuron.name) + self._neuron.name = strdup(value.encode('UTF-8')) + + # Property methods for model_type + @property + def model_type(self): + if self._neuron.model_type is NULL: + return None + return self._neuron.model_type.decode('UTF-8') + + @model_type.setter + def model_type(self, value): + if self._neuron.model_type is not NULL: + free(self._neuron.model_type) + self._neuron.model_type = strdup(value.encode('UTF-8')) + + # Property methods for nstates + @property + def nstates(self): + return self._neuron.nstates + + @nstates.setter + def nstates(self, value): + self._neuron.nstates = value + + # Property methods for nparameters + @property + def nparameters(self): + return self._neuron.nparameters + + @nparameters.setter + def nparameters(self, value): + self._neuron.nparameters = value + + # Property methods for ninputs + @property + def ninputs(self): + return self._neuron.ninputs + + @ninputs.setter + def ninputs(self, value): + self._neuron.ninputs = value + + # Methods to wrap the ODE and output functions + def ode_rhs(self, double time, states, dstates, inputs, weights, noise, drive): + cdef double[:] c_states = states + cdef double[:] c_dstates = dstates + cdef double[:] c_inputs = inputs + cdef double[:] c_weights = weights + cdef double[:] c_noise = noise + # Call the C function directly + self._neuron.ode_rhs_c( + time, c_states, c_dstates, c_inputs, c_weights, c_noise, drive, self._neuron[0] + ) + + def output(self, double time, states): + cdef double[:] c_states = states + # Call the C function and return its result + return self._neuron.output_c( + time, c_states, self._neuron[0] + ) From 967007b6c3884332a92da9179f8cd713c6339d79 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Thu, 2 May 2024 15:47:54 -0400 Subject: [PATCH 002/316] [CORE] Added parameters attr to cdef PyNeuron class --- farms_network/core/neuron.pxd | 2 ++ farms_network/core/neuron.pyx | 1 + 2 files changed, 3 insertions(+) diff --git a/farms_network/core/neuron.pxd b/farms_network/core/neuron.pxd index 81bad54..4b6c21b 100644 --- a/farms_network/core/neuron.pxd +++ b/farms_network/core/neuron.pxd @@ -69,3 +69,5 @@ cdef class PyNeuron: Neuron* _neuron char* _name char* _model_type + + void* _parameters diff --git a/farms_network/core/neuron.pyx b/farms_network/core/neuron.pyx index b90de1a..c753974 100644 --- a/farms_network/core/neuron.pyx +++ b/farms_network/core/neuron.pyx @@ -43,6 +43,7 @@ cdef double output_c(double time, double[:] states, Neuron neuron): cdef class PyNeuron: + """ Python interface to Neuron C-Structure""" def __cinit__(self): self._neuron = malloc(sizeof(Neuron)) From e4e2e9fc258d9885df9ce887482a0816d4227642 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Wed, 8 May 2024 15:30:31 -0400 Subject: [PATCH 003/316] [CORE][NEURON] Updated properties and methods in PyNeuron --- farms_network/core/neuron.pxd | 4 ---- farms_network/core/neuron.pyx | 32 ++++++++++++-------------------- 2 files changed, 12 insertions(+), 24 deletions(-) diff --git a/farms_network/core/neuron.pxd b/farms_network/core/neuron.pxd index 4b6c21b..333e05b 100644 --- a/farms_network/core/neuron.pxd +++ b/farms_network/core/neuron.pxd @@ -67,7 +67,3 @@ cdef class PyNeuron: cdef: Neuron* _neuron - char* _name - char* _model_type - - void* _parameters diff --git a/farms_network/core/neuron.pyx b/farms_network/core/neuron.pyx index c753974..f00e59b 100644 --- a/farms_network/core/neuron.pyx +++ b/farms_network/core/neuron.pyx @@ -19,7 +19,9 @@ limitations under the License. from libc.stdio cimport printf from libc.stdlib cimport free, malloc -from libc.string cimport strcpy, strdup +from libc.string cimport strdup + +from .options import NeuronOptions cdef void ode_rhs_c( @@ -50,7 +52,7 @@ cdef class PyNeuron: if self._neuron is NULL: raise MemoryError("Failed to allocate memory for Neuron") self._neuron.name = NULL - self._neuron.model_type = NULL + self._neuron.model_type = strdup("base".encode('UTF-8')) self._neuron.ode_rhs_c = ode_rhs_c self._neuron.output_c = output_c @@ -62,13 +64,17 @@ cdef class PyNeuron: if self._neuron is not NULL: free(self._neuron) - def __init__(self, name, model_type, nstates, nparameters, ninputs): + def __init__(self, name, ninputs): self.name = name - self.model_type = model_type - self.nstates = nstates - self.nparameters = nparameters self.ninputs = ninputs + @classmethod + def from_options(cls, neuron_options: NeuronOptions): + """ From neuron options """ + name: str = neuron_options.name + ninputs: int = neuron_options.ninputs + return cls(name, ninputs) + # Property methods for name @property def name(self): @@ -89,30 +95,16 @@ cdef class PyNeuron: return None return self._neuron.model_type.decode('UTF-8') - @model_type.setter - def model_type(self, value): - if self._neuron.model_type is not NULL: - free(self._neuron.model_type) - self._neuron.model_type = strdup(value.encode('UTF-8')) - # Property methods for nstates @property def nstates(self): return self._neuron.nstates - @nstates.setter - def nstates(self, value): - self._neuron.nstates = value - # Property methods for nparameters @property def nparameters(self): return self._neuron.nparameters - @nparameters.setter - def nparameters(self, value): - self._neuron.nparameters = value - # Property methods for ninputs @property def ninputs(self): From 1474456efa508196332f78e9191280c7aa936432 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Thu, 9 May 2024 08:56:20 -0400 Subject: [PATCH 004/316] [CORE] Added LI Danner neuron model --- farms_network/core/li_danner.pxd | 58 ++++++++++++++ farms_network/core/li_danner.pyx | 127 +++++++++++++++++++++++++++++++ 2 files changed, 185 insertions(+) create mode 100644 farms_network/core/li_danner.pxd create mode 100644 farms_network/core/li_danner.pyx diff --git a/farms_network/core/li_danner.pxd b/farms_network/core/li_danner.pxd new file mode 100644 index 0000000..a721f1f --- /dev/null +++ b/farms_network/core/li_danner.pxd @@ -0,0 +1,58 @@ +""" +----------------------------------------------------------------------- +Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty +Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +----------------------------------------------------------------------- + +Leaky Integrator Neuron Based on Danner et.al. +""" + +from .neuron cimport Neuron, PyNeuron + + +cdef packed struct LIDannerNeuronParameters: + + double c_m # pF + double g_leak # nS + double e_leak # mV + double v_max # mV + double v_thr # mV + double g_syn_e # nS + double g_syn_i # nS + double e_syn_e # mV + double e_syn_i # mV + double m_e # - + double m_i # - + double b_e # - + double b_i # - + + +cdef: + void ode_rhs_c( + double time, + double[:] states, + double[:] dstates, + double[:] inputs, + double[:] weights, + double[:] noise, + double drive, + Neuron neuron + ) + double output_c(double time, double[:] states, Neuron neuron) + double neuron_inputs_eval_c(double _neuron_out, double _weight) + + +cdef class PyLIDannerNeuron(PyNeuron): + """ Python interface to Leaky Integrator Neuron C-Structure """ diff --git a/farms_network/core/li_danner.pyx b/farms_network/core/li_danner.pyx new file mode 100644 index 0000000..7f65648 --- /dev/null +++ b/farms_network/core/li_danner.pyx @@ -0,0 +1,127 @@ +""" +----------------------------------------------------------------------- +Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty +Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +----------------------------------------------------------------------- + +Leaky Integrator Neuron based on Danner et.al. +""" + +from libc.stdio cimport printf +from libc.stdlib cimport free, malloc +from libc.string cimport strdup + + +cdef void ode_rhs_c( + double time, + double[:] states, + double[:] dstates, + double[:] inputs, + double[:] weights, + double[:] noise, + double drive, + Neuron neuron +): + """ ODE """ + + # # Parameters + cdef LIDannerNeuronParameters params = ( + neuron.parameters + )[0] + # States + cdef double state_v = states[0] + + # External Modulation + cdef double alpha = drive + + # Drive inputs + cdef double d_e = params.m_e * alpha + params.b_e # Excitatory drive + cdef double d_i = params.m_i * alpha + params.b_i # Inhibitory drive + + # Ileak + cdef double i_leak = params.g_leak * (state_v - params.e_leak) + + # ISyn_Excitatory + cdef double i_syn_e = params.g_syn_e * d_e * (state_v - params.e_syn_e) + + # ISyn_Inhibitory + cdef double i_syn_i = params.g_syn_i * d_i * (state_v - params.e_syn_i) + + # Neuron inputs + cdef double _sum = 0.0 + cdef unsigned int j + cdef double _neuron_out + cdef double _weight + + # for j in range(neuron.ninputs): + # _sum += neuron_inputs_eval_c(inputs[j], weights[j]) + + # # noise current + # cdef double i_noise = c_noise_current_update( + # self.state_noise.c_get_value(), &(self.noise_params) + # ) + # self.state_noise.c_set_value(i_noise) + + # dV + cdef i_noise = 0.0 + dstates[0] = ( + -(i_leak + i_syn_e + i_syn_i + i_noise + _sum)/params.c_m + ) + + +cdef double output_c(double time, double[:] states, Neuron neuron): + """ Neuron output. """ + + cdef double state_v = states[0] + cdef double _n_out = 1000.0 + + # cdef LIDannerNeuronParameters params = neuron.parameters + + # if state_v >= params.v_max: + # _n_out = 1.0 + # elif (params.v_thr <= state_v) and (state_v < params.v_max): + # _n_out = (state_v - params.v_thr) / (params.v_max - params.v_thr) + # elif state_v < params.v_thr: + # _n_out = 0.0 + return _n_out + + +cdef double neuron_inputs_eval_c(double _neuron_out, double _weight): + return 0.0 + + +cdef class PyLIDannerNeuron(PyNeuron): + """ Python interface to Leaky Integrator Neuron C-Structure """ + + def __cinit__(self): + self._neuron.model_type = strdup("LI_DANNER".encode('UTF-8')) + # override default ode and out methods + self._neuron.ode_rhs_c = ode_rhs_c + self._neuron.output_c = output_c + # parameters + self._neuron.parameters = malloc( + sizeof(LIDannerNeuronParameters) + ) + + def __dealloc__(self): + if self._neuron.name is not NULL: + free(self._neuron.parameters) + + @classmethod + def from_options(cls, neuron_options: NeuronOptions): + """ From neuron options """ + name: str = neuron_options.name + ninputs: int = neuron_options.ninputs + return cls(name, ninputs) From ff8a7b8c27278e71cadfefe6940fefde1092e772 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Thu, 9 May 2024 08:59:29 -0400 Subject: [PATCH 005/316] [CORE] Added options module --- farms_network/core/options.py | 119 ++++++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 farms_network/core/options.py diff --git a/farms_network/core/options.py b/farms_network/core/options.py new file mode 100644 index 0000000..413618e --- /dev/null +++ b/farms_network/core/options.py @@ -0,0 +1,119 @@ +""" Options to configure the neural and network models """ + + +from typing import List + +from farms_core.options import Options + + +class NeuronOptions(Options): + """ Base class for defining neuron options """ + + def __init__(self, **kwargs): + """ Initialize """ + super().__init__() + self.name: str = kwargs.pop("name") + + self.parameters: NeuronParameterOptions = kwargs.pop("parameters") + self.visual: NeuronVisualOptions = kwargs.pop("visual") + self.state: NeuronStateOptions = kwargs.pop("state", None) + + self.nstates: int = 0 + self.nparameters: int = 0 + self.ninputs: int = 0 + + +class NetworkOptions(Options): + """ Base class for neural network options """ + + def __init__(self, **kwargs): + super().__init__() + self.directed: bool = kwargs.pop("directed") + self.multigraph: bool = kwargs.pop("multigraph") + self.name: str = kwargs.pop("name") + + +class NeuronParameterOptions(Options): + """ Base class for neuron specific parameters """ + + def __init__(self): + super().__init__() + + +class NeuronStateOptions(Options): + """ Base class for neuron specific state options """ + def __init__(self, **kwargs): + super().__init__() + self.initial: List[float] = kwargs.pop("initial") + + +class NeuronVisualOptions(Options): + """ Base class for neuron visualization parameters """ + + def __init__(self, **kwargs): + super().__init__() + self.position: List[float] = kwargs.pop("position") + self.color: List[float] = kwargs.pop("color") + self.layer: str = kwargs.pop("layer") + + +class LIDannerParameterOptions(NeuronParameterOptions): + """ Class to define the parameters of Leaky integrator danner neuron model """ + + def __init__(self, **kwargs): + super().__init__() + self.c_m = kwargs.pop("c_m") # pF + self.g_leak = kwargs.pop("g_leak") # nS + self.e_leak = kwargs.pop("e_leak") # mV + self.v_max = kwargs.pop("v_max") # mV + self.v_thr = kwargs.pop("v_thr") # mV + self.g_syn_e = kwargs.pop("g_syn_e") # nS + self.g_syn_i = kwargs.pop("g_syn_i") # nS + self.e_syn_e = kwargs.pop("e_syn_e") # mV + self.e_syn_i = kwargs.pop("e_syn_i") # mV + self.m_e = kwargs.pop("m_e") # - + self.m_i = kwargs.pop("m_i") # - + self.b_e = kwargs.pop("b_e") # - + self.b_i = kwargs.pop("b_i") # - + + @classmethod + def defaults(cls): + """ Get the default parameters for LI Danner Neuron model """ + + options = {} + + options["c_m"] = 10.0 + options["g_leak"] = 2.8 + options["e_leak"] = -60.0 + options["v_max"] = 0.0 + options["v_thr"] = -50.0 + options["g_syn_e"] = 10.0 + options["g_syn_i"] = 10.0 + options["e_syn_e"] = -10.0 + options["e_syn_i"] = -75.0 + options["m_e"] = 0.0 + options["m_i"] = 0.0 + options["b_e"] = 0.0 + options["b_i"] = 0.0 + + return cls(**options) + + +class LIDannerNeuronOptions(NeuronOptions): + """ Class to define the properties of Leaky integrator danner neuron model """ + + def __init__(self, **kwargs): + """ Initialize """ + super().__init__( + name=kwargs.pop("name"), + parameters=kwargs.pop("parameters"), + visual=kwargs.pop("visual"), + state=kwargs.pop("state"), + ) + self.nstates = 2 + self.nparameters = 13 + + self.ninputs = kwargs.pop("ninputs") + + if kwargs: + raise Exception(f'Unknown kwargs: {kwargs}') From 7fbc23a4efb6a1ce5bce87f0eaaa3a6f73987567 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Fri, 10 May 2024 10:08:13 -0400 Subject: [PATCH 006/316] [CORE] Added network cython module files --- farms_network/core/network.pxd | 6 ++++++ farms_network/core/network.pyx | 15 +++++++++++++++ 2 files changed, 21 insertions(+) create mode 100644 farms_network/core/network.pxd create mode 100644 farms_network/core/network.pyx diff --git a/farms_network/core/network.pxd b/farms_network/core/network.pxd new file mode 100644 index 0000000..62308ed --- /dev/null +++ b/farms_network/core/network.pxd @@ -0,0 +1,6 @@ +# cdef struct Network: +# double* state +# double* dstates +# Neuron* neurons + +# void ode_c(Network network) diff --git a/farms_network/core/network.pyx b/farms_network/core/network.pyx new file mode 100644 index 0000000..0eca138 --- /dev/null +++ b/farms_network/core/network.pyx @@ -0,0 +1,15 @@ +""" Neural Network """ + +from .neuron cimport Neuron +from libc.string cimport strdup + + +cdef class PyNetwork: + """ Python interface to Network ODE """ + + def __cinit__(self, nneurons: int): + """ C initialization for manual memory allocation """ + + def __dealloc__(self): + """ Deallocate any manual memory as part of clean up """ + pass From 487d474cccfcf02c16172ec0dc2ac29b49cc9bb8 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Fri, 10 May 2024 11:46:02 -0400 Subject: [PATCH 007/316] [DUCKS] Renamed docs to ducks to match farmsim --- {docs => ducks}/Makefile | 0 {docs => ducks}/source/conf.py | 28 ++++++++++++++++++++++++---- {docs => ducks}/source/index.rst | 9 +++------ 3 files changed, 27 insertions(+), 10 deletions(-) rename {docs => ducks}/Makefile (100%) rename {docs => ducks}/source/conf.py (76%) rename {docs => ducks}/source/index.rst (94%) diff --git a/docs/Makefile b/ducks/Makefile similarity index 100% rename from docs/Makefile rename to ducks/Makefile diff --git a/docs/source/conf.py b/ducks/source/conf.py similarity index 76% rename from docs/source/conf.py rename to ducks/source/conf.py index 2a2ddbb..7fdf19e 100644 --- a/docs/source/conf.py +++ b/ducks/source/conf.py @@ -22,12 +22,26 @@ author = 'FARMSIM' master_doc = 'index' +# Github +github_username = "farmsim" +github_repository = "farms_network" + # -- General configuration --------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. -extensions = ["sphinx.ext.autodoc",] +extensions = [ + "sphinx.ext.autodoc", + "sphinxcontrib.bibtex", + "sphinxcontrib.youtube", + "sphinx_copybutton", + "sphinx_favicon", + "sphinx_reredirects", + "sphinx_toolbox.collapse", + "sphinx_toolbox.sidebar_links", + "sphinx_toolbox.github", +] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] @@ -35,15 +49,21 @@ # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path. -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] +exclude_patterns = [ + '_build', + 'Thumbs.db', + '.DS_Store' +] +# Bibtex references for sphinxcontrib.bibtex +bibtex_bibfiles = ["references.bib"] # -- Options for HTML output ------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -# -html_theme = 'sphinx_rtd_theme' +# sphinx_rtd_theme +html_theme = "furo" # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, diff --git a/docs/source/index.rst b/ducks/source/index.rst similarity index 94% rename from docs/source/index.rst rename to ducks/source/index.rst index 9baf6e8..e2abf63 100644 --- a/docs/source/index.rst +++ b/ducks/source/index.rst @@ -3,7 +3,7 @@ You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. -Welcome to FARMS_NETWORK's documentation! +Welcome to FARMS Network's documentation! =========================================== .. warning:: Farmers are currently busy! Documentation is work in progress!! @@ -12,11 +12,8 @@ Welcome to FARMS_NETWORK's documentation! :maxdepth: 2 :caption: Contents: - tutorials - modules - tests - contributing - +.. sidebar-links:: + :github: Installation steps ------------------- From d2f6a83f0ad94070c75e69bbc555b975114a1434 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Fri, 10 May 2024 11:46:45 -0400 Subject: [PATCH 008/316] [DUCKS] Added requirements file to build docs --- ducks/source/requirements.txt | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 ducks/source/requirements.txt diff --git a/ducks/source/requirements.txt b/ducks/source/requirements.txt new file mode 100644 index 0000000..bb3e68a --- /dev/null +++ b/ducks/source/requirements.txt @@ -0,0 +1,21 @@ +Sphinx==7.3.7 +furo==2024.5.6 +sphinx-autodoc-typehints==2.1.0 +sphinx-basic-ng==1.0.0b2 +sphinx-copybutton==0.5.2 +sphinx-favicon==1.0.1 +sphinx-jinja2-compat==0.2.0.post1 +sphinx-prompt==1.8.0 +sphinx-reredirects==0.1.3 +sphinx-rtd-theme==2.0.0 +sphinx-tabs==3.4.5 +sphinx-toolbox==3.5.0 +sphinxcontrib-applehelp==1.0.8 +sphinxcontrib-bibtex==2.6.2 +sphinxcontrib-devhelp==1.0.6 +sphinxcontrib-htmlhelp==2.0.5 +sphinxcontrib-jquery==4.1 +sphinxcontrib-jsmath==1.0.1 +sphinxcontrib-qthelp==1.0.7 +sphinxcontrib-serializinghtml==1.1.10 +sphinxcontrib-youtube==1.4.1 From d1a3a14b45a4991a8d88f59ac38f15f4eb95e448 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Fri, 10 May 2024 11:47:24 -0400 Subject: [PATCH 009/316] [DUCKS] Added references bib file --- ducks/source/references.bib | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 ducks/source/references.bib diff --git a/ducks/source/references.bib b/ducks/source/references.bib new file mode 100644 index 0000000..1d55e83 --- /dev/null +++ b/ducks/source/references.bib @@ -0,0 +1,18 @@ +@misc{arreguit-FARMS-2024, + title = {{{FARMS}}: {{Framework}} for {{Animal}} and {{Robot Modeling}} and {{Simulation}}}, + shorttitle = {{{FARMS}}}, + author = {Arreguit, Jonathan and Ramalingasetty, Shravan Tata and Ijspeert, Auke}, + year = {2024}, + month = mar, + primaryclass = {New Results}, + pages = {2023.09.25.559130}, + publisher = {bioRxiv}, + doi = {10.1101/2023.09.25.559130}, + url = {https://www.biorxiv.org/content/10.1101/2023.09.25.559130v2}, + urldate = {2024-03-14}, + abstract = {The study of animal locomotion and neuromechanical control offers valuable insights for advancing research in neuroscience, biomechanics, and robotics. We have developed FARMS (Framework for Animal and Robot Modeling and Simulation), an open-source, interdisciplinary framework, designed to facilitate access to neuromechanical simulations for modeling, simulation, and analysis of animal locomotion and bio-inspired robotic systems. By providing an accessible and user-friendly platform, FARMS aims to lower the barriers for researchers to explore the complex interactions between the nervous system, musculoskeletal structures, and their environment. Integrating the MuJoCo physics engine in a modular manner, FARMS enables realistic simulations and fosters collaboration among neuroscientists, biologists, and roboticists. FARMS has already been extensively used to study locomotion in animals such as mice, drosophila, fish, salamanders, and centipedes, serving as a platform to investigate the role of central pattern generators and sensory feedback. This article provides an overview of the FARMS framework, discusses its interdisciplinary approach, showcases its versatility through specific case studies, and highlights its effectiveness in advancing our understanding of locomotion. In particular, we show how we used FARMS to study amphibious locomotion by presenting experimental demonstrations across morphologies and environments based on neural controllers with central pattern generators and sensory feedback circuits models. Overall, the goal of FARMS is to contribute to a deeper understanding of animal locomotion, the development of innovative bio-inspired robotic systems, and promote accessibility in neuromechanical research.}, + archiveprefix = {bioRxiv}, + chapter = {New Results}, + copyright = {{\copyright} 2024, Posted by Cold Spring Harbor Laboratory. This pre-print is available under a Creative Commons License (Attribution-NonCommercial 4.0 International), CC BY-NC 4.0, as described at http://creativecommons.org/licenses/by-nc/4.0/}, + langid = {english} +} From 02d591c9c81b75b77642791ad91f2edd3017e208 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Fri, 10 May 2024 11:51:55 -0400 Subject: [PATCH 010/316] [CI] Changed docs pipeline to farms-core-integration branch --- .github/workflows/github-page-builder.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/github-page-builder.yml b/.github/workflows/github-page-builder.yml index bf7e9c4..5e2ad0d 100644 --- a/.github/workflows/github-page-builder.yml +++ b/.github/workflows/github-page-builder.yml @@ -12,7 +12,9 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v4 + with: + ref: farms-core-integration - name: Set up Python uses: actions/setup-python@v2 @@ -23,7 +25,7 @@ jobs: run: | sudo apt-get update sudo apt-get install -y make automake gcc g++ subversion python3-dev - pip install sphinx sphinx-rtd-theme cython numpy + pip install ducks/source/requirements.txt - name: Build HTML run: | From 45d1126c86fea1febe63b2055b31fa78d0d9fb25 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Fri, 10 May 2024 13:38:25 -0400 Subject: [PATCH 011/316] [DUCKS] Added usuage section rst files --- ducks/source/usage/installation.rst | 50 +++++++++++++++++++++++++++++ ducks/source/usage/overview.rst | 0 2 files changed, 50 insertions(+) create mode 100644 ducks/source/usage/installation.rst create mode 100644 ducks/source/usage/overview.rst diff --git a/ducks/source/usage/installation.rst b/ducks/source/usage/installation.rst new file mode 100644 index 0000000..1142943 --- /dev/null +++ b/ducks/source/usage/installation.rst @@ -0,0 +1,50 @@ +Installation steps +------------------- + +Requirements +^^^^^^^^^^^^ + +"Code is only currently tested with Python3" + +The installation requires Cython. To install Cython, + +.. code-block:: console + + $ pip install cython + +(**NOTE** : *Depending on your system installation you may want to use pip3 instead of pip to use python3*) + +Installation +^^^^^^^^^^^^^ + +- Navigate to the root of the directory: + + .. code-block:: console + + $ cd farms_network + +- Install system wide with pip: + + .. code-block:: console + + $ pip install . + +- Install for user with pip: + + .. code-block:: console + + $ pip install . --user + +- Install in developer mode so that you don't have to install every time you make changes to the repository: + + .. code-block:: console + + $ pip install -e . + + - (You may use `--user` option with the above command if you want install only for a user): + +- To only compile the module: + + .. code-block:: console + + $ python setup.py build_ext -i diff --git a/ducks/source/usage/overview.rst b/ducks/source/usage/overview.rst new file mode 100644 index 0000000..e69de29 From 66b4f1f893ffe5d7827e98ba12b8e5f1d810a471 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Fri, 10 May 2024 13:39:04 -0400 Subject: [PATCH 012/316] [DUCKS] Updated main index rst file --- ducks/source/index.rst | 55 +++--------------------------------------- 1 file changed, 3 insertions(+), 52 deletions(-) diff --git a/ducks/source/index.rst b/ducks/source/index.rst index e2abf63..3cd6482 100644 --- a/ducks/source/index.rst +++ b/ducks/source/index.rst @@ -12,61 +12,12 @@ Welcome to FARMS Network's documentation! :maxdepth: 2 :caption: Contents: + usage/installation + core/index + .. sidebar-links:: :github: -Installation steps -------------------- - -Requirements -^^^^^^^^^^^^ - -"Code is only currently tested with Python3" - -The installation requires Cython. To install Cython, - -.. code-block:: console - - $ pip install cython - -(**NOTE** : *Depending on your system installation you may want to use pip3 instead of pip to use python3*) - -Installation -^^^^^^^^^^^^^ - -- Navigate to the root of the directory: - - .. code-block:: console - - $ cd farms_network - -- Install system wide with pip: - - .. code-block:: console - - $ pip install . - -- Install for user with pip: - - .. code-block:: console - - $ pip install . --user - -- Install in developer mode so that you don't have to install every time you make changes to the repository: - - .. code-block:: console - - $ pip install -e . - - - (You may use `--user` option with the above command if you want install only for a user): - -- To only compile the module: - - .. code-block:: console - - $ python setup.py build_ext -i - - Indices and tables ================== From 04dde89c4469f7090c1853bbd9d7ef94036727ce Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Fri, 10 May 2024 13:40:45 -0400 Subject: [PATCH 013/316] [DUCKS] Added core rst files --- ducks/source/core/index.rst | 8 ++++++++ ducks/source/core/neuron.rst | 7 +++++++ 2 files changed, 15 insertions(+) create mode 100644 ducks/source/core/index.rst create mode 100644 ducks/source/core/neuron.rst diff --git a/ducks/source/core/index.rst b/ducks/source/core/index.rst new file mode 100644 index 0000000..4a17fbb --- /dev/null +++ b/ducks/source/core/index.rst @@ -0,0 +1,8 @@ +Core +==== + +.. toctree:: + :maxdepth: 3 + :caption: Contents: + +.. include:: neuron.rst diff --git a/ducks/source/core/neuron.rst b/ducks/source/core/neuron.rst new file mode 100644 index 0000000..f5c6f8f --- /dev/null +++ b/ducks/source/core/neuron.rst @@ -0,0 +1,7 @@ +Neuron +------ + +.. automodule:: farms_network.core.neuron + :members: + :show-inheritance: + :noindex: From ea4d79d514e79e6f0ae179247a8a20018a612f10 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Fri, 10 May 2024 15:45:21 -0400 Subject: [PATCH 014/316] [CORE] Added li danner nap model cython files --- farms_network/core/li_nap_danner.pxd | 0 farms_network/core/li_nap_danner.pyx | 120 +++++++++++++++++++++++++++ 2 files changed, 120 insertions(+) create mode 100644 farms_network/core/li_nap_danner.pxd create mode 100644 farms_network/core/li_nap_danner.pyx diff --git a/farms_network/core/li_nap_danner.pxd b/farms_network/core/li_nap_danner.pxd new file mode 100644 index 0000000..e69de29 diff --git a/farms_network/core/li_nap_danner.pyx b/farms_network/core/li_nap_danner.pyx new file mode 100644 index 0000000..bd1fa5b --- /dev/null +++ b/farms_network/core/li_nap_danner.pyx @@ -0,0 +1,120 @@ +""" +----------------------------------------------------------------------- +Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty +Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +----------------------------------------------------------------------- + +Leaky Integrator Neuron with persistent sodium channel based on Danner et.al. +""" + +from libc.stdio cimport printf +from libc.stdlib cimport free, malloc +from libc.string cimport strdup + + +cdef void ode_rhs_c( + double time, + double[:] states, + double[:] dstates, + double[:] inputs, + double[:] weights, + double[:] noise, + double drive, + Neuron neuron +): + """ ODE """ + + # # Parameters + cdef LINapDannerNeuronParameters params = ( + neuron.parameters + )[0] + # States + cdef double state_v = states[0] + + # Drive inputs + cdef double d_e = params.m_e * drive + params.b_e # Excitatory drive + cdef double d_i = params.m_i * drive + params.b_i # Inhibitory drive + + # Ileak + cdef double i_leak = params.g_leak * (state_v - params.e_leak) + + # ISyn_Excitatory + cdef double i_syn_e = params.g_syn_e * d_e * (state_v - params.e_syn_e) + + # ISyn_Inhibitory + cdef double i_syn_i = params.g_syn_i * d_i * (state_v - params.e_syn_i) + + # Neuron inputs + cdef double _sum = 0.0 + cdef unsigned int j + cdef double _neuron_out + cdef double _weight + + # for j in range(neuron.ninputs): + # _sum += neuron_inputs_eval_c(inputs[j], weights[j]) + + # # noise current + # cdef double i_noise = c_noise_current_update( + # self.state_noise.c_get_value(), &(self.noise_params) + # ) + # self.state_noise.c_set_value(i_noise) + + # dV + cdef i_noise = 0.0 + dstates[0] = ( + -(i_leak + i_syn_e + i_syn_i + i_noise + _sum)/params.c_m + ) + + +cdef double output_c(double time, double[:] states, Neuron neuron): + """ Neuron output. """ + + cdef double state_v = states[0] + cdef double _n_out = 1000.0 + + # cdef LIDannerNeuronParameters params = neuron.parameters + + # if state_v >= params.v_max: + # _n_out = 1.0 + # elif (params.v_thr <= state_v) and (state_v < params.v_max): + # _n_out = (state_v - params.v_thr) / (params.v_max - params.v_thr) + # elif state_v < params.v_thr: + # _n_out = 0.0 + return _n_out + + +cdef double neuron_inputs_eval_c(double _neuron_out, double _weight): + return 0.0 + + +cdef class PyLINapDannerNeuron(PyNeuron): + """ Python interface to Leaky Integrator Neuron with persistence sodium C-Structure """ + + def __cinit__(self): + # override defaults + self._neuron.model_type = strdup("LI_NAP_DANNER".encode('UTF-8')) + self._neuron.nstates = 2 + self._neuron.nparameters = 13 + # methods + self._neuron.ode_rhs_c = ode_rhs_c + self._neuron.output_c = output_c + # parameters + self._neuron.parameters = malloc( + sizeof(LIDannerNeuronParameters) + ) + + def __dealloc__(self): + if self._neuron.name is not NULL: + free(self._neuron.parameters) From 5752a363b040a915a0b4feb94632f7698b654029 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Mon, 13 May 2024 11:41:25 -0400 Subject: [PATCH 015/316] [CI] Adapted workflow to renamed docs->ducks --- .github/workflows/github-page-builder.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/github-page-builder.yml b/.github/workflows/github-page-builder.yml index 5e2ad0d..23a0414 100644 --- a/.github/workflows/github-page-builder.yml +++ b/.github/workflows/github-page-builder.yml @@ -29,10 +29,10 @@ jobs: - name: Build HTML run: | - cd docs + cd ducks make html cd .. - mv docs/build/html/ public/ + mv ducks/build/html/ public/ - name: Cache documentation artifacts uses: actions/upload-artifact@v4 From ba663f0620ab864752c9dc274599e6b249f7606e Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Mon, 13 May 2024 11:42:51 -0400 Subject: [PATCH 016/316] [CI] Added workflow on push to farms-core-integration branch --- .github/workflows/github-page-builder.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/github-page-builder.yml b/.github/workflows/github-page-builder.yml index 23a0414..3217833 100644 --- a/.github/workflows/github-page-builder.yml +++ b/.github/workflows/github-page-builder.yml @@ -5,6 +5,7 @@ on: push: branches: - main + - farms-core-integration jobs: build: From 4e9aa5023c06d28a4e4bdbd5d793a86b7a0102e5 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Mon, 13 May 2024 11:44:07 -0400 Subject: [PATCH 017/316] [CI] Fixed missing -r option in pip install sphinx dependencies --- .github/workflows/github-page-builder.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/github-page-builder.yml b/.github/workflows/github-page-builder.yml index 3217833..8cfe425 100644 --- a/.github/workflows/github-page-builder.yml +++ b/.github/workflows/github-page-builder.yml @@ -26,7 +26,7 @@ jobs: run: | sudo apt-get update sudo apt-get install -y make automake gcc g++ subversion python3-dev - pip install ducks/source/requirements.txt + pip install -r ducks/source/requirements.txt - name: Build HTML run: | From 4df2fe395d700ccd6592e5d2ad7605bb3ed69a2e Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Mon, 13 May 2024 11:46:22 -0400 Subject: [PATCH 018/316] [CI] Removed Deploy run if condition for only main branch --- .github/workflows/github-page-builder.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/github-page-builder.yml b/.github/workflows/github-page-builder.yml index 8cfe425..69d5fe8 100644 --- a/.github/workflows/github-page-builder.yml +++ b/.github/workflows/github-page-builder.yml @@ -43,7 +43,7 @@ jobs: - name: Deploy uses: peaceiris/actions-gh-pages@v4 - if: github.ref == 'refs/heads/main' + # if: github.ref == 'refs/heads/main' with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./public From ff21a2a5e8e6ee4fc3ebe19bde35c67d5368de8f Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Mon, 20 May 2024 14:22:50 -0400 Subject: [PATCH 019/316] [MAIN] Updated setup file to compile all core and data cython modules --- setup.py | 113 +++++-------------------------------------------------- 1 file changed, 9 insertions(+), 104 deletions(-) diff --git a/setup.py b/setup.py index 76971d3..6703a4a 100644 --- a/setup.py +++ b/setup.py @@ -33,109 +33,15 @@ # directive_defaults = Cython.Compiler.Options.get_directive_defaults() extensions = [ - Extension("farms_network.network_generator", - ["farms_network/network_generator.pyx"], - include_dirs=[numpy.get_include(), get_include()], - extra_compile_args=['-ffast-math', '-O3'], - extra_link_args=['-O3'] - ), - Extension("farms_network.oscillator", - ["farms_network/oscillator.pyx"], - include_dirs=[numpy.get_include(), get_include()], - extra_compile_args=['-ffast-math', '-O3'], - extra_link_args=['-O3'] - ), - Extension("farms_network.hopf_oscillator", - ["farms_network/hopf_oscillator.pyx"], - include_dirs=[numpy.get_include(), get_include()], - extra_compile_args=['-ffast-math', '-O3'], - extra_link_args=['-O3'] - ), - Extension("farms_network.morphed_oscillator", - ["farms_network/morphed_oscillator.pyx"], - include_dirs=[numpy.get_include(), get_include()], - extra_compile_args=['-ffast-math', '-O3'], - extra_link_args=['-O3'] - ), - Extension("farms_network.leaky_integrator", - ["farms_network/leaky_integrator.pyx"], - include_dirs=[numpy.get_include(), get_include()], - extra_compile_args=['-ffast-math', '-O3'], - extra_link_args=['-O3'] - ), - Extension("farms_network.neuron", - ["farms_network/neuron.pyx"], - include_dirs=[numpy.get_include(), get_include()], - extra_compile_args=['-ffast-math', '-O3'], - extra_link_args=['-O3'] - ), - Extension("farms_network.lif_danner_nap", - ["farms_network/lif_danner_nap.pyx"], - include_dirs=[numpy.get_include(), get_include()], - extra_compile_args=['-ffast-math', '-O3'], - extra_link_args=['-O3'] - ), - Extension("farms_network.lif_danner", - ["farms_network/lif_danner.pyx"], - include_dirs=[numpy.get_include(), get_include()], - extra_compile_args=['-ffast-math', '-O3'], - extra_link_args=['-O3'] - ), - Extension("farms_network.lif_daun_interneuron", - ["farms_network/lif_daun_interneuron.pyx"], - include_dirs=[numpy.get_include(), get_include()], - extra_compile_args=['-ffast-math', '-O3'], - extra_link_args=['-O3'] - ), - Extension("farms_network.hh_daun_motorneuron", - ["farms_network/hh_daun_motorneuron.pyx"], - include_dirs=[numpy.get_include(), get_include()], - extra_compile_args=['-ffast-math', '-O3'], - extra_link_args=['-O3'] - ), - Extension("farms_network.sensory_neuron", - ["farms_network/sensory_neuron.pyx"], - include_dirs=[numpy.get_include(), get_include()], - extra_compile_args=['-ffast-math', '-O3'], - extra_link_args=['-O3'] - ), - Extension("farms_network.fitzhugh_nagumo", - ["farms_network/fitzhugh_nagumo.pyx"], - include_dirs=[numpy.get_include(), get_include()], - extra_compile_args=['-ffast-math', '-O3'], - extra_link_args=['-O3'] - ), - Extension("farms_network.matsuoka_neuron", - ["farms_network/matsuoka_neuron.pyx"], - include_dirs=[numpy.get_include(), get_include()], - extra_compile_args=['-ffast-math', '-O3'], - extra_link_args=['-O3'] - ), - Extension("farms_network.morris_lecar", - ["farms_network/morris_lecar.pyx"], - include_dirs=[numpy.get_include(), get_include()], - extra_compile_args=['-ffast-math', '-O3'], - extra_link_args=['-O3'] - ), - Extension("farms_network.relu", - ["farms_network/relu.pyx"], - include_dirs=[numpy.get_include(), get_include()], - extra_compile_args=['-ffast-math', '-O3'], - extra_link_args=['-O3'] - ), - Extension("farms_network.integrators", - ["farms_network/integrators.pyx"], - include_dirs=[numpy.get_include(), get_include()], - extra_compile_args=['-ffast-math', '-O3'], - extra_link_args=['-O3'] - ), - Extension("farms_network.utils.ornstein_uhlenbeck", - ["farms_network/utils/ornstein_uhlenbeck.pyx"], - include_dirs=[numpy.get_include(), get_include()], - # libraries=["c", "stdc++"], - extra_compile_args=['-ffast-math', '-O3'], - extra_link_args=['-O3'], - ) + Extension( + f"farms_network.{subpackage}.*", + [f"farms_network/{subpackage}/*.pyx"], + include_dirs=[numpy.get_include(), get_include()], + # libraries=["c", "stdc++"], + extra_compile_args=['-ffast-math', '-O3'], + extra_link_args=['-O3'], + ) + for subpackage in ('core', 'data') ] setup( @@ -191,6 +97,5 @@ ), package_data={ 'farms_network': ['*.pxd'], - 'farms_container': ['*.pxd'], }, ) From 8d6033188f743e0f88b8710d872d22564e7c299c Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Wed, 22 May 2024 11:06:04 -0400 Subject: [PATCH 020/316] [MAIN] Fixed numpy include header files path in setup.py --- setup.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 6703a4a..ab6f977 100644 --- a/setup.py +++ b/setup.py @@ -10,6 +10,9 @@ from Cython.Build import cythonize from Cython.Compiler import Options +dist.Distribution().fetch_build_eggs(['farms_core']) +from farms_core import get_include_paths # pylint: disable=wrong-import-position + DEBUG = False Options.docstrings = True @@ -36,7 +39,7 @@ Extension( f"farms_network.{subpackage}.*", [f"farms_network/{subpackage}/*.pyx"], - include_dirs=[numpy.get_include(), get_include()], + include_dirs=[numpy.get_include(),], # libraries=["c", "stdc++"], extra_compile_args=['-ffast-math', '-O3'], extra_link_args=['-O3'], @@ -64,7 +67,7 @@ zip_safe=False, ext_modules=cythonize( extensions, - include_path=[numpy.get_include(), get_include(), 'farms_container'], + include_path=[numpy.get_include()] + get_include_paths(), compiler_directives={ # Directives 'binding': False, From f765aeb7f2ab7141004e82e627000380ef59b64a Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Wed, 22 May 2024 15:04:43 -0400 Subject: [PATCH 021/316] [DATA][WIP] Added basic working idea for data structure --- farms_network/data/data.py | 46 +++++++++++++------------ farms_network/data/data_cy.pxd | 57 +++++++++++++++++-------------- farms_network/data/data_cy.pyx | 61 ++++++++++++++++++++++++++++++++++ 3 files changed, 118 insertions(+), 46 deletions(-) diff --git a/farms_network/data/data.py b/farms_network/data/data.py index c3a5eee..11a8c24 100644 --- a/farms_network/data/data.py +++ b/farms_network/data/data.py @@ -20,41 +20,45 @@ """ -from .data_cy import NetworkDataCy, NeuronDataCy, NeuronsDataCy +from .data_cy import NetworkDataCy, StatesArrayCy class NetworkData(NetworkDataCy): """ Network data """ - def __init__(self): - """Network data structure""" + def __init__(self, nstates, states): + """ Network data structure """ - super().__init__() + super().__init__( + nstates, + states + ) - self.neurons = None - self.connectivity = None - self.states = None - self.inputs = None - self.outputs = None +class StatesArray(StatesArrayCy): + """ State array data """ -class NeuronsData(NeuronsDataCy): - """ Neuronal data """ + def __init__(self, array): + super().__init__(array) - def __init__(self): - """ Neurons data """ - super().__init__() +# class NeuronData(NeuronDataCy): +# """ Base class for representing an arbitrary neuron data """ +# def __init__(self): +# """Neuron data initialization """ +# super().__init__() -class NeuronData(NeuronDataCy): - """ Base class for representing an arbitrary neuron data """ +# self.states = None +# self.constants = None +# self.variables = None - def __init__(self): - """Neuron data initialization """ - super().__init__() +def main(): - self.consts = None - self.variables = None + data = NetworkData(100) + + +if __name__ == '__main__': + main() diff --git a/farms_network/data/data_cy.pxd b/farms_network/data/data_cy.pxd index c928731..81a9efc 100644 --- a/farms_network/data/data_cy.pxd +++ b/farms_network/data/data_cy.pxd @@ -18,39 +18,46 @@ limitations under the License. """ -################################## -########## Network data ########## -################################## +from farms_core.array.array_cy cimport DoubleArray1D, DoubleArray2D + cdef class NetworkDataCy: - """ Network data """ - def __init__(self): - """ network data initialization """ + cdef: + public StatesArrayCy states - super().__init__() - ... + cdef: + public neurons + public connectivity -################################## -########## Neurons Data ########## -################################## - -cdef class NeuronsDataCy: - """ Neurons data """ +cdef class NeuronDataCy: + """ Neuron data """ - def __init__(self): - """ neurons data initialization """ - super().__init__() - ... - +cdef class StatesArrayCy(DoubleArray2D): + """ State array """ -cdef class NeuronDataCy: - """ Neuron data """ - def __init__(self): - """ neurons data initialization """ +cdef class DStatesArrayCy(DoubleArray2D): + """ DStates array """ + + +cdef class ParametersArrayCy(DoubleArray2D): + """ Parameters array """ + + +cdef class OutputsArrayCy(DoubleArray2D): + """ Outputs array """ + + +cdef class InputsArrayCy(DoubleArray2D): + """ Inputs array """ + + +cdef class DriveArrayCy(DoubleArray2D): + """ Drive Array """ + - super().__init__() - ... +# # class User2DArrayCy(DoubleArray2D): +# # ... diff --git a/farms_network/data/data_cy.pyx b/farms_network/data/data_cy.pyx index 4b3700e..0128ca6 100644 --- a/farms_network/data/data_cy.pyx +++ b/farms_network/data/data_cy.pyx @@ -17,3 +17,64 @@ limitations under the License. ----------------------------------------------------------------------- """ +cimport numpy as cnp + +import numpy as np + + +################################## +########## Network data ########## +################################## +cdef class NetworkDataCy: + """ Network data """ + + def __init__( + self, + nstates: int, + states + # neurons = None, + # connectivity = None, + # inputs = None, + # drives = None, + # outputs = None, + ): + """ neurons data initialization """ + + super().__init__() + self.states = states + # self.neurons = neurons + # self.connectivity = connectivity + # self.inputs = inputs + # self.drives = drives + # self.outputs = outputs + + +cdef class StatesArrayCy(DoubleArray2D): + """ State array """ + + def __init__(self, array): + super().__init__(array) + + +cdef class DStatesArrayCy(DoubleArray2D): + """ DStates array """ + + +cdef class ParametersArrayCy(DoubleArray2D): + """ Parameters array """ + + +cdef class OutputsArrayCy(DoubleArray2D): + """ Outputs array """ + + +cdef class InputsArrayCy(DoubleArray2D): + """ Inputs array """ + + +cdef class DriveArrayCy(DoubleArray2D): + """ Drive Array """ + + +# # class User2DArrayCy(DoubleArray2D): +# # ... From 90c5667c704ea1aee3c13750743fbdf8c2c907d1 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Wed, 22 May 2024 15:05:09 -0400 Subject: [PATCH 022/316] [CORE] Added new option clases for network and neurons --- farms_network/core/options.py | 87 ++++++++++++++++++++++++----------- 1 file changed, 60 insertions(+), 27 deletions(-) diff --git a/farms_network/core/options.py b/farms_network/core/options.py index 413618e..54e4199 100644 --- a/farms_network/core/options.py +++ b/farms_network/core/options.py @@ -6,6 +6,25 @@ from farms_core.options import Options +############################## +# Network Base Class Options # +############################## +class NetworkOptions(Options): + """ Base class for neural network options """ + + def __init__(self, **kwargs): + super().__init__() + self.directed: bool = kwargs.pop("directed") + self.multigraph: bool = kwargs.pop("multigraph") + self.name: str = kwargs.pop("name") + + self.neurons: List[NeuronOptions] = kwargs.pop("neurons") + self.connections: List = kwargs.pop("connections") + + +############################# +# Neuron Base Class Options # +############################# class NeuronOptions(Options): """ Base class for defining neuron options """ @@ -16,23 +35,13 @@ def __init__(self, **kwargs): self.parameters: NeuronParameterOptions = kwargs.pop("parameters") self.visual: NeuronVisualOptions = kwargs.pop("visual") - self.state: NeuronStateOptions = kwargs.pop("state", None) + self.state: NeuronStateOptions = kwargs.pop("state") self.nstates: int = 0 self.nparameters: int = 0 self.ninputs: int = 0 -class NetworkOptions(Options): - """ Base class for neural network options """ - - def __init__(self, **kwargs): - super().__init__() - self.directed: bool = kwargs.pop("directed") - self.multigraph: bool = kwargs.pop("multigraph") - self.name: str = kwargs.pop("name") - - class NeuronParameterOptions(Options): """ Base class for neuron specific parameters """ @@ -42,6 +51,7 @@ def __init__(self): class NeuronStateOptions(Options): """ Base class for neuron specific state options """ + def __init__(self, **kwargs): super().__init__() self.initial: List[float] = kwargs.pop("initial") @@ -52,9 +62,34 @@ class NeuronVisualOptions(Options): def __init__(self, **kwargs): super().__init__() - self.position: List[float] = kwargs.pop("position") - self.color: List[float] = kwargs.pop("color") - self.layer: str = kwargs.pop("layer") + self.position: List[float] = kwargs.pop("position", [0.0, 0.0, 0.0]) + self.radius: float = kwargs.pop("radius", 1.0) + self.color: List[float] = kwargs.pop("color", [1.0, 0.0, 0.0]) + self.label: str = kwargs.pop("label", "n") + self.layer: str = kwargs.pop("layer", "background") + + +######################################### +# Leaky Integrator Danner Model Options # +######################################### +class LIDannerNeuronOptions(NeuronOptions): + """ Class to define the properties of Leaky integrator danner neuron model """ + + def __init__(self, **kwargs): + """ Initialize """ + super().__init__( + name=kwargs.pop("name"), + parameters=kwargs.pop("parameters"), + visual=kwargs.pop("visual"), + state=kwargs.pop("state"), + ) + self.nstates = 2 + self.nparameters = 13 + + self.ninputs = kwargs.pop("ninputs") + + if kwargs: + raise Exception(f'Unknown kwargs: {kwargs}') class LIDannerParameterOptions(NeuronParameterOptions): @@ -99,21 +134,19 @@ def defaults(cls): return cls(**options) -class LIDannerNeuronOptions(NeuronOptions): - """ Class to define the properties of Leaky integrator danner neuron model """ +class LIDannerStateOptions(NeuronStateOptions): + """ LI Danner neuron state options """ def __init__(self, **kwargs): - """ Initialize """ super().__init__( - name=kwargs.pop("name"), - parameters=kwargs.pop("parameters"), - visual=kwargs.pop("visual"), - state=kwargs.pop("state"), + initial=kwargs.pop("initial") ) - self.nstates = 2 - self.nparameters = 13 - - self.ninputs = kwargs.pop("ninputs") + assert len(self.initial) == 2, f"Number of initial states {len(self.initial)} should be 2" - if kwargs: - raise Exception(f'Unknown kwargs: {kwargs}') + @classmethod + def from_kwargs(cls, **kwargs): + """ From neuron specific name-value kwargs """ + v0 = kwargs.pop("v0") + h0 = kwargs.pop("h0") + initial = [v0, h0] + return cls(initial=initial) From 5e1f1e7e5de5c2695e6152109a5636ae5286c5ff Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Wed, 22 May 2024 15:07:22 -0400 Subject: [PATCH 023/316] [CORE] Added test functions in network for proof-of-concept code --- farms_network/core/network.pxd | 55 +++++++++++++++++++++++--- farms_network/core/network.pyx | 70 ++++++++++++++++++++++++++++++++-- 2 files changed, 117 insertions(+), 8 deletions(-) diff --git a/farms_network/core/network.pxd b/farms_network/core/network.pxd index 62308ed..6f205d2 100644 --- a/farms_network/core/network.pxd +++ b/farms_network/core/network.pxd @@ -1,6 +1,51 @@ -# cdef struct Network: -# double* state -# double* dstates -# Neuron* neurons +from .neuron cimport Neuron -# void ode_c(Network network) + +cdef struct Network: + unsigned int nstates + Neuron* neurons + void step() + void ode_c() + + +# cdef void ode_c( +# Neuron *neurons, +# unsigned int iteration, +# unsigned int nneurons, +# double[:] dstates +# ): +# """ Network step function """ +# cdef Neuron neuron +# cdef NeuronData neuron_data +# cdef unsigned int j +# cdef nneurons = sizeof(neurons)/sizeof(neuron) + +# # double[:, :] states +# # double[:, :] dstates +# # double[:, :] inputs +# # double[:, :] weights +# # double[:, :] noise + +# for j in range(nneurons): +# neuron_data = network_data[j] +# neurons[j].ode_rhs_c( +# neuron_data.curr_state, +# dstates, +# inputs, +# weights, +# noise, +# drive, +# neurons[j] +# ) + + +cdef class PyNetwork: + """ Python interface to Network ODE """ + + cdef: + Network *_network + unsigned int nneurons + list neurons + Neuron **c_neurons + + cpdef void test(self) diff --git a/farms_network/core/network.pyx b/farms_network/core/network.pyx index 0eca138..27d6f45 100644 --- a/farms_network/core/network.pyx +++ b/farms_network/core/network.pyx @@ -1,15 +1,79 @@ -""" Neural Network """ +""" +----------------------------------------------------------------------- +Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty +Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne -from .neuron cimport Neuron +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +----------------------------------------------------------------------- +""" + +import numpy as np + +from libc.stdlib cimport free, malloc from libc.string cimport strdup +from .neuron cimport Neuron, PyNeuron +from .li_danner cimport PyLIDannerNeuron + + cdef class PyNetwork: """ Python interface to Network ODE """ def __cinit__(self, nneurons: int): """ C initialization for manual memory allocation """ + self.nneurons = nneurons + self._network = malloc(sizeof(Network)) + if self._network is NULL: + raise MemoryError("Failed to allocate memory for Network") + self.c_neurons = malloc(nneurons * sizeof(Neuron *)) + # if self._network.neurons is NULL: + # raise MemoryError("Failed to allocate memory for neurons in Network") + + self.neurons = [] + cdef Neuron *c_neuron + cdef PyLIDannerNeuron pyn + + for n in range(self.nneurons): + self.neurons.append(PyLIDannerNeuron(f"{n}", 0)) + pyn = self.neurons[n] + c_neuron = (pyn._neuron) + self.c_neurons[n] = c_neuron def __dealloc__(self): """ Deallocate any manual memory as part of clean up """ - pass + if self._network.neurons is not NULL: + free(self._network.neurons) + if self._network is not NULL: + free(self._network) + + cpdef void test(self, data): + cdef double[:] states = np.empty((2,)) + cdef double[:] dstates = np.empty((2,)) + cdef double[:] inputs = np.empty((10,)) + cdef double[:] weights = np.empty((10,)) + cdef double[:] noise = np.empty((10,)) + cdef double drive = 0.0 + cdef Neuron **neurons = self.c_neurons + cdef unsigned int t, j + for t in range(int(1000*1e3)): + for j in range(self.nneurons): + neurons[j][0].nstates + neurons[j][0].ode_rhs_c( + 0.0, states, dstates, inputs, weights, noise, drive, + neurons[j][0] + ) + + def step(self): + """ Network step function """ + self._network.step() From 169429373378492a4b1ac380c927e9ec91e64c0d Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Wed, 3 Jul 2024 11:40:46 +0200 Subject: [PATCH 024/316] [NEURAL] Added option for n_substeps for neural integrator Default is 2 --- farms_network/neural_system.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/farms_network/neural_system.py b/farms_network/neural_system.py index 68d2f3b..5dc51c9 100644 --- a/farms_network/neural_system.py +++ b/farms_network/neural_system.py @@ -109,11 +109,11 @@ def rk5(self, time, state, func, step_size=1e-3): new_state = np.array(state) + (7/90*K1 + 32/90*K3 + 12/90*K4 + 32/90*K5 + 7/90*K6)*(step_size) return new_state - def step(self, dt=1, update=True): + def step(self, dt=1, n_substeps=2, update=True): """Step ode system. """ self.time += dt self.state = self.rk4( - self.time, self.state, self.network.ode, step_size=dt, n_substeps=2 + self.time, self.state, self.network.ode, step_size=dt, n_substeps=n_substeps ) # self.state = c_rk4( # self.time, self.state, self.network.ode, step_size=dt From 0c2b03e002520263bce3e30936eff159ca594665 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Mon, 9 Sep 2024 14:44:48 -0400 Subject: [PATCH 025/316] [CORE] Updated li danner with inline and noexceptions --- farms_network/core/li_danner.pxd | 2 +- farms_network/core/li_danner.pyx | 17 ++++++++++++----- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/farms_network/core/li_danner.pxd b/farms_network/core/li_danner.pxd index a721f1f..23ff0ef 100644 --- a/farms_network/core/li_danner.pxd +++ b/farms_network/core/li_danner.pxd @@ -51,7 +51,7 @@ cdef: Neuron neuron ) double output_c(double time, double[:] states, Neuron neuron) - double neuron_inputs_eval_c(double _neuron_out, double _weight) + inline double neuron_inputs_eval_c(double _neuron_out, double _weight) cdef class PyLIDannerNeuron(PyNeuron): diff --git a/farms_network/core/li_danner.pyx b/farms_network/core/li_danner.pyx index 7f65648..9aea170 100644 --- a/farms_network/core/li_danner.pyx +++ b/farms_network/core/li_danner.pyx @@ -40,6 +40,7 @@ cdef void ode_rhs_c( cdef LIDannerNeuronParameters params = ( neuron.parameters )[0] + # States cdef double state_v = states[0] @@ -63,10 +64,16 @@ cdef void ode_rhs_c( cdef double _sum = 0.0 cdef unsigned int j cdef double _neuron_out + cdef double res + + cdef double _input cdef double _weight - # for j in range(neuron.ninputs): - # _sum += neuron_inputs_eval_c(inputs[j], weights[j]) + + for j in range(10): + _input = inputs[j] + _weight = weights[j] + _sum += neuron_inputs_eval_c(_input, _weight) # # noise current # cdef double i_noise = c_noise_current_update( @@ -75,9 +82,9 @@ cdef void ode_rhs_c( # self.state_noise.c_set_value(i_noise) # dV - cdef i_noise = 0.0 + cdef double i_noise = 0.0 dstates[0] = ( - -(i_leak + i_syn_e + i_syn_i + i_noise + _sum)/params.c_m + -(i_leak + i_syn_e + i_syn_i + i_noise + _sum)# /params.c_m ) @@ -98,7 +105,7 @@ cdef double output_c(double time, double[:] states, Neuron neuron): return _n_out -cdef double neuron_inputs_eval_c(double _neuron_out, double _weight): +cdef inline double neuron_inputs_eval_c(double _neuron_out, double _weight) noexcept: return 0.0 From 16c5ff72069626324a9402079451229243f20e3a Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Mon, 16 Sep 2024 15:25:14 -0400 Subject: [PATCH 026/316] [OPTS][WIP] Added working example of new network options --- farms_network/core/options.py | 279 ++++++++++++++++++++++++++++------ 1 file changed, 234 insertions(+), 45 deletions(-) diff --git a/farms_network/core/options.py b/farms_network/core/options.py index 54e4199..262b37f 100644 --- a/farms_network/core/options.py +++ b/farms_network/core/options.py @@ -1,8 +1,9 @@ """ Options to configure the neural and network models """ - from typing import List +import matplotlib.pyplot as plt +import networkx as nx from farms_core.options import Options @@ -14,51 +15,112 @@ class NetworkOptions(Options): def __init__(self, **kwargs): super().__init__() - self.directed: bool = kwargs.pop("directed") - self.multigraph: bool = kwargs.pop("multigraph") - self.name: str = kwargs.pop("name") - - self.neurons: List[NeuronOptions] = kwargs.pop("neurons") - self.connections: List = kwargs.pop("connections") - - -############################# -# Neuron Base Class Options # -############################# -class NeuronOptions(Options): - """ Base class for defining neuron options """ + _name: str = kwargs.pop("name", "") + # Default properties to make it compatible with networkx + self.directed: bool = kwargs.pop("directed", True) + self.multigraph: bool = kwargs.pop("multigraph", False) + self.graph: dict = { + "name": _name + } + + self.units = None + + self.nodes: List[NodeOptions] = kwargs.pop("nodes", []) + self.edges: List[EdgeOptions] = kwargs.pop("edges", []) + + def add_node(self, options: "NodeOptions"): + """ Add a node if it does not already exist in the list """ + assert isinstance(options, NodeOptions), f"{type(options)} not an instance of NodeOptions" + if options not in self.nodes: + self.nodes.append(options) + else: + print(f"Node {options.name} already exists and will not be added again.") + + def add_edge(self, options: "EdgeOptions"): + """ Add a node if it does not already exist in the list """ + if (options.from_node in self.nodes) and (options.to_node in self.nodes): + self.edges.append(options) + else: + print(f"Edge {options} does not contain the nodes.") + + +########################### +# Node Base Class Options # +########################### +class NodeOptions(Options): + """ Base class for defining node options """ def __init__(self, **kwargs): """ Initialize """ super().__init__() self.name: str = kwargs.pop("name") - self.parameters: NeuronParameterOptions = kwargs.pop("parameters") - self.visual: NeuronVisualOptions = kwargs.pop("visual") - self.state: NeuronStateOptions = kwargs.pop("state") + self.parameters: NodeParameterOptions = kwargs.pop("parameters") + self.visual: NodeVisualOptions = kwargs.pop("visual") + self.state: NodeStateOptions = kwargs.pop("state") + + self._nstates: int = 0 + self._nparameters: int = 0 - self.nstates: int = 0 - self.nparameters: int = 0 - self.ninputs: int = 0 + def __eq__(self, other): + if isinstance(other, NodeOptions): + return self.name == other.name + elif isinstance(other, str): + return self.name == other + return False + def __hash__(self): + return hash(self.name) # Hash based on the node name (or any unique property) -class NeuronParameterOptions(Options): - """ Base class for neuron specific parameters """ + +class NodeParameterOptions(Options): + """ Base class for node specific parameters """ def __init__(self): super().__init__() -class NeuronStateOptions(Options): - """ Base class for neuron specific state options """ +class NodeStateOptions(Options): + """ Base class for node specific state options """ def __init__(self, **kwargs): super().__init__() self.initial: List[float] = kwargs.pop("initial") -class NeuronVisualOptions(Options): - """ Base class for neuron visualization parameters """ +class NodeVisualOptions(Options): + """ Base class for node visualization parameters """ + + def __init__(self, **kwargs): + super().__init__() + self.position: List[float] = kwargs.pop("position", [0.0, 0.0, 0.0]) + self.radius: float = kwargs.pop("radius", 1.0) + self.color: List[float] = kwargs.pop("color", [1.0, 0.0, 0.0]) + self.label: str = kwargs.pop("label", "n") + self.layer: str = kwargs.pop("layer", "background") + self.latex: dict = kwargs.pop("latex", "{}") + + +################ +# Edge Options # +################ +class EdgeOptions(Options): + """ Base class for defining edge options between nodes """ + + def __init__(self, **kwargs): + """ Initialize """ + super().__init__() + + self.from_node: str = kwargs.pop("from_node") + self.to_node: str = kwargs.pop("to_node") + self.weight: float = kwargs.pop("weight") + self.type: str = kwargs.pop("type") + + self.visual: NodeVisualOptions = kwargs.pop("visual") + + +class EdgeVisualOptions(Options): + """ Base class for edge visualization parameters """ def __init__(self, **kwargs): super().__init__() @@ -67,13 +129,14 @@ def __init__(self, **kwargs): self.color: List[float] = kwargs.pop("color", [1.0, 0.0, 0.0]) self.label: str = kwargs.pop("label", "n") self.layer: str = kwargs.pop("layer", "background") + self.latex: dict = kwargs.pop("latex", "{}") ######################################### # Leaky Integrator Danner Model Options # ######################################### -class LIDannerNeuronOptions(NeuronOptions): - """ Class to define the properties of Leaky integrator danner neuron model """ +class LIDannerNodeOptions(NodeOptions): + """ Class to define the properties of Leaky integrator danner node model """ def __init__(self, **kwargs): """ Initialize """ @@ -83,17 +146,88 @@ def __init__(self, **kwargs): visual=kwargs.pop("visual"), state=kwargs.pop("state"), ) - self.nstates = 2 - self.nparameters = 13 + self._nstates = 2 + self._nparameters = 13 + + if kwargs: + raise Exception(f'Unknown kwargs: {kwargs}') + + +class LIDannerParameterOptions(NodeParameterOptions): + """ Class to define the parameters of Leaky integrator danner node model """ + + def __init__(self, **kwargs): + super().__init__() + self.c_m = kwargs.pop("c_m") # pF + self.g_leak = kwargs.pop("g_leak") # nS + self.e_leak = kwargs.pop("e_leak") # mV + self.v_max = kwargs.pop("v_max") # mV + self.v_thr = kwargs.pop("v_thr") # mV + self.g_syn_e = kwargs.pop("g_syn_e") # nS + self.g_syn_i = kwargs.pop("g_syn_i") # nS + self.e_syn_e = kwargs.pop("e_syn_e") # mV + self.e_syn_i = kwargs.pop("e_syn_i") # mV + + @classmethod + def defaults(cls): + """ Get the default parameters for LI Danner Node model """ + + options = {} + + options["c_m"] = 10.0 + options["g_leak"] = 2.8 + options["e_leak"] = -60.0 + options["v_max"] = 0.0 + options["v_thr"] = -50.0 + options["g_syn_e"] = 10.0 + options["g_syn_i"] = 10.0 + options["e_syn_e"] = -10.0 + options["e_syn_i"] = -75.0 + + return cls(**options) + + +class LIDannerStateOptions(NodeStateOptions): + """ LI Danner node state options """ + + def __init__(self, **kwargs): + super().__init__( + initial=kwargs.pop("initial") + ) + assert len(self.initial) == 2, f"Number of initial states {len(self.initial)} should be 2" - self.ninputs = kwargs.pop("ninputs") + @classmethod + def from_kwargs(cls, **kwargs): + """ From node specific name-value kwargs """ + v0 = kwargs.pop("v0") + h0 = kwargs.pop("h0") + initial = [v0, h0] + return cls(initial=initial) + + +################################################## +# Leaky Integrator With NaP Danner Model Options # +################################################## +class LIDannerNodeOptions(NodeOptions): + """ Class to define the properties of Leaky integrator danner node model """ + + def __init__(self, **kwargs): + """ Initialize """ + super().__init__( + name=kwargs.pop("name"), + parameters=kwargs.pop("parameters"), + visual=kwargs.pop("visual"), + state=kwargs.pop("state"), + ) + self._nstates = 2 + self._nparameters = 9 if kwargs: raise Exception(f'Unknown kwargs: {kwargs}') -class LIDannerParameterOptions(NeuronParameterOptions): - """ Class to define the parameters of Leaky integrator danner neuron model """ +class LIDannerParameterOptions(NodeParameterOptions): + """ Class to define the parameters of Leaky integrator danner node model """ def __init__(self, **kwargs): super().__init__() @@ -106,14 +240,10 @@ def __init__(self, **kwargs): self.g_syn_i = kwargs.pop("g_syn_i") # nS self.e_syn_e = kwargs.pop("e_syn_e") # mV self.e_syn_i = kwargs.pop("e_syn_i") # mV - self.m_e = kwargs.pop("m_e") # - - self.m_i = kwargs.pop("m_i") # - - self.b_e = kwargs.pop("b_e") # - - self.b_i = kwargs.pop("b_i") # - @classmethod def defaults(cls): - """ Get the default parameters for LI Danner Neuron model """ + """ Get the default parameters for LI Danner Node model """ options = {} @@ -126,16 +256,12 @@ def defaults(cls): options["g_syn_i"] = 10.0 options["e_syn_e"] = -10.0 options["e_syn_i"] = -75.0 - options["m_e"] = 0.0 - options["m_i"] = 0.0 - options["b_e"] = 0.0 - options["b_i"] = 0.0 return cls(**options) -class LIDannerStateOptions(NeuronStateOptions): - """ LI Danner neuron state options """ +class LIDannerStateOptions(NodeStateOptions): + """ LI Danner node state options """ def __init__(self, **kwargs): super().__init__( @@ -145,8 +271,71 @@ def __init__(self, **kwargs): @classmethod def from_kwargs(cls, **kwargs): - """ From neuron specific name-value kwargs """ + """ From node specific name-value kwargs """ v0 = kwargs.pop("v0") h0 = kwargs.pop("h0") initial = [v0, h0] return cls(initial=initial) + + + +######## +# MAIN # +######## +def main(): + """ Main """ + + import typing + + network_opts = NetworkOptions(name="new-network") + + li_opts = LIDannerNodeOptions( + name="li", + parameters=LIDannerParameterOptions.defaults(), + visual=NodeVisualOptions(), + state=LIDannerStateOptions.from_kwargs( + v0=-60.0, h0=0.1 + ), + ) + + print(f"Is hashable {isinstance(li_opts, typing.Hashable)}") + + network_opts.add_node(li_opts) + li_opts = LIDannerNodeOptions( + name="li-2", + parameters=LIDannerParameterOptions.defaults(), + visual=NodeVisualOptions(), + state=LIDannerStateOptions.from_kwargs( + v0=-60.0, h0=0.1 + ), + ) + network_opts.add_node(li_opts) + + network_opts.add_edge( + EdgeOptions( + from_node="li", + to_node="li-2", + weight=0.0, + type="excitatory", + visual=EdgeVisualOptions() + ) + ) + + network_opts.save("/tmp/network_opts.yaml") + + network = NetworkOptions.load("/tmp/network_opts.yaml") + + graph = nx.node_link_graph( + network, + directed=True, + multigraph=False, + link="edges", + name="name", + source="from_node", + target="to_node" + ) + nx.draw(graph) + plt.show() + +if __name__ == '__main__': + main() From 641ff51c623efcdae91ff1ac20f561cb4d197b4f Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Tue, 17 Sep 2024 09:48:00 -0400 Subject: [PATCH 027/316] [CORE] Added LIDannerNaP node options --- farms_network/core/options.py | 79 +++++++++++++++++++++++++---------- 1 file changed, 56 insertions(+), 23 deletions(-) diff --git a/farms_network/core/options.py b/farms_network/core/options.py index 262b37f..544d10e 100644 --- a/farms_network/core/options.py +++ b/farms_network/core/options.py @@ -55,6 +55,7 @@ def __init__(self, **kwargs): super().__init__() self.name: str = kwargs.pop("name") + self.model: str = kwargs.pop("model") self.parameters: NodeParameterOptions = kwargs.pop("parameters") self.visual: NodeVisualOptions = kwargs.pop("visual") self.state: NodeStateOptions = kwargs.pop("state") @@ -118,16 +119,22 @@ def __init__(self, **kwargs): self.visual: NodeVisualOptions = kwargs.pop("visual") + def __eq__(self, other): + if isinstance(other, EdgeOptions): + return ( + (self.source == other.source) and + (self.target == other.target) + ) + return False + class EdgeVisualOptions(Options): """ Base class for edge visualization parameters """ def __init__(self, **kwargs): super().__init__() - self.position: List[float] = kwargs.pop("position", [0.0, 0.0, 0.0]) - self.radius: float = kwargs.pop("radius", 1.0) self.color: List[float] = kwargs.pop("color", [1.0, 0.0, 0.0]) - self.label: str = kwargs.pop("label", "n") + self.label: str = kwargs.pop("label", "") self.layer: str = kwargs.pop("layer", "background") self.latex: dict = kwargs.pop("latex", "{}") @@ -140,8 +147,10 @@ class LIDannerNodeOptions(NodeOptions): def __init__(self, **kwargs): """ Initialize """ + model = "li_danner" super().__init__( name=kwargs.pop("name"), + model=model, parameters=kwargs.pop("parameters"), visual=kwargs.pop("visual"), state=kwargs.pop("state"), @@ -208,32 +217,45 @@ def from_kwargs(cls, **kwargs): ################################################## # Leaky Integrator With NaP Danner Model Options # ################################################## -class LIDannerNodeOptions(NodeOptions): +class LIDannerNaPNodeOptions(NodeOptions): """ Class to define the properties of Leaky integrator danner node model """ def __init__(self, **kwargs): """ Initialize """ + model = "li_danner_nap" super().__init__( name=kwargs.pop("name"), + model=model, parameters=kwargs.pop("parameters"), visual=kwargs.pop("visual"), state=kwargs.pop("state"), ) self._nstates = 2 - self._nparameters = 9 + self._nparameters = 19 if kwargs: raise Exception(f'Unknown kwargs: {kwargs}') -class LIDannerParameterOptions(NodeParameterOptions): +class LIDannerNaPParameterOptions(NodeParameterOptions): """ Class to define the parameters of Leaky integrator danner node model """ def __init__(self, **kwargs): super().__init__() + self.c_m = kwargs.pop("c_m") # pF + self.g_nap = kwargs.pop("g_nap") # nS + self.e_na = kwargs.pop("e_na") # mV + self.v1_2_m = kwargs.pop("v1_2_m") # mV + self.k_m = kwargs.pop("k_m") # + self.v1_2_h = kwargs.pop("v1_2_h") # mV + self.k_h = kwargs.pop("k_h") # + self.v1_2_t = kwargs.pop("v1_2_t") # mV + self.k_t = kwargs.pop("k_t") # self.g_leak = kwargs.pop("g_leak") # nS self.e_leak = kwargs.pop("e_leak") # mV + self.tau_0 = kwargs.pop("tau_0") # mS + self.tau_max = kwargs.pop("tau_max") # mS self.v_max = kwargs.pop("v_max") # mV self.v_thr = kwargs.pop("v_thr") # mV self.g_syn_e = kwargs.pop("g_syn_e") # nS @@ -242,25 +264,35 @@ def __init__(self, **kwargs): self.e_syn_i = kwargs.pop("e_syn_i") # mV @classmethod - def defaults(cls): - """ Get the default parameters for LI Danner Node model """ + def defaults(cls, **kwargs): + """ Get the default parameters for LI NaP Danner Node model """ options = {} - options["c_m"] = 10.0 - options["g_leak"] = 2.8 - options["e_leak"] = -60.0 - options["v_max"] = 0.0 - options["v_thr"] = -50.0 - options["g_syn_e"] = 10.0 - options["g_syn_i"] = 10.0 - options["e_syn_e"] = -10.0 - options["e_syn_i"] = -75.0 + options["c_m"] = kwargs.pop("c_m", 10.0) # pF + options["g_nap"] = kwargs.pop("g_nap", 4.5) # nS + options["e_na"] = kwargs.pop("e_na", 50.0) # mV + options["v1_2_m"] = kwargs.pop("v1_2_m", -40.0) # mV + options["k_m"] = kwargs.pop("k_m", -6.0) # + options["v1_2_h"] = kwargs.pop("v1_2_h", -45.0) # mV + options["k_h"] = kwargs.pop("k_h", 4.0) # + options["v1_2_t"] = kwargs.pop("v1_2_t", -35.0) # mV + options["k_t"] = kwargs.pop("k_t", 15.0) # + options["g_leak"] = kwargs.pop("g_leak", 4.5) # nS + options["e_leak"] = kwargs.pop("e_leak", -62.5) # mV + options["tau_0"] = kwargs.pop("tau_0", 80.0) # mS + options["tau_max"] = kwargs.pop("tau_max", 160.0) # mS + options["v_max"] = kwargs.pop("v_max", 0.0) # mV + options["v_thr"] = kwargs.pop("v_thr", -50.0) # mV + options["g_syn_e"] = kwargs.pop("g_syn_e", 10.0) # nS + options["g_syn_i"] = kwargs.pop("g_syn_i", 10.0) # nS + options["e_syn_e"] = kwargs.pop("e_syn_e", -10.0) # mV + options["e_syn_i"] = kwargs.pop("e_syn_i", -75.0) # mV return cls(**options) -class LIDannerStateOptions(NodeStateOptions): +class LIDannerNaPStateOptions(NodeStateOptions): """ LI Danner node state options """ def __init__(self, **kwargs): @@ -291,9 +323,9 @@ def main(): li_opts = LIDannerNodeOptions( name="li", - parameters=LIDannerParameterOptions.defaults(), + parameters=LIDannerNaPParameterOptions.defaults(), visual=NodeVisualOptions(), - state=LIDannerStateOptions.from_kwargs( + state=LIDannerNaPStateOptions.from_kwargs( v0=-60.0, h0=0.1 ), ) @@ -301,11 +333,11 @@ def main(): print(f"Is hashable {isinstance(li_opts, typing.Hashable)}") network_opts.add_node(li_opts) - li_opts = LIDannerNodeOptions( + li_opts = LIDannerNaPNodeOptions( name="li-2", - parameters=LIDannerParameterOptions.defaults(), + parameters=LIDannerNaPParameterOptions.defaults(), visual=NodeVisualOptions(), - state=LIDannerStateOptions.from_kwargs( + state=LIDannerNaPStateOptions.from_kwargs( v0=-60.0, h0=0.1 ), ) @@ -337,5 +369,6 @@ def main(): nx.draw(graph) plt.show() + if __name__ == '__main__': main() From 1c82d343bb4c59a3aab1d1d4616d15bf2b87498b Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Tue, 17 Sep 2024 09:50:53 -0400 Subject: [PATCH 028/316] [CORE][OPTIONS] Added kwargs to li danner node defaults --- farms_network/core/options.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/farms_network/core/options.py b/farms_network/core/options.py index 544d10e..4e6ba96 100644 --- a/farms_network/core/options.py +++ b/farms_network/core/options.py @@ -178,20 +178,20 @@ def __init__(self, **kwargs): self.e_syn_i = kwargs.pop("e_syn_i") # mV @classmethod - def defaults(cls): + def defaults(cls, **kwargs): """ Get the default parameters for LI Danner Node model """ options = {} - options["c_m"] = 10.0 - options["g_leak"] = 2.8 - options["e_leak"] = -60.0 - options["v_max"] = 0.0 - options["v_thr"] = -50.0 - options["g_syn_e"] = 10.0 - options["g_syn_i"] = 10.0 - options["e_syn_e"] = -10.0 - options["e_syn_i"] = -75.0 + options["c_m"] = kwargs.pop("c_m", 10.0) + options["g_leak"] = kwargs.pop("g_leak", 2.8) + options["e_leak"] = kwargs.pop("e_leak", -60.0) + options["v_max"] = kwargs.pop("v_max", 0.0) + options["v_thr"] = kwargs.pop("v_thr", -50.0) + options["g_syn_e"] = kwargs.pop("g_syn_e", 10.0) + options["g_syn_i"] = kwargs.pop("g_syn_i", 10.0) + options["e_syn_e"] = kwargs.pop("e_syn_e", -10.0) + options["e_syn_i"] = kwargs.pop("e_syn_i", -75.0) return cls(**options) From fd689a57c8c772c47d943c4aa7ec1103b578edc8 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Tue, 17 Sep 2024 13:36:51 -0400 Subject: [PATCH 029/316] [CORE][OPTIONS] Fixed state vars for LI Danner model --- farms_network/core/options.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/farms_network/core/options.py b/farms_network/core/options.py index 4e6ba96..745010c 100644 --- a/farms_network/core/options.py +++ b/farms_network/core/options.py @@ -203,14 +203,13 @@ def __init__(self, **kwargs): super().__init__( initial=kwargs.pop("initial") ) - assert len(self.initial) == 2, f"Number of initial states {len(self.initial)} should be 2" + assert len(self.initial) == 1, f"Number of initial states {len(self.initial)} should be 1" @classmethod def from_kwargs(cls, **kwargs): """ From node specific name-value kwargs """ v0 = kwargs.pop("v0") - h0 = kwargs.pop("h0") - initial = [v0, h0] + initial = [v0,] return cls(initial=initial) From a4957aab69204e7577d1f8d5ca31b22b30731d92 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Tue, 17 Sep 2024 13:37:22 -0400 Subject: [PATCH 030/316] [CORE][OPTS] Added support for adding two network options objects --- farms_network/core/options.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/farms_network/core/options.py b/farms_network/core/options.py index 745010c..d12eb2d 100644 --- a/farms_network/core/options.py +++ b/farms_network/core/options.py @@ -1,6 +1,6 @@ """ Options to configure the neural and network models """ -from typing import List +from typing import List, Self import matplotlib.pyplot as plt import networkx as nx @@ -43,6 +43,15 @@ def add_edge(self, options: "EdgeOptions"): else: print(f"Edge {options} does not contain the nodes.") + def __add__(self, other: Self): + """ Combine two network options """ + assert isinstance(other, NetworkOptions) + for node in other.nodes: + self.add_node(node) + for edge in other.edges: + self.add_edge(edge) + return self + ########################### # Node Base Class Options # @@ -365,7 +374,11 @@ def main(): source="from_node", target="to_node" ) - nx.draw(graph) + nx.draw( + graph, pos=nx.nx_agraph.graphviz_layout(graph), + node_shape="s", + connectionstyle="arc3,rad=-0.2" + ) plt.show() From 4a72a427446c1aa75c3048599a1b0acf93e917e8 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Thu, 19 Sep 2024 11:37:21 -0400 Subject: [PATCH 031/316] [CORE] Removed drive currents from LI Danner model --- farms_network/core/li_danner.pxd | 2 +- farms_network/core/li_danner.pyx | 17 ++--------------- 2 files changed, 3 insertions(+), 16 deletions(-) diff --git a/farms_network/core/li_danner.pxd b/farms_network/core/li_danner.pxd index 23ff0ef..b7e1a23 100644 --- a/farms_network/core/li_danner.pxd +++ b/farms_network/core/li_danner.pxd @@ -49,7 +49,7 @@ cdef: double[:] noise, double drive, Neuron neuron - ) + ) noexcept double output_c(double time, double[:] states, Neuron neuron) inline double neuron_inputs_eval_c(double _neuron_out, double _weight) diff --git a/farms_network/core/li_danner.pyx b/farms_network/core/li_danner.pyx index 9aea170..4c72613 100644 --- a/farms_network/core/li_danner.pyx +++ b/farms_network/core/li_danner.pyx @@ -33,7 +33,7 @@ cdef void ode_rhs_c( double[:] noise, double drive, Neuron neuron -): +) noexcept: """ ODE """ # # Parameters @@ -44,22 +44,9 @@ cdef void ode_rhs_c( # States cdef double state_v = states[0] - # External Modulation - cdef double alpha = drive - - # Drive inputs - cdef double d_e = params.m_e * alpha + params.b_e # Excitatory drive - cdef double d_i = params.m_i * alpha + params.b_i # Inhibitory drive - # Ileak cdef double i_leak = params.g_leak * (state_v - params.e_leak) - # ISyn_Excitatory - cdef double i_syn_e = params.g_syn_e * d_e * (state_v - params.e_syn_e) - - # ISyn_Inhibitory - cdef double i_syn_i = params.g_syn_i * d_i * (state_v - params.e_syn_i) - # Neuron inputs cdef double _sum = 0.0 cdef unsigned int j @@ -84,7 +71,7 @@ cdef void ode_rhs_c( # dV cdef double i_noise = 0.0 dstates[0] = ( - -(i_leak + i_syn_e + i_syn_i + i_noise + _sum)# /params.c_m + -(i_leak + i_noise + _sum)/params.c_m ) From 0276f2769b4ed80cac723491e764baa8a89580a9 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Thu, 19 Sep 2024 11:41:45 -0400 Subject: [PATCH 032/316] [OPTIONS] Added docstring to LIDannerParameter options --- farms_network/core/options.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/farms_network/core/options.py b/farms_network/core/options.py index d12eb2d..6b194ae 100644 --- a/farms_network/core/options.py +++ b/farms_network/core/options.py @@ -172,7 +172,20 @@ def __init__(self, **kwargs): class LIDannerParameterOptions(NodeParameterOptions): - """ Class to define the parameters of Leaky integrator danner node model """ + """ + Class to define the parameters of Leaky Integrator Danner node model. + + Attributes: + c_m (float): Membrane capacitance (in pF). + g_leak (float): Leak conductance (in nS). + e_leak (float): Leak reversal potential (in mV). + v_max (float): Maximum voltage (in mV). + v_thr (float): Threshold voltage (in mV). + g_syn_e (float): Excitatory synaptic conductance (in nS). + g_syn_i (float): Inhibitory synaptic conductance (in nS). + e_syn_e (float): Excitatory synaptic reversal potential (in mV). + e_syn_i (float): Inhibitory synaptic reversal potential (in mV). + """ def __init__(self, **kwargs): super().__init__() @@ -363,7 +376,7 @@ def main(): network_opts.save("/tmp/network_opts.yaml") - network = NetworkOptions.load("/tmp/network_opts.yaml") + network = NetworkOptions.load("/tmp/rhythm_opts.yaml") graph = nx.node_link_graph( network, From e7c1619b4be9e0f2c22afd83587c542ba988ea5f Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Thu, 19 Sep 2024 11:42:20 -0400 Subject: [PATCH 033/316] [CORE] Refactored li nap danner model --- farms_network/core/li_nap_danner.pxd | 22 +++ farms_network/core/li_nap_danner.pyx | 198 +++++++++++++-------------- 2 files changed, 121 insertions(+), 99 deletions(-) diff --git a/farms_network/core/li_nap_danner.pxd b/farms_network/core/li_nap_danner.pxd index e69de29..c34edad 100644 --- a/farms_network/core/li_nap_danner.pxd +++ b/farms_network/core/li_nap_danner.pxd @@ -0,0 +1,22 @@ +""" +----------------------------------------------------------------------- +Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty +Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +----------------------------------------------------------------------- + +Leaky Integrator Neuron Based on Danner et.al. +""" + +from .neuron cimport Neuron, PyNeuron diff --git a/farms_network/core/li_nap_danner.pyx b/farms_network/core/li_nap_danner.pyx index bd1fa5b..0dd21d8 100644 --- a/farms_network/core/li_nap_danner.pyx +++ b/farms_network/core/li_nap_danner.pyx @@ -19,102 +19,102 @@ limitations under the License. Leaky Integrator Neuron with persistent sodium channel based on Danner et.al. """ -from libc.stdio cimport printf -from libc.stdlib cimport free, malloc -from libc.string cimport strdup - - -cdef void ode_rhs_c( - double time, - double[:] states, - double[:] dstates, - double[:] inputs, - double[:] weights, - double[:] noise, - double drive, - Neuron neuron -): - """ ODE """ - - # # Parameters - cdef LINapDannerNeuronParameters params = ( - neuron.parameters - )[0] - # States - cdef double state_v = states[0] - - # Drive inputs - cdef double d_e = params.m_e * drive + params.b_e # Excitatory drive - cdef double d_i = params.m_i * drive + params.b_i # Inhibitory drive - - # Ileak - cdef double i_leak = params.g_leak * (state_v - params.e_leak) - - # ISyn_Excitatory - cdef double i_syn_e = params.g_syn_e * d_e * (state_v - params.e_syn_e) - - # ISyn_Inhibitory - cdef double i_syn_i = params.g_syn_i * d_i * (state_v - params.e_syn_i) - - # Neuron inputs - cdef double _sum = 0.0 - cdef unsigned int j - cdef double _neuron_out - cdef double _weight - - # for j in range(neuron.ninputs): - # _sum += neuron_inputs_eval_c(inputs[j], weights[j]) - - # # noise current - # cdef double i_noise = c_noise_current_update( - # self.state_noise.c_get_value(), &(self.noise_params) - # ) - # self.state_noise.c_set_value(i_noise) - - # dV - cdef i_noise = 0.0 - dstates[0] = ( - -(i_leak + i_syn_e + i_syn_i + i_noise + _sum)/params.c_m - ) - - -cdef double output_c(double time, double[:] states, Neuron neuron): - """ Neuron output. """ - - cdef double state_v = states[0] - cdef double _n_out = 1000.0 - - # cdef LIDannerNeuronParameters params = neuron.parameters - - # if state_v >= params.v_max: - # _n_out = 1.0 - # elif (params.v_thr <= state_v) and (state_v < params.v_max): - # _n_out = (state_v - params.v_thr) / (params.v_max - params.v_thr) - # elif state_v < params.v_thr: - # _n_out = 0.0 - return _n_out - - -cdef double neuron_inputs_eval_c(double _neuron_out, double _weight): - return 0.0 - - -cdef class PyLINapDannerNeuron(PyNeuron): - """ Python interface to Leaky Integrator Neuron with persistence sodium C-Structure """ - - def __cinit__(self): - # override defaults - self._neuron.model_type = strdup("LI_NAP_DANNER".encode('UTF-8')) - self._neuron.nstates = 2 - self._neuron.nparameters = 13 - # methods - self._neuron.ode_rhs_c = ode_rhs_c - self._neuron.output_c = output_c - # parameters - self._neuron.parameters = malloc( - sizeof(LIDannerNeuronParameters) - ) - - def __dealloc__(self): - if self._neuron.name is not NULL: - free(self._neuron.parameters) +# from libc.stdio cimport printf +# from libc.stdlib cimport free, malloc +# from libc.string cimport strdup + + +# cdef void ode_rhs_c( +# double time, +# double[:] states, +# double[:] dstates, +# double[:] inputs, +# double[:] weights, +# double[:] noise, +# double drive, +# Neuron neuron +# ): +# """ ODE """ + +# # # Parameters +# cdef LINapDannerNeuronParameters params = ( +# neuron.parameters +# )[0] +# # States +# cdef double state_v = states[0] + +# # Drive inputs +# cdef double d_e = params.m_e * drive + params.b_e # Excitatory drive +# cdef double d_i = params.m_i * drive + params.b_i # Inhibitory drive + +# # Ileak +# cdef double i_leak = params.g_leak * (state_v - params.e_leak) + +# # ISyn_Excitatory +# cdef double i_syn_e = params.g_syn_e * d_e * (state_v - params.e_syn_e) + +# # ISyn_Inhibitory +# cdef double i_syn_i = params.g_syn_i * d_i * (state_v - params.e_syn_i) + +# # Neuron inputs +# cdef double _sum = 0.0 +# cdef unsigned int j +# cdef double _neuron_out +# cdef double _weight + +# # for j in range(neuron.ninputs): +# # _sum += neuron_inputs_eval_c(inputs[j], weights[j]) + +# # # noise current +# # cdef double i_noise = c_noise_current_update( +# # self.state_noise.c_get_value(), &(self.noise_params) +# # ) +# # self.state_noise.c_set_value(i_noise) + +# # dV +# cdef i_noise = 0.0 +# dstates[0] = ( +# -(i_leak + i_syn_e + i_syn_i + i_noise + _sum)/params.c_m +# ) + + +# cdef double output_c(double time, double[:] states, Neuron neuron): +# """ Neuron output. """ + +# cdef double state_v = states[0] +# cdef double _n_out = 1000.0 + +# # cdef LIDannerNeuronParameters params = neuron.parameters + +# # if state_v >= params.v_max: +# # _n_out = 1.0 +# # elif (params.v_thr <= state_v) and (state_v < params.v_max): +# # _n_out = (state_v - params.v_thr) / (params.v_max - params.v_thr) +# # elif state_v < params.v_thr: +# # _n_out = 0.0 +# return _n_out + + +# cdef double neuron_inputs_eval_c(double _neuron_out, double _weight): +# return 0.0 + + +# cdef class PyLINapDannerNeuron(PyNeuron): +# """ Python interface to Leaky Integrator Neuron with persistence sodium C-Structure """ + +# def __cinit__(self): +# # override defaults +# self._neuron.model_type = strdup("LI_NAP_DANNER".encode('UTF-8')) +# self._neuron.nstates = 2 +# self._neuron.nparameters = 13 +# # methods +# self._neuron.ode_rhs_c = ode_rhs_c +# self._neuron.output_c = output_c +# # parameters +# self._neuron.parameters = malloc( +# sizeof(LIDannerNeuronParameters) +# ) + +# def __dealloc__(self): +# if self._neuron.name is not NULL: +# free(self._neuron.parameters) From 6dc6adc09287ca9a45d60c41d235e4115658d658 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Thu, 19 Sep 2024 11:42:44 -0400 Subject: [PATCH 034/316] [MAIN][WIP] Testing network data --- farms_network/core/network.pxd | 2 +- farms_network/core/network.pyx | 8 +++++++- farms_network/core/neuron.pyx | 2 +- farms_network/data/data.py | 31 +++++++++++++++++++------------ 4 files changed, 28 insertions(+), 15 deletions(-) diff --git a/farms_network/core/network.pxd b/farms_network/core/network.pxd index 6f205d2..44087e1 100644 --- a/farms_network/core/network.pxd +++ b/farms_network/core/network.pxd @@ -48,4 +48,4 @@ cdef class PyNetwork: list neurons Neuron **c_neurons - cpdef void test(self) + cpdef void test(self, data) diff --git a/farms_network/core/network.pyx b/farms_network/core/network.pyx index 27d6f45..cc6c061 100644 --- a/farms_network/core/network.pyx +++ b/farms_network/core/network.pyx @@ -70,7 +70,13 @@ cdef class PyNetwork: for j in range(self.nneurons): neurons[j][0].nstates neurons[j][0].ode_rhs_c( - 0.0, states, dstates, inputs, weights, noise, drive, + 0.0, + states, + dstates, + inputs, + weights, + noise, + drive, neurons[j][0] ) diff --git a/farms_network/core/neuron.pyx b/farms_network/core/neuron.pyx index f00e59b..01591c2 100644 --- a/farms_network/core/neuron.pyx +++ b/farms_network/core/neuron.pyx @@ -33,7 +33,7 @@ cdef void ode_rhs_c( double[:] noise, double drive, Neuron neuron -): +) noexcept: """ Neuron ODE """ printf("Base implementation of ODE C function \n") diff --git a/farms_network/data/data.py b/farms_network/data/data.py index 11a8c24..89f564d 100644 --- a/farms_network/data/data.py +++ b/farms_network/data/data.py @@ -34,25 +34,32 @@ def __init__(self, nstates, states): states ) + _connectivity = None + _states = None + _dstates = None + _outputs = None + _neurons = None -class StatesArray(StatesArrayCy): - """ State array data """ - def __init__(self, array): - super().__init__(array) +class NeuronData(NeuronDataCy): + """ Base class for representing an arbitrary neuron data """ + def __init__(self): + """Neuron data initialization """ -# class NeuronData(NeuronDataCy): -# """ Base class for representing an arbitrary neuron data """ + super().__init__() -# def __init__(self): -# """Neuron data initialization """ + self.states = None + self.output = None + self.variables = None + self.user = None -# super().__init__() -# self.states = None -# self.constants = None -# self.variables = None +class StatesArray(StatesArrayCy): + """ State array data """ + + def __init__(self, array): + super().__init__(array) def main(): From c66ca4fe480ebfcd3e6b278416109a03f80bb796 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Fri, 20 Sep 2024 14:44:02 -0400 Subject: [PATCH 035/316] [CORE] Reorganized files between core and models --- farms_network/{data => core}/data.py | 0 farms_network/{data => core}/data_cy.pxd | 0 farms_network/{data => core}/data_cy.pyx | 8 ----- farms_network/core/neuron.pyx | 6 ++-- farms_network/{data => models}/__init__.py | 0 farms_network/{core => models}/li_danner.pxd | 0 farms_network/{core => models}/li_danner.pyx | 4 --- .../{core => models}/li_nap_danner.pxd | 0 .../{core => models}/li_nap_danner.pyx | 0 farms_network/models/sensory_neuron.pxd | 28 +++++++++++++++++ farms_network/models/sensory_neuron.pyx | 31 +++++++++++++++++++ 11 files changed, 63 insertions(+), 14 deletions(-) rename farms_network/{data => core}/data.py (100%) rename farms_network/{data => core}/data_cy.pxd (100%) rename farms_network/{data => core}/data_cy.pyx (93%) rename farms_network/{data => models}/__init__.py (100%) rename farms_network/{core => models}/li_danner.pxd (100%) rename farms_network/{core => models}/li_danner.pyx (96%) rename farms_network/{core => models}/li_nap_danner.pxd (100%) rename farms_network/{core => models}/li_nap_danner.pyx (100%) create mode 100644 farms_network/models/sensory_neuron.pxd create mode 100644 farms_network/models/sensory_neuron.pyx diff --git a/farms_network/data/data.py b/farms_network/core/data.py similarity index 100% rename from farms_network/data/data.py rename to farms_network/core/data.py diff --git a/farms_network/data/data_cy.pxd b/farms_network/core/data_cy.pxd similarity index 100% rename from farms_network/data/data_cy.pxd rename to farms_network/core/data_cy.pxd diff --git a/farms_network/data/data_cy.pyx b/farms_network/core/data_cy.pyx similarity index 93% rename from farms_network/data/data_cy.pyx rename to farms_network/core/data_cy.pyx index 0128ca6..94c9e17 100644 --- a/farms_network/data/data_cy.pyx +++ b/farms_network/core/data_cy.pyx @@ -60,10 +60,6 @@ cdef class DStatesArrayCy(DoubleArray2D): """ DStates array """ -cdef class ParametersArrayCy(DoubleArray2D): - """ Parameters array """ - - cdef class OutputsArrayCy(DoubleArray2D): """ Outputs array """ @@ -72,9 +68,5 @@ cdef class InputsArrayCy(DoubleArray2D): """ Inputs array """ -cdef class DriveArrayCy(DoubleArray2D): - """ Drive Array """ - - # # class User2DArrayCy(DoubleArray2D): # # ... diff --git a/farms_network/core/neuron.pyx b/farms_network/core/neuron.pyx index 01591c2..d3a8fb7 100644 --- a/farms_network/core/neuron.pyx +++ b/farms_network/core/neuron.pyx @@ -61,6 +61,8 @@ cdef class PyNeuron: free(self._neuron.name) if self._neuron.model_type is not NULL: free(self._neuron.model_type) + if self._neuron.parameters is not NULL: + free(self._neuron.parameters) if self._neuron is not NULL: free(self._neuron) @@ -115,7 +117,7 @@ cdef class PyNeuron: self._neuron.ninputs = value # Methods to wrap the ODE and output functions - def ode_rhs(self, double time, states, dstates, inputs, weights, noise, drive): + def ode_rhs(self, double time, states, dstates, inputs, weights, noise): cdef double[:] c_states = states cdef double[:] c_dstates = dstates cdef double[:] c_inputs = inputs @@ -123,7 +125,7 @@ cdef class PyNeuron: cdef double[:] c_noise = noise # Call the C function directly self._neuron.ode_rhs_c( - time, c_states, c_dstates, c_inputs, c_weights, c_noise, drive, self._neuron[0] + time, c_states, c_dstates, c_inputs, c_weights, c_noise, self._neuron[0] ) def output(self, double time, states): diff --git a/farms_network/data/__init__.py b/farms_network/models/__init__.py similarity index 100% rename from farms_network/data/__init__.py rename to farms_network/models/__init__.py diff --git a/farms_network/core/li_danner.pxd b/farms_network/models/li_danner.pxd similarity index 100% rename from farms_network/core/li_danner.pxd rename to farms_network/models/li_danner.pxd diff --git a/farms_network/core/li_danner.pyx b/farms_network/models/li_danner.pyx similarity index 96% rename from farms_network/core/li_danner.pyx rename to farms_network/models/li_danner.pyx index 4c72613..82fccc2 100644 --- a/farms_network/core/li_danner.pyx +++ b/farms_network/models/li_danner.pyx @@ -109,10 +109,6 @@ cdef class PyLIDannerNeuron(PyNeuron): sizeof(LIDannerNeuronParameters) ) - def __dealloc__(self): - if self._neuron.name is not NULL: - free(self._neuron.parameters) - @classmethod def from_options(cls, neuron_options: NeuronOptions): """ From neuron options """ diff --git a/farms_network/core/li_nap_danner.pxd b/farms_network/models/li_nap_danner.pxd similarity index 100% rename from farms_network/core/li_nap_danner.pxd rename to farms_network/models/li_nap_danner.pxd diff --git a/farms_network/core/li_nap_danner.pyx b/farms_network/models/li_nap_danner.pyx similarity index 100% rename from farms_network/core/li_nap_danner.pyx rename to farms_network/models/li_nap_danner.pyx diff --git a/farms_network/models/sensory_neuron.pxd b/farms_network/models/sensory_neuron.pxd new file mode 100644 index 0000000..92b93b1 --- /dev/null +++ b/farms_network/models/sensory_neuron.pxd @@ -0,0 +1,28 @@ +""" +----------------------------------------------------------------------- +Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty +Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +----------------------------------------------------------------------- + +Sensory afferent neurons. +""" + + +from .neuron cimport Neuron + + +cdef: + void ode_rhs_c(double time, double[:] states, double[:] dstates, Neuron neuron) + double output_c(double time, double[:] states, Neuron neuron) diff --git a/farms_network/models/sensory_neuron.pyx b/farms_network/models/sensory_neuron.pyx new file mode 100644 index 0000000..fb712a5 --- /dev/null +++ b/farms_network/models/sensory_neuron.pyx @@ -0,0 +1,31 @@ +""" +----------------------------------------------------------------------- +Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty +Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +----------------------------------------------------------------------- + +Sensory afferent neurons. + +""" + + +cdef void ode_rhs_c(double time, double[:] state, double[:] dstates, Neuron neuron): + """ Sensory neuron ODE computation """ + ... + + +cdef double output_c(double time, double[:] state, Neuron neuron): + """ Sensory neuron output computation """ + return 0.0 From 7e8e3c464a13c87ea171d90b57ee9065235268ec Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Fri, 20 Sep 2024 15:45:05 -0400 Subject: [PATCH 036/316] [SETUP] Updated cython extensions to core files reorganization --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index ab6f977..6c711ab 100644 --- a/setup.py +++ b/setup.py @@ -44,7 +44,7 @@ extra_compile_args=['-ffast-math', '-O3'], extra_link_args=['-O3'], ) - for subpackage in ('core', 'data') + for subpackage in ('core', 'models') ] setup( From 0198cbf9cd49e6a172d7628250a802440134106b Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Fri, 20 Sep 2024 15:46:25 -0400 Subject: [PATCH 037/316] [MODELS] Fixed imports for file organization --- farms_network/core/neuron.pyx | 4 ++-- farms_network/models/li_danner.pxd | 2 +- farms_network/models/li_nap_danner.pxd | 2 +- farms_network/models/sensory_neuron.pxd | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/farms_network/core/neuron.pyx b/farms_network/core/neuron.pyx index d3a8fb7..696340e 100644 --- a/farms_network/core/neuron.pyx +++ b/farms_network/core/neuron.pyx @@ -117,7 +117,7 @@ cdef class PyNeuron: self._neuron.ninputs = value # Methods to wrap the ODE and output functions - def ode_rhs(self, double time, states, dstates, inputs, weights, noise): + def ode_rhs(self, double time, states, dstates, inputs, weights, noise, drive): cdef double[:] c_states = states cdef double[:] c_dstates = dstates cdef double[:] c_inputs = inputs @@ -125,7 +125,7 @@ cdef class PyNeuron: cdef double[:] c_noise = noise # Call the C function directly self._neuron.ode_rhs_c( - time, c_states, c_dstates, c_inputs, c_weights, c_noise, self._neuron[0] + time, c_states, c_dstates, c_inputs, c_weights, c_noise, drive, self._neuron[0] ) def output(self, double time, states): diff --git a/farms_network/models/li_danner.pxd b/farms_network/models/li_danner.pxd index b7e1a23..b8243fe 100644 --- a/farms_network/models/li_danner.pxd +++ b/farms_network/models/li_danner.pxd @@ -19,7 +19,7 @@ limitations under the License. Leaky Integrator Neuron Based on Danner et.al. """ -from .neuron cimport Neuron, PyNeuron +from ..core.neuron cimport Neuron, PyNeuron cdef packed struct LIDannerNeuronParameters: diff --git a/farms_network/models/li_nap_danner.pxd b/farms_network/models/li_nap_danner.pxd index c34edad..addbd3f 100644 --- a/farms_network/models/li_nap_danner.pxd +++ b/farms_network/models/li_nap_danner.pxd @@ -19,4 +19,4 @@ limitations under the License. Leaky Integrator Neuron Based on Danner et.al. """ -from .neuron cimport Neuron, PyNeuron +from ..core.neuron cimport Neuron, PyNeuron diff --git a/farms_network/models/sensory_neuron.pxd b/farms_network/models/sensory_neuron.pxd index 92b93b1..b0bc4f6 100644 --- a/farms_network/models/sensory_neuron.pxd +++ b/farms_network/models/sensory_neuron.pxd @@ -20,7 +20,7 @@ Sensory afferent neurons. """ -from .neuron cimport Neuron +from ..core.neuron cimport Neuron cdef: From c4c65dac9e0127ac44aa0eb80dc397dfd0d5cf21 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Fri, 20 Sep 2024 16:08:28 -0400 Subject: [PATCH 038/316] [CORE] Fixed imports and types to run test code --- farms_network/core/data.py | 20 +++++++++++--------- farms_network/core/network.pyx | 9 +++++---- farms_network/core/neuron.pyx | 2 +- 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/farms_network/core/data.py b/farms_network/core/data.py index 89f564d..61c3900 100644 --- a/farms_network/core/data.py +++ b/farms_network/core/data.py @@ -34,6 +34,8 @@ def __init__(self, nstates, states): states ) + self.states = states + _connectivity = None _states = None _dstates = None @@ -41,18 +43,18 @@ def __init__(self, nstates, states): _neurons = None -class NeuronData(NeuronDataCy): - """ Base class for representing an arbitrary neuron data """ +# class NeuronData(NeuronDataCy): +# """ Base class for representing an arbitrary neuron data """ - def __init__(self): - """Neuron data initialization """ +# def __init__(self): +# """Neuron data initialization """ - super().__init__() +# super().__init__() - self.states = None - self.output = None - self.variables = None - self.user = None +# self.states = None +# self.output = None +# self.variables = None +# self.user = None class StatesArray(StatesArrayCy): diff --git a/farms_network/core/network.pyx b/farms_network/core/network.pyx index cc6c061..7961be3 100644 --- a/farms_network/core/network.pyx +++ b/farms_network/core/network.pyx @@ -22,9 +22,10 @@ import numpy as np from libc.stdlib cimport free, malloc from libc.string cimport strdup - +from ..models.li_danner cimport PyLIDannerNeuron from .neuron cimport Neuron, PyNeuron -from .li_danner cimport PyLIDannerNeuron + +from tqdm import tqdm cdef class PyNetwork: @@ -58,7 +59,7 @@ cdef class PyNetwork: free(self._network) cpdef void test(self, data): - cdef double[:] states = np.empty((2,)) + cdef double[:] states = data.states.array[0, :] cdef double[:] dstates = np.empty((2,)) cdef double[:] inputs = np.empty((10,)) cdef double[:] weights = np.empty((10,)) @@ -66,7 +67,7 @@ cdef class PyNetwork: cdef double drive = 0.0 cdef Neuron **neurons = self.c_neurons cdef unsigned int t, j - for t in range(int(1000*1e3)): + for t in tqdm(range(int(1000*1e3))): for j in range(self.nneurons): neurons[j][0].nstates neurons[j][0].ode_rhs_c( diff --git a/farms_network/core/neuron.pyx b/farms_network/core/neuron.pyx index 696340e..1fc51d8 100644 --- a/farms_network/core/neuron.pyx +++ b/farms_network/core/neuron.pyx @@ -21,7 +21,7 @@ from libc.stdio cimport printf from libc.stdlib cimport free, malloc from libc.string cimport strdup -from .options import NeuronOptions +from .options import NodeOptions cdef void ode_rhs_c( From c2faabb6e3b9839dca8e946e25acc368c878e430 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Fri, 20 Sep 2024 16:08:55 -0400 Subject: [PATCH 039/316] [SCRATCH] Added files to test during devel --- scratch/profile_network.py | 17 +++++++++++ scratch/test_data.py | 12 ++++++++ scratch/test_neuron.py | 62 ++++++++++++++++++++++++++++++++++++++ scratch/test_options.py | 35 +++++++++++++++++++++ 4 files changed, 126 insertions(+) create mode 100644 scratch/profile_network.py create mode 100644 scratch/test_data.py create mode 100644 scratch/test_neuron.py create mode 100644 scratch/test_options.py diff --git a/scratch/profile_network.py b/scratch/profile_network.py new file mode 100644 index 0000000..bc72c1d --- /dev/null +++ b/scratch/profile_network.py @@ -0,0 +1,17 @@ +""" Profile network implementation """ + + +import numpy as np +from farms_core.utils.profile import profile +from farms_network.core import network +from farms_network.core.data import NetworkData, StatesArray +from farms_network.models import li_danner + +nstates = 100 +niterations = int(100e3) +states = StatesArray(np.empty((niterations, nstates))) + +data = NetworkData(nstates=100, states=states) + +net = network.PyNetwork(nneurons=100) +profile(net.test, data) diff --git a/scratch/test_data.py b/scratch/test_data.py new file mode 100644 index 0000000..df94515 --- /dev/null +++ b/scratch/test_data.py @@ -0,0 +1,12 @@ +import numpy as np +from farms_network.core.data import NetworkData, StatesArray + +nstates = 100 +niterations = 1000 +states = StatesArray( + np.empty((niterations, nstates)) +) + +data = NetworkData(nstates=100, states=states) + +print(data.states.array[0, 0]) diff --git a/scratch/test_neuron.py b/scratch/test_neuron.py new file mode 100644 index 0000000..2395569 --- /dev/null +++ b/scratch/test_neuron.py @@ -0,0 +1,62 @@ +import numpy as np +from farms_network.core import li_danner, network, neuron, options +from farms_network.data.data import NetworkData, StatesArray + + +nstates = 100 +niterations = 1000 +states = StatesArray(np.empty((niterations, nstates))) + +data = NetworkData(nstates=100, states=states) + + +net = network.PyNetwork(nneurons=10) +net.test(data) + +n1_opts = options.NeuronOptions( + name="n1", + parameters=options.NeuronParameterOptions(), + visual=options.NeuronVisualOptions(), + state=options.NeuronStateOptions(initial=[0, 0]), +) +n1 = neuron.PyNeuron.from_options(n1_opts) +n1_opts.save("/tmp/opts.yaml") + + +print(n1.name) +n1.name = "n2" +print(n1.model_type) +print(n1.name) + +states = np.empty((1,)) +dstates = np.empty((1,)) +inputs = np.empty((10,)) +weights = np.empty((10,)) +noise = np.empty((10,)) +drive = 0.0 + +print( + n1.ode_rhs(0.0, states, dstates, inputs, weights, noise, drive) +) + +print( + n1.output(0.0, states) +) + +n2 = li_danner.PyLIDannerNeuron("n2", ninputs=50) + +print(n2.name) +print(n2.model_type) +n2.name = "n2" +print(n2.name) + +states = np.empty((1,)) +dstates = np.empty((1,)) +inputs = np.empty((10,)) +weights = np.empty((10,)) +noise = np.empty((10,)) +drive = 0.0 + +print( + n2.output(0.0, states) +) diff --git a/scratch/test_options.py b/scratch/test_options.py new file mode 100644 index 0000000..5e56639 --- /dev/null +++ b/scratch/test_options.py @@ -0,0 +1,35 @@ +""" Test farms network options """ + + +from pprint import pprint + +import networkx as nx +from farms_core.io.yaml import read_yaml, write_yaml +from farms_core.options import Options +from farms_network.core import options + +param_opts = options.LIDannerParameterOptions.defaults() +state_opts = options.LIDannerStateOptions.from_kwargs(v0=0.0, h0=1.0) +vis_opts = options.NeuronVisualOptions() + +n1_opts = options.LIDannerNeuronOptions( + name="n1", + parameters=param_opts, + visual=vis_opts, + state=state_opts, + ninputs=10, +) + +network = options.NetworkOptions( + directed=True, + multigraph=False, + name="network", + neurons=[n1_opts, n1_opts], + connections=[], +) + +print(type(network)) +network.save("/tmp/opts.yaml") + +pprint(options.NetworkOptions.load("/tmp/opts.yaml")) +print(type(options.NetworkOptions.load("/tmp/opts.yaml"))) From 4947a58ab1a77aa702a9763316aff4a61c40a450 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Mon, 23 Sep 2024 10:50:23 -0400 Subject: [PATCH 040/316] [CORE] Removed drive from ode_rhs_c func signature --- farms_network/core/network.pyx | 3 +-- farms_network/core/neuron.pxd | 2 -- farms_network/core/neuron.pyx | 5 ++--- farms_network/models/li_danner.pxd | 1 - farms_network/models/li_danner.pyx | 1 - 5 files changed, 3 insertions(+), 9 deletions(-) diff --git a/farms_network/core/network.pyx b/farms_network/core/network.pyx index 7961be3..79c19f7 100644 --- a/farms_network/core/network.pyx +++ b/farms_network/core/network.pyx @@ -64,7 +64,7 @@ cdef class PyNetwork: cdef double[:] inputs = np.empty((10,)) cdef double[:] weights = np.empty((10,)) cdef double[:] noise = np.empty((10,)) - cdef double drive = 0.0 + cdef Neuron **neurons = self.c_neurons cdef unsigned int t, j for t in tqdm(range(int(1000*1e3))): @@ -77,7 +77,6 @@ cdef class PyNetwork: inputs, weights, noise, - drive, neurons[j][0] ) diff --git a/farms_network/core/neuron.pxd b/farms_network/core/neuron.pxd index 333e05b..47fcc54 100644 --- a/farms_network/core/neuron.pxd +++ b/farms_network/core/neuron.pxd @@ -42,7 +42,6 @@ cdef packed struct Neuron: double[:] inputs, double[:] weights, double[:] noise, - double drive, Neuron neuron ) double output_c(double time, double[:] states, Neuron neuron) @@ -56,7 +55,6 @@ cdef: double[:] inputs, double[:] weights, double[:] noise, - double drive, Neuron neuron ) double output_c(double time, double[:] states, Neuron neuron) diff --git a/farms_network/core/neuron.pyx b/farms_network/core/neuron.pyx index 1fc51d8..582740f 100644 --- a/farms_network/core/neuron.pyx +++ b/farms_network/core/neuron.pyx @@ -31,7 +31,6 @@ cdef void ode_rhs_c( double[:] inputs, double[:] weights, double[:] noise, - double drive, Neuron neuron ) noexcept: """ Neuron ODE """ @@ -117,7 +116,7 @@ cdef class PyNeuron: self._neuron.ninputs = value # Methods to wrap the ODE and output functions - def ode_rhs(self, double time, states, dstates, inputs, weights, noise, drive): + def ode_rhs(self, double time, states, dstates, inputs, weights, noise): cdef double[:] c_states = states cdef double[:] c_dstates = dstates cdef double[:] c_inputs = inputs @@ -125,7 +124,7 @@ cdef class PyNeuron: cdef double[:] c_noise = noise # Call the C function directly self._neuron.ode_rhs_c( - time, c_states, c_dstates, c_inputs, c_weights, c_noise, drive, self._neuron[0] + time, c_states, c_dstates, c_inputs, c_weights, c_noise, self._neuron[0] ) def output(self, double time, states): diff --git a/farms_network/models/li_danner.pxd b/farms_network/models/li_danner.pxd index b8243fe..a2a1c86 100644 --- a/farms_network/models/li_danner.pxd +++ b/farms_network/models/li_danner.pxd @@ -47,7 +47,6 @@ cdef: double[:] inputs, double[:] weights, double[:] noise, - double drive, Neuron neuron ) noexcept double output_c(double time, double[:] states, Neuron neuron) diff --git a/farms_network/models/li_danner.pyx b/farms_network/models/li_danner.pyx index 82fccc2..08bf10d 100644 --- a/farms_network/models/li_danner.pyx +++ b/farms_network/models/li_danner.pyx @@ -31,7 +31,6 @@ cdef void ode_rhs_c( double[:] inputs, double[:] weights, double[:] noise, - double drive, Neuron neuron ) noexcept: """ ODE """ From c49a256f221cea96d0d02cde4fabe1c4e63c0f2b Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Tue, 24 Sep 2024 14:04:26 -0400 Subject: [PATCH 041/316] [OPTIONS] Added names to state options --- farms_network/core/options.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/farms_network/core/options.py b/farms_network/core/options.py index 6b194ae..6bfb13f 100644 --- a/farms_network/core/options.py +++ b/farms_network/core/options.py @@ -96,6 +96,9 @@ class NodeStateOptions(Options): def __init__(self, **kwargs): super().__init__() self.initial: List[float] = kwargs.pop("initial") + self.names: List[str] = kwargs.pop("names") + if kwargs: + raise Exception(f'Unknown kwargs: {kwargs}') class NodeVisualOptions(Options): @@ -221,17 +224,22 @@ def defaults(cls, **kwargs): class LIDannerStateOptions(NodeStateOptions): """ LI Danner node state options """ + STATE_NAMES = ["v0",] + def __init__(self, **kwargs): super().__init__( - initial=kwargs.pop("initial") + initial=kwargs.pop("initial"), + names=LIDannerStateOptions.STATE_NAMES ) assert len(self.initial) == 1, f"Number of initial states {len(self.initial)} should be 1" @classmethod def from_kwargs(cls, **kwargs): """ From node specific name-value kwargs """ - v0 = kwargs.pop("v0") - initial = [v0,] + initial = [ + kwargs.pop(name) + for name in cls.STATE_NAMES + ] return cls(initial=initial) From a799574eaa7cf6341326a1001ef9860f51473552 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Tue, 24 Sep 2024 14:04:54 -0400 Subject: [PATCH 042/316] [OPTIONS] Added exception to check unknown kwargs --- farms_network/core/options.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/farms_network/core/options.py b/farms_network/core/options.py index 6bfb13f..a95a533 100644 --- a/farms_network/core/options.py +++ b/farms_network/core/options.py @@ -19,15 +19,15 @@ def __init__(self, **kwargs): # Default properties to make it compatible with networkx self.directed: bool = kwargs.pop("directed", True) self.multigraph: bool = kwargs.pop("multigraph", False) - self.graph: dict = { - "name": _name - } - + self.graph: dict = {"name": _name,} self.units = None self.nodes: List[NodeOptions] = kwargs.pop("nodes", []) self.edges: List[EdgeOptions] = kwargs.pop("edges", []) + if kwargs: + raise Exception(f'Unknown kwargs: {kwargs}') + def add_node(self, options: "NodeOptions"): """ Add a node if it does not already exist in the list """ assert isinstance(options, NodeOptions), f"{type(options)} not an instance of NodeOptions" @@ -71,6 +71,8 @@ def __init__(self, **kwargs): self._nstates: int = 0 self._nparameters: int = 0 + if kwargs: + raise Exception(f'Unknown kwargs: {kwargs}') def __eq__(self, other): if isinstance(other, NodeOptions): @@ -112,6 +114,8 @@ def __init__(self, **kwargs): self.label: str = kwargs.pop("label", "n") self.layer: str = kwargs.pop("layer", "background") self.latex: dict = kwargs.pop("latex", "{}") + if kwargs: + raise Exception(f'Unknown kwargs: {kwargs}') ################ @@ -130,6 +134,8 @@ def __init__(self, **kwargs): self.type: str = kwargs.pop("type") self.visual: NodeVisualOptions = kwargs.pop("visual") + if kwargs: + raise Exception(f'Unknown kwargs: {kwargs}') def __eq__(self, other): if isinstance(other, EdgeOptions): @@ -149,6 +155,8 @@ def __init__(self, **kwargs): self.label: str = kwargs.pop("label", "") self.layer: str = kwargs.pop("layer", "background") self.latex: dict = kwargs.pop("latex", "{}") + if kwargs: + raise Exception(f'Unknown kwargs: {kwargs}') ######################################### @@ -201,6 +209,8 @@ def __init__(self, **kwargs): self.g_syn_i = kwargs.pop("g_syn_i") # nS self.e_syn_e = kwargs.pop("e_syn_e") # mV self.e_syn_i = kwargs.pop("e_syn_i") # mV + if kwargs: + raise Exception(f'Unknown kwargs: {kwargs}') @classmethod def defaults(cls, **kwargs): From 6db99086f11591ef76520ff7c1f5fd7c949b3093 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Tue, 24 Sep 2024 14:41:40 -0400 Subject: [PATCH 043/316] [CORE] Renamed neuron to node to be more generic --- farms_network/core/data.py | 57 +++++++--- farms_network/core/data_cy.pxd | 6 +- farms_network/core/data_cy.pyx | 22 ++-- farms_network/core/network.pxd | 30 +++--- farms_network/core/network.pyx | 44 ++++---- farms_network/core/neuron.pxd | 67 ------------ farms_network/core/neuron.pyx | 135 ------------------------ farms_network/models/li_danner.pxd | 16 +-- farms_network/models/li_danner.pyx | 44 ++++---- farms_network/models/li_nap_danner.pxd | 4 +- farms_network/models/li_nap_danner.pyx | 46 ++++---- farms_network/models/sensory_neuron.pxd | 28 ----- farms_network/models/sensory_neuron.pyx | 31 ------ 13 files changed, 149 insertions(+), 381 deletions(-) delete mode 100644 farms_network/core/neuron.pxd delete mode 100644 farms_network/core/neuron.pyx delete mode 100644 farms_network/models/sensory_neuron.pxd delete mode 100644 farms_network/models/sensory_neuron.pyx diff --git a/farms_network/core/data.py b/farms_network/core/data.py index 61c3900..39fe595 100644 --- a/farms_network/core/data.py +++ b/farms_network/core/data.py @@ -20,46 +20,74 @@ """ -from .data_cy import NetworkDataCy, StatesArrayCy +from typing import List + +import numpy as np +from farms_core.array.types import (NDARRAY_V1, NDARRAY_V1_D, NDARRAY_V2_D, + NDARRAY_V3_D) + +from data_cy import NetworkDataCy, StatesArrayCy +from .options import NodeOptions, NodeStateOptions class NetworkData(NetworkDataCy): """ Network data """ - def __init__(self, nstates, states): + def __init__(self, nstates,): """ Network data structure """ super().__init__( nstates, - states + # states ) - self.states = states + # self.states = states + self.nodes: List[NodeData] = [NodeData(),] _connectivity = None _states = None _dstates = None _outputs = None - _neurons = None + _nodes = None + +class NodeData: + """ Base class for representing an arbitrary node data """ -# class NeuronData(NeuronDataCy): -# """ Base class for representing an arbitrary neuron data """ + def __init__(self, states_arr: StatesArray, out_arr: OutputArray): + """Node data initialization """ -# def __init__(self): -# """Neuron data initialization """ + super().__init__() -# super().__init__() + self.states = states_arr + self.output = out_arr + self.variables = None -# self.states = None -# self.output = None -# self.variables = None -# self.user = None + @classmethod + def from_options(cls, options: NodeOptions): + """ Node data from class """ + nstates = options._nstates + return cls( + + ) class StatesArray(StatesArrayCy): """ State array data """ + def __init__(self, array: NDARRAY_V2_D, names: List): + super().__init__(array) + self.names = names + + @classmethod + def from_options(cls, options: NodeStateOptions): + """ State options """ + + + +class OutputArray: + """ Output array data """ + def __init__(self, array): super().__init__(array) @@ -67,6 +95,7 @@ def __init__(self, array): def main(): data = NetworkData(100) + print(data.nodes[0].states.names) if __name__ == '__main__': diff --git a/farms_network/core/data_cy.pxd b/farms_network/core/data_cy.pxd index 81a9efc..6f0e4d2 100644 --- a/farms_network/core/data_cy.pxd +++ b/farms_network/core/data_cy.pxd @@ -27,12 +27,12 @@ cdef class NetworkDataCy: public StatesArrayCy states cdef: - public neurons + public nodes public connectivity -cdef class NeuronDataCy: - """ Neuron data """ +cdef class NodeDataCy: + """ Node data """ cdef class StatesArrayCy(DoubleArray2D): diff --git a/farms_network/core/data_cy.pyx b/farms_network/core/data_cy.pyx index 94c9e17..b1881da 100644 --- a/farms_network/core/data_cy.pyx +++ b/farms_network/core/data_cy.pyx @@ -31,24 +31,28 @@ cdef class NetworkDataCy: def __init__( self, nstates: int, - states - # neurons = None, + # states + # nodes = None, # connectivity = None, # inputs = None, # drives = None, # outputs = None, ): - """ neurons data initialization """ + """ nodes data initialization """ super().__init__() - self.states = states - # self.neurons = neurons + # self.states = states + # self.nodes = nodes # self.connectivity = connectivity # self.inputs = inputs # self.drives = drives # self.outputs = outputs +cdef class NodeData: + """ Base Class for node data """ + + cdef class StatesArrayCy(DoubleArray2D): """ State array """ @@ -64,9 +68,5 @@ cdef class OutputsArrayCy(DoubleArray2D): """ Outputs array """ -cdef class InputsArrayCy(DoubleArray2D): - """ Inputs array """ - - -# # class User2DArrayCy(DoubleArray2D): -# # ... +# class User2DArrayCy(DoubleArray2D): +# ... diff --git a/farms_network/core/network.pxd b/farms_network/core/network.pxd index 44087e1..11e90a2 100644 --- a/farms_network/core/network.pxd +++ b/farms_network/core/network.pxd @@ -1,24 +1,24 @@ -from .neuron cimport Neuron +from .node cimport Node cdef struct Network: unsigned int nstates - Neuron* neurons + Node* nodes void step() void ode_c() # cdef void ode_c( -# Neuron *neurons, +# Node *nodes, # unsigned int iteration, -# unsigned int nneurons, +# unsigned int nnodes, # double[:] dstates # ): # """ Network step function """ -# cdef Neuron neuron -# cdef NeuronData neuron_data +# cdef Node node +# cdef NodeData node_data # cdef unsigned int j -# cdef nneurons = sizeof(neurons)/sizeof(neuron) +# cdef nnodes = sizeof(nodes)/sizeof(node) # # double[:, :] states # # double[:, :] dstates @@ -26,16 +26,16 @@ cdef struct Network: # # double[:, :] weights # # double[:, :] noise -# for j in range(nneurons): -# neuron_data = network_data[j] -# neurons[j].ode_rhs_c( -# neuron_data.curr_state, +# for j in range(nnodes): +# node_data = network_data[j] +# nodes[j].ode_rhs_c( +# node_data.curr_state, # dstates, # inputs, # weights, # noise, # drive, -# neurons[j] +# nodes[j] # ) @@ -44,8 +44,8 @@ cdef class PyNetwork: cdef: Network *_network - unsigned int nneurons - list neurons - Neuron **c_neurons + unsigned int nnodes + list nodes + Node **c_nodes cpdef void test(self, data) diff --git a/farms_network/core/network.pyx b/farms_network/core/network.pyx index 79c19f7..988897a 100644 --- a/farms_network/core/network.pyx +++ b/farms_network/core/network.pyx @@ -22,8 +22,8 @@ import numpy as np from libc.stdlib cimport free, malloc from libc.string cimport strdup -from ..models.li_danner cimport PyLIDannerNeuron -from .neuron cimport Neuron, PyNeuron +from ..models.li_danner cimport PyLIDannerNode +from .node cimport Node, PyNode from tqdm import tqdm @@ -31,30 +31,30 @@ from tqdm import tqdm cdef class PyNetwork: """ Python interface to Network ODE """ - def __cinit__(self, nneurons: int): + def __cinit__(self, nnodes: int): """ C initialization for manual memory allocation """ - self.nneurons = nneurons + self.nnodes = nnodes self._network = malloc(sizeof(Network)) if self._network is NULL: raise MemoryError("Failed to allocate memory for Network") - self.c_neurons = malloc(nneurons * sizeof(Neuron *)) - # if self._network.neurons is NULL: - # raise MemoryError("Failed to allocate memory for neurons in Network") + self.c_nodes = malloc(nnodes * sizeof(Node *)) + # if self._network.nodes is NULL: + # raise MemoryError("Failed to allocate memory for nodes in Network") - self.neurons = [] - cdef Neuron *c_neuron - cdef PyLIDannerNeuron pyn + self.nodes = [] + cdef Node *c_node + cdef PyLIDannerNode pyn - for n in range(self.nneurons): - self.neurons.append(PyLIDannerNeuron(f"{n}", 0)) - pyn = self.neurons[n] - c_neuron = (pyn._neuron) - self.c_neurons[n] = c_neuron + for n in range(self.nnodes): + self.nodes.append(PyLIDannerNode(f"{n}", 0)) + pyn = self.nodes[n] + c_node = (pyn._node) + self.c_nodes[n] = c_node def __dealloc__(self): """ Deallocate any manual memory as part of clean up """ - if self._network.neurons is not NULL: - free(self._network.neurons) + if self._network.nodes is not NULL: + free(self._network.nodes) if self._network is not NULL: free(self._network) @@ -65,19 +65,19 @@ cdef class PyNetwork: cdef double[:] weights = np.empty((10,)) cdef double[:] noise = np.empty((10,)) - cdef Neuron **neurons = self.c_neurons + cdef Node **nodes = self.c_nodes cdef unsigned int t, j for t in tqdm(range(int(1000*1e3))): - for j in range(self.nneurons): - neurons[j][0].nstates - neurons[j][0].ode_rhs_c( + for j in range(self.nnodes): + nodes[j][0].nstates + nodes[j][0].ode_rhs_c( 0.0, states, dstates, inputs, weights, noise, - neurons[j][0] + nodes[j][0] ) def step(self): diff --git a/farms_network/core/neuron.pxd b/farms_network/core/neuron.pxd deleted file mode 100644 index 47fcc54..0000000 --- a/farms_network/core/neuron.pxd +++ /dev/null @@ -1,67 +0,0 @@ -""" - ------------------------------------------------------------------------ -Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty -Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. ------------------------------------------------------------------------ - -Header for Neuron Base Struture. - -""" - - -cdef packed struct Neuron: - # Generic parameters - unsigned int nstates - unsigned int nparameters - unsigned int ninputs - - char* model_type - char* name - - # Parameters - void* parameters - - # Functions - void ode_rhs_c( - double time, - double[:] states, - double[:] dstates, - double[:] inputs, - double[:] weights, - double[:] noise, - Neuron neuron - ) - double output_c(double time, double[:] states, Neuron neuron) - - -cdef: - void ode_rhs_c( - double time, - double[:] states, - double[:] dstates, - double[:] inputs, - double[:] weights, - double[:] noise, - Neuron neuron - ) - double output_c(double time, double[:] states, Neuron neuron) - - -cdef class PyNeuron: - """ Python interface to Neuron C-Structure""" - - cdef: - Neuron* _neuron diff --git a/farms_network/core/neuron.pyx b/farms_network/core/neuron.pyx deleted file mode 100644 index 582740f..0000000 --- a/farms_network/core/neuron.pyx +++ /dev/null @@ -1,135 +0,0 @@ -""" ------------------------------------------------------------------------ -Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty -Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. ------------------------------------------------------------------------ -""" - -from libc.stdio cimport printf -from libc.stdlib cimport free, malloc -from libc.string cimport strdup - -from .options import NodeOptions - - -cdef void ode_rhs_c( - double time, - double[:] states, - double[:] dstates, - double[:] inputs, - double[:] weights, - double[:] noise, - Neuron neuron -) noexcept: - """ Neuron ODE """ - printf("Base implementation of ODE C function \n") - - -cdef double output_c(double time, double[:] states, Neuron neuron): - """ Neuron output """ - printf("Base implementation of neuron output C function \n") - return 0.0 - - -cdef class PyNeuron: - """ Python interface to Neuron C-Structure""" - - def __cinit__(self): - self._neuron = malloc(sizeof(Neuron)) - if self._neuron is NULL: - raise MemoryError("Failed to allocate memory for Neuron") - self._neuron.name = NULL - self._neuron.model_type = strdup("base".encode('UTF-8')) - self._neuron.ode_rhs_c = ode_rhs_c - self._neuron.output_c = output_c - - def __dealloc__(self): - if self._neuron.name is not NULL: - free(self._neuron.name) - if self._neuron.model_type is not NULL: - free(self._neuron.model_type) - if self._neuron.parameters is not NULL: - free(self._neuron.parameters) - if self._neuron is not NULL: - free(self._neuron) - - def __init__(self, name, ninputs): - self.name = name - self.ninputs = ninputs - - @classmethod - def from_options(cls, neuron_options: NeuronOptions): - """ From neuron options """ - name: str = neuron_options.name - ninputs: int = neuron_options.ninputs - return cls(name, ninputs) - - # Property methods for name - @property - def name(self): - if self._neuron.name is NULL: - return None - return self._neuron.name.decode('UTF-8') - - @name.setter - def name(self, value): - if self._neuron.name is not NULL: - free(self._neuron.name) - self._neuron.name = strdup(value.encode('UTF-8')) - - # Property methods for model_type - @property - def model_type(self): - if self._neuron.model_type is NULL: - return None - return self._neuron.model_type.decode('UTF-8') - - # Property methods for nstates - @property - def nstates(self): - return self._neuron.nstates - - # Property methods for nparameters - @property - def nparameters(self): - return self._neuron.nparameters - - # Property methods for ninputs - @property - def ninputs(self): - return self._neuron.ninputs - - @ninputs.setter - def ninputs(self, value): - self._neuron.ninputs = value - - # Methods to wrap the ODE and output functions - def ode_rhs(self, double time, states, dstates, inputs, weights, noise): - cdef double[:] c_states = states - cdef double[:] c_dstates = dstates - cdef double[:] c_inputs = inputs - cdef double[:] c_weights = weights - cdef double[:] c_noise = noise - # Call the C function directly - self._neuron.ode_rhs_c( - time, c_states, c_dstates, c_inputs, c_weights, c_noise, self._neuron[0] - ) - - def output(self, double time, states): - cdef double[:] c_states = states - # Call the C function and return its result - return self._neuron.output_c( - time, c_states, self._neuron[0] - ) diff --git a/farms_network/models/li_danner.pxd b/farms_network/models/li_danner.pxd index a2a1c86..8e720f5 100644 --- a/farms_network/models/li_danner.pxd +++ b/farms_network/models/li_danner.pxd @@ -16,13 +16,13 @@ See the License for the specific language governing permissions and limitations under the License. ----------------------------------------------------------------------- -Leaky Integrator Neuron Based on Danner et.al. +Leaky Integrator Node Based on Danner et.al. """ -from ..core.neuron cimport Neuron, PyNeuron +from ..core.node cimport Node, PyNode -cdef packed struct LIDannerNeuronParameters: +cdef packed struct LIDannerNodeParameters: double c_m # pF double g_leak # nS @@ -47,11 +47,11 @@ cdef: double[:] inputs, double[:] weights, double[:] noise, - Neuron neuron + Node node ) noexcept - double output_c(double time, double[:] states, Neuron neuron) - inline double neuron_inputs_eval_c(double _neuron_out, double _weight) + double output_c(double time, double[:] states, Node node) + inline double node_inputs_eval_c(double _node_out, double _weight) -cdef class PyLIDannerNeuron(PyNeuron): - """ Python interface to Leaky Integrator Neuron C-Structure """ +cdef class PyLIDannerNode(PyNode): + """ Python interface to Leaky Integrator Node C-Structure """ diff --git a/farms_network/models/li_danner.pyx b/farms_network/models/li_danner.pyx index 08bf10d..77079f9 100644 --- a/farms_network/models/li_danner.pyx +++ b/farms_network/models/li_danner.pyx @@ -16,7 +16,7 @@ See the License for the specific language governing permissions and limitations under the License. ----------------------------------------------------------------------- -Leaky Integrator Neuron based on Danner et.al. +Leaky Integrator Node based on Danner et.al. """ from libc.stdio cimport printf @@ -31,13 +31,13 @@ cdef void ode_rhs_c( double[:] inputs, double[:] weights, double[:] noise, - Neuron neuron + Node node ) noexcept: """ ODE """ # # Parameters - cdef LIDannerNeuronParameters params = ( - neuron.parameters + cdef LIDannerNodeParameters params = ( + node.parameters )[0] # States @@ -46,10 +46,10 @@ cdef void ode_rhs_c( # Ileak cdef double i_leak = params.g_leak * (state_v - params.e_leak) - # Neuron inputs + # Node inputs cdef double _sum = 0.0 cdef unsigned int j - cdef double _neuron_out + cdef double _node_out cdef double res cdef double _input @@ -59,7 +59,7 @@ cdef void ode_rhs_c( for j in range(10): _input = inputs[j] _weight = weights[j] - _sum += neuron_inputs_eval_c(_input, _weight) + _sum += node_inputs_eval_c(_input, _weight) # # noise current # cdef double i_noise = c_noise_current_update( @@ -74,13 +74,13 @@ cdef void ode_rhs_c( ) -cdef double output_c(double time, double[:] states, Neuron neuron): - """ Neuron output. """ +cdef double output_c(double time, double[:] states, Node node): + """ Node output. """ cdef double state_v = states[0] cdef double _n_out = 1000.0 - # cdef LIDannerNeuronParameters params = neuron.parameters + # cdef LIDannerNodeParameters params = node.parameters # if state_v >= params.v_max: # _n_out = 1.0 @@ -91,26 +91,26 @@ cdef double output_c(double time, double[:] states, Neuron neuron): return _n_out -cdef inline double neuron_inputs_eval_c(double _neuron_out, double _weight) noexcept: +cdef inline double node_inputs_eval_c(double _node_out, double _weight) noexcept: return 0.0 -cdef class PyLIDannerNeuron(PyNeuron): - """ Python interface to Leaky Integrator Neuron C-Structure """ +cdef class PyLIDannerNode(PyNode): + """ Python interface to Leaky Integrator Node C-Structure """ def __cinit__(self): - self._neuron.model_type = strdup("LI_DANNER".encode('UTF-8')) + self._node.model_type = strdup("LI_DANNER".encode('UTF-8')) # override default ode and out methods - self._neuron.ode_rhs_c = ode_rhs_c - self._neuron.output_c = output_c + self._node.ode_rhs_c = ode_rhs_c + self._node.output_c = output_c # parameters - self._neuron.parameters = malloc( - sizeof(LIDannerNeuronParameters) + self._node.parameters = malloc( + sizeof(LIDannerNodeParameters) ) @classmethod - def from_options(cls, neuron_options: NeuronOptions): - """ From neuron options """ - name: str = neuron_options.name - ninputs: int = neuron_options.ninputs + def from_options(cls, node_options: NodeOptions): + """ From node options """ + name: str = node_options.name + ninputs: int = node_options.ninputs return cls(name, ninputs) diff --git a/farms_network/models/li_nap_danner.pxd b/farms_network/models/li_nap_danner.pxd index addbd3f..5c0934f 100644 --- a/farms_network/models/li_nap_danner.pxd +++ b/farms_network/models/li_nap_danner.pxd @@ -16,7 +16,7 @@ See the License for the specific language governing permissions and limitations under the License. ----------------------------------------------------------------------- -Leaky Integrator Neuron Based on Danner et.al. +Leaky Integrator Node Based on Danner et.al. """ -from ..core.neuron cimport Neuron, PyNeuron +from ..core.node cimport Node, PyNode diff --git a/farms_network/models/li_nap_danner.pyx b/farms_network/models/li_nap_danner.pyx index 0dd21d8..5fc1dfb 100644 --- a/farms_network/models/li_nap_danner.pyx +++ b/farms_network/models/li_nap_danner.pyx @@ -16,7 +16,7 @@ See the License for the specific language governing permissions and limitations under the License. ----------------------------------------------------------------------- -Leaky Integrator Neuron with persistent sodium channel based on Danner et.al. +Leaky Integrator Node with persistent sodium channel based on Danner et.al. """ # from libc.stdio cimport printf @@ -32,13 +32,13 @@ Leaky Integrator Neuron with persistent sodium channel based on Danner et.al. # double[:] weights, # double[:] noise, # double drive, -# Neuron neuron +# Node node # ): # """ ODE """ # # # Parameters -# cdef LINapDannerNeuronParameters params = ( -# neuron.parameters +# cdef LINapDannerNodeParameters params = ( +# node.parameters # )[0] # # States # cdef double state_v = states[0] @@ -56,14 +56,14 @@ Leaky Integrator Neuron with persistent sodium channel based on Danner et.al. # # ISyn_Inhibitory # cdef double i_syn_i = params.g_syn_i * d_i * (state_v - params.e_syn_i) -# # Neuron inputs +# # Node inputs # cdef double _sum = 0.0 # cdef unsigned int j -# cdef double _neuron_out +# cdef double _node_out # cdef double _weight -# # for j in range(neuron.ninputs): -# # _sum += neuron_inputs_eval_c(inputs[j], weights[j]) +# # for j in range(node.ninputs): +# # _sum += node_inputs_eval_c(inputs[j], weights[j]) # # # noise current # # cdef double i_noise = c_noise_current_update( @@ -78,13 +78,13 @@ Leaky Integrator Neuron with persistent sodium channel based on Danner et.al. # ) -# cdef double output_c(double time, double[:] states, Neuron neuron): -# """ Neuron output. """ +# cdef double output_c(double time, double[:] states, Node node): +# """ Node output. """ # cdef double state_v = states[0] # cdef double _n_out = 1000.0 -# # cdef LIDannerNeuronParameters params = neuron.parameters +# # cdef LIDannerNodeParameters params = node.parameters # # if state_v >= params.v_max: # # _n_out = 1.0 @@ -95,26 +95,26 @@ Leaky Integrator Neuron with persistent sodium channel based on Danner et.al. # return _n_out -# cdef double neuron_inputs_eval_c(double _neuron_out, double _weight): +# cdef double node_inputs_eval_c(double _node_out, double _weight): # return 0.0 -# cdef class PyLINapDannerNeuron(PyNeuron): -# """ Python interface to Leaky Integrator Neuron with persistence sodium C-Structure """ +# cdef class PyLINapDannerNode(PyNode): +# """ Python interface to Leaky Integrator Node with persistence sodium C-Structure """ # def __cinit__(self): # # override defaults -# self._neuron.model_type = strdup("LI_NAP_DANNER".encode('UTF-8')) -# self._neuron.nstates = 2 -# self._neuron.nparameters = 13 +# self._node.model_type = strdup("LI_NAP_DANNER".encode('UTF-8')) +# self._node.nstates = 2 +# self._node.nparameters = 13 # # methods -# self._neuron.ode_rhs_c = ode_rhs_c -# self._neuron.output_c = output_c +# self._node.ode_rhs_c = ode_rhs_c +# self._node.output_c = output_c # # parameters -# self._neuron.parameters = malloc( -# sizeof(LIDannerNeuronParameters) +# self._node.parameters = malloc( +# sizeof(LIDannerNodeParameters) # ) # def __dealloc__(self): -# if self._neuron.name is not NULL: -# free(self._neuron.parameters) +# if self._node.name is not NULL: +# free(self._node.parameters) diff --git a/farms_network/models/sensory_neuron.pxd b/farms_network/models/sensory_neuron.pxd deleted file mode 100644 index b0bc4f6..0000000 --- a/farms_network/models/sensory_neuron.pxd +++ /dev/null @@ -1,28 +0,0 @@ -""" ------------------------------------------------------------------------ -Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty -Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. ------------------------------------------------------------------------ - -Sensory afferent neurons. -""" - - -from ..core.neuron cimport Neuron - - -cdef: - void ode_rhs_c(double time, double[:] states, double[:] dstates, Neuron neuron) - double output_c(double time, double[:] states, Neuron neuron) diff --git a/farms_network/models/sensory_neuron.pyx b/farms_network/models/sensory_neuron.pyx deleted file mode 100644 index fb712a5..0000000 --- a/farms_network/models/sensory_neuron.pyx +++ /dev/null @@ -1,31 +0,0 @@ -""" ------------------------------------------------------------------------ -Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty -Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. ------------------------------------------------------------------------ - -Sensory afferent neurons. - -""" - - -cdef void ode_rhs_c(double time, double[:] state, double[:] dstates, Neuron neuron): - """ Sensory neuron ODE computation """ - ... - - -cdef double output_c(double time, double[:] state, Neuron neuron): - """ Sensory neuron output computation """ - return 0.0 From dad00dd70addab72def14f837f596e149a53f433 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Tue, 24 Sep 2024 15:16:25 -0400 Subject: [PATCH 044/316] [OPTIONS] Refactored node state options classes --- farms_network/core/options.py | 37 +++++++++++++++-------------------- 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/farms_network/core/options.py b/farms_network/core/options.py index a95a533..609bb5f 100644 --- a/farms_network/core/options.py +++ b/farms_network/core/options.py @@ -15,12 +15,12 @@ class NetworkOptions(Options): def __init__(self, **kwargs): super().__init__() - _name: str = kwargs.pop("name", "") + # Default properties to make it compatible with networkx self.directed: bool = kwargs.pop("directed", True) self.multigraph: bool = kwargs.pop("multigraph", False) - self.graph: dict = {"name": _name,} - self.units = None + self.graph: dict = kwargs.pop("graph", {"name": ""}) + self.units = kwargs.pop("units", None) self.nodes: List[NodeOptions] = kwargs.pop("nodes", []) self.edges: List[EdgeOptions] = kwargs.pop("edges", []) @@ -102,6 +102,15 @@ def __init__(self, **kwargs): if kwargs: raise Exception(f'Unknown kwargs: {kwargs}') + @classmethod + def from_kwargs(cls, **kwargs): + """ From node specific name-value kwargs """ + initial = [ + kwargs.pop(name) + for name in cls.STATE_NAMES + ] + return cls(initial=initial) + class NodeVisualOptions(Options): """ Base class for node visualization parameters """ @@ -243,15 +252,6 @@ def __init__(self, **kwargs): ) assert len(self.initial) == 1, f"Number of initial states {len(self.initial)} should be 1" - @classmethod - def from_kwargs(cls, **kwargs): - """ From node specific name-value kwargs """ - initial = [ - kwargs.pop(name) - for name in cls.STATE_NAMES - ] - return cls(initial=initial) - ################################################## # Leaky Integrator With NaP Danner Model Options # @@ -334,20 +334,15 @@ def defaults(cls, **kwargs): class LIDannerNaPStateOptions(NodeStateOptions): """ LI Danner node state options """ + STATE_NAMES = ["v0", "h0"] + def __init__(self, **kwargs): super().__init__( - initial=kwargs.pop("initial") + initial=kwargs.pop("initial"), + names=LIDannerNaPStateOptions.STATE_NAMES ) assert len(self.initial) == 2, f"Number of initial states {len(self.initial)} should be 2" - @classmethod - def from_kwargs(cls, **kwargs): - """ From node specific name-value kwargs """ - v0 = kwargs.pop("v0") - h0 = kwargs.pop("h0") - initial = [v0, h0] - return cls(initial=initial) - ######## From b27e635467ff5171cf9e1fdcac34606a4b73c80c Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Thu, 26 Sep 2024 15:02:51 -0400 Subject: [PATCH 045/316] [DUCKS] Renamed neuron to node --- ducks/source/core/index.rst | 3 ++- ducks/source/core/neuron.rst | 7 ------- ducks/source/core/node.rst | 12 ++++++++++++ ducks/source/core/options.rst | 12 ++++++++++++ ducks/source/index.rst | 7 +++++-- ducks/source/models/index.rst | 8 ++++++++ ducks/source/models/li_danner.rst | 0 7 files changed, 39 insertions(+), 10 deletions(-) delete mode 100644 ducks/source/core/neuron.rst create mode 100644 ducks/source/core/node.rst create mode 100644 ducks/source/core/options.rst create mode 100644 ducks/source/models/index.rst create mode 100644 ducks/source/models/li_danner.rst diff --git a/ducks/source/core/index.rst b/ducks/source/core/index.rst index 4a17fbb..375ed08 100644 --- a/ducks/source/core/index.rst +++ b/ducks/source/core/index.rst @@ -5,4 +5,5 @@ Core :maxdepth: 3 :caption: Contents: -.. include:: neuron.rst +.. include:: node.rst +.. include:: options.rst diff --git a/ducks/source/core/neuron.rst b/ducks/source/core/neuron.rst deleted file mode 100644 index f5c6f8f..0000000 --- a/ducks/source/core/neuron.rst +++ /dev/null @@ -1,7 +0,0 @@ -Neuron ------- - -.. automodule:: farms_network.core.neuron - :members: - :show-inheritance: - :noindex: diff --git a/ducks/source/core/node.rst b/ducks/source/core/node.rst new file mode 100644 index 0000000..4faa5d6 --- /dev/null +++ b/ducks/source/core/node.rst @@ -0,0 +1,12 @@ +Neuron +------ + +All nodes are many-inputs-single-output (MISO). +The simplest case would be a node with one input and one input. +A node can have N-states that will be integrated by a numerical integrator over time. +A stateless node will have zero states and is useful in using the node as a transfer function. + +.. automodule:: farms_network.core.node + :members: + :show-inheritance: + :noindex: diff --git a/ducks/source/core/options.rst b/ducks/source/core/options.rst new file mode 100644 index 0000000..f338e31 --- /dev/null +++ b/ducks/source/core/options.rst @@ -0,0 +1,12 @@ +Options +------- + +.. automodule:: farms_network.core.options + :members: + :show-inheritance: + :noindex: + +===== ====== +hello simple +world table +===== ====== diff --git a/ducks/source/index.rst b/ducks/source/index.rst index 3cd6482..1372730 100644 --- a/ducks/source/index.rst +++ b/ducks/source/index.rst @@ -4,16 +4,19 @@ contain the root `toctree` directive. Welcome to FARMS Network's documentation! -=========================================== +========================================= .. warning:: Farmers are currently busy! Documentation is work in progress!! +farms network provides commonly used neural models for locomotion circuits. + .. toctree:: - :maxdepth: 2 + :maxdepth: 1 :caption: Contents: usage/installation core/index + models/index .. sidebar-links:: :github: diff --git a/ducks/source/models/index.rst b/ducks/source/models/index.rst new file mode 100644 index 0000000..787bb2c --- /dev/null +++ b/ducks/source/models/index.rst @@ -0,0 +1,8 @@ +Models +====== + +.. toctree:: + :maxdepth: 3 + :caption: Contents: + +.. include:: li_danner.rst diff --git a/ducks/source/models/li_danner.rst b/ducks/source/models/li_danner.rst new file mode 100644 index 0000000..e69de29 From 4c4f2b390048e94761da255dfc1bdbbf01ec4923 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Fri, 27 Sep 2024 14:47:47 -0400 Subject: [PATCH 046/316] [CORE] Renamed neuron module as node module to be generic --- farms_network/core/node.pxd | 71 +++++++++++++++++++ farms_network/core/node.pyx | 133 ++++++++++++++++++++++++++++++++++++ 2 files changed, 204 insertions(+) create mode 100644 farms_network/core/node.pxd create mode 100644 farms_network/core/node.pyx diff --git a/farms_network/core/node.pxd b/farms_network/core/node.pxd new file mode 100644 index 0000000..8759377 --- /dev/null +++ b/farms_network/core/node.pxd @@ -0,0 +1,71 @@ +""" + +----------------------------------------------------------------------- +Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty +Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +----------------------------------------------------------------------- + +Header for Node Base Struture. + +""" + + +cdef packed struct Node: + # Generic parameters + unsigned int nstates + unsigned int nparameters + unsigned int ninputs + + char* model_type + char* name + + bint statefull + + void* states + + # Parameters + void* parameters + + # Functions + void ode( + double time, + double[:] states, + double[:] dstates, + double[:] inputs, + double[:] weights, + double[:] noise, + Node node + ) + double output(double time, double[:] states, Node node) + + +cdef: + void ode( + double time, + double[:] states, + double[:] dstates, + double[:] inputs, + double[:] weights, + double[:] noise, + Node node + ) + double output(double time, double[:] states, Node node) + + +cdef class PyNode: + """ Python interface to Node C-Structure""" + + cdef: + Node* _node diff --git a/farms_network/core/node.pyx b/farms_network/core/node.pyx new file mode 100644 index 0000000..b88f5c9 --- /dev/null +++ b/farms_network/core/node.pyx @@ -0,0 +1,133 @@ +""" +----------------------------------------------------------------------- +Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty +Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +----------------------------------------------------------------------- +""" + +from libc.stdio cimport printf +from libc.stdlib cimport free, malloc +from libc.string cimport strdup + +from .options import NodeOptions + + +cdef void ode( + double time, + double[:] states, + double[:] derivaties, + double[:] inputs, + double[:] weights, + double[:] noise, + Node node +) noexcept: + """ Node ODE """ + printf("Base implementation of ODE C function \n") + + +cdef double output(double time, double[:] states, Node node): + """ Node output """ + printf("Base implementation of node output C function \n") + return 0.0 + + +cdef class PyNode: + """ Python interface to Node C-Structure""" + + def __cinit__(self): + self._node = malloc(sizeof(Node)) + if self._node is NULL: + raise MemoryError("Failed to allocate memory for Node") + self._node.name = NULL + self._node.model_type = strdup("base".encode('UTF-8')) + self._node.ode = ode + self._node.output = output + + def __dealloc__(self): + if self._node.name is not NULL: + free(self._node.name) + if self._node.model_type is not NULL: + free(self._node.model_type) + if self._node.parameters is not NULL: + free(self._node.parameters) + if self._node is not NULL: + free(self._node) + + def __init__(self, name, ninputs): + self.name = name + self.ninputs = ninputs + + @classmethod + def from_options(cls, node_options: NodeOptions): + """ From node options """ + name: str = node_options.name + ninputs: int = node_options.ninputs + return cls(name, ninputs) + + # Property methods for name + @property + def name(self): + if self._node.name is NULL: + return None + return self._node.name.decode('UTF-8') + + @name.setter + def name(self, value): + if self._node.name is not NULL: + free(self._node.name) + self._node.name = strdup(value.encode('UTF-8')) + + # Property methods for model_type + @property + def model_type(self): + if self._node.model_type is NULL: + return None + return self._node.model_type.decode('UTF-8') + + # Property methods for nstates + @property + def nstates(self): + return self._node.nstates + + # Property methods for nparameters + @property + def nparameters(self): + return self._node.nparameters + + # Property methods for ninputs + @property + def ninputs(self): + return self._node.ninputs + + @ninputs.setter + def ninputs(self, value): + self._node.ninputs = value + + # Methods to wrap the ODE and output functions + def ode(self, double time, states, dstates, inputs, weights, noise): + cdef double[:] c_states = states + cdef double[:] c_dstates = dstates + cdef double[:] c_inputs = inputs + cdef double[:] c_weights = weights + cdef double[:] c_noise = noise + # Call the C function directly + self._node.ode( + time, c_states, c_dstates, c_inputs, c_weights, c_noise, self._node[0] + ) + + def output(self, double time, states): + cdef double[:] c_states = states + # Call the C function and return its result + return self._node.output(time, c_states, self._node[0]) From 6763bf053010b013cc29ba6077187244942bf89c Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Fri, 27 Sep 2024 14:48:31 -0400 Subject: [PATCH 047/316] [SCRATCH] Updated test scripts for core changes --- scratch/profile_network.py | 2 +- scratch/test_network.py | 22 ++++++++++++++++++++++ scratch/test_neuron.py | 16 ++++++++-------- scratch/test_options.py | 13 ++++++------- 4 files changed, 37 insertions(+), 16 deletions(-) create mode 100644 scratch/test_network.py diff --git a/scratch/profile_network.py b/scratch/profile_network.py index bc72c1d..7d57c50 100644 --- a/scratch/profile_network.py +++ b/scratch/profile_network.py @@ -13,5 +13,5 @@ data = NetworkData(nstates=100, states=states) -net = network.PyNetwork(nneurons=100) +net = network.PyNetwork(nnodes=100) profile(net.test, data) diff --git a/scratch/test_network.py b/scratch/test_network.py new file mode 100644 index 0000000..8e61888 --- /dev/null +++ b/scratch/test_network.py @@ -0,0 +1,22 @@ +""" Test network """ + +import numpy as np +from farms_network.core.network import PyNetwork +from farms_network.core.options import NetworkOptions +from scipy.integrate import ode + +nstates = 100 +network = PyNetwork.from_options(nnodes=nstates) + +integrator = ode(network.ode).set_integrator( + u'dopri5', + method=u'adams', + max_step=0.0, +) +integrator.set_initial_value(np.zeros((nstates,)), 0.0) + + +# # Integrate +# for iteration in range(0, 100): +# integrator.set_initial_value(integrator.y, integrator.t) +# integrator.integrate(integrator.t+(iteration*1e-3)) diff --git a/scratch/test_neuron.py b/scratch/test_neuron.py index 2395569..eed8f87 100644 --- a/scratch/test_neuron.py +++ b/scratch/test_neuron.py @@ -1,5 +1,5 @@ import numpy as np -from farms_network.core import li_danner, network, neuron, options +from farms_network.core import li_danner, network, node, options from farms_network.data.data import NetworkData, StatesArray @@ -10,16 +10,16 @@ data = NetworkData(nstates=100, states=states) -net = network.PyNetwork(nneurons=10) +net = network.PyNetwork(nnodes=10) net.test(data) -n1_opts = options.NeuronOptions( +n1_opts = options.NodeOptions( name="n1", - parameters=options.NeuronParameterOptions(), - visual=options.NeuronVisualOptions(), - state=options.NeuronStateOptions(initial=[0, 0]), + parameters=options.NodeParameterOptions(), + visual=options.NodeVisualOptions(), + state=options.NodeStateOptions(initial=[0, 0]), ) -n1 = neuron.PyNeuron.from_options(n1_opts) +n1 = node.PyNode.from_options(n1_opts) n1_opts.save("/tmp/opts.yaml") @@ -43,7 +43,7 @@ n1.output(0.0, states) ) -n2 = li_danner.PyLIDannerNeuron("n2", ninputs=50) +n2 = li_danner.PyLIDannerNode("n2", ninputs=50) print(n2.name) print(n2.model_type) diff --git a/scratch/test_options.py b/scratch/test_options.py index 5e56639..d82b991 100644 --- a/scratch/test_options.py +++ b/scratch/test_options.py @@ -9,23 +9,22 @@ from farms_network.core import options param_opts = options.LIDannerParameterOptions.defaults() -state_opts = options.LIDannerStateOptions.from_kwargs(v0=0.0, h0=1.0) -vis_opts = options.NeuronVisualOptions() +state_opts = options.LIDannerNaPStateOptions.from_kwargs(v0=0.0, h0=-70.0) +vis_opts = options.NodeVisualOptions() -n1_opts = options.LIDannerNeuronOptions( +n1_opts = options.LIDannerNodeOptions( name="n1", parameters=param_opts, visual=vis_opts, state=state_opts, - ninputs=10, ) network = options.NetworkOptions( directed=True, multigraph=False, - name="network", - neurons=[n1_opts, n1_opts], - connections=[], + graph={"name": "network"}, + nodes=[n1_opts, n1_opts], + edges=[], ) print(type(network)) From 65d4ab1334e8dd094ff01edfec0fd3be5ab84703 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Fri, 27 Sep 2024 14:48:51 -0400 Subject: [PATCH 048/316] [CORE][WIP] Updated network for working example template --- farms_network/core/network.pxd | 88 ++++++++++++++++++++-------------- farms_network/core/network.pyx | 73 +++++++++++++++++----------- 2 files changed, 97 insertions(+), 64 deletions(-) diff --git a/farms_network/core/network.pxd b/farms_network/core/network.pxd index 11e90a2..3698552 100644 --- a/farms_network/core/network.pxd +++ b/farms_network/core/network.pxd @@ -2,41 +2,58 @@ from .node cimport Node cdef struct Network: - unsigned int nstates + + # info + unsigned long int nnodes + unsigned long int nstates + + # nodes list Node* nodes - void step() - void ode_c() - - -# cdef void ode_c( -# Node *nodes, -# unsigned int iteration, -# unsigned int nnodes, -# double[:] dstates -# ): -# """ Network step function """ -# cdef Node node -# cdef NodeData node_data -# cdef unsigned int j -# cdef nnodes = sizeof(nodes)/sizeof(node) - -# # double[:, :] states -# # double[:, :] dstates -# # double[:, :] inputs -# # double[:, :] weights -# # double[:, :] noise - -# for j in range(nnodes): -# node_data = network_data[j] -# nodes[j].ode_rhs_c( -# node_data.curr_state, -# dstates, -# inputs, -# weights, -# noise, -# drive, -# nodes[j] -# ) + + # functions + void ode( + double time, + unsigned int iteration, + double[:] states, + double[:] derivatives, + Node **nodes, + ) + + +cdef void ode( + double time, + unsigned int iteration, + double[:] states, + double[:] derivatives, + Node **nodes, +) noexcept + # cdef Node __node + # cdef NodeData node_data + # cdef unsigned int j + # cdef nnodes = sizeof(nodes)/sizeof(node) + + # double[:, :] states + # double[:, :] dstates + # double[:, :] inputs + # double[:, :] weights + # double[:, :] noise + + # for j in range(nnodes): + # node = node[j] + # node_data = network_data[j] + # if node.statefull: + # nstates = 2 + # node.ode(t, state) + # nodes[j].step( + # time, + # node_data.curr_state, + # dstates, + # inputs, + # weights, + # noise, + # drive, + # nodes[j] + # ) cdef class PyNetwork: @@ -48,4 +65,5 @@ cdef class PyNetwork: list nodes Node **c_nodes - cpdef void test(self, data) + cpdef void step(self) + cpdef void ode(self, double time, double[:] states) diff --git a/farms_network/core/network.pyx b/farms_network/core/network.pyx index 988897a..8358099 100644 --- a/farms_network/core/network.pyx +++ b/farms_network/core/network.pyx @@ -19,15 +19,34 @@ limitations under the License. import numpy as np +from libc.stdio cimport printf from libc.stdlib cimport free, malloc from libc.string cimport strdup -from ..models.li_danner cimport PyLIDannerNode -from .node cimport Node, PyNode +# from ..models.li_danner cimport PyLIDannerNode + +from .options import NetworkOptions + +from .node cimport PyNode from tqdm import tqdm +cdef void ode( + double time, + unsigned int iteration, + double[:] states, + double[:] derivatives, + Node **nodes, +) noexcept: + """ C Implementation to compute full network state """ + cdef Node __node + cdef unsigned int t, j + for t in range(int(1000*1e3)): + for j in range(100): + __node = nodes[j][0] + + cdef class PyNetwork: """ Python interface to Network ODE """ @@ -37,17 +56,22 @@ cdef class PyNetwork: self._network = malloc(sizeof(Network)) if self._network is NULL: raise MemoryError("Failed to allocate memory for Network") + self._network.ode = ode self.c_nodes = malloc(nnodes * sizeof(Node *)) # if self._network.nodes is NULL: # raise MemoryError("Failed to allocate memory for nodes in Network") self.nodes = [] cdef Node *c_node - cdef PyLIDannerNode pyn for n in range(self.nnodes): - self.nodes.append(PyLIDannerNode(f"{n}", 0)) - pyn = self.nodes[n] + # self.nodes.append(PyLIDannerNode(f"{n}", 0)) + # pyn = self.nodes[n] + # c_node = (pyn._node) + # self.c_nodes[n] = c_node + + self.nodes.append(PyNode(f"{n}", 0)) + pyn = self.nodes[n] c_node = (pyn._node) self.c_nodes[n] = c_node @@ -58,28 +82,19 @@ cdef class PyNetwork: if self._network is not NULL: free(self._network) - cpdef void test(self, data): - cdef double[:] states = data.states.array[0, :] - cdef double[:] dstates = np.empty((2,)) - cdef double[:] inputs = np.empty((10,)) - cdef double[:] weights = np.empty((10,)) - cdef double[:] noise = np.empty((10,)) - - cdef Node **nodes = self.c_nodes - cdef unsigned int t, j - for t in tqdm(range(int(1000*1e3))): - for j in range(self.nnodes): - nodes[j][0].nstates - nodes[j][0].ode_rhs_c( - 0.0, - states, - dstates, - inputs, - weights, - noise, - nodes[j][0] - ) - - def step(self): + @classmethod + def from_options(cls, options: NetworkOptions): + """ Initialize network from NetworkOptions """ + options + return cls + + def setup_integrator(options: IntegratorOptions): + """ Setup integrator for neural network """ + ... + + cpdef void ode(self, double time, double[:] states): + self._network.ode(time, 0, states, states, self.c_nodes) + + cpdef void step(self): """ Network step function """ - self._network.step() + ... From 25e497545845d9ee263a252a0dfb911c18a96727 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Mon, 30 Sep 2024 10:30:49 -0400 Subject: [PATCH 049/316] [CORE] Updated generic function signatures for output and ODE c-functions --- farms_network/core/network.pxd | 2 +- farms_network/core/network.pyx | 30 +++++++++++++++++++++--------- farms_network/core/node.pxd | 27 +++++++++++++++++++++------ farms_network/core/node.pyx | 16 +++++++++++----- 4 files changed, 54 insertions(+), 21 deletions(-) diff --git a/farms_network/core/network.pxd b/farms_network/core/network.pxd index 3698552..9a86cfd 100644 --- a/farms_network/core/network.pxd +++ b/farms_network/core/network.pxd @@ -65,5 +65,5 @@ cdef class PyNetwork: list nodes Node **c_nodes - cpdef void step(self) + # cpdef void step(self) cpdef void ode(self, double time, double[:] states) diff --git a/farms_network/core/network.pyx b/farms_network/core/network.pyx index 8358099..e7fa430 100644 --- a/farms_network/core/network.pyx +++ b/farms_network/core/network.pyx @@ -42,9 +42,17 @@ cdef void ode( """ C Implementation to compute full network state """ cdef Node __node cdef unsigned int t, j - for t in range(int(1000*1e3)): + cdef double[:] arr = None + printf("\n") + for t in range(int(10)): + printf("%i \t", t) for j in range(100): __node = nodes[j][0] + if __node.statefull: + __node.output(0.0, states, arr, arr, arr, __node) + else: + __node.output(0.0, arr, arr, arr, arr, __node) + __node.ode(0.0, states, arr, arr, arr, arr, __node) cdef class PyNetwork: @@ -57,6 +65,9 @@ cdef class PyNetwork: if self._network is NULL: raise MemoryError("Failed to allocate memory for Network") self._network.ode = ode + + def __init__(self, nnodes): + """ Initialize """ self.c_nodes = malloc(nnodes * sizeof(Node *)) # if self._network.nodes is NULL: # raise MemoryError("Failed to allocate memory for nodes in Network") @@ -65,11 +76,6 @@ cdef class PyNetwork: cdef Node *c_node for n in range(self.nnodes): - # self.nodes.append(PyLIDannerNode(f"{n}", 0)) - # pyn = self.nodes[n] - # c_node = (pyn._node) - # self.c_nodes[n] = c_node - self.nodes.append(PyNode(f"{n}", 0)) pyn = self.nodes[n] c_node = (pyn._node) @@ -95,6 +101,12 @@ cdef class PyNetwork: cpdef void ode(self, double time, double[:] states): self._network.ode(time, 0, states, states, self.c_nodes) - cpdef void step(self): - """ Network step function """ - ... + # def step(self, time, states): + # """ Update the network """ + # self._network.ode(time, 0, states, states, self.c_nodes) + # dstates = None + # return dstates + + # cpdef void step(self) + # """ Network step function """ + # ... diff --git a/farms_network/core/node.pxd b/farms_network/core/node.pxd index 8759377..2492483 100644 --- a/farms_network/core/node.pxd +++ b/farms_network/core/node.pxd @@ -42,26 +42,41 @@ cdef packed struct Node: void ode( double time, double[:] states, - double[:] dstates, + double[:] derivatives, double[:] inputs, double[:] weights, double[:] noise, Node node - ) - double output(double time, double[:] states, Node node) + ) noexcept + + double output( + double time, + double[:] states, + double[:] inputs, + double[:] weights, + double[:] noise, + Node node + ) noexcept cdef: void ode( double time, double[:] states, - double[:] dstates, + double[:] derivatives, + double[:] inputs, + double[:] weights, + double[:] noise, + Node node + ) noexcept + double output( + double time, + double[:] states, double[:] inputs, double[:] weights, double[:] noise, Node node - ) - double output(double time, double[:] states, Node node) + ) noexcept cdef class PyNode: diff --git a/farms_network/core/node.pyx b/farms_network/core/node.pyx index b88f5c9..95a2d1a 100644 --- a/farms_network/core/node.pyx +++ b/farms_network/core/node.pyx @@ -37,9 +37,16 @@ cdef void ode( printf("Base implementation of ODE C function \n") -cdef double output(double time, double[:] states, Node node): +cdef double output( + double time, + double[:] states, + double[:] inputs, + double[:] weights, + double[:] noise, + Node node +) noexcept: """ Node output """ - printf("Base implementation of node output C function \n") + printf("Base implementation of output C function \n") return 0.0 @@ -127,7 +134,6 @@ cdef class PyNode: time, c_states, c_dstates, c_inputs, c_weights, c_noise, self._node[0] ) - def output(self, double time, states): - cdef double[:] c_states = states + def output(self, double time, states, inputs, weights, noise): # Call the C function and return its result - return self._node.output(time, c_states, self._node[0]) + return self._node[0].output(time, states, inputs, weights, noise, self._node[0]) From 6b52d280052fff274b50369a07ba63cf8a2e04ae Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Mon, 30 Sep 2024 10:31:21 -0400 Subject: [PATCH 050/316] [SCRATCH] Updated test_network to changes in base function signatures --- scratch/test_network.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/scratch/test_network.py b/scratch/test_network.py index 8e61888..c83a640 100644 --- a/scratch/test_network.py +++ b/scratch/test_network.py @@ -5,16 +5,24 @@ from farms_network.core.options import NetworkOptions from scipy.integrate import ode -nstates = 100 -network = PyNetwork.from_options(nnodes=nstates) +nnodes = 100 + +states = np.zeros((nnodes,)) +dstates = np.zeros((nnodes,)) +outputs = np.zeros((nnodes,)) +weights = np.zeros((nnodes,)) + +network = PyNetwork(nnodes=nnodes) integrator = ode(network.ode).set_integrator( u'dopri5', method=u'adams', max_step=0.0, + nsteps=4 ) -integrator.set_initial_value(np.zeros((nstates,)), 0.0) +integrator.set_initial_value(np.zeros((nnodes,)), 0.0) +integrator.integrate(integrator.t+1.0) # # Integrate # for iteration in range(0, 100): From ce2f8eb40837fe11fe77026bc7efd958e3ff8426 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Mon, 30 Sep 2024 11:41:14 -0400 Subject: [PATCH 051/316] [CORE] Added usr_input to node and network functions --- farms_network/core/network.pxd | 11 +++--- farms_network/core/network.pyx | 67 ++++++++++++++++++++------------ farms_network/core/node.pxd | 22 ++++++----- farms_network/core/node.pyx | 71 ++++++++++++++++------------------ 4 files changed, 93 insertions(+), 78 deletions(-) diff --git a/farms_network/core/network.pxd b/farms_network/core/network.pxd index 9a86cfd..4a48139 100644 --- a/farms_network/core/network.pxd +++ b/farms_network/core/network.pxd @@ -5,10 +5,11 @@ cdef struct Network: # info unsigned long int nnodes + unsigned long int nedges unsigned long int nstates # nodes list - Node* nodes + Node **nodes # functions void ode( @@ -16,7 +17,7 @@ cdef struct Network: unsigned int iteration, double[:] states, double[:] derivatives, - Node **nodes, + Network network, ) @@ -25,7 +26,7 @@ cdef void ode( unsigned int iteration, double[:] states, double[:] derivatives, - Node **nodes, + Network network, ) noexcept # cdef Node __node # cdef NodeData node_data @@ -60,10 +61,8 @@ cdef class PyNetwork: """ Python interface to Network ODE """ cdef: - Network *_network - unsigned int nnodes + Network *network list nodes - Node **c_nodes # cpdef void step(self) cpdef void ode(self, double time, double[:] states) diff --git a/farms_network/core/network.pyx b/farms_network/core/network.pyx index e7fa430..06d2374 100644 --- a/farms_network/core/network.pyx +++ b/farms_network/core/network.pyx @@ -23,9 +23,9 @@ from libc.stdio cimport printf from libc.stdlib cimport free, malloc from libc.string cimport strdup -# from ..models.li_danner cimport PyLIDannerNode +from typing import Iterable -from .options import NetworkOptions +from .options import EdgeOptions, NetworkOptions, NodeOptions from .node cimport PyNode @@ -37,10 +37,11 @@ cdef void ode( unsigned int iteration, double[:] states, double[:] derivatives, - Node **nodes, + Network network, ) noexcept: """ C Implementation to compute full network state """ cdef Node __node + cdef Node **nodes = network.nodes cdef unsigned int t, j cdef double[:] arr = None printf("\n") @@ -49,44 +50,44 @@ cdef void ode( for j in range(100): __node = nodes[j][0] if __node.statefull: - __node.output(0.0, states, arr, arr, arr, __node) + __node.output(0.0, states, 0.0, arr, arr, arr, __node) else: - __node.output(0.0, arr, arr, arr, arr, __node) - __node.ode(0.0, states, arr, arr, arr, arr, __node) + __node.output(0.0, arr, 0.0, arr, arr, arr, __node) + __node.ode(0.0, states, arr, 0.0, arr, arr, arr, __node) cdef class PyNetwork: """ Python interface to Network ODE """ - def __cinit__(self, nnodes: int): + def __cinit__(self): """ C initialization for manual memory allocation """ - self.nnodes = nnodes - self._network = malloc(sizeof(Network)) - if self._network is NULL: + self.network = malloc(sizeof(Network)) + if self.network is NULL: raise MemoryError("Failed to allocate memory for Network") - self._network.ode = ode + self.network.ode = ode - def __init__(self, nnodes): + def __init__(self, nodes: Iterable[NodeOptions], edges: Iterable[EdgeOptions]): """ Initialize """ - self.c_nodes = malloc(nnodes * sizeof(Node *)) - # if self._network.nodes is NULL: - # raise MemoryError("Failed to allocate memory for nodes in Network") + self.network.nnodes = len(nodes) + self.network.nedges = len(edges) - self.nodes = [] + # Allocate memory for c-node structs + self.network.nodes = malloc(self.nnodes * sizeof(Node *)) cdef Node *c_node + self.nodes = [] for n in range(self.nnodes): - self.nodes.append(PyNode(f"{n}", 0)) + self.nodes.append(PyNode(f"{n}")) pyn = self.nodes[n] - c_node = (pyn._node) - self.c_nodes[n] = c_node + c_node = (pyn.node) + self.network.nodes[n] = c_node def __dealloc__(self): """ Deallocate any manual memory as part of clean up """ - if self._network.nodes is not NULL: - free(self._network.nodes) - if self._network is not NULL: - free(self._network) + if self.network.nodes is not NULL: + free(self.network.nodes) + if self.network is not NULL: + free(self.network) @classmethod def from_options(cls, options: NetworkOptions): @@ -99,11 +100,27 @@ cdef class PyNetwork: ... cpdef void ode(self, double time, double[:] states): - self._network.ode(time, 0, states, states, self.c_nodes) + self.network.ode(time, 0, states, states, self.network[0]) + + + @property + def nnodes(self): + """ Number of nodes in the network """ + return self.network.nnodes + + @property + def nedges(self): + """ Number of edges in the network """ + return self.network.nedges + + @property + def nstates(self): + """ Number of states in the network """ + return self.network.nstates # def step(self, time, states): # """ Update the network """ - # self._network.ode(time, 0, states, states, self.c_nodes) + # self.network.ode(time, 0, states, states, self.c_nodes) # dstates = None # return dstates diff --git a/farms_network/core/node.pxd b/farms_network/core/node.pxd index 2492483..b0ec7af 100644 --- a/farms_network/core/node.pxd +++ b/farms_network/core/node.pxd @@ -24,25 +24,24 @@ Header for Node Base Struture. cdef packed struct Node: # Generic parameters - unsigned int nstates - unsigned int nparameters - unsigned int ninputs + unsigned int nstates # Number of state variables in the node. + unsigned int nparameters # Number of parameters for the node. + unsigned int ninputs # Number of inputs to the node. - char* model_type - char* name + char* model_type # Type of the model (e.g., "empty"). + char* name # Unique name of the node. - bint statefull - - void* states + bint statefull # Flag indicating whether the node is stateful. (ODE) # Parameters - void* parameters + void* parameters # Pointer to the parameters of the node. # Functions void ode( double time, double[:] states, double[:] derivatives, + double usr_input, double[:] inputs, double[:] weights, double[:] noise, @@ -52,6 +51,7 @@ cdef packed struct Node: double output( double time, double[:] states, + double usr_input, double[:] inputs, double[:] weights, double[:] noise, @@ -64,6 +64,7 @@ cdef: double time, double[:] states, double[:] derivatives, + double usr_input, double[:] inputs, double[:] weights, double[:] noise, @@ -72,6 +73,7 @@ cdef: double output( double time, double[:] states, + double usr_input, double[:] inputs, double[:] weights, double[:] noise, @@ -83,4 +85,4 @@ cdef class PyNode: """ Python interface to Node C-Structure""" cdef: - Node* _node + Node* node diff --git a/farms_network/core/node.pyx b/farms_network/core/node.pyx index 95a2d1a..2348d4e 100644 --- a/farms_network/core/node.pyx +++ b/farms_network/core/node.pyx @@ -28,6 +28,7 @@ cdef void ode( double time, double[:] states, double[:] derivaties, + double usr_input, double[:] inputs, double[:] weights, double[:] noise, @@ -40,6 +41,7 @@ cdef void ode( cdef double output( double time, double[:] states, + double usr_input, double[:] inputs, double[:] weights, double[:] noise, @@ -54,86 +56,81 @@ cdef class PyNode: """ Python interface to Node C-Structure""" def __cinit__(self): - self._node = malloc(sizeof(Node)) - if self._node is NULL: + self.node = malloc(sizeof(Node)) + if self.node is NULL: raise MemoryError("Failed to allocate memory for Node") - self._node.name = NULL - self._node.model_type = strdup("base".encode('UTF-8')) - self._node.ode = ode - self._node.output = output + self.node.name = NULL + self.node.model_type = strdup("base".encode('UTF-8')) + self.node.ode = ode + self.node.output = output def __dealloc__(self): - if self._node.name is not NULL: - free(self._node.name) - if self._node.model_type is not NULL: - free(self._node.model_type) - if self._node.parameters is not NULL: - free(self._node.parameters) - if self._node is not NULL: - free(self._node) - - def __init__(self, name, ninputs): + if self.node.name is not NULL: + free(self.node.name) + if self.node.model_type is not NULL: + free(self.node.model_type) + if self.node.parameters is not NULL: + free(self.node.parameters) + if self.node is not NULL: + free(self.node) + + def __init__(self, name): self.name = name - self.ninputs = ninputs @classmethod def from_options(cls, node_options: NodeOptions): """ From node options """ name: str = node_options.name ninputs: int = node_options.ninputs - return cls(name, ninputs) + return cls(name) # Property methods for name @property def name(self): - if self._node.name is NULL: + if self.node.name is NULL: return None - return self._node.name.decode('UTF-8') + return self.node.name.decode('UTF-8') @name.setter def name(self, value): - if self._node.name is not NULL: - free(self._node.name) - self._node.name = strdup(value.encode('UTF-8')) + if self.node.name is not NULL: + free(self.node.name) + self.node.name = strdup(value.encode('UTF-8')) # Property methods for model_type @property def model_type(self): - if self._node.model_type is NULL: + if self.node.model_type is NULL: return None - return self._node.model_type.decode('UTF-8') + return self.node.model_type.decode('UTF-8') # Property methods for nstates @property def nstates(self): - return self._node.nstates + return self.node.nstates # Property methods for nparameters @property def nparameters(self): - return self._node.nparameters + return self.node.nparameters # Property methods for ninputs @property def ninputs(self): - return self._node.ninputs - - @ninputs.setter - def ninputs(self, value): - self._node.ninputs = value + return self.node.ninputs # Methods to wrap the ODE and output functions - def ode(self, double time, states, dstates, inputs, weights, noise): + def ode(self, double time, states, dstates, usr_input, inputs, weights, noise): cdef double[:] c_states = states cdef double[:] c_dstates = dstates cdef double[:] c_inputs = inputs cdef double[:] c_weights = weights cdef double[:] c_noise = noise # Call the C function directly - self._node.ode( - time, c_states, c_dstates, c_inputs, c_weights, c_noise, self._node[0] + self.node.ode( + time, c_states, c_dstates, usr_input, c_inputs, c_weights, c_noise, self.node[0] ) - def output(self, double time, states, inputs, weights, noise): + def output(self, double time, states, usr_input, inputs, weights, noise): # Call the C function and return its result - return self._node[0].output(time, states, inputs, weights, noise, self._node[0]) + return self.node[0].output(time, states, usr_input, inputs, weights, noise, self.node[0]) From 74b73cba7399c3777a77c90c840f2eb027d3cf5d Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Tue, 1 Oct 2024 10:39:14 -0400 Subject: [PATCH 052/316] [OPTIONS] Added integration options with defaults --- farms_network/core/options.py | 38 +++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/farms_network/core/options.py b/farms_network/core/options.py index 609bb5f..f1b8c33 100644 --- a/farms_network/core/options.py +++ b/farms_network/core/options.py @@ -22,6 +22,8 @@ def __init__(self, **kwargs): self.graph: dict = kwargs.pop("graph", {"name": ""}) self.units = kwargs.pop("units", None) + self.integration = kwargs.pop("integration", IntegrationOptions.defaults()) + self.nodes: List[NodeOptions] = kwargs.pop("nodes", []) self.edges: List[EdgeOptions] = kwargs.pop("edges", []) @@ -53,6 +55,42 @@ def __add__(self, other: Self): return self +################################# +# Numerical Integration Options # +################################# +class IntegrationOptions(Options): + """ Class to set the options for numerical integration """ + + def __init__(self, **kwargs): + super().__init__() + + self.timestep: float = kwargs.pop("timestep") + self.integrator: str = kwargs.pop("integrator") + self.method: str = kwargs.pop("method") + self.atol: float = kwargs.pop("atol") + self.rtol: float = kwargs.pop("rtol") + self.max_step: float = kwargs.pop("max_step") + self.checks: bool = kwargs.pop("checks") + + if kwargs: + raise Exception(f'Unknown kwargs: {kwargs}') + + @classmethod + def defaults(cls, **kwargs): + + options = {} + + options["timestep"] = kwargs.pop("timestep", "1e-3") + options["integrator"] = kwargs.pop("integrator", "dopri5") + options["method"] = kwargs.pop("method", "adams") + options["atol"] = kwargs.pop("atol", 1e-12) + options["rtol"] = kwargs.pop("rtol", 1e-6) + options["max_step"] = kwargs.pop("max_step", 0.0) + options["checks"] = kwargs.pop("checks", True) + return cls(**options) + + + ########################### # Node Base Class Options # ########################### From ba4a168f51e05a9614f74d4804898af5dc6ce412 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Fri, 4 Oct 2024 00:24:40 -0400 Subject: [PATCH 053/316] [CORE] Added working minimum data structures for testing --- farms_network/core/data.py | 181 ++++++++++++++++++++++++++------- farms_network/core/data_cy.pxd | 50 ++++----- farms_network/core/data_cy.pyx | 43 ++++---- 3 files changed, 181 insertions(+), 93 deletions(-) diff --git a/farms_network/core/data.py b/farms_network/core/data.py index 39fe595..c142e90 100644 --- a/farms_network/core/data.py +++ b/farms_network/core/data.py @@ -20,76 +20,181 @@ """ -from typing import List +from typing import Dict, List import numpy as np +from farms_core import pylog +from farms_core.array.array import to_array +from farms_core.array.array_cy import DoubleArray1D from farms_core.array.types import (NDARRAY_V1, NDARRAY_V1_D, NDARRAY_V2_D, NDARRAY_V3_D) +from farms_core.io.hdf5 import dict_to_hdf5, hdf5_to_dict -from data_cy import NetworkDataCy, StatesArrayCy -from .options import NodeOptions, NodeStateOptions +from .data_cy import NetworkConnectivityCy, NetworkDataCy, NetworkStatesCy +from .options import NetworkOptions, NodeOptions, NodeStateOptions class NetworkData(NetworkDataCy): """ Network data """ - def __init__(self, nstates,): + def __init__( + self, + states, + derivatives, + connectivity, + outputs, + external_inputs, + **kwargs, + ): """ Network data structure """ - super().__init__( - nstates, - # states - ) + super().__init__() + self.states = states + self.derivatives = derivatives + self.connectivity = connectivity + self.outputs = outputs + self.external_inputs = external_inputs - # self.states = states - self.nodes: List[NodeData] = [NodeData(),] + # self.nodes: List[NodeData] = [NodeData(),] - _connectivity = None - _states = None - _dstates = None - _outputs = None - _nodes = None + @classmethod + def from_options(cls, network_options: NetworkOptions): + """ From options """ + + states = NetworkStates.from_options(network_options) + derivatives = NetworkStates.from_options(network_options) + connectivity = NetworkConnectivity.from_options(network_options) + outputs = DoubleArray1D(array=np.zeros(len(network_options.nodes),)) + external_inputs = DoubleArray1D(array=np.zeros(len(network_options.nodes),)) + return cls( + states=states, + derivatives=derivatives, + connectivity=connectivity, + outputs=outputs, + external_inputs=external_inputs + ) + def to_dict(self, iteration: int = None) -> Dict: + """Convert data to dictionary""" + return { + 'states': self.states.to_dict(), + 'derivatives': self.derivatives.to_dict(), + 'connectivity': self.connectivity.to_dict(), + 'outputs': to_array(self.outputs.array), + 'external_inputs': to_array(self.external_inputs.array), + } -class NodeData: - """ Base class for representing an arbitrary node data """ + def to_file(self, filename: str, iteration: int = None): + """Save data to file""" + pylog.info('Exporting to dictionary') + data_dict = self.to_dict(iteration) + pylog.info('Saving data to %s', filename) + dict_to_hdf5(filename=filename, data=data_dict) + pylog.info('Saved data to %s', filename) - def __init__(self, states_arr: StatesArray, out_arr: OutputArray): - """Node data initialization """ - super().__init__() +class NetworkStates(NetworkStatesCy): - self.states = states_arr - self.output = out_arr - self.variables = None + def __init__(self, array, indices): + super().__init__(array, indices) @classmethod - def from_options(cls, options: NodeOptions): - """ Node data from class """ - nstates = options._nstates - return cls( + def from_options(cls, network_options: NetworkOptions): + nodes = network_options.nodes + nstates = 0 + indices = [0,] + for index, node in enumerate(nodes): + nstates += node._nstates + indices.append(nstates) + + return cls( + array=np.zeros((nstates,)), + indices=np.array(indices) ) + def to_dict(self, iteration: int = None) -> Dict: + """Convert data to dictionary""" + return { + 'array': to_array(self.array), + 'indices': to_array(self.indices), + } + -class StatesArray(StatesArrayCy): - """ State array data """ - def __init__(self, array: NDARRAY_V2_D, names: List): - super().__init__(array) - self.names = names +class NetworkConnectivity(NetworkConnectivityCy): + + def __init__(self, array, indices): + super().__init__(array, indices) @classmethod - def from_options(cls, options: NodeStateOptions): - """ State options """ + def from_options(cls, network_options: NetworkOptions): + + nodes = network_options.nodes + edges = network_options.edges + + connectivity = np.empty((len(edges), 2)) + node_names = [node.name for node in nodes] + + for index, edge in enumerate(edges): + connectivity[index][0] = int(node_names.index(edge.from_node)) + connectivity[index][1] = int(node_names.index(edge.to_node)) + connectivity = np.array(sorted(connectivity, key=lambda row: row[1])) + + sources = np.empty((len(edges),)) + nedges = 0 + indices = [0,] + for index, node in enumerate(nodes): + node_sources = connectivity[connectivity[:, 1] == index][:, 0].tolist() + nedges += len(node_sources) + indices.append(nedges) + sources[indices[index]:indices[index+1]] += node_sources + + return cls( + array=np.array(sources, dtype=np.uintc), + indices=np.array(indices, dtype=np.uintc) + ) + + def to_dict(self, iteration: int = None) -> Dict: + """Convert data to dictionary""" + return { + 'array': to_array(self.array), + 'indices': to_array(self.indices), + } + + +# class NodeData: +# """ Base class for representing an arbitrary node data """ + +# def __init__(self, states_arr: StatesArray, out_arr: OutputArray): +# """Node data initialization """ + +# super().__init__() + +# self.states = states_arr +# self.dstates = states_arr +# self.usr_inputs = None +# self.state_noise = None +# self.output_noise = None +# self.output = out_arr + +# @classmethod +# def from_options(cls, options: NodeOptions): +# """ Node data from class """ +# nstates = options._nstates +# return cls() +# class StatesArray(StatesArrayCy): +# """ State array data """ -class OutputArray: - """ Output array data """ +# def __init__(self, array: NDARRAY_V2_D, names: List): +# super().__init__(array) +# self.names = names - def __init__(self, array): - super().__init__(array) +# @classmethod +# def from_options(cls, options: NodeStateOptions): +# """ State options """ def main(): diff --git a/farms_network/core/data_cy.pxd b/farms_network/core/data_cy.pxd index 6f0e4d2..e1d168d 100644 --- a/farms_network/core/data_cy.pxd +++ b/farms_network/core/data_cy.pxd @@ -18,46 +18,38 @@ limitations under the License. """ -from farms_core.array.array_cy cimport DoubleArray1D, DoubleArray2D +from farms_core.array.array_cy cimport (DoubleArray1D, DoubleArray2D, + IntegerArray1D) +include 'types.pxd' -cdef class NetworkDataCy: - cdef: - public StatesArrayCy states +cdef class NetworkDataCy: cdef: - public nodes - public connectivity + public NetworkStatesCy states + public NetworkStatesCy derivatives + public DoubleArray1D external_inputs + public DoubleArray1D outputs + public DoubleArray2D weights + public DoubleArray1D states_noise + public DoubleArray1D outputs_noise -cdef class NodeDataCy: - """ Node data """ + # _state_indices = None + # _state_index_skips = None + # _connectivity_indices = None + # _connectivity_index_skips = None + # _outputs = None -cdef class StatesArrayCy(DoubleArray2D): +cdef class NetworkStatesCy(DoubleArray1D): """ State array """ + cdef public UITYPEv1 indices -cdef class DStatesArrayCy(DoubleArray2D): - """ DStates array """ - - -cdef class ParametersArrayCy(DoubleArray2D): - """ Parameters array """ - - -cdef class OutputsArrayCy(DoubleArray2D): - """ Outputs array """ - - -cdef class InputsArrayCy(DoubleArray2D): - """ Inputs array """ - - -cdef class DriveArrayCy(DoubleArray2D): - """ Drive Array """ +cdef class NetworkConnectivityCy(IntegerArray1D): + """ Network connectivity array """ -# # class User2DArrayCy(DoubleArray2D): -# # ... + cdef public UITYPEv1 indices diff --git a/farms_network/core/data_cy.pyx b/farms_network/core/data_cy.pyx index b1881da..104f0c8 100644 --- a/farms_network/core/data_cy.pyx +++ b/farms_network/core/data_cy.pyx @@ -28,20 +28,10 @@ import numpy as np cdef class NetworkDataCy: """ Network data """ - def __init__( - self, - nstates: int, - # states - # nodes = None, - # connectivity = None, - # inputs = None, - # drives = None, - # outputs = None, - ): + def __init__(self): """ nodes data initialization """ super().__init__() - # self.states = states # self.nodes = nodes # self.connectivity = connectivity # self.inputs = inputs @@ -49,24 +39,25 @@ cdef class NetworkDataCy: # self.outputs = outputs -cdef class NodeData: - """ Base Class for node data """ - - -cdef class StatesArrayCy(DoubleArray2D): +cdef class NetworkStatesCy(DoubleArray1D): """ State array """ - def __init__(self, array): + def __init__( + self, + array: NDArray[(Any,), np.double], + indices: NDArray[(Any,), np.uintc], + ): super().__init__(array) + self.indices = np.array(indices, dtype=np.uintc) -cdef class DStatesArrayCy(DoubleArray2D): - """ DStates array """ +cdef class NetworkConnectivityCy(IntegerArray1D): + """ Connectivity array """ - -cdef class OutputsArrayCy(DoubleArray2D): - """ Outputs array """ - - -# class User2DArrayCy(DoubleArray2D): -# ... + def __init__( + self, + array: NDArray[(Any,), np.uintc], + indices: NDArray[(Any,), np.uintc], + ): + super().__init__(array) + self.indices = np.array(indices, dtype=np.uintc) From 4c6e488557e523685d649e327acfd6bacceb9af7 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Fri, 4 Oct 2024 00:25:09 -0400 Subject: [PATCH 054/316] [CORE] Adapted network module to changes in data structure --- farms_network/core/network.pxd | 31 ++++--------------------------- farms_network/core/network.pyx | 27 ++++++++++++++++++++------- 2 files changed, 24 insertions(+), 34 deletions(-) diff --git a/farms_network/core/network.pxd b/farms_network/core/network.pxd index 4a48139..475d8b1 100644 --- a/farms_network/core/network.pxd +++ b/farms_network/core/network.pxd @@ -1,4 +1,5 @@ from .node cimport Node +from .data_cy cimport NetworkDataCy cdef struct Network: @@ -18,6 +19,7 @@ cdef struct Network: double[:] states, double[:] derivatives, Network network, + NetworkDataCy data, ) @@ -27,34 +29,8 @@ cdef void ode( double[:] states, double[:] derivatives, Network network, + NetworkDataCy data, ) noexcept - # cdef Node __node - # cdef NodeData node_data - # cdef unsigned int j - # cdef nnodes = sizeof(nodes)/sizeof(node) - - # double[:, :] states - # double[:, :] dstates - # double[:, :] inputs - # double[:, :] weights - # double[:, :] noise - - # for j in range(nnodes): - # node = node[j] - # node_data = network_data[j] - # if node.statefull: - # nstates = 2 - # node.ode(t, state) - # nodes[j].step( - # time, - # node_data.curr_state, - # dstates, - # inputs, - # weights, - # noise, - # drive, - # nodes[j] - # ) cdef class PyNetwork: @@ -63,6 +39,7 @@ cdef class PyNetwork: cdef: Network *network list nodes + NetworkDataCy data # cpdef void step(self) cpdef void ode(self, double time, double[:] states) diff --git a/farms_network/core/network.pyx b/farms_network/core/network.pyx index 06d2374..dd232c6 100644 --- a/farms_network/core/network.pyx +++ b/farms_network/core/network.pyx @@ -17,6 +17,8 @@ limitations under the License. ----------------------------------------------------------------------- """ +include "types.pxd" + import numpy as np from libc.stdio cimport printf @@ -25,12 +27,14 @@ from libc.string cimport strdup from typing import Iterable -from .options import EdgeOptions, NetworkOptions, NodeOptions +from .data import NetworkData, NetworkStates from .node cimport PyNode from tqdm import tqdm +from .options import EdgeOptions, NetworkOptions, NodeOptions + cdef void ode( double time, @@ -38,6 +42,7 @@ cdef void ode( double[:] states, double[:] derivatives, Network network, + NetworkDataCy data, ) noexcept: """ C Implementation to compute full network state """ cdef Node __node @@ -45,15 +50,17 @@ cdef void ode( cdef unsigned int t, j cdef double[:] arr = None printf("\n") - for t in range(int(10)): + cdef double[:] _states = data.states.array + cdef long unsigned int nnodes = network.nnodes + for t in range(nnodes): printf("%i \t", t) for j in range(100): __node = nodes[j][0] + __node.ode(0.0, _states, arr, 0.0, arr, arr, arr, __node) if __node.statefull: __node.output(0.0, states, 0.0, arr, arr, arr, __node) else: __node.output(0.0, arr, 0.0, arr, arr, arr, __node) - __node.ode(0.0, states, arr, 0.0, arr, arr, arr, __node) cdef class PyNetwork: @@ -66,11 +73,14 @@ cdef class PyNetwork: raise MemoryError("Failed to allocate memory for Network") self.network.ode = ode - def __init__(self, nodes: Iterable[NodeOptions], edges: Iterable[EdgeOptions]): + def __init__(self, network_options): """ Initialize """ + nodes = network_options.nodes + edges = network_options.edges self.network.nnodes = len(nodes) self.network.nedges = len(edges) + self.data = NetworkData.from_options(network_options) # Allocate memory for c-node structs self.network.nodes = malloc(self.nnodes * sizeof(Node *)) cdef Node *c_node @@ -92,15 +102,18 @@ cdef class PyNetwork: @classmethod def from_options(cls, options: NetworkOptions): """ Initialize network from NetworkOptions """ - options - return cls + return cls(options) def setup_integrator(options: IntegratorOptions): """ Setup integrator for neural network """ ... cpdef void ode(self, double time, double[:] states): - self.network.ode(time, 0, states, states, self.network[0]) + cdef double[:] _states = self.data.states.array + cdef UITYPEv1 _indices = self.data.states.indices + self.network.ode( + time, 0, _states, states, self.network[0], self.data + ) @property From bd82db0f7deac43886b5c41cbd16c299652054be Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Fri, 4 Oct 2024 00:28:44 -0400 Subject: [PATCH 055/316] [SCRATCH] Adapted test network to changes in data structs --- scratch/test_network.py | 97 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 89 insertions(+), 8 deletions(-) diff --git a/scratch/test_network.py b/scratch/test_network.py index c83a640..e07dfca 100644 --- a/scratch/test_network.py +++ b/scratch/test_network.py @@ -1,28 +1,109 @@ """ Test network """ +from pprint import pprint + +from copy import deepcopy +import networkx as nx import numpy as np +from farms_core.io.yaml import read_yaml, write_yaml +from farms_core.options import Options +from farms_network.core import options from farms_network.core.network import PyNetwork from farms_network.core.options import NetworkOptions from scipy.integrate import ode +from farms_network.core.data import NetworkData, NetworkConnectivity, NetworkStates + + +param_opts = options.LIDannerParameterOptions.defaults() +state_opts = options.LIDannerNaPStateOptions.from_kwargs(v0=0.0, h0=-70.0) +vis_opts = options.NodeVisualOptions() + +n1_opts = options.LIDannerNodeOptions( + name="n1", + parameters=param_opts, + visual=vis_opts, + state=state_opts, +) + +n2_opts = deepcopy(n1_opts) +n2_opts.name = "n2" + +edge_n1_n2 = options.EdgeOptions( + source="n1", + target="n2", + weight=1.0, + type="excitatory", + visual=options.EdgeVisualOptions(), +) + +edge_n2_n1 = options.EdgeOptions( + source="n2", + target="n1", + weight=1.0, + type="excitatory", + visual=options.EdgeVisualOptions(), +) -nnodes = 100 +network = options.NetworkOptions( + directed=True, + multigraph=False, + graph={"name": "network"}, +) + +network.add_node(n1_opts) +network.add_node(n2_opts) +network.add_edge(edge_n1_n2) +network.add_edge(edge_n2_n1) + +nnodes = 10 + +danner_network = nx.read_graphml("/Users/tatarama/projects/work/research/neuromechanics/quadruped/mice/mouse-locomotion/data/config/network/siggraph_network.graphml") + +network_options = options.NetworkOptions( + directed=True, + multigraph=False, + graph={"name": "network"}, +) + +for node in danner_network.nodes: + network.add_node( + options.LIDannerNodeOptions( + name=node, + parameters=param_opts, + visual=vis_opts, + state=state_opts, + ) + ) + +for edge in danner_network.edges: + network.add_edge( + options.EdgeOptions( + source=edge[0], + target=edge[1], + weight=1.0, + type="excitatory", + visual=options.EdgeVisualOptions(), + ) + ) -states = np.zeros((nnodes,)) -dstates = np.zeros((nnodes,)) -outputs = np.zeros((nnodes,)) -weights = np.zeros((nnodes,)) +data = NetworkData.from_options(network_options) -network = PyNetwork(nnodes=nnodes) +network = PyNetwork.from_options(network) integrator = ode(network.ode).set_integrator( u'dopri5', method=u'adams', max_step=0.0, - nsteps=4 + nsteps=0 ) +nnodes = len(network_options.nodes) integrator.set_initial_value(np.zeros((nnodes,)), 0.0) -integrator.integrate(integrator.t+1.0) +print(network.nnodes) + +data.to_file("/tmp/sim.hdf5") + +# integrator.integrate(integrator.t) # # Integrate # for iteration in range(0, 100): From 907223ac5862106b34afa8c78861328273253c41 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Fri, 4 Oct 2024 10:11:09 -0400 Subject: [PATCH 056/316] [MODELS] Added factory module to initiate nodes --- farms_network/models/factory.py | 97 +++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 farms_network/models/factory.py diff --git a/farms_network/models/factory.py b/farms_network/models/factory.py new file mode 100644 index 0000000..92dd2c2 --- /dev/null +++ b/farms_network/models/factory.py @@ -0,0 +1,97 @@ +""" +----------------------------------------------------------------------- +Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty +Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +----------------------------------------------------------------------- + +Factory class for generating the node model. +""" + +from farms_network.models.fitzhugh_nagumo import FitzhughNagumo +from farms_network.models.hh_daun_motoneuron import HHDaunMotoneuron +from farms_network.models.hopf_oscillator import HopfOscillator +from farms_network.models.leaky_integrator import LeakyIntegrator +from farms_network.models.lif_danner import LIFDanner +from farms_network.models.lif_danner_nap import LIFDannerNap +from farms_network.models.lif_daun_interneuron import LIFDaunInterneuron +from farms_network.models.matsuoka_node import MatsuokaNode +from farms_network.models.morphed_oscillator import MorphedOscillator +from farms_network.models.morris_lecar import MorrisLecarNode +from farms_network.models.oscillator import Oscillator +from farms_network.models.relu import ReLUNode +from farms_network.models.sensory_node import SensoryNode + + +class NodeFactory: + """Implementation of Factory Node class. + """ + nodes = { # 'if': IntegrateAndFire, + 'oscillator': Oscillator, + 'hopf_oscillator': HopfOscillator, + 'morphed_oscillator': MorphedOscillator, + 'leaky': LeakyIntegrator, + 'sensory': SensoryNode, + 'lif_danner_nap': LIFDannerNap, + 'lif_danner': LIFDanner, + 'lif_daun_interneuron': LIFDaunInterneuron, + 'hh_daun_motoneuron': HHDaunMotoneuron, + 'fitzhugh_nagumo': FitzhughNagumo, + 'matsuoka_node': MatsuokaNode, + 'morris_lecar': MorrisLecarNode, + 'relu': ReLUNode, + } + + def __init__(self): + """Factory initialization.""" + super(NodeFactory, self).__init__() + + @staticmethod + def register_node(node_type, node_instance): + """ + Register a new type of node that is a child class of Node. + Parameters + ---------- + self: type + description + node_type: + String to identifier for the node. + node_instance: + Class of the node to register. + """ + NodeFactory.nodes[node_type] = node_instance + + @staticmethod + def gen_node(node_type): + """Generate the necessary type of node. + Parameters + ---------- + self: type + description + node_type: + One of the following list of available nodes. + 1. if - Integrate and Fire + 2. lif_danner_nap - LIF Danner Nap + 3. lif_danner - LIF Danner + 4. lif_daun_internode - LIF Daun Internode + 5. hh_daun_motornode - HH_Daun_Motornode + Returns + ------- + node: + Appropriate node class. + """ + node = NodeFactory.nodes.get(node_type) + if not node: + raise ValueError(node_type) + return node From f7cabea2e62db5340c9bbd6878defa54ae4d815d Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Mon, 7 Oct 2024 14:51:55 -0400 Subject: [PATCH 057/316] [NODE] Added additional properties for initialization --- farms_network/core/node.pxd | 19 +++++++++++++------ farms_network/core/node.pyx | 22 +++++++++++++--------- 2 files changed, 26 insertions(+), 15 deletions(-) diff --git a/farms_network/core/node.pxd b/farms_network/core/node.pxd index b0ec7af..b4fb9df 100644 --- a/farms_network/core/node.pxd +++ b/farms_network/core/node.pxd @@ -22,11 +22,11 @@ Header for Node Base Struture. """ -cdef packed struct Node: +cdef struct Node: # Generic parameters unsigned int nstates # Number of state variables in the node. unsigned int nparameters # Number of parameters for the node. - unsigned int ninputs # Number of inputs to the node. + unsigned int ninputs # Number of inputs char* model_type # Type of the model (e.g., "empty"). char* name # Unique name of the node. @@ -37,7 +37,7 @@ cdef packed struct Node: void* parameters # Pointer to the parameters of the node. # Functions - void ode( + inline void (*ode)( double time, double[:] states, double[:] derivatives, @@ -45,7 +45,7 @@ cdef packed struct Node: double[:] inputs, double[:] weights, double[:] noise, - Node node + Node* node ) noexcept double output( @@ -58,9 +58,16 @@ cdef packed struct Node: Node node ) noexcept + void (*test_function)(double time, + double* states, + double* derivaties, + double usr_input, + double* inputs, + Node* node) noexcept + cdef: - void ode( + inline void ode( double time, double[:] states, double[:] derivatives, @@ -68,7 +75,7 @@ cdef: double[:] inputs, double[:] weights, double[:] noise, - Node node + Node* node ) noexcept double output( double time, diff --git a/farms_network/core/node.pyx b/farms_network/core/node.pyx index 2348d4e..030caa9 100644 --- a/farms_network/core/node.pyx +++ b/farms_network/core/node.pyx @@ -24,7 +24,7 @@ from libc.string cimport strdup from .options import NodeOptions -cdef void ode( +cdef inline void ode( double time, double[:] states, double[:] derivaties, @@ -32,7 +32,7 @@ cdef void ode( double[:] inputs, double[:] weights, double[:] noise, - Node node + Node* node ) noexcept: """ Node ODE """ printf("Base implementation of ODE C function \n") @@ -74,15 +74,14 @@ cdef class PyNode: if self.node is not NULL: free(self.node) - def __init__(self, name): + def __init__(self, name: str, **kwargs): self.name = name @classmethod def from_options(cls, node_options: NodeOptions): """ From node options """ name: str = node_options.name - ninputs: int = node_options.ninputs - return cls(name) + return cls(name, **node_options.parameters) # Property methods for name @property @@ -109,15 +108,20 @@ cdef class PyNode: def nstates(self): return self.node.nstates + # Property methods for ninputs + @property + def ninputs(self): + return self.node.ninputs + # Property methods for nparameters @property def nparameters(self): return self.node.nparameters - # Property methods for ninputs @property - def ninputs(self): - return self.node.ninputs + def parameters(self): + """ Number of states in the network """ + return self.node.parameters[0] # Methods to wrap the ODE and output functions def ode(self, double time, states, dstates, usr_input, inputs, weights, noise): @@ -128,7 +132,7 @@ cdef class PyNode: cdef double[:] c_noise = noise # Call the C function directly self.node.ode( - time, c_states, c_dstates, usr_input, c_inputs, c_weights, c_noise, self.node[0] + time, c_states, c_dstates, usr_input, c_inputs, c_weights, c_noise, self.node ) def output(self, double time, states, usr_input, inputs, weights, noise): From d4f37a5c5aaa9459fd8b150924686b3fab303f14 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Mon, 7 Oct 2024 14:53:13 -0400 Subject: [PATCH 058/316] [NETWORK] Changed to test code with function pointers instead of memviews --- farms_network/core/network.pxd | 27 +----- farms_network/core/network.pyx | 171 +++++++++++++++++++++++++-------- 2 files changed, 132 insertions(+), 66 deletions(-) diff --git a/farms_network/core/network.pxd b/farms_network/core/network.pxd index 475d8b1..d67a139 100644 --- a/farms_network/core/network.pxd +++ b/farms_network/core/network.pxd @@ -10,36 +10,15 @@ cdef struct Network: unsigned long int nstates # nodes list - Node **nodes - - # functions - void ode( - double time, - unsigned int iteration, - double[:] states, - double[:] derivatives, - Network network, - NetworkDataCy data, - ) - - -cdef void ode( - double time, - unsigned int iteration, - double[:] states, - double[:] derivatives, - Network network, - NetworkDataCy data, -) noexcept - + Node** nodes cdef class PyNetwork: """ Python interface to Network ODE """ cdef: Network *network - list nodes - NetworkDataCy data + public list pynodes + public NetworkDataCy data # cpdef void step(self) cpdef void ode(self, double time, double[:] states) diff --git a/farms_network/core/network.pyx b/farms_network/core/network.pyx index dd232c6..bf8e364 100644 --- a/farms_network/core/network.pyx +++ b/farms_network/core/network.pyx @@ -27,70 +27,138 @@ from libc.string cimport strdup from typing import Iterable +from ..models.factory import NodeFactory +from ..models.li_danner cimport LIDannerNodeParameters from .data import NetworkData, NetworkStates +from .data_cy cimport NetworkDataCy, NetworkStatesCy -from .node cimport PyNode +from .node cimport Node, PyNode from tqdm import tqdm from .options import EdgeOptions, NetworkOptions, NodeOptions +cdef enum state_conv: + state0 = 0 + state1 = 1 + + +cdef void li_ode( + double time, + double* states, + double* derivatives, + double external_input, + double* network_outputs, + unsigned int* input_neurons, + double* input_weights, + Node* node +) noexcept: + """ ODE """ + # Parameters + cdef LIDannerNodeParameters params = ( node[0].parameters)[0] + + # States + cdef double state_v = states[state_conv.state0] + # printf("states: %f \n", state_v) + + # Ileak + cdef double i_leak = params.g_leak * (state_v - params.e_leak) + + # Node inputs + cdef double _sum = 0.0 + cdef unsigned int j + cdef double _node_out + cdef double res + + cdef double _input + cdef double _weight + + cdef unsigned int ninputs = node[0].ninputs + for j in range(ninputs): + _input = network_outputs[input_neurons[j]] + _weight = input_weights[j] + # _sum += node_inputs_eval_c(_input, _weight) + + # dV + cdef double i_noise = 0.0 + states[state_conv.state0] = (-(i_leak + i_noise + _sum)/params.c_m) + + cdef void ode( double time, unsigned int iteration, - double[:] states, - double[:] derivatives, - Network network, NetworkDataCy data, + Network* network, ) noexcept: """ C Implementation to compute full network state """ + cdef int j, step, steps, nnodes + steps = 4 + cdef Node __node - cdef Node **nodes = network.nodes - cdef unsigned int t, j - cdef double[:] arr = None - printf("\n") - cdef double[:] _states = data.states.array - cdef long unsigned int nnodes = network.nnodes - for t in range(nnodes): - printf("%i \t", t) - for j in range(100): - __node = nodes[j][0] - __node.ode(0.0, _states, arr, 0.0, arr, arr, arr, __node) - if __node.statefull: - __node.output(0.0, states, 0.0, arr, arr, arr, __node) - else: - __node.output(0.0, arr, 0.0, arr, arr, arr, __node) + cdef Node** nodes = network.nodes + nnodes = network.nnodes + + cdef double* states = &data.states.array[0] + cdef unsigned int* states_indices = &data.states.indices[0] + + cdef double* derivatives = &data.derivatives.array[0] + cdef unsigned int* derivatives_indices = &data.derivatives.indices[0] + + cdef double external_input = 0.0 + cdef double* outputs = &data.outputs.array[0] + + cdef unsigned int* input_neurons = &data.connectivity.sources[0] + cdef double* weights = &data.connectivity.weights[0] + cdef unsigned int* input_neurons_indices = &data.connectivity.indices[0] + + # cdef double[::1] node_states + # cdef double[::1] node_derivatives + + # node_derivatives = states[derivatives_indices[0]:derivatives_indices[1]] + + for step in range(steps): + for j in range(nnodes): + # node_states = states[states_indices[0]:states_indices[1]] + # (nodes[j][0]).ode( + # time, node_states, node_derivatives, time, node_states, node_states, node_states, nodes[j] + # ) + li_ode( + time, + states + states_indices_ptr[j], + derivatives + derivatives_indices[j], + external_input, + outputs, + input_neurons + input_neurons_indices[j], + weights + input_neurons_indices[j], + nodes[j] + ) + # if __node.statefull: + # __node.output(0.0, states, 0.0, arr, arr, arr, __node) + # else: + # __node.output(0.0, arr, 0.0, arr, arr, arr, __node) cdef class PyNetwork: """ Python interface to Network ODE """ - def __cinit__(self): + def __cinit__(self, network_options: NetworkOptions): """ C initialization for manual memory allocation """ self.network = malloc(sizeof(Network)) if self.network is NULL: raise MemoryError("Failed to allocate memory for Network") - self.network.ode = ode + self.network.nnodes = len(network_options.nodes) + self.network.nedges = len(network_options.edges) + # Allocate memory for c-node structs + self.network.nodes = malloc(self.nnodes * sizeof(Node *)) def __init__(self, network_options): """ Initialize """ - nodes = network_options.nodes - edges = network_options.edges - self.network.nnodes = len(nodes) - self.network.nedges = len(edges) - - self.data = NetworkData.from_options(network_options) - # Allocate memory for c-node structs - self.network.nodes = malloc(self.nnodes * sizeof(Node *)) - cdef Node *c_node - self.nodes = [] - for n in range(self.nnodes): - self.nodes.append(PyNode(f"{n}")) - pyn = self.nodes[n] - c_node = (pyn.node) - self.network.nodes[n] = c_node + super().__init__() + self.data = NetworkData.from_options(network_options) + self.pynodes = [] + self.setup_network(network_options, self.data) def __dealloc__(self): """ Deallocate any manual memory as part of clean up """ @@ -108,13 +176,32 @@ cdef class PyNetwork: """ Setup integrator for neural network """ ... - cpdef void ode(self, double time, double[:] states): - cdef double[:] _states = self.data.states.array - cdef UITYPEv1 _indices = self.data.states.indices - self.network.ode( - time, 0, _states, states, self.network[0], self.data - ) + def setup_network(self, options: NetworkOptions, data: NetworkData): + """ Setup network """ + cdef Node* c_node + + connectivity = data.connectivity + for index, node_options in enumerate(options.nodes): + self.pynodes.append(self.generate_node(node_options)) + pyn = self.pynodes[index] + c_node = (pyn.node) + c_node.ninputs = len( + connectivity.sources[connectivity.indices[index]:connectivity.indices[index+1]] + ) + self.network.nodes[index] = c_node + + def generate_node(self, node_options: NodeOptions): + """ Generate a node from options """ + Node = NodeFactory.generate_node(node_options.model) + node = Node.from_options(node_options) + return node + + cpdef void ode(self, double time, double[:] states): + """ ODE Wrapper for numerical integrators """ + self.data.states.array[:] = states[:] + cdef int iteration = 0 + ode(time, iteration, self.data, self.network) @property def nnodes(self): From bfbc0d9d82a4b9d881d07155b48867ebd5fccaf6 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Mon, 7 Oct 2024 14:53:45 -0400 Subject: [PATCH 059/316] [DATA] Added network connectivity struct with weights --- farms_network/core/data.py | 22 +++++++++++++--------- farms_network/core/data_cy.pxd | 6 ++++-- farms_network/core/data_cy.pyx | 14 ++++++-------- 3 files changed, 23 insertions(+), 19 deletions(-) diff --git a/farms_network/core/data.py b/farms_network/core/data.py index c142e90..bf7bc04 100644 --- a/farms_network/core/data.py +++ b/farms_network/core/data.py @@ -107,9 +107,8 @@ def from_options(cls, network_options: NetworkOptions): for index, node in enumerate(nodes): nstates += node._nstates indices.append(nstates) - return cls( - array=np.zeros((nstates,)), + array=np.array(np.arange(0, nstates), dtype=np.double), indices=np.array(indices) ) @@ -124,8 +123,8 @@ def to_dict(self, iteration: int = None) -> Dict: class NetworkConnectivity(NetworkConnectivityCy): - def __init__(self, array, indices): - super().__init__(array, indices) + def __init__(self, sources, weights, indices): + super().__init__(sources, weights, indices) @classmethod def from_options(cls, network_options: NetworkOptions): @@ -133,25 +132,30 @@ def from_options(cls, network_options: NetworkOptions): nodes = network_options.nodes edges = network_options.edges - connectivity = np.empty((len(edges), 2)) + connectivity = np.empty((len(edges), 3)) node_names = [node.name for node in nodes] for index, edge in enumerate(edges): connectivity[index][0] = int(node_names.index(edge.from_node)) connectivity[index][1] = int(node_names.index(edge.to_node)) - connectivity = np.array(sorted(connectivity, key=lambda row: row[1])) + connectivity[index][2] = edge.weight + connectivity = np.array(sorted(connectivity, key=lambda col: col[1])) sources = np.empty((len(edges),)) + weights = np.empty((len(edges),)) nedges = 0 indices = [0,] + print(connectivity) for index, node in enumerate(nodes): node_sources = connectivity[connectivity[:, 1] == index][:, 0].tolist() + node_weights = connectivity[connectivity[:, 1] == index][:, 2].tolist() nedges += len(node_sources) indices.append(nedges) - sources[indices[index]:indices[index+1]] += node_sources - + sources[indices[index]:indices[index+1]] = node_sources + weights[indices[index]:indices[index+1]] = node_weights return cls( - array=np.array(sources, dtype=np.uintc), + sources=np.array(sources, dtype=np.uintc), + weights=np.array(weights, dtype=np.double), indices=np.array(indices, dtype=np.uintc) ) diff --git a/farms_network/core/data_cy.pxd b/farms_network/core/data_cy.pxd index e1d168d..226b3d6 100644 --- a/farms_network/core/data_cy.pxd +++ b/farms_network/core/data_cy.pxd @@ -31,7 +31,7 @@ cdef class NetworkDataCy: public NetworkStatesCy derivatives public DoubleArray1D external_inputs public DoubleArray1D outputs - public DoubleArray2D weights + public NetworkConnectivityCy connectivity public DoubleArray1D states_noise public DoubleArray1D outputs_noise @@ -49,7 +49,9 @@ cdef class NetworkStatesCy(DoubleArray1D): cdef public UITYPEv1 indices -cdef class NetworkConnectivityCy(IntegerArray1D): +cdef class NetworkConnectivityCy: """ Network connectivity array """ + cdef public UITYPEv1 sources + cdef public DTYPEv1 weights cdef public UITYPEv1 indices diff --git a/farms_network/core/data_cy.pyx b/farms_network/core/data_cy.pyx index 104f0c8..3a481c2 100644 --- a/farms_network/core/data_cy.pyx +++ b/farms_network/core/data_cy.pyx @@ -32,11 +32,6 @@ cdef class NetworkDataCy: """ nodes data initialization """ super().__init__() - # self.nodes = nodes - # self.connectivity = connectivity - # self.inputs = inputs - # self.drives = drives - # self.outputs = outputs cdef class NetworkStatesCy(DoubleArray1D): @@ -51,13 +46,16 @@ cdef class NetworkStatesCy(DoubleArray1D): self.indices = np.array(indices, dtype=np.uintc) -cdef class NetworkConnectivityCy(IntegerArray1D): +cdef class NetworkConnectivityCy: """ Connectivity array """ def __init__( self, - array: NDArray[(Any,), np.uintc], + sources: NDArray[(Any,), np.uintc], + weights: NDArray[(Any,), np.double], indices: NDArray[(Any,), np.uintc], ): - super().__init__(array) + super().__init__() + self.sources = np.array(sources, dtype=np.uintc) + self.weights = np.array(weights, dtype=np.double) self.indices = np.array(indices, dtype=np.uintc) From 3e0a73aefbbfe6df512bdbe85c8519a2339f8f3c Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Mon, 7 Oct 2024 14:54:00 -0400 Subject: [PATCH 060/316] [SCRATCH] Updated test script to run siggraph graphml network --- scratch/test_network.py | 77 +++++++++++------------------------------ 1 file changed, 21 insertions(+), 56 deletions(-) diff --git a/scratch/test_network.py b/scratch/test_network.py index e07dfca..35a5724 100644 --- a/scratch/test_network.py +++ b/scratch/test_network.py @@ -1,62 +1,24 @@ """ Test network """ +from copy import deepcopy from pprint import pprint -from copy import deepcopy import networkx as nx import numpy as np from farms_core.io.yaml import read_yaml, write_yaml from farms_core.options import Options from farms_network.core import options +from farms_network.core.data import (NetworkConnectivity, NetworkData, + NetworkStates) from farms_network.core.network import PyNetwork from farms_network.core.options import NetworkOptions from scipy.integrate import ode -from farms_network.core.data import NetworkData, NetworkConnectivity, NetworkStates - +from tqdm import tqdm param_opts = options.LIDannerParameterOptions.defaults() state_opts = options.LIDannerNaPStateOptions.from_kwargs(v0=0.0, h0=-70.0) vis_opts = options.NodeVisualOptions() -n1_opts = options.LIDannerNodeOptions( - name="n1", - parameters=param_opts, - visual=vis_opts, - state=state_opts, -) - -n2_opts = deepcopy(n1_opts) -n2_opts.name = "n2" - -edge_n1_n2 = options.EdgeOptions( - source="n1", - target="n2", - weight=1.0, - type="excitatory", - visual=options.EdgeVisualOptions(), -) - -edge_n2_n1 = options.EdgeOptions( - source="n2", - target="n1", - weight=1.0, - type="excitatory", - visual=options.EdgeVisualOptions(), -) - -network = options.NetworkOptions( - directed=True, - multigraph=False, - graph={"name": "network"}, -) - -network.add_node(n1_opts) -network.add_node(n2_opts) -network.add_edge(edge_n1_n2) -network.add_edge(edge_n2_n1) - -nnodes = 10 - danner_network = nx.read_graphml("/Users/tatarama/projects/work/research/neuromechanics/quadruped/mice/mouse-locomotion/data/config/network/siggraph_network.graphml") network_options = options.NetworkOptions( @@ -66,7 +28,7 @@ ) for node in danner_network.nodes: - network.add_node( + network_options.add_node( options.LIDannerNodeOptions( name=node, parameters=param_opts, @@ -75,20 +37,20 @@ ) ) -for edge in danner_network.edges: - network.add_edge( +for edge, data in danner_network.edges.items(): + network_options.add_edge( options.EdgeOptions( source=edge[0], target=edge[1], - weight=1.0, - type="excitatory", + weight=data["weight"], + type=data.get("type", "excitatory"), visual=options.EdgeVisualOptions(), ) ) data = NetworkData.from_options(network_options) -network = PyNetwork.from_options(network) +network = PyNetwork.from_options(network_options) integrator = ode(network.ode).set_integrator( u'dopri5', @@ -96,16 +58,19 @@ max_step=0.0, nsteps=0 ) -nnodes = len(network_options.nodes) -integrator.set_initial_value(np.zeros((nnodes,)), 0.0) +# nnodes = len(network_options.nodes) +# integrator.set_initial_value(np.zeros(len(data.states.array),), 0.0) -print(network.nnodes) +# print("Data ------------", np.array(network.data.states.array)) -data.to_file("/tmp/sim.hdf5") +# data.to_file("/tmp/sim.hdf5") -# integrator.integrate(integrator.t) +# integrator.integrate(integrator.t + 1e-3) # # Integrate -# for iteration in range(0, 100): -# integrator.set_initial_value(integrator.y, integrator.t) -# integrator.integrate(integrator.t+(iteration*1e-3)) +states = np.array(np.arange(0, len(data.states.array)), dtype=np.double) +network.ode(0.0, states) +for iteration in tqdm(range(0, 100000), colour='green'): + # integrator.set_initial_value(integrator.y, integrator.t) + network.ode(0.0, states) + # integrator.integrate(integrator.t+(iteration*1e-3)) From b12c82c79e978b32e0ab0c339a93723a0057a9ab Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Tue, 8 Oct 2024 15:07:27 -0400 Subject: [PATCH 061/316] [CORE][NODE] Updated function signatures to pointers --- farms_network/core/node.pxd | 61 +++++++++++++--------------- farms_network/core/node.pyx | 81 +++++++++++++++++++++++++++---------- 2 files changed, 86 insertions(+), 56 deletions(-) diff --git a/farms_network/core/node.pxd b/farms_network/core/node.pxd index b4fb9df..8a4776e 100644 --- a/farms_network/core/node.pxd +++ b/farms_network/core/node.pxd @@ -31,60 +31,53 @@ cdef struct Node: char* model_type # Type of the model (e.g., "empty"). char* name # Unique name of the node. - bint statefull # Flag indicating whether the node is stateful. (ODE) + bint is_statefull # Flag indicating whether the node is stateful. (ODE) # Parameters void* parameters # Pointer to the parameters of the node. # Functions - inline void (*ode)( + void ode( double time, - double[:] states, - double[:] derivatives, - double usr_input, - double[:] inputs, - double[:] weights, - double[:] noise, + double* states, + double* derivatives, + double external_input, + double* network_outputs, + unsigned int* inputs, + double* weights, Node* node ) noexcept double output( double time, - double[:] states, - double usr_input, - double[:] inputs, - double[:] weights, - double[:] noise, - Node node + double* states, + double external_input, + double* network_outputs, + unsigned int* inputs, + double* weights, + Node* node ) noexcept - void (*test_function)(double time, - double* states, - double* derivaties, - double usr_input, - double* inputs, - Node* node) noexcept - cdef: - inline void ode( + void ode( double time, - double[:] states, - double[:] derivatives, - double usr_input, - double[:] inputs, - double[:] weights, - double[:] noise, + double* states, + double* derivatives, + double external_input, + double* network_outputs, + unsigned int* inputs, + double* weights, Node* node ) noexcept double output( double time, - double[:] states, - double usr_input, - double[:] inputs, - double[:] weights, - double[:] noise, - Node node + double* states, + double external_input, + double* network_outputs, + unsigned int* inputs, + double* weights, + Node* node ) noexcept diff --git a/farms_network/core/node.pyx b/farms_network/core/node.pyx index 030caa9..b3ce41d 100644 --- a/farms_network/core/node.pyx +++ b/farms_network/core/node.pyx @@ -24,14 +24,14 @@ from libc.string cimport strdup from .options import NodeOptions -cdef inline void ode( +cdef void ode( double time, - double[:] states, - double[:] derivaties, - double usr_input, - double[:] inputs, - double[:] weights, - double[:] noise, + double* states, + double* derivatives, + double external_input, + double* network_outputs, + unsigned int* inputs, + double* weights, Node* node ) noexcept: """ Node ODE """ @@ -40,12 +40,12 @@ cdef inline void ode( cdef double output( double time, - double[:] states, - double usr_input, - double[:] inputs, - double[:] weights, - double[:] noise, - Node node + double* states, + double external_input, + double* network_outputs, + unsigned int* inputs, + double* weights, + Node* node ) noexcept: """ Node output """ printf("Base implementation of output C function \n") @@ -124,17 +124,54 @@ cdef class PyNode: return self.node.parameters[0] # Methods to wrap the ODE and output functions - def ode(self, double time, states, dstates, usr_input, inputs, weights, noise): - cdef double[:] c_states = states - cdef double[:] c_dstates = dstates - cdef double[:] c_inputs = inputs - cdef double[:] c_weights = weights - cdef double[:] c_noise = noise + def ode( + self, + double time, + double[:] states, + double[:] derivatives, + double external_input, + double[:] network_outputs, + unsigned int[:] inputs, + double[:] weights, + ): + cdef double* states_ptr = &states[0] + cdef double* derivatives_ptr = &derivatives[0] + cdef double* network_outputs_ptr = &network_outputs[0] + cdef unsigned int* inputs_ptr = &inputs[0] + cdef double* weights_ptr = &weights[0] + # Call the C function directly self.node.ode( - time, c_states, c_dstates, usr_input, c_inputs, c_weights, c_noise, self.node + time, + states_ptr, + derivatives_ptr, + external_input, + network_outputs_ptr, + inputs_ptr, + weights_ptr, + self.node ) - def output(self, double time, states, usr_input, inputs, weights, noise): + def output( + self, + double time, + double[:] states, + double external_input, + double[:] network_outputs, + unsigned int[:] inputs, + double[:] weights + ): # Call the C function and return its result - return self.node[0].output(time, states, usr_input, inputs, weights, noise, self.node[0]) + cdef double* states_ptr = &states[0] + cdef double* network_outputs_ptr = &network_outputs[0] + cdef unsigned int* inputs_ptr = &inputs[0] + cdef double* weights_ptr = &weights[0] + return self.node.output( + time, + states_ptr, + external_input, + network_outputs_ptr, + inputs_ptr, + weights_ptr, + self.node + ) From a06f6e9a4cf16a038d67cd87fd84858eb0077d0a Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Tue, 8 Oct 2024 15:07:50 -0400 Subject: [PATCH 062/316] [MODELS][LI] Updated function signatures to match node changes --- farms_network/models/li_danner.pxd | 40 +++++--- farms_network/models/li_danner.pyx | 146 +++++++++++++++++------------ 2 files changed, 111 insertions(+), 75 deletions(-) diff --git a/farms_network/models/li_danner.pxd b/farms_network/models/li_danner.pxd index 8e720f5..fa23b40 100644 --- a/farms_network/models/li_danner.pxd +++ b/farms_network/models/li_danner.pxd @@ -22,6 +22,13 @@ Leaky Integrator Node Based on Danner et.al. from ..core.node cimport Node, PyNode +cdef enum: + + #STATES + NSTATES = 1 + STATE_V = 0 + + cdef packed struct LIDannerNodeParameters: double c_m # pF @@ -33,25 +40,32 @@ cdef packed struct LIDannerNodeParameters: double g_syn_i # nS double e_syn_e # mV double e_syn_i # mV - double m_e # - - double m_i # - - double b_e # - - double b_i # - cdef: - void ode_rhs_c( + void ode( double time, - double[:] states, - double[:] dstates, - double[:] inputs, - double[:] weights, - double[:] noise, - Node node + double* states, + double* derivatives, + double external_input, + double* network_outputs, + unsigned int* inputs, + double* weights, + Node* node + ) noexcept + double output( + double time, + double* states, + double external_input, + double* network_outputs, + unsigned int* inputs, + double* weights, + Node* node ) noexcept - double output_c(double time, double[:] states, Node node) - inline double node_inputs_eval_c(double _node_out, double _weight) cdef class PyLIDannerNode(PyNode): """ Python interface to Leaky Integrator Node C-Structure """ + + cdef: + LIDannerNodeParameters parameters diff --git a/farms_network/models/li_danner.pyx b/farms_network/models/li_danner.pyx index 77079f9..ca0219f 100644 --- a/farms_network/models/li_danner.pyx +++ b/farms_network/models/li_danner.pyx @@ -19,98 +19,120 @@ limitations under the License. Leaky Integrator Node based on Danner et.al. """ +from libc.math cimport fabs as cfabs from libc.stdio cimport printf from libc.stdlib cimport free, malloc from libc.string cimport strdup +from ..core.options import LIDannerParameterOptions -cdef void ode_rhs_c( + +cpdef enum STATE: + + #STATES + nstates = NSTATES + v = STATE_V + + +cdef void ode( double time, - double[:] states, - double[:] dstates, - double[:] inputs, - double[:] weights, - double[:] noise, - Node node + double* states, + double* derivatives, + double external_input, + double* network_outputs, + unsigned int* inputs, + double* weights, + Node* node ) noexcept: """ ODE """ - - # # Parameters - cdef LIDannerNodeParameters params = ( - node.parameters - )[0] + # Parameters + cdef LIDannerNodeParameters params = ( node[0].parameters)[0] # States - cdef double state_v = states[0] + cdef double state_v = states[STATE.v] # Ileak cdef double i_leak = params.g_leak * (state_v - params.e_leak) # Node inputs - cdef double _sum = 0.0 - cdef unsigned int j - cdef double _node_out - cdef double res - - cdef double _input - cdef double _weight - - - for j in range(10): - _input = inputs[j] + cdef: + double _sum = 0.0 + unsigned int j + double _node_out, res, _input, _weight + + cdef unsigned int ninputs = node.ninputs + for j in range(ninputs): + _input = network_outputs[inputs[j]] _weight = weights[j] - _sum += node_inputs_eval_c(_input, _weight) - - # # noise current - # cdef double i_noise = c_noise_current_update( - # self.state_noise.c_get_value(), &(self.noise_params) - # ) - # self.state_noise.c_set_value(i_noise) + if _weight >= 0.0: + # Excitatory Synapse + _sum += params.g_syn_e*cfabs(_weight)*_input*(state_v - params.e_syn_e) + elif _weight < 0.0: + # Inhibitory Synapse + _sum += params.g_syn_i*cfabs(_weight)*_input*(state_v - params.e_syn_i) # dV cdef double i_noise = 0.0 - dstates[0] = ( - -(i_leak + i_noise + _sum)/params.c_m - ) + derivatives[STATE.v] = (-(i_leak + i_noise + _sum)/params.c_m) + # printf("%f -- %f -- %f -- %f %f\n ", derivatives[STATE.v], i_leak, i_noise, _sum, params.c_m) -cdef double output_c(double time, double[:] states, Node node): +cdef double output( + double time, + double* states, + double external_input, + double* network_outputs, + unsigned int* inputs, + double* weights, + Node* node +) noexcept: """ Node output. """ - cdef double state_v = states[0] - cdef double _n_out = 1000.0 + cdef LIDannerNodeParameters params = ( node.parameters)[0] - # cdef LIDannerNodeParameters params = node.parameters - - # if state_v >= params.v_max: - # _n_out = 1.0 - # elif (params.v_thr <= state_v) and (state_v < params.v_max): - # _n_out = (state_v - params.v_thr) / (params.v_max - params.v_thr) - # elif state_v < params.v_thr: - # _n_out = 0.0 + cdef double _n_out = 0.0 + if states[STATE.v] >= params.v_max: + _n_out = 1.0 + elif (params.v_thr <= states[STATE.v]) and (states[STATE.v] < params.v_max): + _n_out = (states[STATE.v] - params.v_thr) / (params.v_max - params.v_thr) + elif states[STATE.v] < params.v_thr: + _n_out = 0.0 return _n_out -cdef inline double node_inputs_eval_c(double _node_out, double _weight) noexcept: - return 0.0 - - cdef class PyLIDannerNode(PyNode): """ Python interface to Leaky Integrator Node C-Structure """ def __cinit__(self): - self._node.model_type = strdup("LI_DANNER".encode('UTF-8')) + self.node.model_type = strdup("LI_DANNER".encode('UTF-8')) # override default ode and out methods - self._node.ode_rhs_c = ode_rhs_c - self._node.output_c = output_c + self.node.is_statefull = True + self.node.ode = ode + self.node.output = output # parameters - self._node.parameters = malloc( - sizeof(LIDannerNodeParameters) - ) - - @classmethod - def from_options(cls, node_options: NodeOptions): - """ From node options """ - name: str = node_options.name - ninputs: int = node_options.ninputs - return cls(name, ninputs) + self.node.parameters = malloc(sizeof(LIDannerNodeParameters)) + if self.node.parameters is NULL: + raise MemoryError("Failed to allocate memory for node parameters") + + def __init__(self, name: str, **kwargs): + super().__init__(name) + + # Set node parameters + cdef LIDannerNodeParameters* param = (self.node.parameters) + param.c_m = kwargs.pop("c_m") + param.g_leak = kwargs.pop("g_leak") + param.e_leak = kwargs.pop("e_leak") + param.v_max = kwargs.pop("v_max") + param.v_thr = kwargs.pop("v_thr") + param.g_syn_e = kwargs.pop("g_syn_e") + param.g_syn_i = kwargs.pop("g_syn_i") + param.e_syn_e = kwargs.pop("e_syn_e") + param.e_syn_i = kwargs.pop("e_syn_i") + if kwargs: + raise Exception(f'Unknown kwargs: {kwargs}') + + @property + def parameters(self): + """ Parameters in the network """ + cdef LIDannerNodeParameters params = ( self.node.parameters)[0] + return params From 7ec9ca12b1b3e4c3cd1daa7b1bef67682bab01a2 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Tue, 8 Oct 2024 15:08:17 -0400 Subject: [PATCH 063/316] [MODELS][LINAP] Added working li nap node --- farms_network/models/li_nap_danner.pxd | 62 +++++- farms_network/models/li_nap_danner.pyx | 253 +++++++++++++++---------- 2 files changed, 214 insertions(+), 101 deletions(-) diff --git a/farms_network/models/li_nap_danner.pxd b/farms_network/models/li_nap_danner.pxd index 5c0934f..3424eca 100644 --- a/farms_network/models/li_nap_danner.pxd +++ b/farms_network/models/li_nap_danner.pxd @@ -16,7 +16,67 @@ See the License for the specific language governing permissions and limitations under the License. ----------------------------------------------------------------------- -Leaky Integrator Node Based on Danner et.al. +Leaky Integrator Node Based on Danner et.al. with Na and K channels """ from ..core.node cimport Node, PyNode + + +cdef enum: + + #STATES + NSTATES = 2 + STATE_V = 0 + STATE_H = 1 + + +cdef packed struct LINaPDannerNodeParameters: + + double c_m # pF + double g_leak # nS + double e_leak # mV + double g_nap # nS + double e_na # mV + double g_syn_e # nS + double g_syn_i # nS + double e_syn_e # mV + double e_syn_i # mV + double v1_2_m # mV + double k_m # + double v1_2_h # mV + double k_h # + double v1_2_t # mV + double k_t # + double tau_0 # mS + double tau_max # mS + double v_max # mV + double v_thr # mV + + +cdef: + void ode( + double time, + double* states, + double* derivatives, + double external_input, + double* network_outputs, + unsigned int* inputs, + double* weights, + Node* node + ) noexcept + double output( + double time, + double* states, + double external_input, + double* network_outputs, + unsigned int* inputs, + double* weights, + Node* node + ) noexcept + + +cdef class PyLINaPDannerNode(PyNode): + """ Python interface to Leaky Integrator Node C-Structure """ + + cdef: + LINaPDannerNodeParameters parameters diff --git a/farms_network/models/li_nap_danner.pyx b/farms_network/models/li_nap_danner.pyx index 5fc1dfb..10b7963 100644 --- a/farms_network/models/li_nap_danner.pyx +++ b/farms_network/models/li_nap_danner.pyx @@ -16,105 +16,158 @@ See the License for the specific language governing permissions and limitations under the License. ----------------------------------------------------------------------- -Leaky Integrator Node with persistent sodium channel based on Danner et.al. +Leaky Integrator Node based on Danner et.al. """ -# from libc.stdio cimport printf -# from libc.stdlib cimport free, malloc -# from libc.string cimport strdup - - -# cdef void ode_rhs_c( -# double time, -# double[:] states, -# double[:] dstates, -# double[:] inputs, -# double[:] weights, -# double[:] noise, -# double drive, -# Node node -# ): -# """ ODE """ - -# # # Parameters -# cdef LINapDannerNodeParameters params = ( -# node.parameters -# )[0] -# # States -# cdef double state_v = states[0] - -# # Drive inputs -# cdef double d_e = params.m_e * drive + params.b_e # Excitatory drive -# cdef double d_i = params.m_i * drive + params.b_i # Inhibitory drive - -# # Ileak -# cdef double i_leak = params.g_leak * (state_v - params.e_leak) - -# # ISyn_Excitatory -# cdef double i_syn_e = params.g_syn_e * d_e * (state_v - params.e_syn_e) - -# # ISyn_Inhibitory -# cdef double i_syn_i = params.g_syn_i * d_i * (state_v - params.e_syn_i) - -# # Node inputs -# cdef double _sum = 0.0 -# cdef unsigned int j -# cdef double _node_out -# cdef double _weight - -# # for j in range(node.ninputs): -# # _sum += node_inputs_eval_c(inputs[j], weights[j]) - -# # # noise current -# # cdef double i_noise = c_noise_current_update( -# # self.state_noise.c_get_value(), &(self.noise_params) -# # ) -# # self.state_noise.c_set_value(i_noise) - -# # dV -# cdef i_noise = 0.0 -# dstates[0] = ( -# -(i_leak + i_syn_e + i_syn_i + i_noise + _sum)/params.c_m -# ) - - -# cdef double output_c(double time, double[:] states, Node node): -# """ Node output. """ - -# cdef double state_v = states[0] -# cdef double _n_out = 1000.0 - -# # cdef LIDannerNodeParameters params = node.parameters - -# # if state_v >= params.v_max: -# # _n_out = 1.0 -# # elif (params.v_thr <= state_v) and (state_v < params.v_max): -# # _n_out = (state_v - params.v_thr) / (params.v_max - params.v_thr) -# # elif state_v < params.v_thr: -# # _n_out = 0.0 -# return _n_out - - -# cdef double node_inputs_eval_c(double _node_out, double _weight): -# return 0.0 - - -# cdef class PyLINapDannerNode(PyNode): -# """ Python interface to Leaky Integrator Node with persistence sodium C-Structure """ - -# def __cinit__(self): -# # override defaults -# self._node.model_type = strdup("LI_NAP_DANNER".encode('UTF-8')) -# self._node.nstates = 2 -# self._node.nparameters = 13 -# # methods -# self._node.ode_rhs_c = ode_rhs_c -# self._node.output_c = output_c -# # parameters -# self._node.parameters = malloc( -# sizeof(LIDannerNodeParameters) -# ) - -# def __dealloc__(self): -# if self._node.name is not NULL: -# free(self._node.parameters) +from libc.math cimport cosh as ccosh +from libc.math cimport exp as cexp +from libc.math cimport fabs as cfabs +from libc.stdio cimport printf +from libc.stdlib cimport free, malloc +from libc.string cimport strdup + +from ..core.options import LINaPDannerParameterOptions + + +cpdef enum STATE: + + #STATES + nstates = NSTATES + v = STATE_V + h = STATE_H + + +cdef void ode( + double time, + double* states, + double* derivatives, + double external_input, + double* network_outputs, + unsigned int* inputs, + double* weights, + Node* node +) noexcept: + """ ODE """ + cdef LINaPDannerNodeParameters params = ( node[0].parameters)[0] + + # States + cdef double state_v = states[STATE.v] + cdef double state_h = states[STATE.h] + + # tau_h(V) + cdef double tau_h = params.tau_0 + (params.tau_max - params.tau_0) / \ + ccosh((state_v - params.v1_2_t) / params.k_t) + + # h_inf(V) + cdef double h_inf = 1./(1.0 + cexp((state_v - params.v1_2_h) / params.k_h)) + + # m(V) + cdef double m = 1./(1.0 + cexp((state_v - params.v1_2_m) / params.k_m)) + + # Inap + # pylint: disable=no-member + cdef double i_nap = params.g_nap * m * state_h * (state_v - params.e_na) + + # Ileak + cdef double i_leak = params.g_leak * (state_v - params.e_leak) + + # Neuron inputs + cdef: + double _sum = 0.0 + unsigned int j + double _node_out, res, _input, _weight + + cdef unsigned int ninputs = node.ninputs + for j in range(ninputs): + _input = network_outputs[inputs[j]] + _weight = weights[j] + if _weight >= 0.0: + # Excitatory Synapse + _sum += params.g_syn_e*cfabs(_weight)*_input*(state_v - params.e_syn_e) + elif _weight < 0.0: + # Inhibitory Synapse + _sum += params.g_syn_i*cfabs(_weight)*_input*(state_v - params.e_syn_i) + + # noise current + cdef double i_noise = 0.0 + + # Slow inactivation + derivatives[STATE.h] = (h_inf - state_h) / tau_h + + # dV + derivatives[STATE.v] = -(i_nap + i_leak + i_noise + _sum)/params.c_m + + +cdef double output( + double time, + double* states, + double external_input, + double* network_outputs, + unsigned int* inputs, + double* weights, + Node* node +) noexcept: + """ Node output. """ + + cdef LINaPDannerNodeParameters params = ( node.parameters)[0] + cdef double _n_out = 0.0 + if states[STATE.v] >= params.v_max: + _n_out = 1.0 + elif (params.v_thr <= states[STATE.v]) and (states[STATE.v] < params.v_max): + _n_out = (states[STATE.v] - params.v_thr) / (params.v_max - params.v_thr) + elif states[0] < params.v_thr: + _n_out = 0.0 + return _n_out + + +cdef class PyLINapDannerNode(PyNode): + """ Python interface to Leaky Integrator Node with persistence sodium C-Structure """ + + def __cinit__(self): + # override defaults + self._node.model_type = strdup("LI_NAP_DANNER".encode('UTF-8')) + # override default ode and out methods + self.node.is_statefull = True + self.node.ode = ode + self.node.output = output + # parameters + self.node.parameters = malloc(sizeof(LINaPDannerNodeParameters)) + if self.node.parameters is NULL: + raise MemoryError("Failed to allocate memory for node parameters") + + def __init__(self, name: str, **kwargs): + super().__init__(name) + + # Set node parameters + cdef LINaPDannerNodeParameters* param = ( + self.node.parameters + ) + param.c_m = kwargs.pop("c_m") + param.g_nap = kwargs.pop("g_nap") + param.e_na = kwargs.pop("e_na") + param.v1_2_m = kwargs.pop("v1_2_m") + param.k_m = kwargs.pop("k_m") + param.v1_2_h = kwargs.pop("v1_2_h") + param.k_h = kwargs.pop("k_h") + param.v1_2_t = kwargs.pop("v1_2_t") + param.k_t = kwargs.pop("k_t") + param.g_leak = kwargs.pop("g_leak") + param.e_leak = kwargs.pop("e_leak") + param.tau_0 = kwargs.pop("tau_0") + param.tau_max = kwargs.pop("tau_max") + param.v_max = kwargs.pop("v_max") + param.v_thr = kwargs.pop("v_thr") + param.g_syn_e = kwargs.pop("g_syn_e") + param.g_syn_i = kwargs.pop("g_syn_i") + param.e_syn_e = kwargs.pop("e_syn_e") + param.e_syn_i = kwargs.pop("e_syn_i") + if kwargs: + raise Exception(f'Unknown kwargs: {kwargs}') + + @property + def parameters(self): + """ Parameters in the network """ + cdef LINaPDannerNodeParameters params = ( + self.node.parameters + )[0] + return params From ee4f663de5b2bf76a63437ec55f1efb5be0f4032 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Tue, 8 Oct 2024 15:15:28 -0400 Subject: [PATCH 064/316] [CORE][NETWORK] Updated function signatures to pointers --- farms_network/core/network.pxd | 6 +- farms_network/core/network.pyx | 109 ++++++++++++--------------------- 2 files changed, 44 insertions(+), 71 deletions(-) diff --git a/farms_network/core/network.pxd b/farms_network/core/network.pxd index d67a139..2f849c8 100644 --- a/farms_network/core/network.pxd +++ b/farms_network/core/network.pxd @@ -1,5 +1,5 @@ -from .node cimport Node from .data_cy cimport NetworkDataCy +from .node cimport Node cdef struct Network: @@ -12,6 +12,7 @@ cdef struct Network: # nodes list Node** nodes + cdef class PyNetwork: """ Python interface to Network ODE """ @@ -19,6 +20,7 @@ cdef class PyNetwork: Network *network public list pynodes public NetworkDataCy data + double[:] __tmp_node_outputs # cpdef void step(self) - cpdef void ode(self, double time, double[:] states) + cpdef double[:] ode(self, double time, double[::1] states) diff --git a/farms_network/core/network.pyx b/farms_network/core/network.pyx index bf8e364..fd14232 100644 --- a/farms_network/core/network.pyx +++ b/farms_network/core/network.pyx @@ -19,77 +19,45 @@ limitations under the License. include "types.pxd" +cimport numpy as cnp import numpy as np from libc.stdio cimport printf from libc.stdlib cimport free, malloc from libc.string cimport strdup -from typing import Iterable - from ..models.factory import NodeFactory + from ..models.li_danner cimport LIDannerNodeParameters + from .data import NetworkData, NetworkStates -from .data_cy cimport NetworkDataCy, NetworkStatesCy +from .data_cy cimport NetworkDataCy, NetworkStatesCy from .node cimport Node, PyNode from tqdm import tqdm -from .options import EdgeOptions, NetworkOptions, NodeOptions - +from .options import (EdgeOptions, IntegrationOptions, NetworkOptions, + NodeOptions) -cdef enum state_conv: - state0 = 0 - state1 = 1 +cpdef rk4(double time, cnp.ndarray[double, ndim=1] state, func, double step_size): + """ Runge-kutta order 4 integrator """ -cdef void li_ode( - double time, - double* states, - double* derivatives, - double external_input, - double* network_outputs, - unsigned int* input_neurons, - double* input_weights, - Node* node -) noexcept: - """ ODE """ - # Parameters - cdef LIDannerNodeParameters params = ( node[0].parameters)[0] - - # States - cdef double state_v = states[state_conv.state0] - # printf("states: %f \n", state_v) - - # Ileak - cdef double i_leak = params.g_leak * (state_v - params.e_leak) - - # Node inputs - cdef double _sum = 0.0 - cdef unsigned int j - cdef double _node_out - cdef double res - - cdef double _input - cdef double _weight - - cdef unsigned int ninputs = node[0].ninputs - for j in range(ninputs): - _input = network_outputs[input_neurons[j]] - _weight = input_weights[j] - # _sum += node_inputs_eval_c(_input, _weight) - - # dV - cdef double i_noise = 0.0 - states[state_conv.state0] = (-(i_leak + i_noise + _sum)/params.c_m) + K1 = np.array(func(time, state)) + K2 = np.array(func(time + step_size/2, state + (step_size/2 * K1))) + K3 = np.array(func(time + step_size/2, state + (step_size/2 * K2))) + K4 = np.array(func(time + step_size, state + (step_size * K3))) + state = state + (K1 + 2*K2 + 2*K3 + K4)*(step_size/6) + time += step_size + return state cdef void ode( double time, - unsigned int iteration, NetworkDataCy data, Network* network, + double[:] tmp_node_outputs ) noexcept: """ C Implementation to compute full network state """ cdef int j, step, steps, nnodes @@ -112,20 +80,14 @@ cdef void ode( cdef double* weights = &data.connectivity.weights[0] cdef unsigned int* input_neurons_indices = &data.connectivity.indices[0] - # cdef double[::1] node_states - # cdef double[::1] node_derivatives - - # node_derivatives = states[derivatives_indices[0]:derivatives_indices[1]] + cdef double* tmp_node_outputs_ptr = &tmp_node_outputs[0] - for step in range(steps): - for j in range(nnodes): - # node_states = states[states_indices[0]:states_indices[1]] - # (nodes[j][0]).ode( - # time, node_states, node_derivatives, time, node_states, node_states, node_states, nodes[j] - # ) - li_ode( + for j in range(nnodes): + __node = nodes[j][0] + if __node.is_statefull: + __node.ode( time, - states + states_indices_ptr[j], + states + states_indices[j], derivatives + derivatives_indices[j], external_input, outputs, @@ -133,10 +95,17 @@ cdef void ode( weights + input_neurons_indices[j], nodes[j] ) - # if __node.statefull: - # __node.output(0.0, states, 0.0, arr, arr, arr, __node) - # else: - # __node.output(0.0, arr, 0.0, arr, arr, arr, __node) + tmp_node_outputs_ptr[j] = __node.output( + time, + states + states_indices[j], + external_input, + outputs, + input_neurons + input_neurons_indices[j], + weights + input_neurons_indices[j], + nodes[j] + ) + # Update all node outputs data for next iteration + data.outputs.array[:] = tmp_node_outputs[:] cdef class PyNetwork: @@ -158,6 +127,7 @@ cdef class PyNetwork: super().__init__() self.data = NetworkData.from_options(network_options) self.pynodes = [] + self.__tmp_node_outputs = np.zeros((self.network.nnodes,)) self.setup_network(network_options, self.data) def __dealloc__(self): @@ -172,7 +142,7 @@ cdef class PyNetwork: """ Initialize network from NetworkOptions """ return cls(options) - def setup_integrator(options: IntegratorOptions): + def setup_integrator(options: IntegrationOptions): """ Setup integrator for neural network """ ... @@ -191,17 +161,18 @@ cdef class PyNetwork: ) self.network.nodes[index] = c_node - def generate_node(self, node_options: NodeOptions): + @staticmethod + def generate_node(node_options: NodeOptions): """ Generate a node from options """ Node = NodeFactory.generate_node(node_options.model) node = Node.from_options(node_options) return node - cpdef void ode(self, double time, double[:] states): + cpdef double[:] ode(self, double time, double[::1] states): """ ODE Wrapper for numerical integrators """ self.data.states.array[:] = states[:] - cdef int iteration = 0 - ode(time, iteration, self.data, self.network) + ode(time, self.data, self.network, self.__tmp_node_outputs) + return self.data.derivatives.array @property def nnodes(self): From 6dbbc3084d433a0b2895924e5c90a16ae196252d Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Tue, 8 Oct 2024 15:16:05 -0400 Subject: [PATCH 065/316] [MODELS][LI] Removed print statement --- farms_network/models/li_danner.pyx | 1 - 1 file changed, 1 deletion(-) diff --git a/farms_network/models/li_danner.pyx b/farms_network/models/li_danner.pyx index ca0219f..597f95e 100644 --- a/farms_network/models/li_danner.pyx +++ b/farms_network/models/li_danner.pyx @@ -74,7 +74,6 @@ cdef void ode( # dV cdef double i_noise = 0.0 derivatives[STATE.v] = (-(i_leak + i_noise + _sum)/params.c_m) - # printf("%f -- %f -- %f -- %f %f\n ", derivatives[STATE.v], i_leak, i_noise, _sum, params.c_m) cdef double output( From 922b73f4230af4038777018fd7f5c1e323b129c7 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Tue, 8 Oct 2024 15:16:48 -0400 Subject: [PATCH 066/316] [CORE] Added oscillator node model --- farms_network/models/factory.py | 57 ++++++------- farms_network/models/oscillator.pxd | 66 +++++++++++++++ farms_network/models/oscillator.pyx | 124 ++++++++++++++++++++++++++++ 3 files changed, 219 insertions(+), 28 deletions(-) create mode 100644 farms_network/models/oscillator.pxd create mode 100644 farms_network/models/oscillator.pyx diff --git a/farms_network/models/factory.py b/farms_network/models/factory.py index 92dd2c2..c94b872 100644 --- a/farms_network/models/factory.py +++ b/farms_network/models/factory.py @@ -19,38 +19,39 @@ Factory class for generating the node model. """ -from farms_network.models.fitzhugh_nagumo import FitzhughNagumo -from farms_network.models.hh_daun_motoneuron import HHDaunMotoneuron -from farms_network.models.hopf_oscillator import HopfOscillator -from farms_network.models.leaky_integrator import LeakyIntegrator -from farms_network.models.lif_danner import LIFDanner -from farms_network.models.lif_danner_nap import LIFDannerNap -from farms_network.models.lif_daun_interneuron import LIFDaunInterneuron -from farms_network.models.matsuoka_node import MatsuokaNode -from farms_network.models.morphed_oscillator import MorphedOscillator -from farms_network.models.morris_lecar import MorrisLecarNode -from farms_network.models.oscillator import Oscillator -from farms_network.models.relu import ReLUNode -from farms_network.models.sensory_node import SensoryNode +# from farms_network.models.fitzhugh_nagumo import FitzhughNagumo +# from farms_network.models.hh_daun_motoneuron import HHDaunMotoneuron +# from farms_network.models.hopf_oscillator import HopfOscillator +# from farms_network.models.leaky_integrator import LeakyIntegrator +from farms_network.models.li_danner import PyLIDannerNode +from farms_network.models.li_nap_danner import PyLINaPDannerNode +# from farms_network.models.lif_daun_interneuron import LIFDaunInterneuron +# from farms_network.models.matsuoka_node import MatsuokaNode +# from farms_network.models.morphed_oscillator import MorphedOscillator +# from farms_network.models.morris_lecar import MorrisLecarNode +from farms_network.models.oscillator import PyOscillatorNode +# from farms_network.models.relu import ReLUNode +# from farms_network.models.sensory_node import SensoryNode class NodeFactory: """Implementation of Factory Node class. """ - nodes = { # 'if': IntegrateAndFire, - 'oscillator': Oscillator, - 'hopf_oscillator': HopfOscillator, - 'morphed_oscillator': MorphedOscillator, - 'leaky': LeakyIntegrator, - 'sensory': SensoryNode, - 'lif_danner_nap': LIFDannerNap, - 'lif_danner': LIFDanner, - 'lif_daun_interneuron': LIFDaunInterneuron, - 'hh_daun_motoneuron': HHDaunMotoneuron, - 'fitzhugh_nagumo': FitzhughNagumo, - 'matsuoka_node': MatsuokaNode, - 'morris_lecar': MorrisLecarNode, - 'relu': ReLUNode, + nodes = { + # 'if': IntegrateAndFire, + 'oscillator': PyOscillatorNode, + # 'hopf_oscillator': HopfOscillator, + # 'morphed_oscillator': MorphedOscillator, + # 'leaky': LeakyIntegrator, + # 'sensory': SensoryNode, + 'li_nap_danner': PyLINaPDannerNode, + 'li_danner': PyLIDannerNode, + # 'lif_daun_interneuron': LIFDaunInterneuron, + # 'hh_daun_motoneuron': HHDaunMotoneuron, + # 'fitzhugh_nagumo': FitzhughNagumo, + # 'matsuoka_node': MatsuokaNode, + # 'morris_lecar': MorrisLecarNode, + # 'relu': ReLUNode, } def __init__(self): @@ -73,7 +74,7 @@ def register_node(node_type, node_instance): NodeFactory.nodes[node_type] = node_instance @staticmethod - def gen_node(node_type): + def generate_node(node_type): """Generate the necessary type of node. Parameters ---------- diff --git a/farms_network/models/oscillator.pxd b/farms_network/models/oscillator.pxd new file mode 100644 index 0000000..089bea9 --- /dev/null +++ b/farms_network/models/oscillator.pxd @@ -0,0 +1,66 @@ +""" +----------------------------------------------------------------------- +Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty +Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +----------------------------------------------------------------------- + +Oscillator model +""" + + +from ..core.node cimport Node, PyNode + + +cdef enum: + + #STATES + NSTATES = 1 + STATE_PHASE = 0 + STATE_AMPLITUDE = 1 + + +cdef packed struct OscillatorNodeParameters: + + double intrinsic_frequency # Hz + double nominal_amplitude # + double amplitude_rate # + +cdef: + void ode( + double time, + double* states, + double* derivatives, + double external_input, + double* network_outputs, + unsigned int* inputs, + double* weights, + Node* node + ) noexcept + double output( + double time, + double* states, + double external_input, + double* network_outputs, + unsigned int* inputs, + double* weights, + Node* node + ) noexcept + + +cdef class PyOscillatorNode(PyNode): + """ Python interface to Oscillator Node C-Structure """ + + cdef: + OscillatorNodeParameters parameters diff --git a/farms_network/models/oscillator.pyx b/farms_network/models/oscillator.pyx new file mode 100644 index 0000000..32cf85f --- /dev/null +++ b/farms_network/models/oscillator.pyx @@ -0,0 +1,124 @@ +""" +----------------------------------------------------------------------- +Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty +Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +----------------------------------------------------------------------- + +Oscillator model +""" + + +from libc.math cimport M_PI +from libc.math cimport sin as csin +from libc.stdio cimport printf +from libc.stdlib cimport free, malloc +from libc.string cimport strdup + +from ..core.options import OscillatorParameterOptions + + +cpdef enum STATE: + + #STATES + nstates = NSTATES + phase = STATE_PHASE + amplitude = STATE_AMPLITUDE + + +cdef void ode( + double time, + double* states, + double* derivatives, + double external_input, + double* network_outputs, + unsigned int* inputs, + double* weights, + Node* node +) noexcept: + """ ODE """ + # Parameters + cdef OscillatorNodeParameters params = ( node[0].parameters)[0] + + # States + cdef double state_phase = states[STATE.phase] + cdef double state_amplitude = states[STATE.amplitude] + + cdef: + double _sum = 0.0 + unsigned int j + double _node_out, res, _input, _weight + + cdef unsigned int ninputs = node.ninputs + cdef int SIGN = 1 + for j in range(ninputs): + _input = network_outputs[inputs[j]] + _weight = weights[j] + # _phi = _p[self.neuron_inputs[j].phi_idx] + SIGN *= -1 + _sum += _weight*state_amplitude*csin(_input - state_phase - SIGN*2.5) + + cdef double i_noise = 0.0 + # phidot : phase_dot + derivatives[STATE.phase] = 2*M_PI*params.intrinsic_frequency + _sum + # ampdot + derivatives[STATE.amplitude] = params.amplitude_rate*( + params.nominal_amplitude - state_amplitude + ) + # printf("%f -- %f -- %f -- %f %f\n ", derivatives[STATE.v], i_leak, i_noise, _sum, params.c_m) + + +cdef double output( + double time, + double* states, + double external_input, + double* network_outputs, + unsigned int* inputs, + double* weights, + Node* node +) noexcept: + """ Node output. """ + return states[STATE.phase] + + +cdef class PyOscillatorNode(PyNode): + """ Python interface to Leaky Integrator Node C-Structure """ + + def __cinit__(self): + self.node.model_type = strdup("OSCILLATOR".encode('UTF-8')) + # override default ode and out methods + self.node.is_statefull = True + self.node.ode = ode + self.node.output = output + # parameters + self.node.parameters = malloc(sizeof(OscillatorNodeParameters)) + if self.node.parameters is NULL: + raise MemoryError("Failed to allocate memory for node parameters") + + def __init__(self, name: str, **kwargs): + super().__init__(name) + + # Set node parameters + cdef OscillatorNodeParameters* param = (self.node.parameters) + param.intrinsic_frequency = kwargs.pop("intrinsic_frequency") + param.nominal_amplitude = kwargs.pop("nominal_amplitude") + param.amplitude_rate = kwargs.pop("amplitude_rate") + if kwargs: + raise Exception(f'Unknown kwargs: {kwargs}') + + @property + def parameters(self): + """ Parameters in the network """ + cdef OscillatorNodeParameters params = ( self.node.parameters)[0] + return params From bf3801fcbe5747f8b15c1414a613dbd9b992bfc4 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Tue, 8 Oct 2024 15:19:30 -0400 Subject: [PATCH 067/316] [CORE][FIXED] Replaced default weights array to zeros --- farms_network/core/data.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/farms_network/core/data.py b/farms_network/core/data.py index bf7bc04..a701e81 100644 --- a/farms_network/core/data.py +++ b/farms_network/core/data.py @@ -108,7 +108,7 @@ def from_options(cls, network_options: NetworkOptions): nstates += node._nstates indices.append(nstates) return cls( - array=np.array(np.arange(0, nstates), dtype=np.double), + array=np.array(np.zeros((nstates,)), dtype=np.double), indices=np.array(indices) ) @@ -145,7 +145,6 @@ def from_options(cls, network_options: NetworkOptions): weights = np.empty((len(edges),)) nedges = 0 indices = [0,] - print(connectivity) for index, node in enumerate(nodes): node_sources = connectivity[connectivity[:, 1] == index][:, 0].tolist() node_weights = connectivity[connectivity[:, 1] == index][:, 2].tolist() From e8914017961bf7a3e83d472b0b5e8a87e70ac38f Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Tue, 8 Oct 2024 15:21:12 -0400 Subject: [PATCH 068/316] [MAIN][SETUP] Added models to cython extensions --- setup.py | 63 +++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 56 insertions(+), 7 deletions(-) diff --git a/setup.py b/setup.py index 6c711ab..cda4353 100644 --- a/setup.py +++ b/setup.py @@ -35,16 +35,65 @@ # directive_defaults = Cython.Compiler.Options.get_directive_defaults() +# extensions = [ +# Extension( +# f"farms_network.{subpackage}.*", +# [f"farms_network/{subpackage}/*.pyx"], +# include_dirs=[numpy.get_include(),], +# # libraries=["c", "stdc++"], +# extra_compile_args=['-ffast-math', '-O3'], +# extra_link_args=['-O3'], +# ) +# for subpackage in ( +# 'core', +# # 'models' + +# ) +# ] + extensions = [ Extension( - f"farms_network.{subpackage}.*", - [f"farms_network/{subpackage}/*.pyx"], - include_dirs=[numpy.get_include(),], - # libraries=["c", "stdc++"], + "farms_network.core.network", + ["farms_network/core/network.pyx"], + include_dirs=[numpy.get_include(), get_include()], + extra_compile_args=['-ffast-math', '-O3'], + extra_link_args=['-O3', '-lm'] + ), + Extension( + "farms_network.core.node", + ["farms_network/core/node.pyx"], + include_dirs=[numpy.get_include(), get_include()], + extra_compile_args=['-ffast-math', '-O3'], + extra_link_args=['-O3', '-lm'] + ), + Extension( + "farms_network.core.data_cy", + ["farms_network/core/data_cy.pyx"], + include_dirs=[numpy.get_include(), get_include()], + extra_compile_args=['-ffast-math', '-O3'], + extra_link_args=['-O3', '-lm'] + ), + Extension( + "farms_network.models.li_danner", + ["farms_network/models/li_danner.pyx"], + include_dirs=[numpy.get_include(), get_include()], + extra_compile_args=['-ffast-math', '-O3'], + extra_link_args=['-O3', '-lm'] + ), + Extension( + "farms_network.models.li_nap_danner", + ["farms_network/models/li_nap_danner.pyx"], + include_dirs=[numpy.get_include(), get_include()], extra_compile_args=['-ffast-math', '-O3'], - extra_link_args=['-O3'], - ) - for subpackage in ('core', 'models') + extra_link_args=['-O3', '-lm'] + ), + Extension( + "farms_network.models.oscillator", + ["farms_network/models/oscillator.pyx"], + include_dirs=[numpy.get_include(), get_include()], + extra_compile_args=['-ffast-math', '-O3'], + extra_link_args=['-O3', '-lm'] + ), ] setup( From a4ed3c423168b63ee4a1b793e3cdc8a06e9c9937 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Wed, 9 Oct 2024 13:48:02 -0400 Subject: [PATCH 069/316] [MODELS] Added edges attribute to output function --- farms_network/core/node.pxd | 14 +++++--- farms_network/core/node.pyx | 14 +++++--- farms_network/models/li_danner.pxd | 7 ++-- farms_network/models/li_danner.pyx | 6 ++-- farms_network/models/li_nap_danner.pxd | 7 ++-- farms_network/models/li_nap_danner.pyx | 6 ++-- farms_network/models/oscillator.pxd | 20 +++++++++-- farms_network/models/oscillator.pyx | 48 ++++++++++++++++++++------ 8 files changed, 93 insertions(+), 29 deletions(-) diff --git a/farms_network/core/node.pxd b/farms_network/core/node.pxd index 8a4776e..c559feb 100644 --- a/farms_network/core/node.pxd +++ b/farms_network/core/node.pxd @@ -21,6 +21,8 @@ Header for Node Base Struture. """ +from .edge cimport Edge + cdef struct Node: # Generic parameters @@ -45,7 +47,8 @@ cdef struct Node: double* network_outputs, unsigned int* inputs, double* weights, - Node* node + Node* node, + Edge* edges, ) noexcept double output( @@ -55,7 +58,8 @@ cdef struct Node: double* network_outputs, unsigned int* inputs, double* weights, - Node* node + Node* node, + Edge* edges, ) noexcept @@ -68,7 +72,8 @@ cdef: double* network_outputs, unsigned int* inputs, double* weights, - Node* node + Node* node, + Edge* edges, ) noexcept double output( double time, @@ -77,7 +82,8 @@ cdef: double* network_outputs, unsigned int* inputs, double* weights, - Node* node + Node* node, + Edge* edges, ) noexcept diff --git a/farms_network/core/node.pyx b/farms_network/core/node.pyx index b3ce41d..61f2a0a 100644 --- a/farms_network/core/node.pyx +++ b/farms_network/core/node.pyx @@ -32,7 +32,8 @@ cdef void ode( double* network_outputs, unsigned int* inputs, double* weights, - Node* node + Node* node, + Edge* edges, ) noexcept: """ Node ODE """ printf("Base implementation of ODE C function \n") @@ -45,7 +46,8 @@ cdef double output( double* network_outputs, unsigned int* inputs, double* weights, - Node* node + Node* node, + Edge* edges, ) noexcept: """ Node output """ printf("Base implementation of output C function \n") @@ -139,6 +141,7 @@ cdef class PyNode: cdef double* network_outputs_ptr = &network_outputs[0] cdef unsigned int* inputs_ptr = &inputs[0] cdef double* weights_ptr = &weights[0] + cdef Edge* edges = NULL # Call the C function directly self.node.ode( @@ -149,7 +152,8 @@ cdef class PyNode: network_outputs_ptr, inputs_ptr, weights_ptr, - self.node + self.node, + edges ) def output( @@ -166,6 +170,7 @@ cdef class PyNode: cdef double* network_outputs_ptr = &network_outputs[0] cdef unsigned int* inputs_ptr = &inputs[0] cdef double* weights_ptr = &weights[0] + cdef Edge* edges = NULL return self.node.output( time, states_ptr, @@ -173,5 +178,6 @@ cdef class PyNode: network_outputs_ptr, inputs_ptr, weights_ptr, - self.node + self.node, + edges ) diff --git a/farms_network/models/li_danner.pxd b/farms_network/models/li_danner.pxd index fa23b40..db204ee 100644 --- a/farms_network/models/li_danner.pxd +++ b/farms_network/models/li_danner.pxd @@ -20,6 +20,7 @@ Leaky Integrator Node Based on Danner et.al. """ from ..core.node cimport Node, PyNode +from ..core.edge cimport Edge cdef enum: @@ -51,7 +52,8 @@ cdef: double* network_outputs, unsigned int* inputs, double* weights, - Node* node + Node* node, + Edge* edges, ) noexcept double output( double time, @@ -60,7 +62,8 @@ cdef: double* network_outputs, unsigned int* inputs, double* weights, - Node* node + Node* node, + Edge* edges, ) noexcept diff --git a/farms_network/models/li_danner.pyx b/farms_network/models/li_danner.pyx index 597f95e..3c6e6e5 100644 --- a/farms_network/models/li_danner.pyx +++ b/farms_network/models/li_danner.pyx @@ -42,7 +42,8 @@ cdef void ode( double* network_outputs, unsigned int* inputs, double* weights, - Node* node + Node* node, + Edge* edges, ) noexcept: """ ODE """ # Parameters @@ -83,7 +84,8 @@ cdef double output( double* network_outputs, unsigned int* inputs, double* weights, - Node* node + Node* node, + Edge* edges, ) noexcept: """ Node output. """ diff --git a/farms_network/models/li_nap_danner.pxd b/farms_network/models/li_nap_danner.pxd index 3424eca..7d47d5b 100644 --- a/farms_network/models/li_nap_danner.pxd +++ b/farms_network/models/li_nap_danner.pxd @@ -20,6 +20,7 @@ Leaky Integrator Node Based on Danner et.al. with Na and K channels """ from ..core.node cimport Node, PyNode +from ..core.edge cimport Edge cdef enum: @@ -62,7 +63,8 @@ cdef: double* network_outputs, unsigned int* inputs, double* weights, - Node* node + Node* node, + Edge* edges, ) noexcept double output( double time, @@ -71,7 +73,8 @@ cdef: double* network_outputs, unsigned int* inputs, double* weights, - Node* node + Node* node, + Edge* edges, ) noexcept diff --git a/farms_network/models/li_nap_danner.pyx b/farms_network/models/li_nap_danner.pyx index 10b7963..b5e3be0 100644 --- a/farms_network/models/li_nap_danner.pyx +++ b/farms_network/models/li_nap_danner.pyx @@ -45,7 +45,8 @@ cdef void ode( double* network_outputs, unsigned int* inputs, double* weights, - Node* node + Node* node, + Edge* edges, ) noexcept: """ ODE """ cdef LINaPDannerNodeParameters params = ( node[0].parameters)[0] @@ -105,7 +106,8 @@ cdef double output( double* network_outputs, unsigned int* inputs, double* weights, - Node* node + Node* node, + Edge* edges, ) noexcept: """ Node output. """ diff --git a/farms_network/models/oscillator.pxd b/farms_network/models/oscillator.pxd index 089bea9..6d35aed 100644 --- a/farms_network/models/oscillator.pxd +++ b/farms_network/models/oscillator.pxd @@ -21,6 +21,7 @@ Oscillator model from ..core.node cimport Node, PyNode +from ..core.edge cimport Edge, PyEdge cdef enum: @@ -37,6 +38,12 @@ cdef packed struct OscillatorNodeParameters: double nominal_amplitude # double amplitude_rate # + +cdef packed struct OscillatorEdgeParameters: + + double phase_difference # radians + + cdef: void ode( double time, @@ -46,7 +53,8 @@ cdef: double* network_outputs, unsigned int* inputs, double* weights, - Node* node + Node* node, + Edge* edges, ) noexcept double output( double time, @@ -55,7 +63,8 @@ cdef: double* network_outputs, unsigned int* inputs, double* weights, - Node* node + Node* node, + Edge* edges, ) noexcept @@ -64,3 +73,10 @@ cdef class PyOscillatorNode(PyNode): cdef: OscillatorNodeParameters parameters + + +cdef class PyOscillatorEdge(PyEdge): + """ Python interface to Oscillator Edge C-Structure """ + + cdef: + OscillatorEdgeParameters parameters diff --git a/farms_network/models/oscillator.pyx b/farms_network/models/oscillator.pyx index 32cf85f..d25c989 100644 --- a/farms_network/models/oscillator.pyx +++ b/farms_network/models/oscillator.pyx @@ -26,8 +26,6 @@ from libc.stdio cimport printf from libc.stdlib cimport free, malloc from libc.string cimport strdup -from ..core.options import OscillatorParameterOptions - cpdef enum STATE: @@ -45,11 +43,13 @@ cdef void ode( double* network_outputs, unsigned int* inputs, double* weights, - Node* node + Node* node, + Edge* edges, ) noexcept: """ ODE """ # Parameters cdef OscillatorNodeParameters params = ( node[0].parameters)[0] + cdef OscillatorEdgeParameters edge_params # States cdef double state_phase = states[STATE.phase] @@ -58,16 +58,15 @@ cdef void ode( cdef: double _sum = 0.0 unsigned int j - double _node_out, res, _input, _weight + double _input, _weight cdef unsigned int ninputs = node.ninputs - cdef int SIGN = 1 + for j in range(ninputs): _input = network_outputs[inputs[j]] _weight = weights[j] - # _phi = _p[self.neuron_inputs[j].phi_idx] - SIGN *= -1 - _sum += _weight*state_amplitude*csin(_input - state_phase - SIGN*2.5) + edge_params = ( edges[j].parameters)[0] + _sum += _weight*state_amplitude*csin(_input - state_phase - edge_params.phase_difference) cdef double i_noise = 0.0 # phidot : phase_dot @@ -76,7 +75,6 @@ cdef void ode( derivatives[STATE.amplitude] = params.amplitude_rate*( params.nominal_amplitude - state_amplitude ) - # printf("%f -- %f -- %f -- %f %f\n ", derivatives[STATE.v], i_leak, i_noise, _sum, params.c_m) cdef double output( @@ -86,14 +84,15 @@ cdef double output( double* network_outputs, unsigned int* inputs, double* weights, - Node* node + Node* node, + Edge* edges, ) noexcept: """ Node output. """ return states[STATE.phase] cdef class PyOscillatorNode(PyNode): - """ Python interface to Leaky Integrator Node C-Structure """ + """ Python interface to Oscillator Node C-Structure """ def __cinit__(self): self.node.model_type = strdup("OSCILLATOR".encode('UTF-8')) @@ -122,3 +121,30 @@ cdef class PyOscillatorNode(PyNode): """ Parameters in the network """ cdef OscillatorNodeParameters params = ( self.node.parameters)[0] return params + + +cdef class PyOscillatorEdge(PyEdge): + """ Python interface to Oscillator Edge C-Structure """ + + def __cinit__(self): + + # parameters + self.edge.parameters = malloc(sizeof(OscillatorEdgeParameters)) + if self.edge.parameters is NULL: + raise MemoryError("Failed to allocate memory for edge parameters") + + def __init__(self, source: str, target: str, edge_type: str, **kwargs): + super().__init__(source, target, edge_type) + + # Set edge parameters + cdef OscillatorEdgeParameters* param = (self.edge.parameters) + param.phase_difference = kwargs.pop("phase_difference") + + if kwargs: + raise Exception(f'Unknown kwargs: {kwargs}') + + @property + def parameters(self): + """ Parameters in the network """ + cdef OscillatorEdgeParameters params = ( self.edge.parameters)[0] + return params From db10353cb7f2cd48a72525b9e73e9f05345f5b95 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Wed, 9 Oct 2024 13:48:42 -0400 Subject: [PATCH 070/316] [CORE][NETWORK] Added setup and functionality for edge computation --- farms_network/core/network.pxd | 5 ++++ farms_network/core/network.pyx | 53 ++++++++++++++++++++++------------ 2 files changed, 39 insertions(+), 19 deletions(-) diff --git a/farms_network/core/network.pxd b/farms_network/core/network.pxd index 2f849c8..15b300b 100644 --- a/farms_network/core/network.pxd +++ b/farms_network/core/network.pxd @@ -1,5 +1,6 @@ from .data_cy cimport NetworkDataCy from .node cimport Node +from .edge cimport Edge cdef struct Network: @@ -12,6 +13,9 @@ cdef struct Network: # nodes list Node** nodes + # edges list + Edge** edges + cdef class PyNetwork: """ Python interface to Network ODE """ @@ -19,6 +23,7 @@ cdef class PyNetwork: cdef: Network *network public list pynodes + public list pyedges public NetworkDataCy data double[:] __tmp_node_outputs diff --git a/farms_network/core/network.pyx b/farms_network/core/network.pyx index fd14232..bea6e32 100644 --- a/farms_network/core/network.pyx +++ b/farms_network/core/network.pyx @@ -20,23 +20,23 @@ limitations under the License. include "types.pxd" cimport numpy as cnp + import numpy as np from libc.stdio cimport printf from libc.stdlib cimport free, malloc from libc.string cimport strdup -from ..models.factory import NodeFactory +from ..models.factory import EdgeFactory, NodeFactory from ..models.li_danner cimport LIDannerNodeParameters from .data import NetworkData, NetworkStates from .data_cy cimport NetworkDataCy, NetworkStatesCy +from .edge cimport Edge, PyEdge from .node cimport Node, PyNode -from tqdm import tqdm - from .options import (EdgeOptions, IntegrationOptions, NetworkOptions, NodeOptions) @@ -65,6 +65,7 @@ cdef void ode( cdef Node __node cdef Node** nodes = network.nodes + cdef Edge** edges = network.edges nnodes = network.nnodes cdef double* states = &data.states.array[0] @@ -73,7 +74,7 @@ cdef void ode( cdef double* derivatives = &data.derivatives.array[0] cdef unsigned int* derivatives_indices = &data.derivatives.indices[0] - cdef double external_input = 0.0 + cdef double* external_input = &data.external_inputs.array[0] cdef double* outputs = &data.outputs.array[0] cdef unsigned int* input_neurons = &data.connectivity.sources[0] @@ -89,20 +90,22 @@ cdef void ode( time, states + states_indices[j], derivatives + derivatives_indices[j], - external_input, + external_input[j], outputs, input_neurons + input_neurons_indices[j], weights + input_neurons_indices[j], - nodes[j] + nodes[j], + edges[input_neurons_indices[j]] ) tmp_node_outputs_ptr[j] = __node.output( time, states + states_indices[j], - external_input, + external_input[j], outputs, input_neurons + input_neurons_indices[j], weights + input_neurons_indices[j], - nodes[j] + nodes[j], + edges[input_neurons_indices[j]] ) # Update all node outputs data for next iteration data.outputs.array[:] = tmp_node_outputs[:] @@ -120,6 +123,12 @@ cdef class PyNetwork: self.network.nedges = len(network_options.edges) # Allocate memory for c-node structs self.network.nodes = malloc(self.nnodes * sizeof(Node *)) + if self.network.nodes is NULL: + raise MemoryError("Failed to allocate memory for Network nodes") + # Allocation memory for c-edge structs + self.network.edges = malloc(self.nedges * sizeof(Edge *)) + if self.network.edges is NULL: + raise MemoryError("Failed to allocate memory for Network edges") def __init__(self, network_options): """ Initialize """ @@ -127,6 +136,7 @@ cdef class PyNetwork: super().__init__() self.data = NetworkData.from_options(network_options) self.pynodes = [] + self.pyedges = [] self.__tmp_node_outputs = np.zeros((self.network.nnodes,)) self.setup_network(network_options, self.data) @@ -158,9 +168,16 @@ cdef class PyNetwork: c_node = (pyn.node) c_node.ninputs = len( connectivity.sources[connectivity.indices[index]:connectivity.indices[index+1]] - ) + ) if connectivity.indices else 0 self.network.nodes[index] = c_node + cdef Edge* c_edge + for index, edge_options in enumerate(options.edges): + self.pyedges.append(self.generate_edge(edge_options, options.nodes)) + pye = self.pyedges[index] + c_edge = (pye.edge) + self.network.edges[index] = c_edge + @staticmethod def generate_node(node_options: NodeOptions): """ Generate a node from options """ @@ -168,6 +185,14 @@ cdef class PyNetwork: node = Node.from_options(node_options) return node + @staticmethod + def generate_edge(edge_options: EdgeOptions, nodes_options): + """ Generate a edge from options """ + target = nodes_options[nodes_options.index(edge_options.target)] + Edge = EdgeFactory.generate_edge(target.model) + edge = Edge.from_options(edge_options) + return edge + cpdef double[:] ode(self, double time, double[::1] states): """ ODE Wrapper for numerical integrators """ self.data.states.array[:] = states[:] @@ -188,13 +213,3 @@ cdef class PyNetwork: def nstates(self): """ Number of states in the network """ return self.network.nstates - - # def step(self, time, states): - # """ Update the network """ - # self.network.ode(time, 0, states, states, self.c_nodes) - # dstates = None - # return dstates - - # cpdef void step(self) - # """ Network step function """ - # ... From 8e8b22147b5ed0d34ad4fc2a2323f31e0f30df63 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Wed, 9 Oct 2024 13:49:10 -0400 Subject: [PATCH 071/316] [CORE][DATA] Fixed handling network with no edges --- farms_network/core/data.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/farms_network/core/data.py b/farms_network/core/data.py index a701e81..5daf275 100644 --- a/farms_network/core/data.py +++ b/farms_network/core/data.py @@ -136,22 +136,24 @@ def from_options(cls, network_options: NetworkOptions): node_names = [node.name for node in nodes] for index, edge in enumerate(edges): - connectivity[index][0] = int(node_names.index(edge.from_node)) - connectivity[index][1] = int(node_names.index(edge.to_node)) + connectivity[index][0] = int(node_names.index(edge.source)) + connectivity[index][1] = int(node_names.index(edge.target)) connectivity[index][2] = edge.weight connectivity = np.array(sorted(connectivity, key=lambda col: col[1])) sources = np.empty((len(edges),)) weights = np.empty((len(edges),)) nedges = 0 - indices = [0,] - for index, node in enumerate(nodes): - node_sources = connectivity[connectivity[:, 1] == index][:, 0].tolist() - node_weights = connectivity[connectivity[:, 1] == index][:, 2].tolist() - nedges += len(node_sources) - indices.append(nedges) - sources[indices[index]:indices[index+1]] = node_sources - weights[indices[index]:indices[index+1]] = node_weights + indices = [] + if len(edges) > 0: + indices.append(0) + for index, node in enumerate(nodes): + node_sources = connectivity[connectivity[:, 1] == index][:, 0].tolist() + node_weights = connectivity[connectivity[:, 1] == index][:, 2].tolist() + nedges += len(node_sources) + indices.append(nedges) + sources[indices[index]:indices[index+1]] = node_sources + weights[indices[index]:indices[index+1]] = node_weights return cls( sources=np.array(sources, dtype=np.uintc), weights=np.array(weights, dtype=np.double), From 968c3f64fc2ab99ad35f93f98c81376b1a96dd91 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Wed, 9 Oct 2024 13:49:54 -0400 Subject: [PATCH 072/316] [CORE][OPTIONS] Added linear model and edge options to oscillator --- farms_network/core/options.py | 165 ++++++++++++++++++++++++++++++---- 1 file changed, 148 insertions(+), 17 deletions(-) diff --git a/farms_network/core/options.py b/farms_network/core/options.py index f1b8c33..089ed17 100644 --- a/farms_network/core/options.py +++ b/farms_network/core/options.py @@ -40,7 +40,7 @@ def add_node(self, options: "NodeOptions"): def add_edge(self, options: "EdgeOptions"): """ Add a node if it does not already exist in the list """ - if (options.from_node in self.nodes) and (options.to_node in self.nodes): + if (options.source in self.nodes) and (options.target in self.nodes): self.edges.append(options) else: print(f"Edge {options} does not contain the nodes.") @@ -175,12 +175,15 @@ def __init__(self, **kwargs): """ Initialize """ super().__init__() - self.from_node: str = kwargs.pop("from_node") - self.to_node: str = kwargs.pop("to_node") + self.source: str = kwargs.pop("source") + self.target: str = kwargs.pop("target") self.weight: float = kwargs.pop("weight") self.type: str = kwargs.pop("type") + self.parameters: EdgeParameterOptions = kwargs.pop( + "parameters", EdgeParameterOptions() + ) - self.visual: NodeVisualOptions = kwargs.pop("visual") + self.visual: EdgeVisualOptions = kwargs.pop("visual") if kwargs: raise Exception(f'Unknown kwargs: {kwargs}') @@ -193,6 +196,13 @@ def __eq__(self, other): return False +class EdgeParameterOptions(Options): + """ Base class for edge specific parameters """ + + def __init__(self): + super().__init__() + + class EdgeVisualOptions(Options): """ Base class for edge visualization parameters """ @@ -206,6 +216,127 @@ def __init__(self, **kwargs): raise Exception(f'Unknown kwargs: {kwargs}') +######################## +# Linear Model Options # +######################## +class LinearNodeOptions(NodeOptions): + """ Class to define the properties of Linear node model """ + + def __init__(self, **kwargs): + """ Initialize """ + model = "linear" + super().__init__( + name=kwargs.pop("name"), + model=model, + parameters=kwargs.pop("parameters"), + visual=kwargs.pop("visual"), + state=kwargs.pop("state", None), + ) + self._nstates = 0 + self._nparameters = 1 + + if kwargs: + raise Exception(f'Unknown kwargs: {kwargs}') + + +class LinearParameterOptions(NodeParameterOptions): + + def __init__(self, **kwargs): + super().__init__() + self.bias = kwargs.pop("bias") + + if kwargs: + raise Exception(f'Unknown kwargs: {kwargs}') + + @classmethod + def defaults(cls, **kwargs): + """ Get the default parameters for Linear Node model """ + + options = {} + + options["bias"] = kwargs.pop("bias", 0.0) + + return cls(**options) + +############################################ +# Phase-Amplitude Oscillator Model Options # +############################################ +class OscillatorNodeOptions(NodeOptions): + """ Class to define the properties of Oscillator node model """ + + def __init__(self, **kwargs): + """ Initialize """ + model = "oscillator" + super().__init__( + name=kwargs.pop("name"), + model=model, + parameters=kwargs.pop("parameters"), + visual=kwargs.pop("visual"), + state=kwargs.pop("state"), + ) + self._nstates = 2 + self._nparameters = 3 + + if kwargs: + raise Exception(f'Unknown kwargs: {kwargs}') + + +class OscillatorParameterOptions(NodeParameterOptions): + + def __init__(self, **kwargs): + super().__init__() + self.intrinsic_frequency = kwargs.pop("intrinsic_frequency") # Hz + self.nominal_amplitude = kwargs.pop("nominal_amplitude") # + self.amplitude_rate = kwargs.pop("amplitude_rate") # + + if kwargs: + raise Exception(f'Unknown kwargs: {kwargs}') + + @classmethod + def defaults(cls, **kwargs): + """ Get the default parameters for Oscillator Node model """ + + options = {} + + options["intrinsic_frequency"] = kwargs.pop("intrinsic_frequency", 1.0) + options["nominal_amplitude"] = kwargs.pop("nominal_amplitude", 1.0) + options["amplitude_rate"] = kwargs.pop("amplitude_rate", 1.0) + + return cls(**options) + + +class OscillatorStateOptions(NodeStateOptions): + """ Oscillator node state options """ + + STATE_NAMES = ["phase", "amplitude"] + + def __init__(self, **kwargs): + super().__init__( + initial=kwargs.pop("initial"), + names=OscillatorStateOptions.STATE_NAMES + ) + assert len(self.initial) == 2, f"Number of initial states {len(self.initial)} should be 2" + + +class OscillatorEdgeParameterOptions(EdgeParameterOptions): + """ Oscillator edge parameter options """ + + def __init__(self, **kwargs): + super().__init__() + self.phase_difference = kwargs.pop("phase_difference") # radians + + if kwargs: + raise Exception(f'Unknown kwargs: {kwargs}') + + @classmethod + def defaults(cls, **kwargs): + """ Get the default parameters for Oscillator Node model """ + + options = {} + options["phase_difference"] = kwargs.pop("phase_difference", 0.0) + return cls(**options) + + ######################################### # Leaky Integrator Danner Model Options # ######################################### @@ -294,7 +425,7 @@ def __init__(self, **kwargs): ################################################## # Leaky Integrator With NaP Danner Model Options # ################################################## -class LIDannerNaPNodeOptions(NodeOptions): +class LINaPDannerNodeOptions(NodeOptions): """ Class to define the properties of Leaky integrator danner node model """ def __init__(self, **kwargs): @@ -314,7 +445,7 @@ def __init__(self, **kwargs): raise Exception(f'Unknown kwargs: {kwargs}') -class LIDannerNaPParameterOptions(NodeParameterOptions): +class LINaPDannerParameterOptions(NodeParameterOptions): """ Class to define the parameters of Leaky integrator danner node model """ def __init__(self, **kwargs): @@ -369,7 +500,7 @@ def defaults(cls, **kwargs): return cls(**options) -class LIDannerNaPStateOptions(NodeStateOptions): +class LINaPDannerStateOptions(NodeStateOptions): """ LI Danner node state options """ STATE_NAMES = ["v0", "h0"] @@ -377,7 +508,7 @@ class LIDannerNaPStateOptions(NodeStateOptions): def __init__(self, **kwargs): super().__init__( initial=kwargs.pop("initial"), - names=LIDannerNaPStateOptions.STATE_NAMES + names=LINaPDannerStateOptions.STATE_NAMES ) assert len(self.initial) == 2, f"Number of initial states {len(self.initial)} should be 2" @@ -395,9 +526,9 @@ def main(): li_opts = LIDannerNodeOptions( name="li", - parameters=LIDannerNaPParameterOptions.defaults(), + parameters=LINaPDannerParameterOptions.defaults(), visual=NodeVisualOptions(), - state=LIDannerNaPStateOptions.from_kwargs( + state=LINaPDannerStateOptions.from_kwargs( v0=-60.0, h0=0.1 ), ) @@ -405,11 +536,11 @@ def main(): print(f"Is hashable {isinstance(li_opts, typing.Hashable)}") network_opts.add_node(li_opts) - li_opts = LIDannerNaPNodeOptions( + li_opts = LINaPDannerNodeOptions( name="li-2", - parameters=LIDannerNaPParameterOptions.defaults(), + parameters=LINaPDannerParameterOptions.defaults(), visual=NodeVisualOptions(), - state=LIDannerNaPStateOptions.from_kwargs( + state=LINaPDannerStateOptions.from_kwargs( v0=-60.0, h0=0.1 ), ) @@ -417,8 +548,8 @@ def main(): network_opts.add_edge( EdgeOptions( - from_node="li", - to_node="li-2", + source="li", + target="li-2", weight=0.0, type="excitatory", visual=EdgeVisualOptions() @@ -435,8 +566,8 @@ def main(): multigraph=False, link="edges", name="name", - source="from_node", - target="to_node" + source="source", + target="target" ) nx.draw( graph, pos=nx.nx_agraph.graphviz_layout(graph), From 6a7f4b0314d2f6e7d76e7b3eb3c7d801505552c2 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Wed, 9 Oct 2024 13:50:21 -0400 Subject: [PATCH 073/316] [CORE] Added edge and linear models --- farms_network/models/factory.py | 48 ++++++++++++++++++++++++++++++++- setup.py | 14 ++++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/farms_network/models/factory.py b/farms_network/models/factory.py index c94b872..54b2151 100644 --- a/farms_network/models/factory.py +++ b/farms_network/models/factory.py @@ -19,17 +19,19 @@ Factory class for generating the node model. """ +from farms_network.core.edge import PyEdge # from farms_network.models.fitzhugh_nagumo import FitzhughNagumo # from farms_network.models.hh_daun_motoneuron import HHDaunMotoneuron # from farms_network.models.hopf_oscillator import HopfOscillator # from farms_network.models.leaky_integrator import LeakyIntegrator +from farms_network.models.linear import PyLinearNode from farms_network.models.li_danner import PyLIDannerNode from farms_network.models.li_nap_danner import PyLINaPDannerNode # from farms_network.models.lif_daun_interneuron import LIFDaunInterneuron # from farms_network.models.matsuoka_node import MatsuokaNode # from farms_network.models.morphed_oscillator import MorphedOscillator # from farms_network.models.morris_lecar import MorrisLecarNode -from farms_network.models.oscillator import PyOscillatorNode +from farms_network.models.oscillator import PyOscillatorNode, PyOscillatorEdge # from farms_network.models.relu import ReLUNode # from farms_network.models.sensory_node import SensoryNode @@ -44,6 +46,7 @@ class NodeFactory: # 'morphed_oscillator': MorphedOscillator, # 'leaky': LeakyIntegrator, # 'sensory': SensoryNode, + 'linear': PyLinearNode, 'li_nap_danner': PyLINaPDannerNode, 'li_danner': PyLIDannerNode, # 'lif_daun_interneuron': LIFDaunInterneuron, @@ -96,3 +99,46 @@ def generate_node(node_type): if not node: raise ValueError(node_type) return node + + +class EdgeFactory: + """Implementation of Factory Edge class. + """ + edges = { + 'oscillator': PyOscillatorEdge, + } + + def __init__(self): + """Factory initialization.""" + super().__init__() + + @staticmethod + def register_edge(edge_type, edge_instance): + """ + Register a new type of edge that is a child class of Edge. + Parameters + ---------- + self: type + description + edge_type: + String to identifier for the edge. + edge_instance: + Class of the edge to register. + """ + EdgeFactory.edges[edge_type] = edge_instance + + @staticmethod + def generate_edge(edge_type): + """Generate the necessary type of edge. + Parameters + ---------- + edge_type: + Returns + ------- + edge: + Appropriate edge class. + """ + edge = EdgeFactory.edges.get(edge_type, PyEdge) + if not edge: + raise ValueError(edge_type) + return edge diff --git a/setup.py b/setup.py index cda4353..0550875 100644 --- a/setup.py +++ b/setup.py @@ -66,6 +66,13 @@ extra_compile_args=['-ffast-math', '-O3'], extra_link_args=['-O3', '-lm'] ), + Extension( + "farms_network.core.edge", + ["farms_network/core/edge.pyx"], + include_dirs=[numpy.get_include(), get_include()], + extra_compile_args=['-ffast-math', '-O3'], + extra_link_args=['-O3', '-lm'] + ), Extension( "farms_network.core.data_cy", ["farms_network/core/data_cy.pyx"], @@ -94,6 +101,13 @@ extra_compile_args=['-ffast-math', '-O3'], extra_link_args=['-O3', '-lm'] ), + Extension( + "farms_network.models.linear", + ["farms_network/models/linear.pyx"], + include_dirs=[numpy.get_include(), get_include()], + extra_compile_args=['-ffast-math', '-O3'], + extra_link_args=['-O3', '-lm'] + ), ] setup( From 26bfbb6ca95c861522b9bb8a9edbfdaff1c25d06 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Wed, 9 Oct 2024 13:50:21 -0400 Subject: [PATCH 074/316] [CORE] Added edge and linear models --- farms_network/core/edge.pxd | 42 ++++++++++++++++ farms_network/core/edge.pyx | 88 +++++++++++++++++++++++++++++++++ farms_network/models/factory.py | 48 +++++++++++++++++- farms_network/models/linear.pxd | 53 ++++++++++++++++++++ farms_network/models/linear.pyx | 86 ++++++++++++++++++++++++++++++++ setup.py | 14 ++++++ 6 files changed, 330 insertions(+), 1 deletion(-) create mode 100644 farms_network/core/edge.pxd create mode 100644 farms_network/core/edge.pyx create mode 100644 farms_network/models/linear.pxd create mode 100644 farms_network/models/linear.pyx diff --git a/farms_network/core/edge.pxd b/farms_network/core/edge.pxd new file mode 100644 index 0000000..a4d576e --- /dev/null +++ b/farms_network/core/edge.pxd @@ -0,0 +1,42 @@ +""" + +----------------------------------------------------------------------- +Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty +Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +----------------------------------------------------------------------- + +Header for Edge Base Struture. + +""" + + +cdef struct Edge: + + # + char* source # Source node + char* target # Target node + char* type # Type of connection + + # Edge parameters + unsigned int nparameters + void* parameters + + + +cdef class PyEdge: + """ Python interface to Edge C-Structure""" + + cdef: + Edge* edge diff --git a/farms_network/core/edge.pyx b/farms_network/core/edge.pyx new file mode 100644 index 0000000..c0d5a4c --- /dev/null +++ b/farms_network/core/edge.pyx @@ -0,0 +1,88 @@ +""" +----------------------------------------------------------------------- +Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty +Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +----------------------------------------------------------------------- +""" + +from libc.stdio cimport printf +from libc.stdlib cimport free, malloc +from libc.string cimport strdup + +from .options import EdgeOptions + + +cdef class PyEdge: + """ Python interface to Edge C-Structure""" + + def __cinit__(self): + self.edge = malloc(sizeof(Edge)) + if self.edge is NULL: + raise MemoryError("Failed to allocate memory for Edge") + + def __dealloc__(self): + if self.edge.source is not NULL: + free(self.edge.source) + if self.edge.target is not NULL: + free(self.edge.target) + if self.edge.type is not NULL: + free(self.edge.type) + if self.edge.parameters is not NULL: + free(self.edge.parameters) + if self.edge is not NULL: + free(self.edge) + + def __init__(self, source: str, target: str, edge_type: str, **kwargs): + self.edge.source = strdup(source.encode('UTF-8')) + self.edge.target = strdup(source.encode('UTF-8')) + self.edge.type = strdup(edge_type.encode('UTF-8')) + + @classmethod + def from_options(cls, edge_options: EdgeOptions): + """ From edge options """ + source: str = edge_options.source + target: str = edge_options.target + edge_type: str = edge_options.type + + return cls(source, target, edge_type, **edge_options.parameters) + + # Property methods for source + @property + def source(self): + if self.edge.source is NULL: + return None + return self.edge.source.decode('UTF-8') + + @property + def target(self): + if self.edge.target is NULL: + return None + return self.edge.target.decode('UTF-8') + + @property + def type(self): + if self.edge.type is NULL: + return None + return self.edge.type.decode('UTF-8') + + # Property methods for nparameters + @property + def nparameters(self): + return self.edge.nparameters + + @property + def parameters(self): + """ Number of states in the network """ + return self.edge.parameters[0] diff --git a/farms_network/models/factory.py b/farms_network/models/factory.py index c94b872..54b2151 100644 --- a/farms_network/models/factory.py +++ b/farms_network/models/factory.py @@ -19,17 +19,19 @@ Factory class for generating the node model. """ +from farms_network.core.edge import PyEdge # from farms_network.models.fitzhugh_nagumo import FitzhughNagumo # from farms_network.models.hh_daun_motoneuron import HHDaunMotoneuron # from farms_network.models.hopf_oscillator import HopfOscillator # from farms_network.models.leaky_integrator import LeakyIntegrator +from farms_network.models.linear import PyLinearNode from farms_network.models.li_danner import PyLIDannerNode from farms_network.models.li_nap_danner import PyLINaPDannerNode # from farms_network.models.lif_daun_interneuron import LIFDaunInterneuron # from farms_network.models.matsuoka_node import MatsuokaNode # from farms_network.models.morphed_oscillator import MorphedOscillator # from farms_network.models.morris_lecar import MorrisLecarNode -from farms_network.models.oscillator import PyOscillatorNode +from farms_network.models.oscillator import PyOscillatorNode, PyOscillatorEdge # from farms_network.models.relu import ReLUNode # from farms_network.models.sensory_node import SensoryNode @@ -44,6 +46,7 @@ class NodeFactory: # 'morphed_oscillator': MorphedOscillator, # 'leaky': LeakyIntegrator, # 'sensory': SensoryNode, + 'linear': PyLinearNode, 'li_nap_danner': PyLINaPDannerNode, 'li_danner': PyLIDannerNode, # 'lif_daun_interneuron': LIFDaunInterneuron, @@ -96,3 +99,46 @@ def generate_node(node_type): if not node: raise ValueError(node_type) return node + + +class EdgeFactory: + """Implementation of Factory Edge class. + """ + edges = { + 'oscillator': PyOscillatorEdge, + } + + def __init__(self): + """Factory initialization.""" + super().__init__() + + @staticmethod + def register_edge(edge_type, edge_instance): + """ + Register a new type of edge that is a child class of Edge. + Parameters + ---------- + self: type + description + edge_type: + String to identifier for the edge. + edge_instance: + Class of the edge to register. + """ + EdgeFactory.edges[edge_type] = edge_instance + + @staticmethod + def generate_edge(edge_type): + """Generate the necessary type of edge. + Parameters + ---------- + edge_type: + Returns + ------- + edge: + Appropriate edge class. + """ + edge = EdgeFactory.edges.get(edge_type, PyEdge) + if not edge: + raise ValueError(edge_type) + return edge diff --git a/farms_network/models/linear.pxd b/farms_network/models/linear.pxd new file mode 100644 index 0000000..a99c6e3 --- /dev/null +++ b/farms_network/models/linear.pxd @@ -0,0 +1,53 @@ +""" +----------------------------------------------------------------------- +Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty +Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +----------------------------------------------------------------------- + +Oscillator model +""" + + +from ..core.node cimport Node, PyNode +from ..core.edge cimport Edge, PyEdge + + +cdef enum: + #STATES + NSTATES = 0 + + +cdef packed struct LinearNodeParameters: + double bias + + +cdef: + double output( + double time, + double* states, + double external_input, + double* network_outputs, + unsigned int* inputs, + double* weights, + Node* node, + Edge* edges, + ) noexcept + + +cdef class PyLinearNode(PyNode): + """ Python interface to Linear Node C-Structure """ + + cdef: + LinearNodeParameters parameters diff --git a/farms_network/models/linear.pyx b/farms_network/models/linear.pyx new file mode 100644 index 0000000..7ec0af4 --- /dev/null +++ b/farms_network/models/linear.pyx @@ -0,0 +1,86 @@ +""" +----------------------------------------------------------------------- +Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty +Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +----------------------------------------------------------------------- + +Oscillator model +""" + +from libc.stdio cimport printf +from libc.stdlib cimport free, malloc +from libc.string cimport strdup + + +cpdef enum STATE: + + #STATES + nstates = NSTATES + + +cdef double output( + double time, + double* states, + double external_input, + double* network_outputs, + unsigned int* inputs, + double* weights, + Node* node, + Edge* edges, +) noexcept: + """ Node output. """ + cdef LinearNodeParameters params = ( node[0].parameters)[0] + + cdef: + double _sum = 0.0 + unsigned int j + double _input, _weight + cdef unsigned int ninputs = node.ninputs + _sum += external_input + for j in range(ninputs): + _input = network_outputs[inputs[j]] + _weight = weights[j] + _sum += _weight*_input + + return (_sum + params.bias) + + +cdef class PyLinearNode(PyNode): + """ Python interface to Linear Node C-Structure """ + + def __cinit__(self): + self.node.model_type = strdup("LINEAR".encode('UTF-8')) + # override default ode and out methods + self.node.is_statefull = False + self.node.output = output + # parameters + self.node.parameters = malloc(sizeof(LinearNodeParameters)) + if self.node.parameters is NULL: + raise MemoryError("Failed to allocate memory for node parameters") + + def __init__(self, name: str, **kwargs): + super().__init__(name) + + # Set node parameters + cdef LinearNodeParameters* param = (self.node.parameters) + param.bias = kwargs.pop("bias") + if kwargs: + raise Exception(f'Unknown kwargs: {kwargs}') + + @property + def parameters(self): + """ Parameters in the network """ + cdef LinearNodeParameters params = ( self.node.parameters)[0] + return params diff --git a/setup.py b/setup.py index cda4353..0550875 100644 --- a/setup.py +++ b/setup.py @@ -66,6 +66,13 @@ extra_compile_args=['-ffast-math', '-O3'], extra_link_args=['-O3', '-lm'] ), + Extension( + "farms_network.core.edge", + ["farms_network/core/edge.pyx"], + include_dirs=[numpy.get_include(), get_include()], + extra_compile_args=['-ffast-math', '-O3'], + extra_link_args=['-O3', '-lm'] + ), Extension( "farms_network.core.data_cy", ["farms_network/core/data_cy.pyx"], @@ -94,6 +101,13 @@ extra_compile_args=['-ffast-math', '-O3'], extra_link_args=['-O3', '-lm'] ), + Extension( + "farms_network.models.linear", + ["farms_network/models/linear.pyx"], + include_dirs=[numpy.get_include(), get_include()], + extra_compile_args=['-ffast-math', '-O3'], + extra_link_args=['-O3', '-lm'] + ), ] setup( From 996068be6345997652b9b1efcf7986ac1330ca29 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Wed, 16 Oct 2024 14:20:00 -0400 Subject: [PATCH 075/316] [MODELS] Added 2nd order ampltitude equation to oscillator model --- farms_network/models/oscillator.pxd | 9 +++++---- farms_network/models/oscillator.pyx | 17 ++++++++++------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/farms_network/models/oscillator.pxd b/farms_network/models/oscillator.pxd index 6d35aed..58ccde5 100644 --- a/farms_network/models/oscillator.pxd +++ b/farms_network/models/oscillator.pxd @@ -27,9 +27,10 @@ from ..core.edge cimport Edge, PyEdge cdef enum: #STATES - NSTATES = 1 + NSTATES = 3 STATE_PHASE = 0 - STATE_AMPLITUDE = 1 + STATE_AMPLITUDE= 1 + STATE_AMPLITUDE_0 = 2 cdef packed struct OscillatorNodeParameters: @@ -54,7 +55,7 @@ cdef: unsigned int* inputs, double* weights, Node* node, - Edge* edges, + Edge** edges, ) noexcept double output( double time, @@ -64,7 +65,7 @@ cdef: unsigned int* inputs, double* weights, Node* node, - Edge* edges, + Edge** edges, ) noexcept diff --git a/farms_network/models/oscillator.pyx b/farms_network/models/oscillator.pyx index d25c989..7140061 100644 --- a/farms_network/models/oscillator.pyx +++ b/farms_network/models/oscillator.pyx @@ -33,6 +33,7 @@ cpdef enum STATE: nstates = NSTATES phase = STATE_PHASE amplitude = STATE_AMPLITUDE + amplitude_0 = STATE_AMPLITUDE_0 cdef void ode( @@ -44,7 +45,7 @@ cdef void ode( unsigned int* inputs, double* weights, Node* node, - Edge* edges, + Edge** edges, ) noexcept: """ ODE """ # Parameters @@ -54,6 +55,7 @@ cdef void ode( # States cdef double state_phase = states[STATE.phase] cdef double state_amplitude = states[STATE.amplitude] + cdef double state_amplitude_0 = states[STATE.amplitude_0] cdef: double _sum = 0.0 @@ -61,19 +63,20 @@ cdef void ode( double _input, _weight cdef unsigned int ninputs = node.ninputs - for j in range(ninputs): _input = network_outputs[inputs[j]] _weight = weights[j] edge_params = ( edges[j].parameters)[0] - _sum += _weight*state_amplitude*csin(_input - state_phase - edge_params.phase_difference) + _sum += _weight*state_amplitude*csin( + _input - state_phase - edge_params.phase_difference + ) - cdef double i_noise = 0.0 # phidot : phase_dot derivatives[STATE.phase] = 2*M_PI*params.intrinsic_frequency + _sum # ampdot - derivatives[STATE.amplitude] = params.amplitude_rate*( - params.nominal_amplitude - state_amplitude + derivatives[STATE.amplitude] = state_amplitude_0 + derivatives[STATE.amplitude_0] = params.amplitude_rate*( + (params.amplitude_rate/4.0)*(params.nominal_amplitude - state_amplitude) - state_amplitude_0 ) @@ -85,7 +88,7 @@ cdef double output( unsigned int* inputs, double* weights, Node* node, - Edge* edges, + Edge** edges, ) noexcept: """ Node output. """ return states[STATE.phase] From 5894276cd35e1aaac9e291ed10b1447b20ea6bb6 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Wed, 16 Oct 2024 14:20:32 -0400 Subject: [PATCH 076/316] [MODELS] Fixed linear model equations --- farms_network/models/linear.pxd | 3 ++- farms_network/models/linear.pyx | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/farms_network/models/linear.pxd b/farms_network/models/linear.pxd index a99c6e3..e44a40b 100644 --- a/farms_network/models/linear.pxd +++ b/farms_network/models/linear.pxd @@ -30,6 +30,7 @@ cdef enum: cdef packed struct LinearNodeParameters: + double slope double bias @@ -42,7 +43,7 @@ cdef: unsigned int* inputs, double* weights, Node* node, - Edge* edges, + Edge** edges, ) noexcept diff --git a/farms_network/models/linear.pyx b/farms_network/models/linear.pyx index 7ec0af4..5927fe1 100644 --- a/farms_network/models/linear.pyx +++ b/farms_network/models/linear.pyx @@ -38,7 +38,7 @@ cdef double output( unsigned int* inputs, double* weights, Node* node, - Edge* edges, + Edge** edges, ) noexcept: """ Node output. """ cdef LinearNodeParameters params = ( node[0].parameters)[0] @@ -54,7 +54,7 @@ cdef double output( _weight = weights[j] _sum += _weight*_input - return (_sum + params.bias) + return (params.slope*_sum + params.bias) cdef class PyLinearNode(PyNode): @@ -75,6 +75,7 @@ cdef class PyLinearNode(PyNode): # Set node parameters cdef LinearNodeParameters* param = (self.node.parameters) + param.slope = kwargs.pop("slope") param.bias = kwargs.pop("bias") if kwargs: raise Exception(f'Unknown kwargs: {kwargs}') From bcc1d36686abbf2a391d2b41e3e0b096729b2d51 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Wed, 16 Oct 2024 14:21:36 -0400 Subject: [PATCH 077/316] [MODELS] Fixed edge pointer to list in LI danner models --- farms_network/models/li_danner.pxd | 4 ++-- farms_network/models/li_danner.pyx | 4 ++-- farms_network/models/li_nap_danner.pxd | 4 ++-- farms_network/models/li_nap_danner.pyx | 10 +++++----- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/farms_network/models/li_danner.pxd b/farms_network/models/li_danner.pxd index db204ee..12a06a3 100644 --- a/farms_network/models/li_danner.pxd +++ b/farms_network/models/li_danner.pxd @@ -53,7 +53,7 @@ cdef: unsigned int* inputs, double* weights, Node* node, - Edge* edges, + Edge** edges, ) noexcept double output( double time, @@ -63,7 +63,7 @@ cdef: unsigned int* inputs, double* weights, Node* node, - Edge* edges, + Edge** edges, ) noexcept diff --git a/farms_network/models/li_danner.pyx b/farms_network/models/li_danner.pyx index 3c6e6e5..d2188dc 100644 --- a/farms_network/models/li_danner.pyx +++ b/farms_network/models/li_danner.pyx @@ -43,7 +43,7 @@ cdef void ode( unsigned int* inputs, double* weights, Node* node, - Edge* edges, + Edge** edges, ) noexcept: """ ODE """ # Parameters @@ -85,7 +85,7 @@ cdef double output( unsigned int* inputs, double* weights, Node* node, - Edge* edges, + Edge** edges, ) noexcept: """ Node output. """ diff --git a/farms_network/models/li_nap_danner.pxd b/farms_network/models/li_nap_danner.pxd index 7d47d5b..1ce051b 100644 --- a/farms_network/models/li_nap_danner.pxd +++ b/farms_network/models/li_nap_danner.pxd @@ -64,7 +64,7 @@ cdef: unsigned int* inputs, double* weights, Node* node, - Edge* edges, + Edge** edges, ) noexcept double output( double time, @@ -74,7 +74,7 @@ cdef: unsigned int* inputs, double* weights, Node* node, - Edge* edges, + Edge** edges, ) noexcept diff --git a/farms_network/models/li_nap_danner.pyx b/farms_network/models/li_nap_danner.pyx index b5e3be0..b7ba64d 100644 --- a/farms_network/models/li_nap_danner.pyx +++ b/farms_network/models/li_nap_danner.pyx @@ -46,7 +46,7 @@ cdef void ode( unsigned int* inputs, double* weights, Node* node, - Edge* edges, + Edge** edges, ) noexcept: """ ODE """ cdef LINaPDannerNodeParameters params = ( node[0].parameters)[0] @@ -76,7 +76,7 @@ cdef void ode( cdef: double _sum = 0.0 unsigned int j - double _node_out, res, _input, _weight + double _input, _weight cdef unsigned int ninputs = node.ninputs for j in range(ninputs): @@ -107,7 +107,7 @@ cdef double output( unsigned int* inputs, double* weights, Node* node, - Edge* edges, + Edge** edges, ) noexcept: """ Node output. """ @@ -122,12 +122,12 @@ cdef double output( return _n_out -cdef class PyLINapDannerNode(PyNode): +cdef class PyLINaPDannerNode(PyNode): """ Python interface to Leaky Integrator Node with persistence sodium C-Structure """ def __cinit__(self): # override defaults - self._node.model_type = strdup("LI_NAP_DANNER".encode('UTF-8')) + self.node.model_type = strdup("LI_NAP_DANNER".encode('UTF-8')) # override default ode and out methods self.node.is_statefull = True self.node.ode = ode From 7c42c1085e317e0fa12b2eac1760b702b13f4153 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Wed, 16 Oct 2024 14:22:05 -0400 Subject: [PATCH 078/316] [CORE][NODE] Fixed edge pointers to list --- farms_network/core/node.pxd | 8 ++++---- farms_network/core/node.pyx | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/farms_network/core/node.pxd b/farms_network/core/node.pxd index c559feb..6bec773 100644 --- a/farms_network/core/node.pxd +++ b/farms_network/core/node.pxd @@ -48,7 +48,7 @@ cdef struct Node: unsigned int* inputs, double* weights, Node* node, - Edge* edges, + Edge** edges, ) noexcept double output( @@ -59,7 +59,7 @@ cdef struct Node: unsigned int* inputs, double* weights, Node* node, - Edge* edges, + Edge** edges, ) noexcept @@ -73,7 +73,7 @@ cdef: unsigned int* inputs, double* weights, Node* node, - Edge* edges, + Edge** edges, ) noexcept double output( double time, @@ -83,7 +83,7 @@ cdef: unsigned int* inputs, double* weights, Node* node, - Edge* edges, + Edge** edges, ) noexcept diff --git a/farms_network/core/node.pyx b/farms_network/core/node.pyx index 61f2a0a..0aaf3ce 100644 --- a/farms_network/core/node.pyx +++ b/farms_network/core/node.pyx @@ -33,7 +33,7 @@ cdef void ode( unsigned int* inputs, double* weights, Node* node, - Edge* edges, + Edge** edges, ) noexcept: """ Node ODE """ printf("Base implementation of ODE C function \n") @@ -47,7 +47,7 @@ cdef double output( unsigned int* inputs, double* weights, Node* node, - Edge* edges, + Edge** edges, ) noexcept: """ Node output """ printf("Base implementation of output C function \n") @@ -141,7 +141,7 @@ cdef class PyNode: cdef double* network_outputs_ptr = &network_outputs[0] cdef unsigned int* inputs_ptr = &inputs[0] cdef double* weights_ptr = &weights[0] - cdef Edge* edges = NULL + cdef Edge** edges = NULL # Call the C function directly self.node.ode( @@ -170,7 +170,7 @@ cdef class PyNode: cdef double* network_outputs_ptr = &network_outputs[0] cdef unsigned int* inputs_ptr = &inputs[0] cdef double* weights_ptr = &weights[0] - cdef Edge* edges = NULL + cdef Edge** edges = NULL return self.node.output( time, states_ptr, From 615dfb6f77dbcde2c5f3b28b6bcbdb0c3458561e Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Thu, 17 Oct 2024 10:04:34 -0400 Subject: [PATCH 079/316] [EXAMPLE] Added ijspeert07 oscillator model network --- examples/ijspeert07/run.py | 294 +++++++++++++++++++++++++++++++++++++ 1 file changed, 294 insertions(+) create mode 100644 examples/ijspeert07/run.py diff --git a/examples/ijspeert07/run.py b/examples/ijspeert07/run.py new file mode 100644 index 0000000..8739cef --- /dev/null +++ b/examples/ijspeert07/run.py @@ -0,0 +1,294 @@ +""" Generate and reproduce Zhang, Shevtsova, et al. eLife 2022;11:e73424. DOI: +https://doi.org/10.7554/eLife.73424 paper network """ + + +import itertools +import os +from copy import deepcopy +from pprint import pprint + +import farms_pylog as pylog +import matplotlib.pyplot as plt +import networkx as nx +import numpy as np +from farms_core.io.yaml import read_yaml, write_yaml +from farms_network.core import options +from farms_network.core.data import (NetworkConnectivity, NetworkData, + NetworkStates) +from farms_network.core.network import PyNetwork, rk4 +from tqdm import tqdm +from pprint import pprint + +plt.rcParams['text.usetex'] = False + + +def join_strings(strings): + return "_".join(strings) + + +def oscillator_chain(network_options, n_oscillators, name_prefix, **kwargs): + """ Create a chain of n-oscillators. """ + # Define a network graph + + oscillator_names = [ + "{}_{}".format(name_prefix, n) + for n in range(n_oscillators) + ] + # Oscillators + intrinsic_frequency = kwargs.get('intrinsic_frequency', 0.2) + nominal_amplitude = kwargs.get('nominal_amplitude', 1.0) + amplitude_rate = kwargs.get('amplitude_rate', 20.0) + + origin = kwargs.get('origin', [0, 0]) + for j, osc in enumerate(oscillator_names): + network_options.add_node( + options.OscillatorNodeOptions( + name=osc, + parameters=options.OscillatorNodeParameterOptions.defaults( + intrinsic_frequency=intrinsic_frequency, + nominal_amplitude=nominal_amplitude, + amplitude_rate=amplitude_rate, + ), + visual=options.NodeVisualOptions( + label=f"{j}", color=[1.0, 0.0, 0.0] + ), + state=options.OscillatorStateOptions.from_kwargs( + phase=np.random.uniform(-np.pi, np.pi), + amplitude=np.random.uniform(0, 1), + amplitude_0=np.random.uniform(0, 1) + ), + ) + ) + # Connect + phase_diff = kwargs.get('axial_phi', 2*np.pi/8) + weight = kwargs.get('axial_w', 5) + connections = np.vstack( + (np.arange(n_oscillators), + np.roll(np.arange(n_oscillators), -1)))[:, :-1] + for j in np.arange(n_oscillators-1): + network_options.add_edge( + options.EdgeOptions( + source=oscillator_names[connections[0, j]], + target=oscillator_names[connections[1, j]], + weight=weight, + type="excitatory", + parameters=options.OscillatorEdgeParameterOptions( + phase_difference=-1*phase_diff + ), + visual=options.EdgeVisualOptions(), + ) + ) + + network_options.add_edge( + options.EdgeOptions( + source=oscillator_names[connections[1, j]], + target=oscillator_names[connections[0, j]], + weight=weight, + type="excitatory", + parameters=options.OscillatorEdgeParameterOptions( + phase_difference=phase_diff + ), + visual=options.EdgeVisualOptions(), + ) + ) + return network_options + + +def oscillator_double_chain(network_options, n_oscillators, **kwargs): + """ Create a double chain of n-oscillators. """ + kwargs['origin'] = [-0.05, 0] + network_options = oscillator_chain(network_options, n_oscillators, 'left', **kwargs) + kwargs['origin'] = [0.05, 0] + network_options = oscillator_chain(network_options, n_oscillators, 'right', **kwargs) + + # Connect double chain + phase_diff = kwargs.get('anti_phi', np.pi) + weight = kwargs.get('anti_w', 50) + for n in range(n_oscillators): + network_options.add_edge( + options.EdgeOptions( + source=f'left_{n}', + target=f'right_{n}', + weight=weight, + type="excitatory", + parameters=options.OscillatorEdgeParameterOptions( + phase_difference=phase_diff + ), + visual=options.EdgeVisualOptions(), + ) + ) + network_options.add_edge( + options.EdgeOptions( + source=f'right_{n}', + target=f'left_{n}', + weight=weight, + type="excitatory", + parameters=options.OscillatorEdgeParameterOptions( + phase_difference=phase_diff + ), + visual=options.EdgeVisualOptions(), + ) + ) + return network_options + + + +class RhythmDrive: + """ Generate Drive Network """ + + def __init__(self, name="", anchor_x=0.0, anchor_y=0.0): + """Initialization.""" + super().__init__() + self.name = name + + def nodes(self): + """Add nodes.""" + nodes = {} + name = join_strings((self.name, "RG", "F", "DR")) + nodes[name] = options.LinearNodeOptions( + name=name, + parameters=options.LinearParameterOptions.defaults(slope=0.1, bias=0.0), + visual=options.NodeVisualOptions(), + ) + name = join_strings((self.name, "RG", "E", "DR")) + nodes[name] = options.LinearNodeOptions( + name=name, + parameters=options.LinearParameterOptions.defaults(slope=0.0, bias=0.1), + visual=options.NodeVisualOptions(), + ) + + return nodes + + +def generate_network(): + """ Generate network """ + + # Main network + network_options = options.NetworkOptions( + directed=True, + multigraph=False, + graph={"name": "ijspeert07"}, + ) + + # Generate rhythm centers + n_oscillators = 168 + network_options = oscillator_double_chain(network_options, n_oscillators) + pprint(network_options) + data = NetworkData.from_options(network_options) + + network = PyNetwork.from_options(network_options) + + # nnodes = len(network_options.nodes) + # integrator.set_initial_value(np.zeros(len(data.states.array),), 0.0) + + # print("Data ------------", np.array(network.data.states.array)) + + # data.to_file("/tmp/sim.hdf5") + + # integrator.integrate(integrator.t + 1e-3) + + # # Integrate + iterations = 20000 + states = np.ones((iterations+1, len(data.states.array)))*0.0 + outputs = np.ones((iterations, len(data.outputs.array)))*1.0 + K1 = np.zeros((len(data.states.array),)) + # states[0, 2] = -1.0 + + for index, node in enumerate(network_options.nodes): + print(index, node.name) + # network.data.external_inputs.array[:] = np.ones((1,))*(iteration/iterations)*1.0 + for iteration in tqdm(range(0, iterations), colour='green', ascii=' >='): + network.step() + states[iteration+1, :] = network.data.outputs.array + # rk4( + # iteration*1e-3, states[iteration, :], network.ode, step_size=1e-3, + # ) + outputs[iteration, :] = network.data.outputs.array + + network.data.to_file("/tmp/network.h5") + + plt.figure() + for j in range(int(n_oscillators/2)+1): + plt.fill_between( + np.linspace(0.0, iterations*1e-3, iterations), + j + (1 + np.sin(outputs[:, j])), + j, + alpha=0.2, + lw=1.0, + ) + plt.plot( + np.linspace(0.0, iterations*1e-3, iterations), + j + (1 + np.sin(outputs[:, j])), + label=f"{j}" + ) + plt.legend() + + network_options.save("/tmp/netwok_options.yaml") + + graph = nx.node_link_graph( + network_options, + directed=True, + multigraph=False, + link="edges", + name="name", + source="source", + target="target" + ) + plt.figure() + pos_circular = nx.circular_layout(graph) + pos_spring = nx.spring_layout(graph) + pos_graphviz = nx.nx_agraph.pygraphviz_layout(graph) + _ = nx.draw_networkx_nodes( + graph, + pos=pos_graphviz, + node_color=[data["visual"]["color"] for node, data in graph.nodes.items()], + alpha=0.25, + edgecolors='k', + linewidths=2.0, + ) + nx.draw_networkx_labels( + graph, + pos=pos_graphviz, + labels={node: data["visual"]["label"] for node, data in graph.nodes.items()}, + font_size=11.0, + font_weight='bold', + font_family='sans-serif', + alpha=1.0, + ) + nx.draw_networkx_edges( + graph, + pos=pos_graphviz, + edge_color=[ + [0.0, 1.0, 0.0] if data["type"] == "excitatory" else [1.0, 0.0, 0.0] + for edge, data in graph.edges.items() + ], + width=1., + arrowsize=10, + style='dashed', + arrows=True, + min_source_margin=5, + min_target_margin=5, + connectionstyle="arc3,rad=-0.2", + ) + plt.show() + + # generate_tikz_figure( + # graph, + # paths.get_project_data_path().joinpath("templates", "network",), + # "tikz-full-network.tex", + # paths.get_project_images_path().joinpath("quadruped_network.tex") + # ) + + +def main(): + """Main.""" + + # Generate the network + generate_network() + + # Run the network + # run_network() + + +if __name__ == "__main__": + main() From 23630fa2f6cb67157ee7016b3560bf0e4bbb7cda Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Thu, 17 Oct 2024 10:07:17 -0400 Subject: [PATCH 080/316] [CORE] Fixed edge indices in network ode calls --- farms_network/core/network.pyx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/farms_network/core/network.pyx b/farms_network/core/network.pyx index bea6e32..f2e1c14 100644 --- a/farms_network/core/network.pyx +++ b/farms_network/core/network.pyx @@ -95,7 +95,7 @@ cdef void ode( input_neurons + input_neurons_indices[j], weights + input_neurons_indices[j], nodes[j], - edges[input_neurons_indices[j]] + edges + input_neurons_indices[j], ) tmp_node_outputs_ptr[j] = __node.output( time, @@ -105,7 +105,7 @@ cdef void ode( input_neurons + input_neurons_indices[j], weights + input_neurons_indices[j], nodes[j], - edges[input_neurons_indices[j]] + edges + input_neurons_indices[j], ) # Update all node outputs data for next iteration data.outputs.array[:] = tmp_node_outputs[:] From 82dce61b423305e52b54f20112cf706017e9a9f5 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Thu, 17 Oct 2024 10:08:23 -0400 Subject: [PATCH 081/316] [CORE] Added add_nodes and edges method to network options --- farms_network/core/options.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/farms_network/core/options.py b/farms_network/core/options.py index 089ed17..faece3a 100644 --- a/farms_network/core/options.py +++ b/farms_network/core/options.py @@ -1,6 +1,6 @@ """ Options to configure the neural and network models """ -from typing import List, Self +from typing import List, Self, Iterable import matplotlib.pyplot as plt import networkx as nx @@ -38,6 +38,11 @@ def add_node(self, options: "NodeOptions"): else: print(f"Node {options.name} already exists and will not be added again.") + def add_nodes(self, options: Iterable["NodeOptions"]): + """ Add a collection of nodes """ + for node in options: + self.add_node(node) + def add_edge(self, options: "EdgeOptions"): """ Add a node if it does not already exist in the list """ if (options.source in self.nodes) and (options.target in self.nodes): @@ -45,6 +50,11 @@ def add_edge(self, options: "EdgeOptions"): else: print(f"Edge {options} does not contain the nodes.") + def add_edges(self, options: Iterable["EdgeOptions"]): + """ Add a collection of edges """ + for edge in options: + self.add_edge(edge) + def __add__(self, other: Self): """ Combine two network options """ assert isinstance(other, NetworkOptions) From 4af56f72a846ef9a092b54333f63610171366520 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Thu, 17 Oct 2024 10:09:01 -0400 Subject: [PATCH 082/316] [CORE] Added slope parameter to linear node options --- farms_network/core/options.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/farms_network/core/options.py b/farms_network/core/options.py index faece3a..58dddcf 100644 --- a/farms_network/core/options.py +++ b/farms_network/core/options.py @@ -243,7 +243,7 @@ def __init__(self, **kwargs): state=kwargs.pop("state", None), ) self._nstates = 0 - self._nparameters = 1 + self._nparameters = 2 if kwargs: raise Exception(f'Unknown kwargs: {kwargs}') @@ -253,6 +253,7 @@ class LinearParameterOptions(NodeParameterOptions): def __init__(self, **kwargs): super().__init__() + self.slope = kwargs.pop("slope") self.bias = kwargs.pop("bias") if kwargs: @@ -264,6 +265,7 @@ def defaults(cls, **kwargs): options = {} + options["slope"] = kwargs.pop("slope", 1.0) options["bias"] = kwargs.pop("bias", 0.0) return cls(**options) From 606ad53e96c0fbc328cd4adbf9f69fe1b7e8c75b Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Thu, 17 Oct 2024 10:09:37 -0400 Subject: [PATCH 083/316] [CORE][OPTIONS] Added 2nd order rate equation to oscillator --- farms_network/core/options.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/farms_network/core/options.py b/farms_network/core/options.py index 58dddcf..9710d7e 100644 --- a/farms_network/core/options.py +++ b/farms_network/core/options.py @@ -157,6 +157,8 @@ def from_kwargs(cls, **kwargs): kwargs.pop(name) for name in cls.STATE_NAMES ] + if kwargs: + raise Exception(f'Unknown kwargs: {kwargs}') return cls(initial=initial) @@ -175,6 +177,10 @@ def __init__(self, **kwargs): raise Exception(f'Unknown kwargs: {kwargs}') +class NodeLogOptions(Options): + """ Base class for node logging options """ + + ################ # Edge Options # ################ @@ -286,14 +292,14 @@ def __init__(self, **kwargs): visual=kwargs.pop("visual"), state=kwargs.pop("state"), ) - self._nstates = 2 + self._nstates = 3 self._nparameters = 3 if kwargs: raise Exception(f'Unknown kwargs: {kwargs}') -class OscillatorParameterOptions(NodeParameterOptions): +class OscillatorNodeParameterOptions(NodeParameterOptions): def __init__(self, **kwargs): super().__init__() @@ -320,14 +326,14 @@ def defaults(cls, **kwargs): class OscillatorStateOptions(NodeStateOptions): """ Oscillator node state options """ - STATE_NAMES = ["phase", "amplitude"] + STATE_NAMES = ["phase", "amplitude_0", "amplitude"] def __init__(self, **kwargs): super().__init__( initial=kwargs.pop("initial"), names=OscillatorStateOptions.STATE_NAMES ) - assert len(self.initial) == 2, f"Number of initial states {len(self.initial)} should be 2" + assert len(self.initial) == 3, f"Number of initial states {len(self.initial)} should be 3" class OscillatorEdgeParameterOptions(EdgeParameterOptions): @@ -442,7 +448,7 @@ class LINaPDannerNodeOptions(NodeOptions): def __init__(self, **kwargs): """ Initialize """ - model = "li_danner_nap" + model = "li_nap_danner" super().__init__( name=kwargs.pop("name"), model=model, From dc264219363e31d9faae39488d86ad093c27de49 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Mon, 21 Oct 2024 10:21:24 -0400 Subject: [PATCH 084/316] [SETUP] Removed farms_container get include call --- setup.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/setup.py b/setup.py index 0550875..44ae74f 100644 --- a/setup.py +++ b/setup.py @@ -1,8 +1,6 @@ from setuptools import setup, dist, find_packages from setuptools.extension import Extension -from farms_container import get_include - dist.Distribution().fetch_build_eggs(['numpy']) import numpy From 3180712678a9f39c3558e1bed769e6c64015ae85 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Mon, 21 Oct 2024 10:22:17 -0400 Subject: [PATCH 085/316] [SETUP] Removed farms_container get_include func calls --- setup.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/setup.py b/setup.py index 44ae74f..edae37f 100644 --- a/setup.py +++ b/setup.py @@ -53,56 +53,56 @@ Extension( "farms_network.core.network", ["farms_network/core/network.pyx"], - include_dirs=[numpy.get_include(), get_include()], + include_dirs=[numpy.get_include()], extra_compile_args=['-ffast-math', '-O3'], extra_link_args=['-O3', '-lm'] ), Extension( "farms_network.core.node", ["farms_network/core/node.pyx"], - include_dirs=[numpy.get_include(), get_include()], + include_dirs=[numpy.get_include()], extra_compile_args=['-ffast-math', '-O3'], extra_link_args=['-O3', '-lm'] ), Extension( "farms_network.core.edge", ["farms_network/core/edge.pyx"], - include_dirs=[numpy.get_include(), get_include()], + include_dirs=[numpy.get_include()], extra_compile_args=['-ffast-math', '-O3'], extra_link_args=['-O3', '-lm'] ), Extension( "farms_network.core.data_cy", ["farms_network/core/data_cy.pyx"], - include_dirs=[numpy.get_include(), get_include()], + include_dirs=[numpy.get_include()], extra_compile_args=['-ffast-math', '-O3'], extra_link_args=['-O3', '-lm'] ), Extension( "farms_network.models.li_danner", ["farms_network/models/li_danner.pyx"], - include_dirs=[numpy.get_include(), get_include()], + include_dirs=[numpy.get_include()], extra_compile_args=['-ffast-math', '-O3'], extra_link_args=['-O3', '-lm'] ), Extension( "farms_network.models.li_nap_danner", ["farms_network/models/li_nap_danner.pyx"], - include_dirs=[numpy.get_include(), get_include()], + include_dirs=[numpy.get_include()], extra_compile_args=['-ffast-math', '-O3'], extra_link_args=['-O3', '-lm'] ), Extension( "farms_network.models.oscillator", ["farms_network/models/oscillator.pyx"], - include_dirs=[numpy.get_include(), get_include()], + include_dirs=[numpy.get_include()], extra_compile_args=['-ffast-math', '-O3'], extra_link_args=['-O3', '-lm'] ), Extension( "farms_network.models.linear", ["farms_network/models/linear.pyx"], - include_dirs=[numpy.get_include(), get_include()], + include_dirs=[numpy.get_include()], extra_compile_args=['-ffast-math', '-O3'], extra_link_args=['-O3', '-lm'] ), From 1ef59ac2fae9eed20698c0a99703c79739c9710b Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Mon, 21 Oct 2024 10:25:25 -0400 Subject: [PATCH 086/316] [EX][IJSPEERT07] Updated plots and network figure for testing --- examples/ijspeert07/run.py | 33 +++++++++++++++------------------ 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/examples/ijspeert07/run.py b/examples/ijspeert07/run.py index 8739cef..437fb0c 100644 --- a/examples/ijspeert07/run.py +++ b/examples/ijspeert07/run.py @@ -12,12 +12,12 @@ import networkx as nx import numpy as np from farms_core.io.yaml import read_yaml, write_yaml +from farms_core.utils import profile from farms_network.core import options from farms_network.core.data import (NetworkConnectivity, NetworkData, NetworkStates) from farms_network.core.network import PyNetwork, rk4 from tqdm import tqdm -from pprint import pprint plt.rcParams['text.usetex'] = False @@ -132,7 +132,6 @@ def oscillator_double_chain(network_options, n_oscillators, **kwargs): return network_options - class RhythmDrive: """ Generate Drive Network """ @@ -171,9 +170,9 @@ def generate_network(): ) # Generate rhythm centers - n_oscillators = 168 + n_oscillators = 20 network_options = oscillator_double_chain(network_options, n_oscillators) - pprint(network_options) + data = NetworkData.from_options(network_options) network = PyNetwork.from_options(network_options) @@ -191,21 +190,19 @@ def generate_network(): iterations = 20000 states = np.ones((iterations+1, len(data.states.array)))*0.0 outputs = np.ones((iterations, len(data.outputs.array)))*1.0 - K1 = np.zeros((len(data.states.array),)) # states[0, 2] = -1.0 - for index, node in enumerate(network_options.nodes): - print(index, node.name) + # for index, node in enumerate(network_options.nodes): + # print(index, node.name) # network.data.external_inputs.array[:] = np.ones((1,))*(iteration/iterations)*1.0 for iteration in tqdm(range(0, iterations), colour='green', ascii=' >='): - network.step() - states[iteration+1, :] = network.data.outputs.array - # rk4( - # iteration*1e-3, states[iteration, :], network.ode, step_size=1e-3, - # ) + # network.step(network.ode, iteration*1e-3, network.data.states.array) + # network.step() + # states[iteration+1, :] = network.data.states.array + states[iteration+1, :] = rk4(iteration*1e-3, states[iteration, :], network.ode, step_size=1e-3,) outputs[iteration, :] = network.data.outputs.array - network.data.to_file("/tmp/network.h5") + # network.data.to_file("/tmp/network.h5") plt.figure() for j in range(int(n_oscillators/2)+1): @@ -237,10 +234,10 @@ def generate_network(): plt.figure() pos_circular = nx.circular_layout(graph) pos_spring = nx.spring_layout(graph) - pos_graphviz = nx.nx_agraph.pygraphviz_layout(graph) + # pos_graphviz = nx.nx_agraph.pygraphviz_layout(graph) _ = nx.draw_networkx_nodes( graph, - pos=pos_graphviz, + pos=pos_spring, node_color=[data["visual"]["color"] for node, data in graph.nodes.items()], alpha=0.25, edgecolors='k', @@ -248,7 +245,7 @@ def generate_network(): ) nx.draw_networkx_labels( graph, - pos=pos_graphviz, + pos=pos_spring, labels={node: data["visual"]["label"] for node, data in graph.nodes.items()}, font_size=11.0, font_weight='bold', @@ -257,7 +254,7 @@ def generate_network(): ) nx.draw_networkx_edges( graph, - pos=pos_graphviz, + pos=pos_spring, edge_color=[ [0.0, 1.0, 0.0] if data["type"] == "excitatory" else [1.0, 0.0, 0.0] for edge, data in graph.edges.items() @@ -284,7 +281,7 @@ def main(): """Main.""" # Generate the network - generate_network() + profile.profile(generate_network) # Run the network # run_network() From a67551deba01d2b9c5e6bb6fa248bd8f78aa221c Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Thu, 24 Oct 2024 21:40:06 -0400 Subject: [PATCH 087/316] [CORE][OPTIONS] Added basic network and node logging options --- farms_network/core/options.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/farms_network/core/options.py b/farms_network/core/options.py index 9710d7e..d2ebb0b 100644 --- a/farms_network/core/options.py +++ b/farms_network/core/options.py @@ -17,10 +17,12 @@ def __init__(self, **kwargs): super().__init__() # Default properties to make it compatible with networkx + # seed self.directed: bool = kwargs.pop("directed", True) self.multigraph: bool = kwargs.pop("multigraph", False) self.graph: dict = kwargs.pop("graph", {"name": ""}) self.units = kwargs.pop("units", None) + self.logs = kwargs.pop("logs") self.integration = kwargs.pop("integration", IntegrationOptions.defaults()) @@ -100,6 +102,24 @@ def defaults(cls, **kwargs): return cls(**options) +################### +# Logging Options # +################### +class NetworkLogOptions(Options): + """ Log options for the network level """ + + def __init__(self, n_iterations: int, **kwargs): + super().__init__(**kwargs) + + self.n_iterations = n_iterations + self.buffer_size: int = kwargs.pop('buffer_size', self.n_iterations) + if self.buffer_size == 0: + self.buffer_size = self.n_iterations + self.nodes_all: bool = kwargs.pop("nodes_all", False) + + if kwargs: + raise Exception(f'Unknown kwargs: {kwargs}') + ########################### # Node Base Class Options # @@ -162,6 +182,16 @@ def from_kwargs(cls, **kwargs): return cls(initial=initial) +class NodeLogOptions(Options): + """ Log options for the node level """ + + def __init__(self, buffer_size: int, enable: bool, **kwargs): + super().__init__(**kwargs) + + if kwargs: + raise Exception(f'Unknown kwargs: {kwargs}') + + class NodeVisualOptions(Options): """ Base class for node visualization parameters """ From 094679fc7c98a83a89143e134a4cc6192ac39c81 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Thu, 24 Oct 2024 21:41:11 -0400 Subject: [PATCH 088/316] [CORE][OPTIONS] Replaced print statements with farms-pylog --- farms_network/core/options.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/farms_network/core/options.py b/farms_network/core/options.py index d2ebb0b..9c29fc7 100644 --- a/farms_network/core/options.py +++ b/farms_network/core/options.py @@ -1,9 +1,10 @@ """ Options to configure the neural and network models """ -from typing import List, Self, Iterable +from typing import Iterable, List, Self import matplotlib.pyplot as plt import networkx as nx +from farms_core import pylog from farms_core.options import Options @@ -50,7 +51,11 @@ def add_edge(self, options: "EdgeOptions"): if (options.source in self.nodes) and (options.target in self.nodes): self.edges.append(options) else: - print(f"Edge {options} does not contain the nodes.") + missing_nodes = [ + "" if (options.source in self.nodes) else options.source, + "" if (options.target in self.nodes) else options.target, + ] + pylog.debug(f"Missing node {*missing_nodes,} in Edge {options}") def add_edges(self, options: Iterable["EdgeOptions"]): """ Add a collection of edges """ From a95a9ad7c701f93048ae4473b818bc34fca09f61 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Thu, 24 Oct 2024 21:41:45 -0400 Subject: [PATCH 089/316] [CORE][OPTIONS] Added iterations and timestep to options --- farms_network/core/options.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/farms_network/core/options.py b/farms_network/core/options.py index 9c29fc7..2534702 100644 --- a/farms_network/core/options.py +++ b/farms_network/core/options.py @@ -82,6 +82,7 @@ def __init__(self, **kwargs): super().__init__() self.timestep: float = kwargs.pop("timestep") + self.n_interations: float = kwargs.pop("n_iterations") self.integrator: str = kwargs.pop("integrator") self.method: str = kwargs.pop("method") self.atol: float = kwargs.pop("atol") @@ -97,7 +98,8 @@ def defaults(cls, **kwargs): options = {} - options["timestep"] = kwargs.pop("timestep", "1e-3") + options["timestep"] = kwargs.pop("timestep", 1e-3) + options["n_iterations"] = kwargs.pop("n_interations", 1e3) options["integrator"] = kwargs.pop("integrator", "dopri5") options["method"] = kwargs.pop("method", "adams") options["atol"] = kwargs.pop("atol", 1e-12) @@ -212,10 +214,6 @@ def __init__(self, **kwargs): raise Exception(f'Unknown kwargs: {kwargs}') -class NodeLogOptions(Options): - """ Base class for node logging options """ - - ################ # Edge Options # ################ From 64696f2b429391fec6fabd4e71b45b9b5eaab691 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Thu, 24 Oct 2024 21:42:53 -0400 Subject: [PATCH 090/316] [CORE][OPTIONS] Added fancy arrow edge options --- farms_network/core/options.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/farms_network/core/options.py b/farms_network/core/options.py index 2534702..61ac659 100644 --- a/farms_network/core/options.py +++ b/farms_network/core/options.py @@ -258,9 +258,17 @@ class EdgeVisualOptions(Options): def __init__(self, **kwargs): super().__init__() self.color: List[float] = kwargs.pop("color", [1.0, 0.0, 0.0]) + self.alpha: float = kwargs.pop("alpha", 1.0) self.label: str = kwargs.pop("label", "") self.layer: str = kwargs.pop("layer", "background") self.latex: dict = kwargs.pop("latex", "{}") + + # New options for FancyArrowPatch compatibility + self.arrowstyle: str = kwargs.pop("arrowstyle", "->") + self.connectionstyle: str = kwargs.pop("connectionstyle", "arc3,rad=0.1") + self.linewidth: float = kwargs.pop("linewidth", 1.5) + self.edgecolor: List[float] = kwargs.pop("edgecolor", [0.0, 0.0, 0.0]) + if kwargs: raise Exception(f'Unknown kwargs: {kwargs}') From be80cbdf2208844020c87c67ded88dde314a1713 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Thu, 24 Oct 2024 21:43:11 -0400 Subject: [PATCH 091/316] [CORE][OPTIONS] Fixed nstates for LIDannerNode --- farms_network/core/options.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/farms_network/core/options.py b/farms_network/core/options.py index 61ac659..4f26dab 100644 --- a/farms_network/core/options.py +++ b/farms_network/core/options.py @@ -412,7 +412,7 @@ def __init__(self, **kwargs): visual=kwargs.pop("visual"), state=kwargs.pop("state"), ) - self._nstates = 2 + self._nstates = 1 self._nparameters = 13 if kwargs: From 3f0578a1f5125d27442cb60431ec68b19f54562f Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Thu, 24 Oct 2024 21:44:19 -0400 Subject: [PATCH 092/316] [CORE][DATA] Added basic node data infrastructure --- farms_network/core/data.py | 157 ++++++++++++++++++++++++++------- farms_network/core/data_cy.pxd | 18 +++- farms_network/core/data_cy.pyx | 26 +++++- 3 files changed, 166 insertions(+), 35 deletions(-) diff --git a/farms_network/core/data.py b/farms_network/core/data.py index 5daf275..8e6b4ff 100644 --- a/farms_network/core/data.py +++ b/farms_network/core/data.py @@ -20,19 +20,24 @@ """ -from typing import Dict, List +from pprint import pprint +from typing import Dict, Iterable, List import numpy as np from farms_core import pylog from farms_core.array.array import to_array -from farms_core.array.array_cy import DoubleArray1D +from farms_core.array.array_cy import (DoubleArray1D, DoubleArray2D, + IntegerArray1D) from farms_core.array.types import (NDARRAY_V1, NDARRAY_V1_D, NDARRAY_V2_D, NDARRAY_V3_D) from farms_core.io.hdf5 import dict_to_hdf5, hdf5_to_dict -from .data_cy import NetworkConnectivityCy, NetworkDataCy, NetworkStatesCy +from .data_cy import NetworkConnectivityCy, NetworkDataCy, NetworkStatesCy, NodeDataCy from .options import NetworkOptions, NodeOptions, NodeStateOptions +NPDTYPE = np.float64 +NPUITYPE = np.uintc + class NetworkData(NetworkDataCy): """ Network data """ @@ -44,6 +49,7 @@ def __init__( connectivity, outputs, external_inputs, + nodes, **kwargs, ): """ Network data structure """ @@ -55,7 +61,7 @@ def __init__( self.outputs = outputs self.external_inputs = external_inputs - # self.nodes: List[NodeData] = [NodeData(),] + self.nodes: np.ndarray[NodeDataCy] = nodes @classmethod def from_options(cls, network_options: NetworkOptions): @@ -66,12 +72,23 @@ def from_options(cls, network_options: NetworkOptions): connectivity = NetworkConnectivity.from_options(network_options) outputs = DoubleArray1D(array=np.zeros(len(network_options.nodes),)) external_inputs = DoubleArray1D(array=np.zeros(len(network_options.nodes),)) + nodes = np.array( + [ + NodeData.from_options( + node_options, + buffer_size=network_options.logs.buffer_size + ) + for node_options in network_options.nodes + ], + dtype=NodeDataCy + ) return cls( states=states, derivatives=derivatives, connectivity=connectivity, outputs=outputs, - external_inputs=external_inputs + external_inputs=external_inputs, + nodes=nodes, ) def to_dict(self, iteration: int = None) -> Dict: @@ -82,6 +99,7 @@ def to_dict(self, iteration: int = None) -> Dict: 'connectivity': self.connectivity.to_dict(), 'outputs': to_array(self.outputs.array), 'external_inputs': to_array(self.external_inputs.array), + 'nodes': {node.name: node.to_dict() for node in self.nodes}, } def to_file(self, filename: str, iteration: int = None): @@ -120,7 +138,6 @@ def to_dict(self, iteration: int = None) -> Dict: } - class NetworkConnectivity(NetworkConnectivityCy): def __init__(self, sources, weights, indices): @@ -163,43 +180,121 @@ def from_options(cls, network_options: NetworkOptions): def to_dict(self, iteration: int = None) -> Dict: """Convert data to dictionary""" return { - 'array': to_array(self.array), + 'sources': to_array(self.sources), + 'weights': to_array(self.weights), 'indices': to_array(self.indices), } -# class NodeData: -# """ Base class for representing an arbitrary node data """ +class NodeData(NodeDataCy): + """ Base class for representing an arbitrary node data """ -# def __init__(self, states_arr: StatesArray, out_arr: OutputArray): -# """Node data initialization """ + def __init__( + self, + name: str, + states: "NodeStatesArray", + derivatives: "NodeStatesArray", + output: "NodeOutputArray", + external_input: "NodeExternalInputArray", + ): + """ Node data initialization """ -# super().__init__() + super().__init__( + states=states, + derivatives=derivatives, + output=output, + external_input=external_input, + ) + self.name = name -# self.states = states_arr -# self.dstates = states_arr -# self.usr_inputs = None -# self.state_noise = None -# self.output_noise = None -# self.output = out_arr + @classmethod + def from_options(cls, options: NodeOptions, buffer_size: int): + """ Node data from class """ + return cls( + name=options.name, + states=NodeStatesArray.from_options(options, buffer_size), + derivatives=NodeStatesArray.from_options(options, buffer_size), + output=NodeOutputArray.from_options(options, buffer_size), + external_input=NodeExternalInputArray.from_options(options, buffer_size), + ) -# @classmethod -# def from_options(cls, options: NodeOptions): -# """ Node data from class """ -# nstates = options._nstates -# return cls() + def to_dict(self, iteration: int = None) -> Dict: + """ Concert data to dictionary """ + return { + 'states': self.states.to_dict(iteration), + 'derivatives': self.derivatives.to_dict(iteration), + 'output': to_array(self.output.array), + 'external_input': to_array(self.output.array), + } -# class StatesArray(StatesArrayCy): -# """ State array data """ +class NodeStatesArray(DoubleArray2D): + """ State array data """ -# def __init__(self, array: NDARRAY_V2_D, names: List): -# super().__init__(array) -# self.names = names + def __init__(self, array: NDARRAY_V2_D, names: List): + super().__init__(array) + self.names = names -# @classmethod -# def from_options(cls, options: NodeStateOptions): -# """ State options """ + @classmethod + def from_options(cls, options: NodeOptions, buffer_size: int): + """ State options """ + nstates = options._nstates + if nstates > 0: + names = options.state.names + array = np.full( + shape=[buffer_size, nstates], + fill_value=0, + dtype=NPDTYPE, + ) + else: + names = [] + array = np.full( + shape=[buffer_size, 0], + fill_value=0, + dtype=NPDTYPE, + ) + return cls(array=array, names=names) + + def to_dict(self, iteration: int = None) -> Dict: + """ Concert data to dictionary """ + return { + 'names': self.names, + 'array': to_array(self.array) + } + + +class NodeOutputArray(DoubleArray1D): + """ Output array data """ + + def __init__(self, array: NDARRAY_V1_D): + super().__init__(array) + + @classmethod + def from_options(cls, options: NodeOptions, buffer_size: int): + """ State options """ + array = np.full( + shape=buffer_size, + fill_value=0, + dtype=NPDTYPE, + ) + return cls(array=array) + + +class NodeExternalInputArray(DoubleArray1D): + """ ExternalInput array data """ + + def __init__(self, array: NDARRAY_V1_D): + super().__init__(array) + + @classmethod + def from_options(cls, options: NodeOptions, buffer_size: int): + """ State options """ + array = np.full( + shape=buffer_size, + fill_value=0, + dtype=NPDTYPE, + ) + return cls(array=array) def main(): diff --git a/farms_network/core/data_cy.pxd b/farms_network/core/data_cy.pxd index 226b3d6..c970e6c 100644 --- a/farms_network/core/data_cy.pxd +++ b/farms_network/core/data_cy.pxd @@ -33,6 +33,8 @@ cdef class NetworkDataCy: public DoubleArray1D outputs public NetworkConnectivityCy connectivity + public NodeDataCy[:] nodes + public DoubleArray1D states_noise public DoubleArray1D outputs_noise @@ -52,6 +54,16 @@ cdef class NetworkStatesCy(DoubleArray1D): cdef class NetworkConnectivityCy: """ Network connectivity array """ - cdef public UITYPEv1 sources - cdef public DTYPEv1 weights - cdef public UITYPEv1 indices + cdef: + public DTYPEv1 weights + public UITYPEv1 sources + public UITYPEv1 indices + + +cdef class NodeDataCy: + """ Node data """ + cdef: + public DoubleArray2D states + public DoubleArray2D derivatives + public DoubleArray1D output + public DoubleArray1D external_input diff --git a/farms_network/core/data_cy.pyx b/farms_network/core/data_cy.pyx index 3a481c2..72a025b 100644 --- a/farms_network/core/data_cy.pyx +++ b/farms_network/core/data_cy.pyx @@ -17,6 +17,8 @@ limitations under the License. ----------------------------------------------------------------------- """ +from typing import Dict, Iterable, List + cimport numpy as cnp import numpy as np @@ -29,7 +31,7 @@ cdef class NetworkDataCy: """ Network data """ def __init__(self): - """ nodes data initialization """ + """ network data initialization """ super().__init__() @@ -59,3 +61,25 @@ cdef class NetworkConnectivityCy: self.sources = np.array(sources, dtype=np.uintc) self.weights = np.array(weights, dtype=np.double) self.indices = np.array(indices, dtype=np.uintc) + + +############# +# Node Data # +############# +cdef class NodeDataCy: + """ Node data """ + + def __init__( + self, + states: DoubleArray2D, + derivatives: DoubleArray2D, + output: DoubleArray1D, + external_input: DoubleArray1D, + ): + """ nodes data initialization """ + + super().__init__() + self.states = states + self.derivatives = derivatives + self.output = output + self.external_input = external_input From f06d34b9cd443a55d89e4c9a82ea0032241898e2 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Fri, 25 Oct 2024 00:05:29 -0400 Subject: [PATCH 093/316] [DUCKS] Updated core sections and formatting --- ducks/source/conf.py | 2 +- ducks/source/core/index.rst | 6 +- ducks/source/core/node.rst | 22 +++- ducks/source/core/options.rst | 209 ++++++++++++++++++++++++++++-- ducks/source/index.rst | 4 +- ducks/source/models/li_danner.rst | 16 +++ ducks/source/usage/overview.rst | 6 + 7 files changed, 250 insertions(+), 15 deletions(-) diff --git a/ducks/source/conf.py b/ducks/source/conf.py index 7fdf19e..39c57d0 100644 --- a/ducks/source/conf.py +++ b/ducks/source/conf.py @@ -17,7 +17,7 @@ # -- Project information ----------------------------------------------------- -project = 'FARMS_NETWORK' +project = 'FARMS-NETWORK' copyright = '2019, BioRob' author = 'FARMSIM' master_doc = 'index' diff --git a/ducks/source/core/index.rst b/ducks/source/core/index.rst index 375ed08..9e0a2f3 100644 --- a/ducks/source/core/index.rst +++ b/ducks/source/core/index.rst @@ -2,8 +2,10 @@ Core ==== .. toctree:: - :maxdepth: 3 + :maxdepth: 2 :caption: Contents: -.. include:: node.rst .. include:: options.rst +.. include:: node.rst +.. include:: network.rst +.. include:: data.rst diff --git a/ducks/source/core/node.rst b/ducks/source/core/node.rst index 4faa5d6..78978be 100644 --- a/ducks/source/core/node.rst +++ b/ducks/source/core/node.rst @@ -1,12 +1,30 @@ -Neuron ------- +Node Module Documentation +========================== +This documentation describes the Node structure and Python interface provided in the `node.pyx` and `node.pxd` files. + +Contents +-------- + +- Node C Structure +- PyNode Python Class +- Functions (ODE and Output) + +Node C Structure +---------------- + +The Node C structure defines the internal state and behavior of a node in a dynamical system. +It contains generic attributes like state variables, inputs, and parameters. All nodes are many-inputs-single-output (MISO). The simplest case would be a node with one input and one input. A node can have N-states that will be integrated by a numerical integrator over time. A stateless node will have zero states and is useful in using the node as a transfer function. .. automodule:: farms_network.core.node + :platform: Unix, Windows + :synopsis: Provides Node C-Structure and Python interface for nodes in a dynamical system. :members: :show-inheritance: + :private-members: + :special-members: :noindex: diff --git a/ducks/source/core/options.rst b/ducks/source/core/options.rst index f338e31..ab77a63 100644 --- a/ducks/source/core/options.rst +++ b/ducks/source/core/options.rst @@ -1,12 +1,203 @@ -Options -------- +Options Module Documentation +============================ -.. automodule:: farms_network.core.options +This module contains the configuration options for neural network models, including options for nodes, edges, integration, and visualization. + +NetworkOptions Class +-------------------- +.. autoclass:: farms_network.core.options.NetworkOptions + :members: + :undoc-members: + + **Attributes:** + + - **directed** (bool): Whether the network is directed. Default is `True`. + - **multigraph** (bool): Whether the network allows multiple edges between nodes. Default is `False`. + - **graph** (dict): Graph properties (e.g., name). Default is `{"name": ""}`. + - **units** (optional): Units for the network. Default is `None`. + - **integration** (:class:`IntegrationOptions`): Options for numerical integration. Default values shown in the table below. + +IntegrationOptions Class +------------------------ +.. autoclass:: farms_network.core.options.IntegrationOptions + :members: + :undoc-members: + + The default values for `IntegrationOptions` are as follows: + + +------------+-------------------+ + | Parameter | Default Value | + +------------+-------------------+ + | timestep | ``1e-3`` | + +------------+-------------------+ + | integrator | ``"dopri5"`` | + +------------+-------------------+ + | method | ``"adams"`` | + +------------+-------------------+ + | atol | ``1e-12`` | + +------------+-------------------+ + | rtol | ``1e-6`` | + +------------+-------------------+ + | max_step | ``0.0`` | + +------------+-------------------+ + | checks | ``True`` | + +------------+-------------------+ + +NodeOptions Class +----------------- +.. autoclass:: farms_network.core.options.NodeOptions + :members: + :undoc-members: + + **Attributes:** + + - **name** (str): Name of the node. + - **model** (str): Node model type. + - **parameters** (:class:`NodeParameterOptions`): Node-specific parameters. + - **state** (:class:`NodeStateOptions`): Node state options. + +NodeParameterOptions Class +-------------------------- +.. autoclass:: farms_network.core.options.NodeParameterOptions + :members: + :undoc-members: + + The default values for `NodeParameterOptions` are as follows: + + +----------------+----------------+ + | Parameter | Default Value | + +================+================+ + | c_m | ``10.0`` pF | + +----------------+----------------+ + | g_leak | ``2.8`` nS | + +----------------+----------------+ + | e_leak | ``-60.0`` mV | + +----------------+----------------+ + | v_max | ``0.0`` mV | + +----------------+----------------+ + | v_thr | ``-50.0`` mV | + +----------------+----------------+ + | g_syn_e | ``10.0`` nS | + +----------------+----------------+ + | g_syn_i | ``10.0`` nS | + +----------------+----------------+ + | e_syn_e | ``-10.0`` mV | + +----------------+----------------+ + | e_syn_i | ``-75.0`` mV | + +----------------+----------------+ + +NodeStateOptions Class +---------------------- +.. autoclass:: farms_network.core.options.NodeStateOptions :members: - :show-inheritance: - :noindex: + :undoc-members: + + **Attributes:** + + - **initial** (list of float): Initial state values. + - **names** (list of str): State variable names. + +EdgeOptions Class +----------------- +.. autoclass:: farms_network.core.options.EdgeOptions + :members: + :undoc-members: + + **Attributes:** + + - **from_node** (str): Source node of the edge. + - **to_node** (str): Target node of the edge. + - **weight** (float): Weight of the edge. + - **type** (str): Edge type (e.g., excitatory, inhibitory). + +EdgeVisualOptions Class +----------------------- +.. autoclass:: farms_network.core.options.EdgeVisualOptions + :members: + :undoc-members: + + **Attributes:** + + - **color** (list of float): Color of the edge. + - **label** (str): Label for the edge. + - **layer** (str): Layer in which the edge is displayed. + +LIDannerParameterOptions Class +------------------------------ +.. autoclass:: farms_network.core.options.LIDannerParameterOptions + :members: + :undoc-members: + + The default values for `LIDannerParameterOptions` are as follows: + + +----------------+----------------+ + | Parameter | Default Value | + +================+================+ + | c_m | ``10.0`` pF | + +----------------+----------------+ + | g_leak | ``2.8`` nS | + +----------------+----------------+ + | e_leak | ``-60.0`` mV | + +----------------+----------------+ + | v_max | ``0.0`` mV | + +----------------+----------------+ + | v_thr | ``-50.0`` mV | + +----------------+----------------+ + | g_syn_e | ``10.0`` nS | + +----------------+----------------+ + | g_syn_i | ``10.0`` nS | + +----------------+----------------+ + | e_syn_e | ``-10.0`` mV | + +----------------+----------------+ + | e_syn_i | ``-75.0`` mV | + +----------------+----------------+ + +LIDannerNaPParameterOptions Class +--------------------------------- +.. autoclass:: farms_network.core.options.LIDannerNaPParameterOptions + :members: + :undoc-members: + + The default values for `LIDannerNaPParameterOptions` are as follows: -===== ====== -hello simple -world table -===== ====== + +----------------+----------------+ + | Parameter | Default Value | + +================+================+ + | c_m | ``10.0`` pF | + +----------------+----------------+ + | g_nap | ``4.5`` nS | + +----------------+----------------+ + | e_na | ``50.0`` mV | + +----------------+----------------+ + | v1_2_m | ``-40.0`` mV | + +----------------+----------------+ + | k_m | ``-6.0`` | + +----------------+----------------+ + | v1_2_h | ``-45.0`` mV | + +----------------+----------------+ + | k_h | ``4.0`` | + +----------------+----------------+ + | v1_2_t | ``-35.0`` mV | + +----------------+----------------+ + | k_t | ``15.0`` | + +----------------+----------------+ + | g_leak | ``4.5`` nS | + +----------------+----------------+ + | e_leak | ``-62.5`` mV | + +----------------+----------------+ + | tau_0 | ``80.0`` ms | + +----------------+----------------+ + | tau_max | ``160.0`` ms | + +----------------+----------------+ + | v_max | ``0.0`` mV | + +----------------+----------------+ + | v_thr | ``-50.0`` mV | + +----------------+----------------+ + | g_syn_e | ``10.0`` nS | + +----------------+----------------+ + | g_syn_i | ``10.0`` nS | + +----------------+----------------+ + | e_syn_e | ``-10.0`` mV | + +----------------+----------------+ + | e_syn_i | ``-75.0`` mV | + +----------------+----------------+ diff --git a/ducks/source/index.rst b/ducks/source/index.rst index 1372730..c288c78 100644 --- a/ducks/source/index.rst +++ b/ducks/source/index.rst @@ -10,11 +10,13 @@ Welcome to FARMS Network's documentation! farms network provides commonly used neural models for locomotion circuits. +A neural network simulation library designed for simulating neural models such as leaky integrators with efficient computing through Cython and future GPU integration. + .. toctree:: :maxdepth: 1 :caption: Contents: - usage/installation + usage/index core/index models/index diff --git a/ducks/source/models/li_danner.rst b/ducks/source/models/li_danner.rst index e69de29..e7397dc 100644 --- a/ducks/source/models/li_danner.rst +++ b/ducks/source/models/li_danner.rst @@ -0,0 +1,16 @@ +Models +------ + +.. toctree:: + :maxdepth: 1 + :caption: Contents: + +.. automodule:: farms_network.models.li_danner + :members: + :show-inheritance: + :noindex: + +.. automodule:: farms_network.models.li_nap_danner + :members: + :show-inheritance: + :noindex: diff --git a/ducks/source/usage/overview.rst b/ducks/source/usage/overview.rst index e69de29..1df7830 100644 --- a/ducks/source/usage/overview.rst +++ b/ducks/source/usage/overview.rst @@ -0,0 +1,6 @@ +Overview +======== + +.. toctree:: + :maxdepth: 1 + :caption: Contents: From ae8986b9ea3532ebc1f2e3b063fb08a8a7c88c64 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Fri, 25 Oct 2024 00:06:07 -0400 Subject: [PATCH 094/316] [EX] Added log options to ijspeert07 --- examples/ijspeert07/run.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/examples/ijspeert07/run.py b/examples/ijspeert07/run.py index 437fb0c..ead852a 100644 --- a/examples/ijspeert07/run.py +++ b/examples/ijspeert07/run.py @@ -167,6 +167,9 @@ def generate_network(): directed=True, multigraph=False, graph={"name": "ijspeert07"}, + logs=options.NetworkLogOptions( + n_iterations=10000, + ) ) # Generate rhythm centers From 54588e9478901322eb82221f9e5cef3e8b9cf6aa Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Fri, 25 Oct 2024 00:07:07 -0400 Subject: [PATCH 095/316] [EX] Added mouse example file --- examples/mouse/run.py | 1466 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1466 insertions(+) create mode 100644 examples/mouse/run.py diff --git a/examples/mouse/run.py b/examples/mouse/run.py new file mode 100644 index 0000000..3f01f5b --- /dev/null +++ b/examples/mouse/run.py @@ -0,0 +1,1466 @@ +""" Generate and reproduce Zhang, Shevtsova, et al. eLife 2022;11:e73424. DOI: +https://doi.org/10.7554/eLife.73424 paper network """ + +import os +from pprint import pprint +from typing import List, Iterable + +import farms_pylog as pylog +import matplotlib.pyplot as plt +import networkx as nx +import numpy as np +from farms_network.core import options +from farms_network.core.data import NetworkData +from farms_network.core.network import PyNetwork, rk4 +# from farms_network.gui.gui import NetworkGUI +from jinja2 import Environment, FileSystemLoader +from tqdm import tqdm + +plt.rcParams["text.usetex"] = True + + +def join_str(strings): + return "_".join(strings) + + +def multiply_transform(vec: np.ndarray, transform_mat: np.ndarray) -> np.ndarray: + """ + Multiply a 2D vector with a 2D transformation matrix (3x3). + + Parameters: + vec (np.ndarray): A 2D vector (shape (2,) or (3,)) + transform_mat (np.ndarray): A 3x3 transformation matrix. + + Returns: + np.ndarray: The transformed vector. + """ + + assert transform_mat.shape == (3, 3), "Transformation matrix must be 3x3" + + # Ensure vec is in homogeneous coordinates (i.e., 3 elements). + if vec.shape == (2,): + vec = np.append(vec, 1) + elif vec.shape != (3,): + raise ValueError("Input vector must have shape (2,) or (3,)") + + # Perform the multiplication + return transform_mat @ vec + + +def get_scale_matrix(scale: float) -> np.ndarray: + """Return a scaling matrix.""" + return np.array([[scale, 0, 0], [0, scale, 0], [0, 0, 1]]) + + +def get_mirror_matrix(mirror_x: bool, mirror_y: bool) -> np.ndarray: + """Return a mirror matrix based on the mirror flags.""" + mirror_matrix = np.identity(3) + if mirror_x: + mirror_matrix = np.array([[1, 0, 0], [0, -1, 0], [0, 0, 1]]) + if mirror_y: + mirror_matrix = np.array([[-1, 0, 0], [0, 1, 0], [0, 0, 1]]) + return mirror_matrix + + +def get_translation_matrix(off_x: float, off_y: float) -> np.ndarray: + """Return a translation matrix.""" + return np.array([[1, 0, off_x], [0, 1, off_y], [0, 0, 1]]) + + +def get_rotation_matrix(angle: float) -> np.ndarray: + """Return a rotation matrix for the given angle in degrees.""" + angle_rad = np.radians(angle) + return np.array( + [ + [np.cos(angle_rad), -np.sin(angle_rad), 0], + [np.sin(angle_rad), np.cos(angle_rad), 0], + [0, 0, 1], + ] + ) + + +def get_transform_mat( + angle: float, + off_x: float, + off_y: float, + mirror_x: bool = False, + mirror_y: bool = False, + scale: float = 2.5, +) -> np.ndarray: + """Return a complete transformation matrix based on input parameters.""" + scale_matrix = get_scale_matrix(scale) + mirror_matrix = get_mirror_matrix(mirror_x, mirror_y) + translation_matrix = get_translation_matrix(off_x, off_y) + rotation_matrix = get_rotation_matrix(angle) + + # Combine the transformations in the correct order: translation -> rotation -> mirror -> scale + transform_matrix = translation_matrix @ rotation_matrix @ mirror_matrix + transform_matrix = scale_matrix @ transform_matrix + + return transform_matrix + + +def create_node( + base_name: str, + node_id: str, + node_type: str, + position_vec: np.ndarray, + label: str, + color: list, + transform_mat: np.ndarray, + states: dict, + parameters: dict, +) -> options.LIDannerNodeOptions: + """ + Function to create a node with visual and state options. + + Parameters: + base_name (str): The base name to prepend to node_id. + node_id (str): Unique identifier for the node. + position_vec (np.ndarray): The position of the node. + label (str): The visual label for the node. + color (list): RGB color values for the node. + node_type (str): Type of the node ('LINaPDanner' or 'LIDanner'). + transform_mat (np.ndarray): Transformation matrix for positioning. + v0 (float): Initial value for the state option 'v0'. + h0 (float, optional): Initial value for the state option 'h0', only used for some node types. + + Returns: + options.LIDannerNodeOptions: The configured node options object. + """ + # Generate the full name and position + full_name = join_str((base_name, node_id)) + position = multiply_transform(position_vec, transform_mat).tolist() + + # Determine node type and state options + if node_type == "LINaPDanner": + state_options = options.LINaPDannerStateOptions.from_kwargs(**states) + parameters = options.LINaPDannerParameterOptions.defaults(**parameters) + node_options_class = options.LINaPDannerNodeOptions + elif node_type == "LIDanner": + state_options = options.LIDannerStateOptions.from_kwargs(**states) + parameters = options.LIDannerParameterOptions.defaults(**parameters) + node_options_class = options.LIDannerNodeOptions + elif node_type == "Linear": + state_options = None + parameters = options.LinearParameterOptions.defaults(**parameters) + node_options_class = options.LinearNodeOptions + else: + raise ValueError(f"Unknown node type: {node_type}") + + # Create and return the node options + return node_options_class( + name=full_name, + parameters=parameters, + visual=options.NodeVisualOptions(position=position, label=label, color=color), + state=state_options, + ) + + +def create_nodes( + node_specs: Iterable, + base_name: str, + transform_mat: np.ndarray, +) -> options.NodeOptions: + """ Create node using create_method """ + nodes = {} + for ( + node_id, + node_type, + position_vec, + label, + color, + states, + parameters, + ) in node_specs: + nodes[node_id] = create_node( + base_name, + node_id, + node_type, + position_vec, + label, + color, + transform_mat, + states, + parameters, + ) + return nodes + + +def create_edges( + edge_specs: Iterable, + base_name: str, + visual_options: options.EdgeVisualOptions = options.EdgeVisualOptions(), +) -> options.EdgeOptions: + """ Create edges from specs""" + edges = {} + for source_tuple, target_tuple, weight, edge_type in edge_specs: + source = join_str((base_name, *source_tuple)) + target = join_str((base_name, *target_tuple)) + edges[join_str((source, "to", target))] = options.EdgeOptions( + source=source, + target=target, + weight=weight, + type=edge_type, + visual=options.EdgeVisualOptions(**visual_options), + ) + return edges + + +class RhythmGenerator: + """Generate RhythmGenerator Network""" + + def __init__(self, name="", transform_mat=np.identity(3)): + """Initialization.""" + super().__init__() + self.name = name + self.transform_mat = transform_mat + + def nodes(self): + """Add nodes.""" + node_specs = [ + ( + join_str(("RG", "F")), + "LINaPDanner", + np.array((3.0, 0.0)), + "F", + [1.0, 0.0, 0.0], + {"v0": -62.5, "h0": np.random.uniform(0, 1)}, + {}, + ), + ( + join_str(("RG", "E")), + "LINaPDanner", + np.array((-3.0, 0.0)), + "E", + [0.0, 1.0, 0.0], + {"v0": -62.5, "h0": np.random.uniform(0, 1)}, + {}, + ), + ( + join_str(("RG", "In", "F")), + "LIDanner", + np.array((1.0, -1.5)), + "In", + [0.2, 0.2, 0.2], + {"v0": -60.0}, + {}, + ), + ( + join_str(("RG", "In", "E")), + "LIDanner", + np.array((-1.0, 1.5)), + "In", + [0.2, 0.2, 0.2], + {"v0": -60.0}, + {}, + ), + ( + join_str(("RG", "In", "E2")), + "LIDanner", + np.array((-5.0, 1.0)), + "In", + [0.2, 0.2, 0.2], + {"v0": -60.0}, + {}, + ), + ] + + # Loop through the node specs to create each node using the create_node function + nodes = create_nodes(node_specs, self.name, self.transform_mat) + return nodes + + def edges(self): + """Add edges.""" + + # Define edge details in a list for easier iteration + edge_specs = [ + (("RG", "F"), ("RG", "In", "F"), 0.4, "excitatory"), + (("RG", "In", "F"), ("RG", "E"), -1.0, "inhibitory"), + (("RG", "E"), ("RG", "In", "E"), 0.4, "excitatory"), + (("RG", "In", "E"), ("RG", "F"), -0.08, "inhibitory"), + (("RG", "In", "E2"), ("RG", "F"), -0.04, "excitatory"), + ] + + # Loop through the edge specs to create each edge + edges = create_edges(edge_specs, self.name) + return edges + + +class RhythmDrive: + """Generate Drive Network""" + + def __init__(self, name="", transform_mat=np.identity(3)): + """Initialization.""" + self.name = name + self.transform_mat = transform_mat + + def nodes(self): + """Add nodes.""" + nodes = {} + + node_specs = [ + ( + join_str(("RG", "F", "DR")), + "Linear", + np.array((3.0, 2.0)), # Assuming position is not important + "d", + [0.5, 0.5, 0.5], # Default visual color if needed + None, + {"slope": 0.1, "bias": 0.0}, + ), + ( + join_str(("RG", "E", "DR")), + "Linear", + np.array((-3.0, 2.0)), # Assuming position is not important + "d", + [0.5, 0.5, 0.5], # Default visual color if needed + None, + {"slope": 0.0, "bias": 0.1}, + ), + ] + + # Loop through the node specs to create each node using the create_node function + nodes = create_nodes(node_specs, self.name, self.transform_mat) + return nodes + + +class PatternFormation: + """Generate PatternFormation Network""" + + def __init__(self, name="", transform_mat=np.identity(3)): + """Initialization.""" + super().__init__() + self.name = name + self.transform_mat = transform_mat + + def nodes(self): + """Add nodes.""" + nodes = {} + + node_specs = [ + ( + join_str(("PF", "FA")), + "LINaPDanner", + np.array((-3.0, 0.0)), + "F\\textsubscript{A}", + [1.0, 0.0, 0.0], + {"v0": -60.0, "h0": np.random.uniform(0, 1)}, + {"g_nap": 0.125, "e_leak": -67.5}, + ), + ( + join_str(("PF", "EA")), + "LINaPDanner", + np.array((-9.0, 0.0)), + "F\\textsubscript{A}", + [0.0, 1.0, 0.0], + {"v0": -60.0, "h0": np.random.uniform(0, 1)}, + {"g_nap": 0.125, "e_leak": -67.5}, + ), + ( + join_str(("PF", "In", "FA")), + "LIDanner", + np.array((-5.0, -1.5)), + "In\\textsubscript{A}", + [0.2, 0.2, 0.2], + {"v0": -60.0}, + {}, + ), + ( + join_str(("PF", "In", "EA")), + "LIDanner", + np.array((-7.0, 1.5)), + "In\\textsubscript{A}", + [0.2, 0.2, 0.2], + {"v0": -60.0}, + {}, + ), + ( + join_str(("PF", "FB")), + "LINaPDanner", + np.array((9.0, 0.0)), + "F\\textsubscript{A}", + [1.0, 0.0, 0.0], + {"v0": -60.0, "h0": np.random.uniform(0, 1)}, + {"g_nap": 0.125, "e_leak": -67.5}, + ), + ( + join_str(("PF", "EB")), + "LINaPDanner", + np.array((3.0, 0.0)), + "F\\textsubscript{A}", + [0.0, 1.0, 0.0], + {"v0": -60.0, "h0": np.random.uniform(0, 1)}, + {"g_nap": 0.125, "e_leak": -67.5}, + ), + ( + join_str(("PF", "In", "FB")), + "LIDanner", + np.array((7.0, -1.5)), + "In\\textsubscript{B}", + [0.2, 0.2, 0.2], + {"v0": -60.0}, + {}, + ), + ( + join_str(("PF", "In", "EB")), + "LIDanner", + np.array((5.0, 1.5)), + "In\\textsubscript{B}", + [0.2, 0.2, 0.2], + {"v0": -60.0}, + {}, + ), + ( + join_str(("PF", "In2", "F")), + "LIDanner", + np.array((9.0, -3.0)), + "In\\textsubscript{2F}", + [0.2, 0.2, 0.2], + {"v0": -60.0}, + {"g_leak": 5.0}, + ), + ( + join_str(("PF", "In2", "E")), + "LIDanner", + np.array((3.0, -3.0)), + "In\\textsubscript{2E}", + [0.2, 0.2, 0.2], + {"v0": -60.0}, + {"g_leak": 5.0}, + ), + ] + + # Loop through the node specs to create each node using the create_node function + nodes = create_nodes(node_specs, self.name, self.transform_mat) + return nodes + + def edges(self): + """Add edges.""" + edges = {} + + # Define edge details in a list for easier iteration + edge_specs = [ + (("PF", "FA"), ("PF", "In", "FA"), 0.8, "excitatory"), + (("PF", "In", "FA"), ("PF", "EA"), -1.5, "inhibitory"), + (("PF", "EA"), ("PF", "In", "EA"), 1.0, "excitatory"), + (("PF", "In", "EA"), ("PF", "FA"), -1.0, "inhibitory"), + (("PF", "FB"), ("PF", "In", "FB"), 1.5, "excitatory"), + (("PF", "In", "FB"), ("PF", "EB"), -2.0, "inhibitory"), + (("PF", "EB"), ("PF", "In", "EB"), 1.5, "excitatory"), + (("PF", "In", "EB"), ("PF", "FB"), -0.25, "inhibitory"), + (("PF", "In", "FA"), ("PF", "EB"), -0.5, "inhibitory"), + (("PF", "In", "FA"), ("PF", "FB"), -0.1, "inhibitory"), + (("PF", "In", "EA"), ("PF", "EB"), -0.5, "inhibitory"), + (("PF", "In", "EA"), ("PF", "FB"), -0.25, "inhibitory"), + (("PF", "In", "FB"), ("PF", "EA"), -0.5, "inhibitory"), + (("PF", "In", "FB"), ("PF", "FA"), -0.75, "inhibitory"), + (("PF", "In", "EB"), ("PF", "EA"), -2.0, "inhibitory"), + (("PF", "In", "EB"), ("PF", "FA"), -2.0, "inhibitory"), + (("PF", "In2", "F"), ("PF", "FB"), -3.0, "inhibitory"), + (("PF", "In2", "E"), ("PF", "EB"), -3.0, "inhibitory"), + ] + + # Loop through the edge specs to create each edge + edges = create_edges(edge_specs, self.name) + return edges + + +class Commissural: + """Generate Commissural Network""" + + def __init__(self, name="", transform_mat=np.identity(3)): + """Initialization.""" + super().__init__() + self.name = name + self.transform_mat = transform_mat + + def nodes(self): + """Add nodes.""" + node_specs = [ + ( + "V2a", + "LIDanner", + np.array((0.0, 2.0, 1.0)), + "V2\\textsubscript{a}", + [0.0, 1.0, 0.0], + {"v0": -60.0}, + {}, + ), + ( + "InV0V", + "LIDanner", + np.array((0.0, 0.0, 1.0)), + "In\\textsubscript{i}", + [1.0, 0.0, 1.0], + {"v0": -60.0}, + {}, + ), + ( + "V0V", + "LIDanner", + np.array((2.0, 0.5, 1.0)), + "V0\\textsubscript{V}", + [0.0, 1.0, 0.0], + {"v0": -60.0}, + {}, + ), + ( + "V0D", + "LIDanner", + np.array((2.0, -2.0, 1.0)), + "V0\\textsubscript{D}", + [1.0, 0.0, 1.0], + {"v0": -60.0}, + {}, + ), + ( + "V3E", + "LIDanner", + np.array((2.0, 3.0, 1.0)), + "V3\\textsubscript{E}", + [0.0, 1.0, 0.0], + {"v0": -60.0}, + {}, + ), + ( + "V3F", + "LIDanner", + np.array((2.0, -4.0, 1.0)), + "V3\\textsubscript{F}", + [0.0, 1.0, 0.0], + {"v0": -60.0}, + {}, + ), + ] + + # Loop through the node specs to create each node using the create_node function + nodes = create_nodes(node_specs, self.name, self.transform_mat) + return nodes + + +class CommissuralDrive: + """Generate Drive Network""" + + def __init__(self, name="", transform_mat=np.identity(3)): + """Initialization.""" + self.name = name + self.transform_mat = transform_mat + + def nodes(self): + """Add nodes.""" + + node_specs = [ + ( + join_str(("V0V", "DR")), + "Linear", + np.array((0.0, 0.0)), + "d", + [0.5, 0.5, 0.5], + None, + {"slope": 0.15, "bias": 0.0}, + ), + ( + join_str(("V0D", "DR")), + "Linear", + np.array((0.0, 0.0)), + "d", + [0.5, 0.5, 0.5], + None, + {"slope": 0.75, "bias": 0.0}, + ), + ] + + # Loop through the node specs to create each node using the create_node function + nodes = create_nodes(node_specs, self.name, self.transform_mat) + return nodes + + +class LPSN: + """Generate Long Propriospinal Network""" + + def __init__(self, name="", transform_mat=np.identity(3)): + """Initialization.""" + super().__init__() + self.name = name + self.transform_mat = transform_mat + + def nodes(self): + """Add nodes.""" + + # Define node specs in a list for easier iteration + node_specs = [ + ( + join_str(("V0D", "diag")), + "LIDanner", + np.array((0.0, 0.0, 1.0)), + "V0\\textsubscript{D}", + [0.5, 0.0, 0.5], + {"v0": -60.0}, + {}, + ), + ( + join_str(("fore", "V0V", "diag")), + "LIDanner", + np.array((0.0, -1.25, 1.0)), + "V0\\textsubscript{V}", + [0.0, 1.0, 0.0], + {"v0": -60.0}, + {}, + ), + ( + join_str(("hind", "V3", "diag")), + "LIDanner", + np.array((0.0, -4.0, 1.0)), + "V3\\textsubscript{a}", + [0.0, 1.0, 0.0], + {"v0": -60.0}, + {}, + ), + ( + join_str(("fore", "Ini", "hom")), + "LIDanner", + np.array((-4.0, 0.0, 1.0)), + "LPN\\textsubscript{i}", + [0.0, 1.0, 0.0], + {"v0": -60.0}, + {}, + ), + ( + join_str(("fore", "Sh2", "hom")), + "LIDanner", + np.array((-8.0, 0.0, 1.0)), + "Sh\\textsubscript{2}", + [0.0, 1.0, 0.0], + {"v0": -60.0}, + {}, + ), + ( + join_str(("hind", "Sh2", "hom")), + "LIDanner", + np.array((-8.0, -4.0, 1.0)), + "Sh\\textsubscript{2}", + [0.0, 1.0, 0.0], + {"v0": -60.0}, + {}, + ), + ] + + # Loop through the node specs to create each node using the create_node function + nodes = create_nodes(node_specs, self.name, self.transform_mat) + return nodes + + +class LPSNDrive: + """Generate Drive Network""" + + def __init__(self, name="", transform_mat=np.identity(3)): + """Initialization.""" + self.name = name + self.transform_mat = transform_mat + + def nodes(self): + """Add nodes.""" + + node_specs = [ + ( + join_str(("V0D", "diag", "DR")), + "Linear", + np.array((0.0, 0.0)), + "d", + [0.5, 0.5, 0.5], + None, + {"slope": 0.75, "bias": 0.0}, + ), + ] + + # Loop through the node specs to create each node using the create_node function + nodes = create_nodes(node_specs, self.name, self.transform_mat) + return nodes + + +class MotorLayer: + """Motorneurons and other associated interneurons.""" + + def __init__(self, muscles: List[str], name="", transform_mat=np.identity(3)): + """Initialization.""" + self.name = name + self.muscles = muscles + self.transform_mat = transform_mat + + def nodes(self): + """Add neurons for the motor layer.""" + + neuron_specs = [] + + # Define neurons for the muscles + for x_off, muscle in zip(self._generate_positions(len(self.muscles["agonist"])), self.muscles["agonist"]): + neuron_specs.extend(self._get_muscle_neurons(muscle, x_off, 0.0)) + + for x_off, muscle in zip(self._generate_positions(len(self.muscles["antagonist"])), self.muscles["antagonist"]): + neuron_specs.extend(self._get_muscle_neurons(muscle, x_off, 3.5, mirror_y=True)) + + # Loop through the node specs to create each node using the create_node function + nodes = create_nodes(node_specs, self.name, self.transform_mat) + return nodes + + def edges(self): + """Add motor feedback connections.""" + edges = {} + + # Define edges for each muscle + for muscle in self.muscles["agonist"]: + edges.update(self._generate_motor_connections(muscle)) + for muscle in self.muscles["antagonist"]: + edges.update(self._generate_motor_connections(muscle)) + + return edges + + def _generate_motor_connections(self, muscle): + """Generate the motor connections for a specific muscle.""" + edges = {} + + edge_specs = [ + (f"{muscle}_Ia", f"{muscle}_Mn", 0.01, "excitatory", "Ia_monosynaptic_excitation"), + (f"{muscle}_Mn", f"{muscle}_Rn", 0.01, "excitatory", "Rn_reciprocal_inhibition"), + (f"{muscle}_Rn", f"{muscle}_Mn", -0.01, "inhibitory", "Rn_reciprocal_inhibition"), + (f"{muscle}_Ib", f"{muscle}_IbIn_i", 0.01, "excitatory", "Ib_disynaptic_inhibition"), + (f"{muscle}_IbIn_i", f"{muscle}_Mn", -0.01, "inhibitory", "Ib_disynaptic_inhibition"), + ] + + # Loop through the edge specs to create each edge + edges = create_edges(edge_specs, self.name) + return edges + + def _get_muscle_neurons(self, muscle, x_off, y_off, mirror_y=False): + """Return neuron specifications for a muscle.""" + mirror_y_sign = -1 if mirror_y else 1 + + return [ + ( + f"{muscle}_Mn", "LIDanner", (x_off, y_off, 1.0), "Mn", [1.0, 0.0, 1.0], + {"v0": -60.0}, {"e_leak": -52.5, "g_leak": 1.0} + ), + ( + f"{muscle}_Ia", "LIDanner", (x_off - 0.5, y_off + 0.75 * mirror_y_sign, 1.0), "Ia", [1.0, 0.0, 0.0], + {"init": 0.0}, {} + ), + ( + f"{muscle}_II", "LIDanner", (x_off, y_off + 0.75 * mirror_y_sign, 1.0), "II", [1.0, 0.0, 0.0], + {"init": 0.0}, {} + ), + ( + f"{muscle}_Ib", "LIDanner", (x_off + 0.5, y_off + 0.75 * mirror_y_sign, 1.0), "Ib", [1.0, 0.0, 0.0], + {"init": 0.0}, {} + ), + ( + f"{muscle}_Rn", "LIDanner", (x_off + 0.5, y_off - 1.0 * mirror_y_sign, 1.0), "Rn", [1.0, 0.0, 1.0], + {"v0": -60.0}, {} + ), + ( + f"{muscle}_IbIn_i", "LIDanner", (x_off + 1.0, y_off, 1.0), "Ib\\textsubscript{i}", [0.0, 0.0, 1.0], + {"v0": -60.0}, {} + ), + ( + f"{muscle}_IbIn_e", "LIDanner", (x_off + 1.0, y_off + 1.5 * mirror_y_sign, 1.0), "Ib\\textsubscript{e}", [0.0, 0.0, 1.0], + {"v0": -60.0}, {} + ), + ( + f"{muscle}_IIIn_RG", "LIDanner", (x_off - 1.0, y_off, 1.0), "II\\textsubscript{RG}", [0.0, 0.0, 1.0], + {"v0": -60.0}, {} + ), + ] + + def _generate_positions(self, num_muscles): + """Generate positions for the neurons.""" + spacing = 1.25 + return np.linspace(-spacing * num_muscles, spacing * num_muscles, num_muscles) + + +########################## +# CONNECT RG COMMISSURAL # +########################## +def connect_rg_commissural(): + """Connect RG's to Commissural.""" + + edges = {} + for limb in ("hind", "fore"): + for side in ("left", "right"): + + edges[join_str((side, limb, "RG", "F", "to", side, limb, "V2a"))] = ( + options.EdgeOptions( + source=join_str((side, limb, "RG", "F")), + target=join_str((side, limb, "V2a")), + weight=1.0, + type="excitatory", + visual=options.EdgeVisualOptions(), + ) + ) + + # edges[join_str((side, limb, "RG" "F", "to", side, limb, "V2a", "diag"))] = options.EdgeOptions( + # source=join_str((side, limb, "RG" "F")), + # target=join_str((side, limb, "V2a", "diag")), + # weight=0.5, + # type="excitatory", + # visual=options.EdgeVisualOptions(), + + # ) + edges[join_str((side, limb, "RG", "F", "to", side, limb, "V0D"))] = ( + options.EdgeOptions( + source=join_str((side, limb, "RG", "F")), + target=join_str((side, limb, "V0D")), + weight=0.7, + type="excitatory", + visual=options.EdgeVisualOptions(), + ) + ) + + edges[join_str((side, limb, "RG", "F", "to", side, limb, "V3F"))] = ( + options.EdgeOptions( + source=join_str((side, limb, "RG", "F")), + target=join_str((side, limb, "V3F")), + weight=0.35, + type="excitatory", + visual=options.EdgeVisualOptions(), + ) + ) + + edges[join_str((side, limb, "RG", "E", "to", side, limb, "V3E"))] = ( + options.EdgeOptions( + source=join_str((side, limb, "RG", "E")), + target=join_str((side, limb, "V3E")), + weight=0.35, + type="excitatory", + visual=options.EdgeVisualOptions(), + ) + ) + + edges[join_str((side, limb, "V2a", "to", side, limb, "V0V"))] = ( + options.EdgeOptions( + source=join_str((side, limb, "V2a")), + target=join_str((side, limb, "V0V")), + weight=1.0, + type="excitatory", + visual=options.EdgeVisualOptions(), + ) + ) + + edges[join_str((side, limb, "InV0V", "to", side, limb, "RG", "F"))] = ( + options.EdgeOptions( + source=join_str((side, limb, "InV0V")), + target=join_str((side, limb, "RG", "F")), + weight=-0.07, + type="inhibitory", + visual=options.EdgeVisualOptions(), + ) + ) + + for sides in (("left", "right"), ("right", "left")): + + edges[join_str((sides[0], limb, "V0V", "to", sides[1], limb, "InV0V"))] = ( + options.EdgeOptions( + source=join_str((sides[0], limb, "V0V")), + target=join_str((sides[1], limb, "InV0V")), + weight=0.6, + type="excitatory", + visual=options.EdgeVisualOptions(), + ) + ) + + edges[ + join_str((sides[0], limb, "V0D", "to", sides[1], limb, "RG", "F")) + ] = options.EdgeOptions( + source=join_str((sides[0], limb, "V0D")), + target=join_str((sides[1], limb, "RG", "F")), + weight=-0.07, + type="inhibitory", + visual=options.EdgeVisualOptions(), + ) + + edges[ + join_str((sides[0], limb, "V3F", "to", sides[1], limb, "RG", "F")) + ] = options.EdgeOptions( + source=join_str((sides[0], limb, "V3F")), + target=join_str((sides[1], limb, "RG", "F")), + weight=0.03, + type="excitatory", + visual=options.EdgeVisualOptions(), + ) + + edges[ + join_str((sides[0], limb, "V3E", "to", sides[1], limb, "RG", "E")) + ] = options.EdgeOptions( + source=join_str((sides[0], limb, "V3E")), + target=join_str((sides[1], limb, "RG", "E")), + weight=0.02, + type="excitatory", + visual=options.EdgeVisualOptions(), + ) + + edges[ + join_str((sides[0], limb, "V3E", "to", sides[1], limb, "RG", "In", "E")) + ] = options.EdgeOptions( + source=join_str((sides[0], limb, "V3E")), + target=join_str((sides[1], limb, "RG", "In", "E")), + weight=0.0, + type="excitatory", + visual=options.EdgeVisualOptions(), + ) + + edges[ + join_str( + (sides[0], limb, "V3E", "to", sides[1], limb, "RG", "In", "E2") + ) + ] = options.EdgeOptions( + source=join_str((sides[0], limb, "V3E")), + target=join_str((sides[1], limb, "RG", "In", "E2")), + weight=0.8, + type="excitatory", + visual=options.EdgeVisualOptions(), + ) + return edges + + +def connect_fore_hind_circuits(): + """Connect CPG's to Interneurons.""" + + edges = {} + for side in ("left", "right"): + edges[join_str((side, "fore", "RG", "F", "to", side, "fore", "Ini", "hom"))] = ( + options.EdgeOptions( + source=join_str((side, "fore", "RG", "F")), + target=join_str((side, "fore", "Ini", "hom")), + weight=0.70, + type="excitatory", + visual=options.EdgeVisualOptions(), + ) + ) + + edges[join_str((side, "fore", "RG", "F", "to", side, "V0D", "diag"))] = ( + options.EdgeOptions( + source=join_str((side, "fore", "RG", "F")), + target=join_str((side, "V0D", "diag")), + weight=0.50, + type="excitatory", + visual=options.EdgeVisualOptions(), + ) + ) + + edges[join_str((side, "fore", "Ini", "hom", "to", side, "hind", "RG", "F"))] = ( + options.EdgeOptions( + source=join_str((side, "fore", "Ini", "hom")), + target=join_str((side, "hind", "RG", "F")), + weight=-0.01, + type="excitatory", + visual=options.EdgeVisualOptions(), + ) + ) + + edges[join_str((side, "fore", "Sh2", "hom", "to", side, "hind", "RG", "F"))] = ( + options.EdgeOptions( + source=join_str((side, "fore", "Sh2", "hom")), + target=join_str((side, "hind", "RG", "F")), + weight=0.01, + type="excitatory", + visual=options.EdgeVisualOptions(), + ) + ) + + edges[join_str((side, "hind", "Sh2", "hom", "to", side, "fore", "RG", "F"))] = ( + options.EdgeOptions( + source=join_str((side, "hind", "Sh2", "hom")), + target=join_str((side, "fore", "RG", "F")), + weight=0.05, + type="excitatory", + visual=options.EdgeVisualOptions(), + ) + ) + + edges[ + join_str((side, "fore", "RG", "F", "to", side, "fore", "V0V", "diag")) + ] = options.EdgeOptions( + source=join_str((side, "fore", "RG", "F")), + target=join_str((side, "fore", "V0V", "diag")), + weight=0.325, + type="excitatory", + visual=options.EdgeVisualOptions(), + ) + + edges[join_str((side, "hind", "RG", "F", "to", side, "hind", "V3", "diag"))] = ( + options.EdgeOptions( + source=join_str((side, "hind", "RG", "F")), + target=join_str((side, "hind", "V3", "diag")), + weight=0.325, + type="excitatory", + visual=options.EdgeVisualOptions(), + ) + ) + + # edges[join_str((side, "fore", "V2a-diag", "to", side, "fore", "V0V", "diag"))] = options.EdgeOptions( + # source=join_str((side, "fore", "V2a-diag")), + # target=join_str((side, "fore", "V0V", "diag")), + # weight=0.9, + # type="excitatory", + # visual=options.EdgeVisualOptions(), + # ) + + # edges[join_str((side, "hind", "V2a-diag", "to", side, "hind", "V3", "diag"))] = options.EdgeOptions( + # source=join_str((side, "hind", "V2a-diag")), + # target=join_str((side, "hind", "V3", "diag")), + # weight=0.9, + # type="excitatory", + # visual=options.EdgeVisualOptions(), + # ) + + for sides in (("left", "right"), ("right", "left")): + edges[ + join_str((sides[0], "V0D", "diag", "to", sides[1], "hind", "RG", "F")) + ] = options.EdgeOptions( + source=join_str((sides[0], "V0D", "diag")), + target=join_str((sides[1], "hind", "RG", "F")), + weight=-0.01, + type="inhibitory", + visual=options.EdgeVisualOptions(), + ) + + edges[ + join_str( + (sides[0], "fore", "V0V", "diag", "to", sides[1], "hind", "RG", "F") + ) + ] = options.EdgeOptions( + source=join_str((sides[0], "fore", "V0V", "diag")), + target=join_str((sides[1], "hind", "RG", "F")), + weight=0.005, + type="excitatory", + visual=options.EdgeVisualOptions(), + ) + + edges[ + join_str( + (sides[0], "hind", "V3", "diag", "to", sides[1], "fore", "RG", "F") + ) + ] = options.EdgeOptions( + source=join_str((sides[0], "hind", "V3", "diag")), + target=join_str((sides[1], "fore", "RG", "F")), + weight=0.04, + type="excitatory", + visual=options.EdgeVisualOptions(), + ) + + return edges + + +def generate_network(): + """Generate network""" + + N_ITERATIONS = int(1e4) + + # Main network + network_options = options.NetworkOptions( + directed=True, + multigraph=False, + graph={"name": "mouse"}, + logs=options.NetworkLogOptions( + n_iterations=N_ITERATIONS, + ) + ) + + # Generate rhythm centers + scale = 1.0 + for side in ("left", "right"): + for limb in ("fore", "hind"): + # Rhythm + rg_x, rg_y = 10.0, 7.5 + off_x = -rg_x if side == "left" else rg_x + off_y = rg_y if limb == "fore" else -rg_y + mirror_x = limb == "hind" + mirror_y = side == "right" + rhythm = RhythmGenerator( + name=join_str((side, limb)), + transform_mat=get_transform_mat( + angle=0, + off_x=off_x, + off_y=off_y, + mirror_x=mirror_x, + mirror_y=mirror_y, + ), + ) + network_options.add_nodes((rhythm.nodes()).values()) + network_options.add_edges((rhythm.edges()).values()) + # Rhtyhm Drive + rhythm_drive = RhythmDrive( + name=join_str((side, limb)), + transform_mat=get_transform_mat( + angle=0, + off_x=off_x, + off_y=off_y, + mirror_x=mirror_x, + mirror_y=mirror_y, + ), + ) + network_options.add_nodes((rhythm_drive.nodes()).values()) + # Pattern + pf_x, pf_y = rg_x + 0.0, rg_y + 7.5 + off_x = -pf_x if side == "left" else pf_x + off_y = pf_y if limb == "fore" else -pf_y + mirror_x = limb == "hind" + mirror_y = side == "right" + pattern = PatternFormation( + name=join_str((side, limb)), + transform_mat=get_transform_mat( + angle=0, + off_x=off_x, + off_y=off_y, + mirror_x=mirror_x, + mirror_y=mirror_y, + ), + ) + network_options.add_nodes((pattern.nodes()).values()) + network_options.add_edges((pattern.edges()).values()) + + # Commissural + comm_x, comm_y = rg_x - 7.0, rg_y + 0.0 + off_x = -comm_x if side == "left" else comm_x + off_y = comm_y if limb == "fore" else -comm_y + mirror_x = limb == "hind" + mirror_y = side == "right" + commissural = Commissural( + name=join_str((side, limb)), + transform_mat=get_transform_mat( + angle=0, + off_x=off_x, + off_y=off_y, + mirror_x=mirror_x, + mirror_y=mirror_y, + ), + ) + network_options.add_nodes((commissural.nodes()).values()) + # Drive + commissural_drive = CommissuralDrive( + name=join_str((side, limb)), + transform_mat=get_transform_mat( + angle=0, + off_x=off_x, + off_y=off_y, + mirror_x=mirror_x, + mirror_y=mirror_y, + ), + ) + network_options.add_nodes((commissural_drive.nodes()).values()) + # LPSN + lpsn_x = rg_x - 9.0 + lpsn_y = rg_y - 5.5 + off_x = -lpsn_x if side == "left" else lpsn_x + off_y = lpsn_y + mirror_y = side == "right" + lpsn = LPSN( + name=side, + transform_mat=get_transform_mat( + angle=0, + off_x=off_x, + off_y=off_y, + mirror_y=mirror_y, + ), + ) + network_options.add_nodes((lpsn.nodes()).values()) + lpsn_drive = LPSNDrive( + name=join_str((side,)), + transform_mat=get_transform_mat( + angle=0, + off_x=off_x, + off_y=off_y, + mirror_x=mirror_x, + mirror_y=mirror_y, + ), + ) + network_options.add_nodes((lpsn_drive.nodes()).values()) + + ################################# + # Connect rhythm to commissural # + ################################# + rg_commissural_edges = connect_rg_commissural() + network_options.add_edges(rg_commissural_edges.values()) + + ############################## + # Connect fore and hind lpsn # + ############################## + fore_hind_edges = connect_fore_hind_circuits() + network_options.add_edges(fore_hind_edges.values()) + + for side in ("left", "right"): + for limb in ("fore", "hind"): + network_options.add_edge( + options.EdgeOptions( + source=join_str((side, limb, "RG", "F", "DR")), + target=join_str((side, limb, "RG", "F")), + weight=1.0, + type="excitatory", + visual=options.EdgeVisualOptions(), + ) + ) + network_options.add_edge( + options.EdgeOptions( + source=join_str((side, limb, "RG", "E", "DR")), + target=join_str((side, limb, "RG", "E")), + weight=1.0, + type="excitatory", + visual=options.EdgeVisualOptions(), + ) + ) + network_options.add_edge( + options.EdgeOptions( + source=join_str((side, limb, "V0V", "DR")), + target=join_str((side, limb, "V0V")), + weight=-1.0, + type="inhibitory", + visual=options.EdgeVisualOptions(), + ) + ) + network_options.add_edge( + options.EdgeOptions( + source=join_str((side, limb, "V0D", "DR")), + target=join_str((side, limb, "V0D")), + weight=-1.0, + type="inhibitory", + visual=options.EdgeVisualOptions(), + ) + ) + network_options.add_edge( + options.EdgeOptions( + source=join_str((side, "V0D", "diag", "DR")), + target=join_str((side, "V0D", "diag")), + weight=-1.0, + type="inhibitory", + visual=options.EdgeVisualOptions(), + ) + ) + + data = NetworkData.from_options(network_options) + + network = PyNetwork.from_options(network_options) + + # nnodes = len(network_options.nodes) + # integrator.set_initial_value(np.zeros(len(data.states.array),), 0.0) + + # print("Data ------------", np.array(network.data.states.array)) + + # data.to_file("/tmp/sim.hdf5") + + # integrator.integrate(integrator.t + 1e-3) + + # # Integrate + states = np.ones((len(data.states.array),)) * 1.0 + + # network_gui = NetworkGUI(data=data) + # network_gui.run() + + # for index, node in enumerate(network_options.nodes): + # print(index, node.name) + for iteration in tqdm(range(0, N_ITERATIONS), colour="green", ascii=" >="): + network.data.external_inputs.array[:] = ( + np.ones((1,)) * (iteration / N_ITERATIONS) * 1.0 + ) + states = rk4(iteration * 1e-3, states, network.ode, step_size=1) + network.logging(iteration) + + network.data.to_file("/tmp/network.h5") + + plt.figure() + plt.fill_between( + np.linspace(0.0, N_ITERATIONS * 1e-3, N_ITERATIONS), + np.array(network.data.nodes[15].output.array), + alpha=0.2, + lw=1.0, + ) + plt.plot( + np.linspace(0.0, N_ITERATIONS * 1e-3, N_ITERATIONS), + np.array(network.data.nodes[15].output.array), + label="RG-F" + ) + plt.fill_between( + np.linspace(0.0, N_ITERATIONS * 1e-3, N_ITERATIONS), + np.array(network.data.nodes[1].output.array), + alpha=0.2, + lw=1.0, + ) + plt.plot( + np.linspace(0.0, N_ITERATIONS * 1e-3, N_ITERATIONS), + np.array(network.data.nodes[1].output.array), + label="RG-E" + ) + plt.legend() + + network_options.save("/tmp/netwok_options.yaml") + + graph = nx.node_link_graph( + network_options, + directed=True, + multigraph=False, + link="edges", + name="name", + source="source", + target="target", + ) + + plt.figure() + pos_circular = nx.circular_layout(graph) + pos_spring = nx.spring_layout(graph) + pos_graphviz = nx.nx_agraph.pygraphviz_layout(graph) + + _ = nx.draw_networkx_nodes( + graph, + pos={ + node: data["visual"]["position"][:2] for node, data in graph.nodes.items() + }, + node_color=[data["visual"]["color"] for node, data in graph.nodes.items()], + alpha=0.25, + edgecolors="k", + linewidths=2.0, + ) + nx.draw_networkx_labels( + graph, + pos={ + node: data["visual"]["position"][:2] for node, data in graph.nodes.items() + }, + labels={node: data["visual"]["label"] for node, data in graph.nodes.items()}, + font_size=11.0, + font_weight="bold", + font_family="sans-serif", + alpha=1.0, + ) + nx.draw_networkx_edges( + graph, + pos={ + node: data["visual"]["position"][:2] for node, data in graph.nodes.items() + }, + edge_color=[ + [0.3, 1.0, 0.3] + if data["type"] == "excitatory" + else [0.7, 0.3, 0.3] + for edge, data in graph.edges.items() + ], + width=1.0, + arrowsize=10, + style="dashed", + arrows=True, + min_source_margin=5, + min_target_margin=5, + connectionstyle=[ + data["visual"]["connectionstyle"] + for edge, data in graph.edges.items() + ], + ) + plt.show() + + # generate_tikz_figure( + # graph, + # paths.get_project_data_path().joinpath("templates", "network",), + # "tikz-full-network.tex", + # paths.get_project_images_path().joinpath("quadruped_network.tex") + # ) + + +def generate_tikz_figure( + network, template_env, template_name, export_path, add_axis=False, add_label=False +): + """Generate tikz network""" + + ########################################## + # Remove isolated nodes from the network # + ########################################## + network.remove_nodes_from(list(nx.isolates(network))) + + node_options = { + name: node.get("neuron_class", "interneuron") + for name, node in network.nodes.items() + } + + options = { + "flexor": "flexor-edge", + "extensor": "extensor-edge", + "excitatory": "excitatory-edge", + "inhibitory": "inhibitory-edge", + "interneuron": "inhibitory-edge", + } + edge_options = { + edge: "{}, opacity={}".format( + options.get( + network.nodes[edge[0]].get("neuron_class", "interneuron"), + "undefined-edge", + ), + # max(min(abs(data["weight"]), 1.0), 0.5) + 1.0, + ) + for edge, data in network.edges.items() + } + + raw_latex = nx.to_latex_raw( + network, + pos={name: (node["x"], node["y"]) for name, node in network.nodes.items()}, + node_options=node_options, + # default_node_options="my-node", + node_label={name: node["label"] for name, node in network.nodes.items()}, + # edge_label={ + # name: np.round(edge['weight'], decimals=2) + # for name, edge in network.edges.items() + # }, + edge_label_options={ + name: "fill=white, font={\\tiny}, opacity=1.0" + for name, edge in network.edges.items() + }, + default_edge_options=( + "[color=black, ultra thick, -{Latex[scale=1.0]}, on background layer, opacity=1.0,]" # auto=mid + ), + edge_options=edge_options, + ) + + # Render the network + rhythm_groups = defaultdict(list) + pattern_groups = defaultdict(list) + commissural_groups = defaultdict(list) + lpsn_groups = defaultdict(list) + muscle_sensors_groups = defaultdict(list) + for name, node in network.nodes.items(): + if node["neuron_class"] == "sensory": + muscle_sensors_groups[node["neuron_group"]].append(name) + if node.get("neuron_group") == "rhythm": + rhythm_groups[node["layer"]].append(name) + if node.get("neuron_group") == "pattern": + pattern_groups[node["layer"]].append(name) + if node.get("neuron_group") == "commissural": + commissural_groups[node["layer"]].append(name) + if node.get("neuron_group") == "LPSN": + lpsn_groups[node["layer"]].append(name) + + environment = Environment(loader=FileSystemLoader(template_env)) + template = environment.get_template(template_name) + content = template.render( + network="\n".join(raw_latex.split("\n")[2:-2]), + rhythm_groups=list(rhythm_groups.values()), + pattern_groups=list(pattern_groups.values()), + commissural_groups=list(commissural_groups.values()), + lpsn_groups=list(lpsn_groups.values()), + muscle_sensors_groups=list(muscle_sensors_groups.values()), + add_axis=add_axis, + add_legend=add_label, + ) + with open(export_path, mode="w", encoding="utf-8") as message: + message.write(content) + + result = os.system( + f"pdflatex --shell-escape -output-directory={str(Path(export_path).parents[0])} {export_path}" + ) + + +def main(): + """Main.""" + + # Generate the network + generate_network() + + # Run the network + # run_network() + + +if __name__ == "__main__": + main() From bd2dcb4915e171ab41ff03592737a78e58944b31 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Fri, 25 Oct 2024 00:07:56 -0400 Subject: [PATCH 096/316] [MAIN] Added requirements file --- requirements.txt | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 requirements.txt diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..e69de29 From 2a53d5f20828efada80db3f5db06e21273d100be Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Fri, 25 Oct 2024 10:41:36 -0400 Subject: [PATCH 097/316] [DUCKS] Reorganized index file contents for better sidebar --- ducks/source/core/index.rst | 9 +++------ ducks/source/core/node.rst | 4 ++-- ducks/source/core/options.rst | 8 ++++---- ducks/source/index.rst | 9 ++++----- ducks/source/models/index.rst | 5 ++--- ducks/source/usage/overview.rst | 4 ---- 6 files changed, 15 insertions(+), 24 deletions(-) diff --git a/ducks/source/core/index.rst b/ducks/source/core/index.rst index 9e0a2f3..16d5d4b 100644 --- a/ducks/source/core/index.rst +++ b/ducks/source/core/index.rst @@ -2,10 +2,7 @@ Core ==== .. toctree:: - :maxdepth: 2 - :caption: Contents: + :hidden: -.. include:: options.rst -.. include:: node.rst -.. include:: network.rst -.. include:: data.rst + options + node diff --git a/ducks/source/core/node.rst b/ducks/source/core/node.rst index 78978be..891bc00 100644 --- a/ducks/source/core/node.rst +++ b/ducks/source/core/node.rst @@ -1,5 +1,5 @@ -Node Module Documentation -========================== +Node +==== This documentation describes the Node structure and Python interface provided in the `node.pyx` and `node.pxd` files. diff --git a/ducks/source/core/options.rst b/ducks/source/core/options.rst index ab77a63..09d2a61 100644 --- a/ducks/source/core/options.rst +++ b/ducks/source/core/options.rst @@ -1,5 +1,5 @@ -Options Module Documentation -============================ +Options +======= This module contains the configuration options for neural network models, including options for nodes, edges, integration, and visualization. @@ -152,9 +152,9 @@ LIDannerParameterOptions Class | e_syn_i | ``-75.0`` mV | +----------------+----------------+ -LIDannerNaPParameterOptions Class +LINaPDannerParameterOptions Class --------------------------------- -.. autoclass:: farms_network.core.options.LIDannerNaPParameterOptions +.. autoclass:: farms_network.core.options.LINaPDannerParameterOptions :members: :undoc-members: diff --git a/ducks/source/index.rst b/ducks/source/index.rst index c288c78..e76284b 100644 --- a/ducks/source/index.rst +++ b/ducks/source/index.rst @@ -13,12 +13,11 @@ farms network provides commonly used neural models for locomotion circuits. A neural network simulation library designed for simulating neural models such as leaky integrators with efficient computing through Cython and future GPU integration. .. toctree:: - :maxdepth: 1 - :caption: Contents: + :hidden: - usage/index - core/index - models/index + usage/index.rst + core/index.rst + models/index.rst .. sidebar-links:: :github: diff --git a/ducks/source/models/index.rst b/ducks/source/models/index.rst index 787bb2c..1d56d9d 100644 --- a/ducks/source/models/index.rst +++ b/ducks/source/models/index.rst @@ -2,7 +2,6 @@ Models ====== .. toctree:: - :maxdepth: 3 - :caption: Contents: + :hidden: -.. include:: li_danner.rst + li_danner diff --git a/ducks/source/usage/overview.rst b/ducks/source/usage/overview.rst index 1df7830..802d341 100644 --- a/ducks/source/usage/overview.rst +++ b/ducks/source/usage/overview.rst @@ -1,6 +1,2 @@ Overview ======== - -.. toctree:: - :maxdepth: 1 - :caption: Contents: From a3c6d6c27dae22cde1fd11d14ee6ff8677b0bc8a Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Mon, 28 Oct 2024 15:48:28 -0400 Subject: [PATCH 098/316] [MAIN][CORE] Added external relay node model --- farms_network/core/options.py | 41 +++++++++++++++++-- farms_network/models/external_relay.pxd | 41 +++++++++++++++++++ farms_network/models/external_relay.pyx | 54 +++++++++++++++++++++++++ farms_network/models/factory.py | 3 +- setup.py | 7 ++++ 5 files changed, 142 insertions(+), 4 deletions(-) create mode 100644 farms_network/models/external_relay.pxd create mode 100644 farms_network/models/external_relay.pyx diff --git a/farms_network/core/options.py b/farms_network/core/options.py index 4f26dab..e9ffeb9 100644 --- a/farms_network/core/options.py +++ b/farms_network/core/options.py @@ -82,7 +82,7 @@ def __init__(self, **kwargs): super().__init__() self.timestep: float = kwargs.pop("timestep") - self.n_interations: float = kwargs.pop("n_iterations") + self.n_iterations: int = int(kwargs.pop("n_iterations")) self.integrator: str = kwargs.pop("integrator") self.method: str = kwargs.pop("method") self.atol: float = kwargs.pop("atol") @@ -99,7 +99,7 @@ def defaults(cls, **kwargs): options = {} options["timestep"] = kwargs.pop("timestep", 1e-3) - options["n_iterations"] = kwargs.pop("n_interations", 1e3) + options["n_iterations"] = int(kwargs.pop("n_iterations", 1e3)) options["integrator"] = kwargs.pop("integrator", "dopri5") options["method"] = kwargs.pop("method", "adams") options["atol"] = kwargs.pop("atol", 1e-12) @@ -113,7 +113,15 @@ def defaults(cls, **kwargs): # Logging Options # ################### class NetworkLogOptions(Options): - """ Log options for the network level """ + """ Log options for the network level + + Configure logging for network events and iterations. + + Attributes: + n_iterations (int): Number of iterations to log. + buffer_size (int): Size of the log buffer. Defaults to n_iterations if 0. + nodes_all (bool): Whether to log all nodes or only selected ones. Defaults to False. + """ def __init__(self, n_iterations: int, **kwargs): super().__init__(**kwargs) @@ -273,6 +281,33 @@ def __init__(self, **kwargs): raise Exception(f'Unknown kwargs: {kwargs}') +################################ +# External Relay Model Options # +################################ +class ExternalRelayNodeOptions(NodeOptions): + """ Class to define the properties of ExternalRelay node model + + # TODO: Remove parameters from options + + """ + + def __init__(self, **kwargs): + """ Initialize """ + model = "external_relay" + super().__init__( + name=kwargs.pop("name"), + model=model, + parameters=kwargs.pop("parameters"), + visual=kwargs.pop("visual"), + state=kwargs.pop("state", None), + ) + self._nstates = 0 + self._nparameters = 0 + + if kwargs: + raise Exception(f'Unknown kwargs: {kwargs}') + + ######################## # Linear Model Options # ######################## diff --git a/farms_network/models/external_relay.pxd b/farms_network/models/external_relay.pxd new file mode 100644 index 0000000..34f39b5 --- /dev/null +++ b/farms_network/models/external_relay.pxd @@ -0,0 +1,41 @@ +""" +----------------------------------------------------------------------- +Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty +Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +----------------------------------------------------------------------- + +Oscillator model +""" + + +from ..core.node cimport Node, PyNode +from ..core.edge cimport Edge + + +cdef: + double output( + double time, + double* states, + double external_input, + double* network_outputs, + unsigned int* inputs, + double* weights, + Node* node, + Edge** edges, + ) noexcept + + +cdef class PyExternalRelayNode(PyNode): + """ Python interface to External Relay Node C-Structure """ diff --git a/farms_network/models/external_relay.pyx b/farms_network/models/external_relay.pyx new file mode 100644 index 0000000..e5a8d2b --- /dev/null +++ b/farms_network/models/external_relay.pyx @@ -0,0 +1,54 @@ +""" +----------------------------------------------------------------------- +Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty +Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +----------------------------------------------------------------------- + +External model +""" + +from libc.stdio cimport printf +from libc.stdlib cimport free, malloc +from libc.string cimport strdup + + +cdef double output( + double time, + double* states, + double external_input, + double* network_outputs, + unsigned int* inputs, + double* weights, + Node* node, + Edge** edges, +) noexcept: + """ Node output. """ + return external_input + + +cdef class PyExternalRelayNode(PyNode): + """ Python interface to External Relay Node C-Structure """ + + def __cinit__(self): + self.node.model_type = strdup("EXTERNAL_RELAY".encode('UTF-8')) + # override default ode and out methods + self.node.is_statefull = False + self.node.output = output + + def __init__(self, name: str, **kwargs): + super().__init__(name) + + if kwargs: + raise Exception(f'Unknown kwargs: {kwargs}') diff --git a/farms_network/models/factory.py b/farms_network/models/factory.py index 54b2151..477d41f 100644 --- a/farms_network/models/factory.py +++ b/farms_network/models/factory.py @@ -24,6 +24,7 @@ # from farms_network.models.hh_daun_motoneuron import HHDaunMotoneuron # from farms_network.models.hopf_oscillator import HopfOscillator # from farms_network.models.leaky_integrator import LeakyIntegrator +from farms_network.models.external_relay import PyExternalRelayNode from farms_network.models.linear import PyLinearNode from farms_network.models.li_danner import PyLIDannerNode from farms_network.models.li_nap_danner import PyLINaPDannerNode @@ -45,7 +46,7 @@ class NodeFactory: # 'hopf_oscillator': HopfOscillator, # 'morphed_oscillator': MorphedOscillator, # 'leaky': LeakyIntegrator, - # 'sensory': SensoryNode, + 'external_relay': PyExternalRelayNode, 'linear': PyLinearNode, 'li_nap_danner': PyLINaPDannerNode, 'li_danner': PyLIDannerNode, diff --git a/setup.py b/setup.py index edae37f..d885b15 100644 --- a/setup.py +++ b/setup.py @@ -106,6 +106,13 @@ extra_compile_args=['-ffast-math', '-O3'], extra_link_args=['-O3', '-lm'] ), + Extension( + "farms_network.models.external_relay", + ["farms_network/models/external_relay.pyx"], + include_dirs=[numpy.get_include()], + extra_compile_args=['-ffast-math', '-O3'], + extra_link_args=['-O3', '-lm'] + ), ] setup( From 50f5f9a5dfbf83c81f9c563ca858aae1aa8932f8 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Mon, 28 Oct 2024 15:49:04 -0400 Subject: [PATCH 099/316] [EX][MOUSE] Separated generation and run into components module --- examples/mouse/components.py | 1132 ++++++++++++++++++++++++++++++++++ 1 file changed, 1132 insertions(+) create mode 100644 examples/mouse/components.py diff --git a/examples/mouse/components.py b/examples/mouse/components.py new file mode 100644 index 0000000..3c4594e --- /dev/null +++ b/examples/mouse/components.py @@ -0,0 +1,1132 @@ +""" Components """ + +import os +from pprint import pprint +from typing import Iterable, List + +import farms_pylog as pylog +import matplotlib.pyplot as plt +import networkx as nx +import numpy as np +from farms_network.core import options +from farms_network.core.data import NetworkData +from farms_network.core.network import PyNetwork, rk4 + +# from farms_network.gui.gui import NetworkGUI +from jinja2 import Environment, FileSystemLoader +from tqdm import tqdm + +plt.rcParams["text.usetex"] = True + + +def join_str(strings): + return "_".join(strings) + + +def multiply_transform(vec: np.ndarray, transform_mat: np.ndarray) -> np.ndarray: + """ + Multiply a 2D vector with a 2D transformation matrix (3x3). + + Parameters: + vec (np.ndarray): A 2D vector (shape (2,) or (3,)) + transform_mat (np.ndarray): A 3x3 transformation matrix. + + Returns: + np.ndarray: The transformed vector. + """ + + assert transform_mat.shape == (3, 3), "Transformation matrix must be 3x3" + + # Ensure vec is in homogeneous coordinates (i.e., 3 elements). + if vec.shape == (2,): + vec = np.append(vec, 1) + elif vec.shape != (3,): + raise ValueError("Input vector must have shape (2,) or (3,)") + + # Perform the multiplication + return transform_mat @ vec + + +def get_scale_matrix(scale: float) -> np.ndarray: + """Return a scaling matrix.""" + return np.array([[scale, 0, 0], [0, scale, 0], [0, 0, 1]]) + + +def get_mirror_matrix(mirror_x: bool, mirror_y: bool) -> np.ndarray: + """Return a mirror matrix based on the mirror flags.""" + mirror_matrix = np.identity(3) + if mirror_x: + mirror_matrix = np.array([[1, 0, 0], [0, -1, 0], [0, 0, 1]]) + if mirror_y: + mirror_matrix = np.array([[-1, 0, 0], [0, 1, 0], [0, 0, 1]]) + return mirror_matrix + + +def get_translation_matrix(off_x: float, off_y: float) -> np.ndarray: + """Return a translation matrix.""" + return np.array([[1, 0, off_x], [0, 1, off_y], [0, 0, 1]]) + + +def get_rotation_matrix(angle: float) -> np.ndarray: + """Return a rotation matrix for the given angle in degrees.""" + angle_rad = np.radians(angle) + return np.array( + [ + [np.cos(angle_rad), -np.sin(angle_rad), 0], + [np.sin(angle_rad), np.cos(angle_rad), 0], + [0, 0, 1], + ] + ) + + +def get_transform_mat( + angle: float, + off_x: float, + off_y: float, + mirror_x: bool = False, + mirror_y: bool = False, + scale: float = 2.5, +) -> np.ndarray: + """Return a complete transformation matrix based on input parameters.""" + scale_matrix = get_scale_matrix(scale) + mirror_matrix = get_mirror_matrix(mirror_x, mirror_y) + translation_matrix = get_translation_matrix(off_x, off_y) + rotation_matrix = get_rotation_matrix(angle) + + # Combine the transformations in the correct order: translation -> rotation -> mirror -> scale + transform_matrix = translation_matrix @ rotation_matrix @ mirror_matrix + transform_matrix = scale_matrix @ transform_matrix + + return transform_matrix + + +def create_node( + base_name: str, + node_id: str, + node_type: str, + position_vec: np.ndarray, + label: str, + color: list, + transform_mat: np.ndarray, + states: dict, + parameters: dict, +) -> options.LIDannerNodeOptions: + """ + Function to create a node with visual and state options. + + Parameters: + base_name (str): The base name to prepend to node_id. + node_id (str): Unique identifier for the node. + position_vec (np.ndarray): The position of the node. + label (str): The visual label for the node. + color (list): RGB color values for the node. + node_type (str): Type of the node ('LINaPDanner' or 'LIDanner'). + transform_mat (np.ndarray): Transformation matrix for positioning. + v0 (float): Initial value for the state option 'v0'. + h0 (float, optional): Initial value for the state option 'h0', only used for some node types. + + Returns: + options.LIDannerNodeOptions: The configured node options object. + """ + # Generate the full name and position + full_name = join_str((base_name, node_id)) + position = multiply_transform(position_vec, transform_mat).tolist() + + # Determine node type and state options + if node_type == "LINaPDanner": + state_options = options.LINaPDannerStateOptions.from_kwargs(**states) + parameters = options.LINaPDannerParameterOptions.defaults(**parameters) + node_options_class = options.LINaPDannerNodeOptions + elif node_type == "LIDanner": + state_options = options.LIDannerStateOptions.from_kwargs(**states) + parameters = options.LIDannerParameterOptions.defaults(**parameters) + node_options_class = options.LIDannerNodeOptions + elif node_type == "Linear": + state_options = None + parameters = options.LinearParameterOptions.defaults(**parameters) + node_options_class = options.LinearNodeOptions + elif node_type == "ExternalRelay": + state_options = None + parameters = options.NodeParameterOptions() + node_options_class = options.ExternalRelayNodeOptions + else: + raise ValueError(f"Unknown node type: {node_type}") + + # Create and return the node options + return node_options_class( + name=full_name, + parameters=parameters, + visual=options.NodeVisualOptions(position=position, label=label, color=color), + state=state_options, + ) + + +def create_nodes( + node_specs: Iterable, + base_name: str, + transform_mat: np.ndarray, +) -> options.NodeOptions: + """Create node using create_method""" + nodes = {} + for ( + node_id, + node_type, + position_vec, + label, + color, + states, + parameters, + ) in node_specs: + nodes[node_id] = create_node( + base_name, + node_id, + node_type, + position_vec, + label, + color, + transform_mat, + states, + parameters, + ) + return nodes + + +def create_edges( + edge_specs: Iterable, + base_name: str, + visual_options: options.EdgeVisualOptions = options.EdgeVisualOptions(), +) -> options.EdgeOptions: + """Create edges from specs""" + edges = {} + for source_tuple, target_tuple, weight, edge_type in edge_specs: + source = join_str((base_name, *source_tuple)) + target = join_str((base_name, *target_tuple)) + edges[join_str((source, "to", target))] = options.EdgeOptions( + source=source, + target=target, + weight=weight, + type=edge_type, + visual=options.EdgeVisualOptions(**visual_options), + ) + return edges + + +class RhythmGenerator: + """Generate RhythmGenerator Network""" + + def __init__(self, name="", transform_mat=np.identity(3)): + """Initialization.""" + super().__init__() + self.name = name + self.transform_mat = transform_mat + + def nodes(self): + """Add nodes.""" + node_specs = [ + ( + join_str(("RG", "F")), + "LINaPDanner", + np.array((3.0, 0.0)), + "F", + [1.0, 0.0, 0.0], + {"v0": -62.5, "h0": np.random.uniform(0, 1)}, + {}, + ), + ( + join_str(("RG", "E")), + "LINaPDanner", + np.array((-3.0, 0.0)), + "E", + [0.0, 1.0, 0.0], + {"v0": -62.5, "h0": np.random.uniform(0, 1)}, + {}, + ), + ( + join_str(("RG", "In", "F")), + "LIDanner", + np.array((1.0, -1.5)), + "In", + [0.2, 0.2, 0.2], + {"v0": -60.0}, + {}, + ), + ( + join_str(("RG", "In", "E")), + "LIDanner", + np.array((-1.0, 1.5)), + "In", + [0.2, 0.2, 0.2], + {"v0": -60.0}, + {}, + ), + ( + join_str(("RG", "In", "E2")), + "LIDanner", + np.array((-5.0, 1.0)), + "In", + [0.2, 0.2, 0.2], + {"v0": -60.0}, + {}, + ), + ] + + # Loop through the node specs to create each node using the create_node function + nodes = create_nodes(node_specs, self.name, self.transform_mat) + return nodes + + def edges(self): + """Add edges.""" + + # Define edge details in a list for easier iteration + edge_specs = [ + (("RG", "F"), ("RG", "In", "F"), 0.4, "excitatory"), + (("RG", "In", "F"), ("RG", "E"), -1.0, "inhibitory"), + (("RG", "E"), ("RG", "In", "E"), 0.4, "excitatory"), + (("RG", "In", "E"), ("RG", "F"), -0.08, "inhibitory"), + (("RG", "In", "E2"), ("RG", "F"), -0.04, "excitatory"), + ] + + # Loop through the edge specs to create each edge + edges = create_edges(edge_specs, self.name) + return edges + + +class RhythmDrive: + """Generate Drive Network""" + + def __init__(self, name="", transform_mat=np.identity(3)): + """Initialization.""" + self.name = name + self.transform_mat = transform_mat + + def nodes(self): + """Add nodes.""" + nodes = {} + + node_specs = [ + ( + join_str(("RG", "F", "DR")), + "Linear", + np.array((3.0, 2.0)), # Assuming position is not important + "d", + [0.5, 0.5, 0.5], # Default visual color if needed + None, + {"slope": 0.1, "bias": 0.0}, + ), + ( + join_str(("RG", "E", "DR")), + "Linear", + np.array((-3.0, 2.0)), # Assuming position is not important + "d", + [0.5, 0.5, 0.5], # Default visual color if needed + None, + {"slope": 0.0, "bias": 0.1}, + ), + ] + + # Loop through the node specs to create each node using the create_node function + nodes = create_nodes(node_specs, self.name, self.transform_mat) + return nodes + + +class PatternFormation: + """Generate PatternFormation Network""" + + def __init__(self, name="", transform_mat=np.identity(3)): + """Initialization.""" + super().__init__() + self.name = name + self.transform_mat = transform_mat + + def nodes(self): + """Add nodes.""" + nodes = {} + + node_specs = [ + ( + join_str(("PF", "FA")), + "LINaPDanner", + np.array((-3.0, 0.0)), + "F\\textsubscript{A}", + [1.0, 0.0, 0.0], + {"v0": -60.0, "h0": np.random.uniform(0, 1)}, + {"g_nap": 0.125, "e_leak": -67.5}, + ), + ( + join_str(("PF", "EA")), + "LINaPDanner", + np.array((-9.0, 0.0)), + "F\\textsubscript{A}", + [0.0, 1.0, 0.0], + {"v0": -60.0, "h0": np.random.uniform(0, 1)}, + {"g_nap": 0.125, "e_leak": -67.5}, + ), + ( + join_str(("PF", "In", "FA")), + "LIDanner", + np.array((-5.0, -1.5)), + "In\\textsubscript{A}", + [0.2, 0.2, 0.2], + {"v0": -60.0}, + {}, + ), + ( + join_str(("PF", "In", "EA")), + "LIDanner", + np.array((-7.0, 1.5)), + "In\\textsubscript{A}", + [0.2, 0.2, 0.2], + {"v0": -60.0}, + {}, + ), + ( + join_str(("PF", "FB")), + "LINaPDanner", + np.array((9.0, 0.0)), + "F\\textsubscript{A}", + [1.0, 0.0, 0.0], + {"v0": -60.0, "h0": np.random.uniform(0, 1)}, + {"g_nap": 0.125, "e_leak": -67.5}, + ), + ( + join_str(("PF", "EB")), + "LINaPDanner", + np.array((3.0, 0.0)), + "F\\textsubscript{A}", + [0.0, 1.0, 0.0], + {"v0": -60.0, "h0": np.random.uniform(0, 1)}, + {"g_nap": 0.125, "e_leak": -67.5}, + ), + ( + join_str(("PF", "In", "FB")), + "LIDanner", + np.array((7.0, -1.5)), + "In\\textsubscript{B}", + [0.2, 0.2, 0.2], + {"v0": -60.0}, + {}, + ), + ( + join_str(("PF", "In", "EB")), + "LIDanner", + np.array((5.0, 1.5)), + "In\\textsubscript{B}", + [0.2, 0.2, 0.2], + {"v0": -60.0}, + {}, + ), + ( + join_str(("PF", "In2", "F")), + "LIDanner", + np.array((9.0, -3.0)), + "In\\textsubscript{2F}", + [0.2, 0.2, 0.2], + {"v0": -60.0}, + {"g_leak": 5.0}, + ), + ( + join_str(("PF", "In2", "E")), + "LIDanner", + np.array((3.0, -3.0)), + "In\\textsubscript{2E}", + [0.2, 0.2, 0.2], + {"v0": -60.0}, + {"g_leak": 5.0}, + ), + ] + + # Loop through the node specs to create each node using the create_node function + nodes = create_nodes(node_specs, self.name, self.transform_mat) + return nodes + + def edges(self): + """Add edges.""" + edges = {} + + # Define edge details in a list for easier iteration + edge_specs = [ + (("PF", "FA"), ("PF", "In", "FA"), 0.8, "excitatory"), + (("PF", "In", "FA"), ("PF", "EA"), -1.5, "inhibitory"), + (("PF", "EA"), ("PF", "In", "EA"), 1.0, "excitatory"), + (("PF", "In", "EA"), ("PF", "FA"), -1.0, "inhibitory"), + (("PF", "FB"), ("PF", "In", "FB"), 1.5, "excitatory"), + (("PF", "In", "FB"), ("PF", "EB"), -2.0, "inhibitory"), + (("PF", "EB"), ("PF", "In", "EB"), 1.5, "excitatory"), + (("PF", "In", "EB"), ("PF", "FB"), -0.25, "inhibitory"), + (("PF", "In", "FA"), ("PF", "EB"), -0.5, "inhibitory"), + (("PF", "In", "FA"), ("PF", "FB"), -0.1, "inhibitory"), + (("PF", "In", "EA"), ("PF", "EB"), -0.5, "inhibitory"), + (("PF", "In", "EA"), ("PF", "FB"), -0.25, "inhibitory"), + (("PF", "In", "FB"), ("PF", "EA"), -0.5, "inhibitory"), + (("PF", "In", "FB"), ("PF", "FA"), -0.75, "inhibitory"), + (("PF", "In", "EB"), ("PF", "EA"), -2.0, "inhibitory"), + (("PF", "In", "EB"), ("PF", "FA"), -2.0, "inhibitory"), + (("PF", "In2", "F"), ("PF", "FB"), -3.0, "inhibitory"), + (("PF", "In2", "E"), ("PF", "EB"), -3.0, "inhibitory"), + ] + + # Loop through the edge specs to create each edge + edges = create_edges(edge_specs, self.name) + return edges + + +class Commissural: + """Generate Commissural Network""" + + def __init__(self, name="", transform_mat=np.identity(3)): + """Initialization.""" + super().__init__() + self.name = name + self.transform_mat = transform_mat + + def nodes(self): + """Add nodes.""" + node_specs = [ + ( + "V2a", + "LIDanner", + np.array((0.0, 2.0, 1.0)), + "V2\\textsubscript{a}", + [0.0, 1.0, 0.0], + {"v0": -60.0}, + {}, + ), + ( + "InV0V", + "LIDanner", + np.array((0.0, 0.0, 1.0)), + "In\\textsubscript{i}", + [1.0, 0.0, 1.0], + {"v0": -60.0}, + {}, + ), + ( + "V0V", + "LIDanner", + np.array((2.0, 0.5, 1.0)), + "V0\\textsubscript{V}", + [0.0, 1.0, 0.0], + {"v0": -60.0}, + {}, + ), + ( + "V0D", + "LIDanner", + np.array((2.0, -2.0, 1.0)), + "V0\\textsubscript{D}", + [1.0, 0.0, 1.0], + {"v0": -60.0}, + {}, + ), + ( + "V3E", + "LIDanner", + np.array((2.0, 3.0, 1.0)), + "V3\\textsubscript{E}", + [0.0, 1.0, 0.0], + {"v0": -60.0}, + {}, + ), + ( + "V3F", + "LIDanner", + np.array((2.0, -4.0, 1.0)), + "V3\\textsubscript{F}", + [0.0, 1.0, 0.0], + {"v0": -60.0}, + {}, + ), + ] + + # Loop through the node specs to create each node using the create_node function + nodes = create_nodes(node_specs, self.name, self.transform_mat) + return nodes + + +class CommissuralDrive: + """Generate Drive Network""" + + def __init__(self, name="", transform_mat=np.identity(3)): + """Initialization.""" + self.name = name + self.transform_mat = transform_mat + + def nodes(self): + """Add nodes.""" + + node_specs = [ + ( + join_str(("V0V", "DR")), + "Linear", + np.array((0.0, 0.0)), + "d", + [0.5, 0.5, 0.5], + None, + {"slope": 0.15, "bias": 0.0}, + ), + ( + join_str(("V0D", "DR")), + "Linear", + np.array((0.0, 0.0)), + "d", + [0.5, 0.5, 0.5], + None, + {"slope": 0.75, "bias": 0.0}, + ), + ] + + # Loop through the node specs to create each node using the create_node function + nodes = create_nodes(node_specs, self.name, self.transform_mat) + return nodes + + +class LPSN: + """Generate Long Propriospinal Network""" + + def __init__(self, name="", transform_mat=np.identity(3)): + """Initialization.""" + super().__init__() + self.name = name + self.transform_mat = transform_mat + + def nodes(self): + """Add nodes.""" + + # Define node specs in a list for easier iteration + node_specs = [ + ( + join_str(("V0D", "diag")), + "LIDanner", + np.array((0.0, 0.0, 1.0)), + "V0\\textsubscript{D}", + [0.5, 0.0, 0.5], + {"v0": -60.0}, + {}, + ), + ( + join_str(("fore", "V0V", "diag")), + "LIDanner", + np.array((0.0, -1.25, 1.0)), + "V0\\textsubscript{V}", + [0.0, 1.0, 0.0], + {"v0": -60.0}, + {}, + ), + ( + join_str(("hind", "V3", "diag")), + "LIDanner", + np.array((0.0, -4.0, 1.0)), + "V3\\textsubscript{a}", + [0.0, 1.0, 0.0], + {"v0": -60.0}, + {}, + ), + ( + join_str(("fore", "Ini", "hom")), + "LIDanner", + np.array((-4.0, 0.0, 1.0)), + "LPN\\textsubscript{i}", + [0.0, 1.0, 0.0], + {"v0": -60.0}, + {}, + ), + ( + join_str(("fore", "Sh2", "hom")), + "LIDanner", + np.array((-8.0, 0.0, 1.0)), + "Sh\\textsubscript{2}", + [0.0, 1.0, 0.0], + {"v0": -60.0}, + {}, + ), + ( + join_str(("hind", "Sh2", "hom")), + "LIDanner", + np.array((-8.0, -4.0, 1.0)), + "Sh\\textsubscript{2}", + [0.0, 1.0, 0.0], + {"v0": -60.0}, + {}, + ), + ] + + # Loop through the node specs to create each node using the create_node function + nodes = create_nodes(node_specs, self.name, self.transform_mat) + return nodes + + +class LPSNDrive: + """Generate Drive Network""" + + def __init__(self, name="", transform_mat=np.identity(3)): + """Initialization.""" + self.name = name + self.transform_mat = transform_mat + + def nodes(self): + """Add nodes.""" + + node_specs = [ + ( + join_str(("V0D", "diag", "DR")), + "Linear", + np.array((0.0, 0.0)), + "d", + [0.5, 0.5, 0.5], + None, + {"slope": 0.75, "bias": 0.0}, + ), + ] + + # Loop through the node specs to create each node using the create_node function + nodes = create_nodes(node_specs, self.name, self.transform_mat) + return nodes + + +class MotorLayer: + """Motorneurons and other associated interneurons.""" + + def __init__(self, muscles: List[str], name="", transform_mat=np.identity(3)): + """Initialization.""" + self.name = name + self.muscles = muscles + self.transform_mat = transform_mat + + def nodes(self): + """Add neurons for the motor layer.""" + + node_specs = [] + + # Define neurons for the muscles + for x_off, muscle in zip( + self._generate_positions(len(self.muscles["agonist"])), + self.muscles["agonist"], + ): + node_specs.extend(self._get_muscle_neurons(muscle, x_off, 0.0)) + + for x_off, muscle in zip( + self._generate_positions(len(self.muscles["antagonist"])), + self.muscles["antagonist"], + ): + node_specs.extend( + self._get_muscle_neurons(muscle, x_off, 3.5, mirror_y=True) + ) + + # Loop through the node specs to create each node using the create_node function + nodes = create_nodes(node_specs, self.name, self.transform_mat) + return nodes + + def edges(self): + """Add motor feedback connections.""" + edges = {} + + # Define edges for each muscle + for muscle in self.muscles["agonist"]: + edges.update(self._generate_motor_connections(muscle)) + for muscle in self.muscles["antagonist"]: + edges.update(self._generate_motor_connections(muscle)) + + return edges + + def _generate_motor_connections(self, muscle): + """Generate the motor connections for a specific muscle.""" + edges = {} + + edge_specs = [ + ( + f"{muscle}_Ia", + f"{muscle}_Mn", + 0.01, + "excitatory", + "Ia_monosynaptic_excitation", + ), + ( + f"{muscle}_Mn", + f"{muscle}_Rn", + 0.01, + "excitatory", + "Rn_reciprocal_inhibition", + ), + ( + f"{muscle}_Rn", + f"{muscle}_Mn", + -0.01, + "inhibitory", + "Rn_reciprocal_inhibition", + ), + ( + f"{muscle}_Ib", + f"{muscle}_IbIn_i", + 0.01, + "excitatory", + "Ib_disynaptic_inhibition", + ), + ( + f"{muscle}_IbIn_i", + f"{muscle}_Mn", + -0.01, + "inhibitory", + "Ib_disynaptic_inhibition", + ), + ] + + # Loop through the edge specs to create each edge + edges = create_edges(edge_specs, self.name) + return edges + + def _get_muscle_neurons(self, muscle, x_off, y_off, mirror_y=False): + """Return neuron specifications for a muscle.""" + mirror_y_sign = -1 if mirror_y else 1 + + return [ + ( + f"{muscle}_Mn", + "LIDanner", + np.array((x_off, y_off, 1.0)), + "Mn", + [1.0, 0.0, 1.0], + {"v0": -60.0}, + {"e_leak": -52.5, "g_leak": 1.0}, + ), + ( + f"{muscle}_Ia", + "ExternalRelay", + np.array((x_off - 0.5, y_off + 0.75 * mirror_y_sign, 1.0)), + "Ia", + [1.0, 0.0, 0.0], + {}, + {}, + ), + ( + f"{muscle}_II", + "ExternalRelay", + np.array((x_off, y_off + 0.75 * mirror_y_sign, 1.0)), + "II", + [1.0, 0.0, 0.0], + {"v0": 0.0}, + {}, + ), + ( + f"{muscle}_Ib", + "ExternalRelay", + np.array((x_off + 0.5, y_off + 0.75 * mirror_y_sign, 1.0)), + "Ib", + [1.0, 0.0, 0.0], + {"v0": 0.0}, + {}, + ), + ( + f"{muscle}_Rn", + "LIDanner", + np.array((x_off + 0.5, y_off - 1.0 * mirror_y_sign, 1.0)), + "Rn", + [1.0, 0.0, 1.0], + {"v0": -60.0}, + {}, + ), + ( + f"{muscle}_IbIn_i", + "LIDanner", + np.array((x_off + 1.0, y_off, 1.0)), + "Ib\\textsubscript{i}", + [0.0, 0.0, 1.0], + {"v0": -60.0}, + {}, + ), + ( + f"{muscle}_IbIn_e", + "LIDanner", + np.array((x_off + 1.0, y_off + 1.5 * mirror_y_sign, 1.0)), + "Ib\\textsubscript{e}", + [0.0, 0.0, 1.0], + {"v0": -60.0}, + {}, + ), + ( + f"{muscle}_IIIn_RG", + "LIDanner", + np.array((x_off - 1.0, y_off, 1.0)), + "II\\textsubscript{RG}", + [0.0, 0.0, 1.0], + {"v0": -60.0}, + {}, + ), + ] + + def _generate_positions(self, num_muscles): + """Generate positions for the neurons.""" + spacing = 1.25 + return np.linspace(-spacing * num_muscles, spacing * num_muscles, num_muscles) + + +########################## +# CONNECT RG COMMISSURAL # +########################## +def connect_rg_commissural(): + """Connect RG's to Commissural.""" + + edges = {} + for limb in ("hind", "fore"): + for side in ("left", "right"): + + edges[join_str((side, limb, "RG", "F", "to", side, limb, "V2a"))] = ( + options.EdgeOptions( + source=join_str((side, limb, "RG", "F")), + target=join_str((side, limb, "V2a")), + weight=1.0, + type="excitatory", + visual=options.EdgeVisualOptions(), + ) + ) + + # edges[join_str((side, limb, "RG" "F", "to", side, limb, "V2a", "diag"))] = options.EdgeOptions( + # source=join_str((side, limb, "RG" "F")), + # target=join_str((side, limb, "V2a", "diag")), + # weight=0.5, + # type="excitatory", + # visual=options.EdgeVisualOptions(), + + # ) + edges[join_str((side, limb, "RG", "F", "to", side, limb, "V0D"))] = ( + options.EdgeOptions( + source=join_str((side, limb, "RG", "F")), + target=join_str((side, limb, "V0D")), + weight=0.7, + type="excitatory", + visual=options.EdgeVisualOptions(), + ) + ) + + edges[join_str((side, limb, "RG", "F", "to", side, limb, "V3F"))] = ( + options.EdgeOptions( + source=join_str((side, limb, "RG", "F")), + target=join_str((side, limb, "V3F")), + weight=0.35, + type="excitatory", + visual=options.EdgeVisualOptions(), + ) + ) + + edges[join_str((side, limb, "RG", "E", "to", side, limb, "V3E"))] = ( + options.EdgeOptions( + source=join_str((side, limb, "RG", "E")), + target=join_str((side, limb, "V3E")), + weight=0.35, + type="excitatory", + visual=options.EdgeVisualOptions(), + ) + ) + + edges[join_str((side, limb, "V2a", "to", side, limb, "V0V"))] = ( + options.EdgeOptions( + source=join_str((side, limb, "V2a")), + target=join_str((side, limb, "V0V")), + weight=1.0, + type="excitatory", + visual=options.EdgeVisualOptions(), + ) + ) + + edges[join_str((side, limb, "InV0V", "to", side, limb, "RG", "F"))] = ( + options.EdgeOptions( + source=join_str((side, limb, "InV0V")), + target=join_str((side, limb, "RG", "F")), + weight=-0.07, + type="inhibitory", + visual=options.EdgeVisualOptions(), + ) + ) + + for sides in (("left", "right"), ("right", "left")): + + edges[join_str((sides[0], limb, "V0V", "to", sides[1], limb, "InV0V"))] = ( + options.EdgeOptions( + source=join_str((sides[0], limb, "V0V")), + target=join_str((sides[1], limb, "InV0V")), + weight=0.6, + type="excitatory", + visual=options.EdgeVisualOptions(), + ) + ) + + edges[ + join_str((sides[0], limb, "V0D", "to", sides[1], limb, "RG", "F")) + ] = options.EdgeOptions( + source=join_str((sides[0], limb, "V0D")), + target=join_str((sides[1], limb, "RG", "F")), + weight=-0.07, + type="inhibitory", + visual=options.EdgeVisualOptions(), + ) + + edges[ + join_str((sides[0], limb, "V3F", "to", sides[1], limb, "RG", "F")) + ] = options.EdgeOptions( + source=join_str((sides[0], limb, "V3F")), + target=join_str((sides[1], limb, "RG", "F")), + weight=0.03, + type="excitatory", + visual=options.EdgeVisualOptions(), + ) + + edges[ + join_str((sides[0], limb, "V3E", "to", sides[1], limb, "RG", "E")) + ] = options.EdgeOptions( + source=join_str((sides[0], limb, "V3E")), + target=join_str((sides[1], limb, "RG", "E")), + weight=0.02, + type="excitatory", + visual=options.EdgeVisualOptions(), + ) + + edges[ + join_str((sides[0], limb, "V3E", "to", sides[1], limb, "RG", "In", "E")) + ] = options.EdgeOptions( + source=join_str((sides[0], limb, "V3E")), + target=join_str((sides[1], limb, "RG", "In", "E")), + weight=0.0, + type="excitatory", + visual=options.EdgeVisualOptions(), + ) + + edges[ + join_str( + (sides[0], limb, "V3E", "to", sides[1], limb, "RG", "In", "E2") + ) + ] = options.EdgeOptions( + source=join_str((sides[0], limb, "V3E")), + target=join_str((sides[1], limb, "RG", "In", "E2")), + weight=0.8, + type="excitatory", + visual=options.EdgeVisualOptions(), + ) + return edges + + +def connect_fore_hind_circuits(): + """Connect CPG's to Interneurons.""" + + edges = {} + for side in ("left", "right"): + edges[join_str((side, "fore", "RG", "F", "to", side, "fore", "Ini", "hom"))] = ( + options.EdgeOptions( + source=join_str((side, "fore", "RG", "F")), + target=join_str((side, "fore", "Ini", "hom")), + weight=0.70, + type="excitatory", + visual=options.EdgeVisualOptions(), + ) + ) + + edges[join_str((side, "fore", "RG", "F", "to", side, "V0D", "diag"))] = ( + options.EdgeOptions( + source=join_str((side, "fore", "RG", "F")), + target=join_str((side, "V0D", "diag")), + weight=0.50, + type="excitatory", + visual=options.EdgeVisualOptions(), + ) + ) + + edges[join_str((side, "fore", "Ini", "hom", "to", side, "hind", "RG", "F"))] = ( + options.EdgeOptions( + source=join_str((side, "fore", "Ini", "hom")), + target=join_str((side, "hind", "RG", "F")), + weight=-0.01, + type="excitatory", + visual=options.EdgeVisualOptions(), + ) + ) + + edges[join_str((side, "fore", "Sh2", "hom", "to", side, "hind", "RG", "F"))] = ( + options.EdgeOptions( + source=join_str((side, "fore", "Sh2", "hom")), + target=join_str((side, "hind", "RG", "F")), + weight=0.01, + type="excitatory", + visual=options.EdgeVisualOptions(), + ) + ) + + edges[join_str((side, "hind", "Sh2", "hom", "to", side, "fore", "RG", "F"))] = ( + options.EdgeOptions( + source=join_str((side, "hind", "Sh2", "hom")), + target=join_str((side, "fore", "RG", "F")), + weight=0.05, + type="excitatory", + visual=options.EdgeVisualOptions(), + ) + ) + + edges[ + join_str((side, "fore", "RG", "F", "to", side, "fore", "V0V", "diag")) + ] = options.EdgeOptions( + source=join_str((side, "fore", "RG", "F")), + target=join_str((side, "fore", "V0V", "diag")), + weight=0.325, + type="excitatory", + visual=options.EdgeVisualOptions(), + ) + + edges[join_str((side, "hind", "RG", "F", "to", side, "hind", "V3", "diag"))] = ( + options.EdgeOptions( + source=join_str((side, "hind", "RG", "F")), + target=join_str((side, "hind", "V3", "diag")), + weight=0.325, + type="excitatory", + visual=options.EdgeVisualOptions(), + ) + ) + + # edges[join_str((side, "fore", "V2a-diag", "to", side, "fore", "V0V", "diag"))] = options.EdgeOptions( + # source=join_str((side, "fore", "V2a-diag")), + # target=join_str((side, "fore", "V0V", "diag")), + # weight=0.9, + # type="excitatory", + # visual=options.EdgeVisualOptions(), + # ) + + # edges[join_str((side, "hind", "V2a-diag", "to", side, "hind", "V3", "diag"))] = options.EdgeOptions( + # source=join_str((side, "hind", "V2a-diag")), + # target=join_str((side, "hind", "V3", "diag")), + # weight=0.9, + # type="excitatory", + # visual=options.EdgeVisualOptions(), + # ) + + for sides in (("left", "right"), ("right", "left")): + edges[ + join_str((sides[0], "V0D", "diag", "to", sides[1], "hind", "RG", "F")) + ] = options.EdgeOptions( + source=join_str((sides[0], "V0D", "diag")), + target=join_str((sides[1], "hind", "RG", "F")), + weight=-0.01, + type="inhibitory", + visual=options.EdgeVisualOptions(), + ) + + edges[ + join_str( + (sides[0], "fore", "V0V", "diag", "to", sides[1], "hind", "RG", "F") + ) + ] = options.EdgeOptions( + source=join_str((sides[0], "fore", "V0V", "diag")), + target=join_str((sides[1], "hind", "RG", "F")), + weight=0.005, + type="excitatory", + visual=options.EdgeVisualOptions(), + ) + + edges[ + join_str( + (sides[0], "hind", "V3", "diag", "to", sides[1], "fore", "RG", "F") + ) + ] = options.EdgeOptions( + source=join_str((sides[0], "hind", "V3", "diag")), + target=join_str((sides[1], "fore", "RG", "F")), + weight=0.04, + type="excitatory", + visual=options.EdgeVisualOptions(), + ) + + return edges From 13b47ca8888bc2709d40823eb72a7f782c75e631 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Mon, 28 Oct 2024 16:00:02 -0400 Subject: [PATCH 100/316] [EX][MOUSE] Added basic motor layer connections --- examples/mouse/run.py | 1140 +++-------------------------------------- 1 file changed, 73 insertions(+), 1067 deletions(-) diff --git a/examples/mouse/run.py b/examples/mouse/run.py index 3f01f5b..de9dded 100644 --- a/examples/mouse/run.py +++ b/examples/mouse/run.py @@ -1,1070 +1,62 @@ """ Generate and reproduce Zhang, Shevtsova, et al. eLife 2022;11:e73424. DOI: https://doi.org/10.7554/eLife.73424 paper network """ -import os -from pprint import pprint -from typing import List, Iterable -import farms_pylog as pylog -import matplotlib.pyplot as plt -import networkx as nx -import numpy as np +from farms_core.io.yaml import read_yaml from farms_network.core import options -from farms_network.core.data import NetworkData -from farms_network.core.network import PyNetwork, rk4 -# from farms_network.gui.gui import NetworkGUI -from jinja2 import Environment, FileSystemLoader -from tqdm import tqdm -plt.rcParams["text.usetex"] = True +from components import * -def join_str(strings): - return "_".join(strings) - - -def multiply_transform(vec: np.ndarray, transform_mat: np.ndarray) -> np.ndarray: - """ - Multiply a 2D vector with a 2D transformation matrix (3x3). - - Parameters: - vec (np.ndarray): A 2D vector (shape (2,) or (3,)) - transform_mat (np.ndarray): A 3x3 transformation matrix. - - Returns: - np.ndarray: The transformed vector. - """ - - assert transform_mat.shape == (3, 3), "Transformation matrix must be 3x3" - - # Ensure vec is in homogeneous coordinates (i.e., 3 elements). - if vec.shape == (2,): - vec = np.append(vec, 1) - elif vec.shape != (3,): - raise ValueError("Input vector must have shape (2,) or (3,)") - - # Perform the multiplication - return transform_mat @ vec - - -def get_scale_matrix(scale: float) -> np.ndarray: - """Return a scaling matrix.""" - return np.array([[scale, 0, 0], [0, scale, 0], [0, 0, 1]]) - - -def get_mirror_matrix(mirror_x: bool, mirror_y: bool) -> np.ndarray: - """Return a mirror matrix based on the mirror flags.""" - mirror_matrix = np.identity(3) - if mirror_x: - mirror_matrix = np.array([[1, 0, 0], [0, -1, 0], [0, 0, 1]]) - if mirror_y: - mirror_matrix = np.array([[-1, 0, 0], [0, 1, 0], [0, 0, 1]]) - return mirror_matrix - - -def get_translation_matrix(off_x: float, off_y: float) -> np.ndarray: - """Return a translation matrix.""" - return np.array([[1, 0, off_x], [0, 1, off_y], [0, 0, 1]]) - - -def get_rotation_matrix(angle: float) -> np.ndarray: - """Return a rotation matrix for the given angle in degrees.""" - angle_rad = np.radians(angle) - return np.array( - [ - [np.cos(angle_rad), -np.sin(angle_rad), 0], - [np.sin(angle_rad), np.cos(angle_rad), 0], - [0, 0, 1], - ] - ) - - -def get_transform_mat( - angle: float, - off_x: float, - off_y: float, - mirror_x: bool = False, - mirror_y: bool = False, - scale: float = 2.5, -) -> np.ndarray: - """Return a complete transformation matrix based on input parameters.""" - scale_matrix = get_scale_matrix(scale) - mirror_matrix = get_mirror_matrix(mirror_x, mirror_y) - translation_matrix = get_translation_matrix(off_x, off_y) - rotation_matrix = get_rotation_matrix(angle) - - # Combine the transformations in the correct order: translation -> rotation -> mirror -> scale - transform_matrix = translation_matrix @ rotation_matrix @ mirror_matrix - transform_matrix = scale_matrix @ transform_matrix - - return transform_matrix - - -def create_node( - base_name: str, - node_id: str, - node_type: str, - position_vec: np.ndarray, - label: str, - color: list, - transform_mat: np.ndarray, - states: dict, - parameters: dict, -) -> options.LIDannerNodeOptions: - """ - Function to create a node with visual and state options. - - Parameters: - base_name (str): The base name to prepend to node_id. - node_id (str): Unique identifier for the node. - position_vec (np.ndarray): The position of the node. - label (str): The visual label for the node. - color (list): RGB color values for the node. - node_type (str): Type of the node ('LINaPDanner' or 'LIDanner'). - transform_mat (np.ndarray): Transformation matrix for positioning. - v0 (float): Initial value for the state option 'v0'. - h0 (float, optional): Initial value for the state option 'h0', only used for some node types. - - Returns: - options.LIDannerNodeOptions: The configured node options object. - """ - # Generate the full name and position - full_name = join_str((base_name, node_id)) - position = multiply_transform(position_vec, transform_mat).tolist() - - # Determine node type and state options - if node_type == "LINaPDanner": - state_options = options.LINaPDannerStateOptions.from_kwargs(**states) - parameters = options.LINaPDannerParameterOptions.defaults(**parameters) - node_options_class = options.LINaPDannerNodeOptions - elif node_type == "LIDanner": - state_options = options.LIDannerStateOptions.from_kwargs(**states) - parameters = options.LIDannerParameterOptions.defaults(**parameters) - node_options_class = options.LIDannerNodeOptions - elif node_type == "Linear": - state_options = None - parameters = options.LinearParameterOptions.defaults(**parameters) - node_options_class = options.LinearNodeOptions - else: - raise ValueError(f"Unknown node type: {node_type}") - - # Create and return the node options - return node_options_class( - name=full_name, - parameters=parameters, - visual=options.NodeVisualOptions(position=position, label=label, color=color), - state=state_options, - ) - - -def create_nodes( - node_specs: Iterable, - base_name: str, - transform_mat: np.ndarray, -) -> options.NodeOptions: - """ Create node using create_method """ - nodes = {} - for ( - node_id, - node_type, - position_vec, - label, - color, - states, - parameters, - ) in node_specs: - nodes[node_id] = create_node( - base_name, - node_id, - node_type, - position_vec, - label, - color, - transform_mat, - states, - parameters, - ) - return nodes - - -def create_edges( - edge_specs: Iterable, - base_name: str, - visual_options: options.EdgeVisualOptions = options.EdgeVisualOptions(), -) -> options.EdgeOptions: - """ Create edges from specs""" - edges = {} - for source_tuple, target_tuple, weight, edge_type in edge_specs: - source = join_str((base_name, *source_tuple)) - target = join_str((base_name, *target_tuple)) - edges[join_str((source, "to", target))] = options.EdgeOptions( - source=source, - target=target, - weight=weight, - type=edge_type, - visual=options.EdgeVisualOptions(**visual_options), - ) - return edges - - -class RhythmGenerator: - """Generate RhythmGenerator Network""" - - def __init__(self, name="", transform_mat=np.identity(3)): - """Initialization.""" - super().__init__() - self.name = name - self.transform_mat = transform_mat - - def nodes(self): - """Add nodes.""" - node_specs = [ - ( - join_str(("RG", "F")), - "LINaPDanner", - np.array((3.0, 0.0)), - "F", - [1.0, 0.0, 0.0], - {"v0": -62.5, "h0": np.random.uniform(0, 1)}, - {}, - ), - ( - join_str(("RG", "E")), - "LINaPDanner", - np.array((-3.0, 0.0)), - "E", - [0.0, 1.0, 0.0], - {"v0": -62.5, "h0": np.random.uniform(0, 1)}, - {}, - ), - ( - join_str(("RG", "In", "F")), - "LIDanner", - np.array((1.0, -1.5)), - "In", - [0.2, 0.2, 0.2], - {"v0": -60.0}, - {}, - ), - ( - join_str(("RG", "In", "E")), - "LIDanner", - np.array((-1.0, 1.5)), - "In", - [0.2, 0.2, 0.2], - {"v0": -60.0}, - {}, - ), - ( - join_str(("RG", "In", "E2")), - "LIDanner", - np.array((-5.0, 1.0)), - "In", - [0.2, 0.2, 0.2], - {"v0": -60.0}, - {}, - ), - ] - - # Loop through the node specs to create each node using the create_node function - nodes = create_nodes(node_specs, self.name, self.transform_mat) - return nodes - - def edges(self): - """Add edges.""" - - # Define edge details in a list for easier iteration - edge_specs = [ - (("RG", "F"), ("RG", "In", "F"), 0.4, "excitatory"), - (("RG", "In", "F"), ("RG", "E"), -1.0, "inhibitory"), - (("RG", "E"), ("RG", "In", "E"), 0.4, "excitatory"), - (("RG", "In", "E"), ("RG", "F"), -0.08, "inhibitory"), - (("RG", "In", "E2"), ("RG", "F"), -0.04, "excitatory"), - ] - - # Loop through the edge specs to create each edge - edges = create_edges(edge_specs, self.name) - return edges - - -class RhythmDrive: - """Generate Drive Network""" - - def __init__(self, name="", transform_mat=np.identity(3)): - """Initialization.""" - self.name = name - self.transform_mat = transform_mat - - def nodes(self): - """Add nodes.""" - nodes = {} - - node_specs = [ - ( - join_str(("RG", "F", "DR")), - "Linear", - np.array((3.0, 2.0)), # Assuming position is not important - "d", - [0.5, 0.5, 0.5], # Default visual color if needed - None, - {"slope": 0.1, "bias": 0.0}, - ), - ( - join_str(("RG", "E", "DR")), - "Linear", - np.array((-3.0, 2.0)), # Assuming position is not important - "d", - [0.5, 0.5, 0.5], # Default visual color if needed - None, - {"slope": 0.0, "bias": 0.1}, - ), - ] - - # Loop through the node specs to create each node using the create_node function - nodes = create_nodes(node_specs, self.name, self.transform_mat) - return nodes - - -class PatternFormation: - """Generate PatternFormation Network""" - - def __init__(self, name="", transform_mat=np.identity(3)): - """Initialization.""" - super().__init__() - self.name = name - self.transform_mat = transform_mat - - def nodes(self): - """Add nodes.""" - nodes = {} - - node_specs = [ - ( - join_str(("PF", "FA")), - "LINaPDanner", - np.array((-3.0, 0.0)), - "F\\textsubscript{A}", - [1.0, 0.0, 0.0], - {"v0": -60.0, "h0": np.random.uniform(0, 1)}, - {"g_nap": 0.125, "e_leak": -67.5}, - ), - ( - join_str(("PF", "EA")), - "LINaPDanner", - np.array((-9.0, 0.0)), - "F\\textsubscript{A}", - [0.0, 1.0, 0.0], - {"v0": -60.0, "h0": np.random.uniform(0, 1)}, - {"g_nap": 0.125, "e_leak": -67.5}, - ), - ( - join_str(("PF", "In", "FA")), - "LIDanner", - np.array((-5.0, -1.5)), - "In\\textsubscript{A}", - [0.2, 0.2, 0.2], - {"v0": -60.0}, - {}, - ), - ( - join_str(("PF", "In", "EA")), - "LIDanner", - np.array((-7.0, 1.5)), - "In\\textsubscript{A}", - [0.2, 0.2, 0.2], - {"v0": -60.0}, - {}, - ), - ( - join_str(("PF", "FB")), - "LINaPDanner", - np.array((9.0, 0.0)), - "F\\textsubscript{A}", - [1.0, 0.0, 0.0], - {"v0": -60.0, "h0": np.random.uniform(0, 1)}, - {"g_nap": 0.125, "e_leak": -67.5}, - ), - ( - join_str(("PF", "EB")), - "LINaPDanner", - np.array((3.0, 0.0)), - "F\\textsubscript{A}", - [0.0, 1.0, 0.0], - {"v0": -60.0, "h0": np.random.uniform(0, 1)}, - {"g_nap": 0.125, "e_leak": -67.5}, - ), - ( - join_str(("PF", "In", "FB")), - "LIDanner", - np.array((7.0, -1.5)), - "In\\textsubscript{B}", - [0.2, 0.2, 0.2], - {"v0": -60.0}, - {}, - ), - ( - join_str(("PF", "In", "EB")), - "LIDanner", - np.array((5.0, 1.5)), - "In\\textsubscript{B}", - [0.2, 0.2, 0.2], - {"v0": -60.0}, - {}, - ), - ( - join_str(("PF", "In2", "F")), - "LIDanner", - np.array((9.0, -3.0)), - "In\\textsubscript{2F}", - [0.2, 0.2, 0.2], - {"v0": -60.0}, - {"g_leak": 5.0}, - ), - ( - join_str(("PF", "In2", "E")), - "LIDanner", - np.array((3.0, -3.0)), - "In\\textsubscript{2E}", - [0.2, 0.2, 0.2], - {"v0": -60.0}, - {"g_leak": 5.0}, - ), - ] - - # Loop through the node specs to create each node using the create_node function - nodes = create_nodes(node_specs, self.name, self.transform_mat) - return nodes - - def edges(self): - """Add edges.""" - edges = {} - - # Define edge details in a list for easier iteration - edge_specs = [ - (("PF", "FA"), ("PF", "In", "FA"), 0.8, "excitatory"), - (("PF", "In", "FA"), ("PF", "EA"), -1.5, "inhibitory"), - (("PF", "EA"), ("PF", "In", "EA"), 1.0, "excitatory"), - (("PF", "In", "EA"), ("PF", "FA"), -1.0, "inhibitory"), - (("PF", "FB"), ("PF", "In", "FB"), 1.5, "excitatory"), - (("PF", "In", "FB"), ("PF", "EB"), -2.0, "inhibitory"), - (("PF", "EB"), ("PF", "In", "EB"), 1.5, "excitatory"), - (("PF", "In", "EB"), ("PF", "FB"), -0.25, "inhibitory"), - (("PF", "In", "FA"), ("PF", "EB"), -0.5, "inhibitory"), - (("PF", "In", "FA"), ("PF", "FB"), -0.1, "inhibitory"), - (("PF", "In", "EA"), ("PF", "EB"), -0.5, "inhibitory"), - (("PF", "In", "EA"), ("PF", "FB"), -0.25, "inhibitory"), - (("PF", "In", "FB"), ("PF", "EA"), -0.5, "inhibitory"), - (("PF", "In", "FB"), ("PF", "FA"), -0.75, "inhibitory"), - (("PF", "In", "EB"), ("PF", "EA"), -2.0, "inhibitory"), - (("PF", "In", "EB"), ("PF", "FA"), -2.0, "inhibitory"), - (("PF", "In2", "F"), ("PF", "FB"), -3.0, "inhibitory"), - (("PF", "In2", "E"), ("PF", "EB"), -3.0, "inhibitory"), - ] - - # Loop through the edge specs to create each edge - edges = create_edges(edge_specs, self.name) - return edges - - -class Commissural: - """Generate Commissural Network""" - - def __init__(self, name="", transform_mat=np.identity(3)): - """Initialization.""" - super().__init__() - self.name = name - self.transform_mat = transform_mat - - def nodes(self): - """Add nodes.""" - node_specs = [ - ( - "V2a", - "LIDanner", - np.array((0.0, 2.0, 1.0)), - "V2\\textsubscript{a}", - [0.0, 1.0, 0.0], - {"v0": -60.0}, - {}, - ), - ( - "InV0V", - "LIDanner", - np.array((0.0, 0.0, 1.0)), - "In\\textsubscript{i}", - [1.0, 0.0, 1.0], - {"v0": -60.0}, - {}, - ), - ( - "V0V", - "LIDanner", - np.array((2.0, 0.5, 1.0)), - "V0\\textsubscript{V}", - [0.0, 1.0, 0.0], - {"v0": -60.0}, - {}, - ), - ( - "V0D", - "LIDanner", - np.array((2.0, -2.0, 1.0)), - "V0\\textsubscript{D}", - [1.0, 0.0, 1.0], - {"v0": -60.0}, - {}, - ), - ( - "V3E", - "LIDanner", - np.array((2.0, 3.0, 1.0)), - "V3\\textsubscript{E}", - [0.0, 1.0, 0.0], - {"v0": -60.0}, - {}, - ), - ( - "V3F", - "LIDanner", - np.array((2.0, -4.0, 1.0)), - "V3\\textsubscript{F}", - [0.0, 1.0, 0.0], - {"v0": -60.0}, - {}, - ), - ] - - # Loop through the node specs to create each node using the create_node function - nodes = create_nodes(node_specs, self.name, self.transform_mat) - return nodes - - -class CommissuralDrive: - """Generate Drive Network""" - - def __init__(self, name="", transform_mat=np.identity(3)): - """Initialization.""" - self.name = name - self.transform_mat = transform_mat - - def nodes(self): - """Add nodes.""" - - node_specs = [ - ( - join_str(("V0V", "DR")), - "Linear", - np.array((0.0, 0.0)), - "d", - [0.5, 0.5, 0.5], - None, - {"slope": 0.15, "bias": 0.0}, - ), - ( - join_str(("V0D", "DR")), - "Linear", - np.array((0.0, 0.0)), - "d", - [0.5, 0.5, 0.5], - None, - {"slope": 0.75, "bias": 0.0}, - ), - ] - - # Loop through the node specs to create each node using the create_node function - nodes = create_nodes(node_specs, self.name, self.transform_mat) - return nodes - - -class LPSN: - """Generate Long Propriospinal Network""" - - def __init__(self, name="", transform_mat=np.identity(3)): - """Initialization.""" - super().__init__() - self.name = name - self.transform_mat = transform_mat - - def nodes(self): - """Add nodes.""" - - # Define node specs in a list for easier iteration - node_specs = [ - ( - join_str(("V0D", "diag")), - "LIDanner", - np.array((0.0, 0.0, 1.0)), - "V0\\textsubscript{D}", - [0.5, 0.0, 0.5], - {"v0": -60.0}, - {}, - ), - ( - join_str(("fore", "V0V", "diag")), - "LIDanner", - np.array((0.0, -1.25, 1.0)), - "V0\\textsubscript{V}", - [0.0, 1.0, 0.0], - {"v0": -60.0}, - {}, - ), - ( - join_str(("hind", "V3", "diag")), - "LIDanner", - np.array((0.0, -4.0, 1.0)), - "V3\\textsubscript{a}", - [0.0, 1.0, 0.0], - {"v0": -60.0}, - {}, - ), - ( - join_str(("fore", "Ini", "hom")), - "LIDanner", - np.array((-4.0, 0.0, 1.0)), - "LPN\\textsubscript{i}", - [0.0, 1.0, 0.0], - {"v0": -60.0}, - {}, - ), - ( - join_str(("fore", "Sh2", "hom")), - "LIDanner", - np.array((-8.0, 0.0, 1.0)), - "Sh\\textsubscript{2}", - [0.0, 1.0, 0.0], - {"v0": -60.0}, - {}, - ), - ( - join_str(("hind", "Sh2", "hom")), - "LIDanner", - np.array((-8.0, -4.0, 1.0)), - "Sh\\textsubscript{2}", - [0.0, 1.0, 0.0], - {"v0": -60.0}, - {}, - ), - ] - - # Loop through the node specs to create each node using the create_node function - nodes = create_nodes(node_specs, self.name, self.transform_mat) - return nodes - - -class LPSNDrive: - """Generate Drive Network""" - - def __init__(self, name="", transform_mat=np.identity(3)): - """Initialization.""" - self.name = name - self.transform_mat = transform_mat - - def nodes(self): - """Add nodes.""" - - node_specs = [ - ( - join_str(("V0D", "diag", "DR")), - "Linear", - np.array((0.0, 0.0)), - "d", - [0.5, 0.5, 0.5], - None, - {"slope": 0.75, "bias": 0.0}, - ), - ] - - # Loop through the node specs to create each node using the create_node function - nodes = create_nodes(node_specs, self.name, self.transform_mat) - return nodes - - -class MotorLayer: - """Motorneurons and other associated interneurons.""" - - def __init__(self, muscles: List[str], name="", transform_mat=np.identity(3)): - """Initialization.""" - self.name = name - self.muscles = muscles - self.transform_mat = transform_mat - - def nodes(self): - """Add neurons for the motor layer.""" - - neuron_specs = [] - - # Define neurons for the muscles - for x_off, muscle in zip(self._generate_positions(len(self.muscles["agonist"])), self.muscles["agonist"]): - neuron_specs.extend(self._get_muscle_neurons(muscle, x_off, 0.0)) - - for x_off, muscle in zip(self._generate_positions(len(self.muscles["antagonist"])), self.muscles["antagonist"]): - neuron_specs.extend(self._get_muscle_neurons(muscle, x_off, 3.5, mirror_y=True)) - - # Loop through the node specs to create each node using the create_node function - nodes = create_nodes(node_specs, self.name, self.transform_mat) - return nodes - - def edges(self): - """Add motor feedback connections.""" - edges = {} - - # Define edges for each muscle - for muscle in self.muscles["agonist"]: - edges.update(self._generate_motor_connections(muscle)) - for muscle in self.muscles["antagonist"]: - edges.update(self._generate_motor_connections(muscle)) - - return edges - - def _generate_motor_connections(self, muscle): - """Generate the motor connections for a specific muscle.""" - edges = {} - - edge_specs = [ - (f"{muscle}_Ia", f"{muscle}_Mn", 0.01, "excitatory", "Ia_monosynaptic_excitation"), - (f"{muscle}_Mn", f"{muscle}_Rn", 0.01, "excitatory", "Rn_reciprocal_inhibition"), - (f"{muscle}_Rn", f"{muscle}_Mn", -0.01, "inhibitory", "Rn_reciprocal_inhibition"), - (f"{muscle}_Ib", f"{muscle}_IbIn_i", 0.01, "excitatory", "Ib_disynaptic_inhibition"), - (f"{muscle}_IbIn_i", f"{muscle}_Mn", -0.01, "inhibitory", "Ib_disynaptic_inhibition"), - ] - - # Loop through the edge specs to create each edge - edges = create_edges(edge_specs, self.name) - return edges - - def _get_muscle_neurons(self, muscle, x_off, y_off, mirror_y=False): - """Return neuron specifications for a muscle.""" - mirror_y_sign = -1 if mirror_y else 1 - - return [ - ( - f"{muscle}_Mn", "LIDanner", (x_off, y_off, 1.0), "Mn", [1.0, 0.0, 1.0], - {"v0": -60.0}, {"e_leak": -52.5, "g_leak": 1.0} - ), - ( - f"{muscle}_Ia", "LIDanner", (x_off - 0.5, y_off + 0.75 * mirror_y_sign, 1.0), "Ia", [1.0, 0.0, 0.0], - {"init": 0.0}, {} - ), - ( - f"{muscle}_II", "LIDanner", (x_off, y_off + 0.75 * mirror_y_sign, 1.0), "II", [1.0, 0.0, 0.0], - {"init": 0.0}, {} - ), - ( - f"{muscle}_Ib", "LIDanner", (x_off + 0.5, y_off + 0.75 * mirror_y_sign, 1.0), "Ib", [1.0, 0.0, 0.0], - {"init": 0.0}, {} - ), - ( - f"{muscle}_Rn", "LIDanner", (x_off + 0.5, y_off - 1.0 * mirror_y_sign, 1.0), "Rn", [1.0, 0.0, 1.0], - {"v0": -60.0}, {} - ), - ( - f"{muscle}_IbIn_i", "LIDanner", (x_off + 1.0, y_off, 1.0), "Ib\\textsubscript{i}", [0.0, 0.0, 1.0], - {"v0": -60.0}, {} - ), - ( - f"{muscle}_IbIn_e", "LIDanner", (x_off + 1.0, y_off + 1.5 * mirror_y_sign, 1.0), "Ib\\textsubscript{e}", [0.0, 0.0, 1.0], - {"v0": -60.0}, {} - ), - ( - f"{muscle}_IIIn_RG", "LIDanner", (x_off - 1.0, y_off, 1.0), "II\\textsubscript{RG}", [0.0, 0.0, 1.0], - {"v0": -60.0}, {} - ), - ] - - def _generate_positions(self, num_muscles): - """Generate positions for the neurons.""" - spacing = 1.25 - return np.linspace(-spacing * num_muscles, spacing * num_muscles, num_muscles) - - -########################## -# CONNECT RG COMMISSURAL # -########################## -def connect_rg_commissural(): - """Connect RG's to Commissural.""" - - edges = {} - for limb in ("hind", "fore"): - for side in ("left", "right"): - - edges[join_str((side, limb, "RG", "F", "to", side, limb, "V2a"))] = ( - options.EdgeOptions( - source=join_str((side, limb, "RG", "F")), - target=join_str((side, limb, "V2a")), - weight=1.0, - type="excitatory", - visual=options.EdgeVisualOptions(), - ) - ) - - # edges[join_str((side, limb, "RG" "F", "to", side, limb, "V2a", "diag"))] = options.EdgeOptions( - # source=join_str((side, limb, "RG" "F")), - # target=join_str((side, limb, "V2a", "diag")), - # weight=0.5, - # type="excitatory", - # visual=options.EdgeVisualOptions(), - - # ) - edges[join_str((side, limb, "RG", "F", "to", side, limb, "V0D"))] = ( - options.EdgeOptions( - source=join_str((side, limb, "RG", "F")), - target=join_str((side, limb, "V0D")), - weight=0.7, - type="excitatory", - visual=options.EdgeVisualOptions(), - ) - ) - - edges[join_str((side, limb, "RG", "F", "to", side, limb, "V3F"))] = ( - options.EdgeOptions( - source=join_str((side, limb, "RG", "F")), - target=join_str((side, limb, "V3F")), - weight=0.35, - type="excitatory", - visual=options.EdgeVisualOptions(), - ) - ) - - edges[join_str((side, limb, "RG", "E", "to", side, limb, "V3E"))] = ( - options.EdgeOptions( - source=join_str((side, limb, "RG", "E")), - target=join_str((side, limb, "V3E")), - weight=0.35, - type="excitatory", - visual=options.EdgeVisualOptions(), - ) - ) - - edges[join_str((side, limb, "V2a", "to", side, limb, "V0V"))] = ( - options.EdgeOptions( - source=join_str((side, limb, "V2a")), - target=join_str((side, limb, "V0V")), - weight=1.0, - type="excitatory", - visual=options.EdgeVisualOptions(), - ) - ) - - edges[join_str((side, limb, "InV0V", "to", side, limb, "RG", "F"))] = ( - options.EdgeOptions( - source=join_str((side, limb, "InV0V")), - target=join_str((side, limb, "RG", "F")), - weight=-0.07, - type="inhibitory", - visual=options.EdgeVisualOptions(), - ) - ) - - for sides in (("left", "right"), ("right", "left")): - - edges[join_str((sides[0], limb, "V0V", "to", sides[1], limb, "InV0V"))] = ( - options.EdgeOptions( - source=join_str((sides[0], limb, "V0V")), - target=join_str((sides[1], limb, "InV0V")), - weight=0.6, - type="excitatory", - visual=options.EdgeVisualOptions(), - ) - ) - - edges[ - join_str((sides[0], limb, "V0D", "to", sides[1], limb, "RG", "F")) - ] = options.EdgeOptions( - source=join_str((sides[0], limb, "V0D")), - target=join_str((sides[1], limb, "RG", "F")), - weight=-0.07, - type="inhibitory", - visual=options.EdgeVisualOptions(), - ) - - edges[ - join_str((sides[0], limb, "V3F", "to", sides[1], limb, "RG", "F")) - ] = options.EdgeOptions( - source=join_str((sides[0], limb, "V3F")), - target=join_str((sides[1], limb, "RG", "F")), - weight=0.03, - type="excitatory", - visual=options.EdgeVisualOptions(), - ) - - edges[ - join_str((sides[0], limb, "V3E", "to", sides[1], limb, "RG", "E")) - ] = options.EdgeOptions( - source=join_str((sides[0], limb, "V3E")), - target=join_str((sides[1], limb, "RG", "E")), - weight=0.02, - type="excitatory", - visual=options.EdgeVisualOptions(), - ) - - edges[ - join_str((sides[0], limb, "V3E", "to", sides[1], limb, "RG", "In", "E")) - ] = options.EdgeOptions( - source=join_str((sides[0], limb, "V3E")), - target=join_str((sides[1], limb, "RG", "In", "E")), - weight=0.0, - type="excitatory", - visual=options.EdgeVisualOptions(), - ) - - edges[ - join_str( - (sides[0], limb, "V3E", "to", sides[1], limb, "RG", "In", "E2") - ) - ] = options.EdgeOptions( - source=join_str((sides[0], limb, "V3E")), - target=join_str((sides[1], limb, "RG", "In", "E2")), - weight=0.8, - type="excitatory", - visual=options.EdgeVisualOptions(), - ) - return edges - - -def connect_fore_hind_circuits(): - """Connect CPG's to Interneurons.""" - - edges = {} - for side in ("left", "right"): - edges[join_str((side, "fore", "RG", "F", "to", side, "fore", "Ini", "hom"))] = ( - options.EdgeOptions( - source=join_str((side, "fore", "RG", "F")), - target=join_str((side, "fore", "Ini", "hom")), - weight=0.70, - type="excitatory", - visual=options.EdgeVisualOptions(), - ) - ) - - edges[join_str((side, "fore", "RG", "F", "to", side, "V0D", "diag"))] = ( - options.EdgeOptions( - source=join_str((side, "fore", "RG", "F")), - target=join_str((side, "V0D", "diag")), - weight=0.50, - type="excitatory", - visual=options.EdgeVisualOptions(), - ) - ) - - edges[join_str((side, "fore", "Ini", "hom", "to", side, "hind", "RG", "F"))] = ( - options.EdgeOptions( - source=join_str((side, "fore", "Ini", "hom")), - target=join_str((side, "hind", "RG", "F")), - weight=-0.01, - type="excitatory", - visual=options.EdgeVisualOptions(), - ) - ) - - edges[join_str((side, "fore", "Sh2", "hom", "to", side, "hind", "RG", "F"))] = ( - options.EdgeOptions( - source=join_str((side, "fore", "Sh2", "hom")), - target=join_str((side, "hind", "RG", "F")), - weight=0.01, - type="excitatory", - visual=options.EdgeVisualOptions(), - ) - ) - - edges[join_str((side, "hind", "Sh2", "hom", "to", side, "fore", "RG", "F"))] = ( - options.EdgeOptions( - source=join_str((side, "hind", "Sh2", "hom")), - target=join_str((side, "fore", "RG", "F")), - weight=0.05, - type="excitatory", - visual=options.EdgeVisualOptions(), - ) - ) - - edges[ - join_str((side, "fore", "RG", "F", "to", side, "fore", "V0V", "diag")) - ] = options.EdgeOptions( - source=join_str((side, "fore", "RG", "F")), - target=join_str((side, "fore", "V0V", "diag")), - weight=0.325, - type="excitatory", - visual=options.EdgeVisualOptions(), - ) - - edges[join_str((side, "hind", "RG", "F", "to", side, "hind", "V3", "diag"))] = ( - options.EdgeOptions( - source=join_str((side, "hind", "RG", "F")), - target=join_str((side, "hind", "V3", "diag")), - weight=0.325, - type="excitatory", - visual=options.EdgeVisualOptions(), - ) - ) - - # edges[join_str((side, "fore", "V2a-diag", "to", side, "fore", "V0V", "diag"))] = options.EdgeOptions( - # source=join_str((side, "fore", "V2a-diag")), - # target=join_str((side, "fore", "V0V", "diag")), - # weight=0.9, - # type="excitatory", - # visual=options.EdgeVisualOptions(), - # ) - - # edges[join_str((side, "hind", "V2a-diag", "to", side, "hind", "V3", "diag"))] = options.EdgeOptions( - # source=join_str((side, "hind", "V2a-diag")), - # target=join_str((side, "hind", "V3", "diag")), - # weight=0.9, - # type="excitatory", - # visual=options.EdgeVisualOptions(), - # ) - - for sides in (("left", "right"), ("right", "left")): - edges[ - join_str((sides[0], "V0D", "diag", "to", sides[1], "hind", "RG", "F")) - ] = options.EdgeOptions( - source=join_str((sides[0], "V0D", "diag")), - target=join_str((sides[1], "hind", "RG", "F")), - weight=-0.01, - type="inhibitory", - visual=options.EdgeVisualOptions(), - ) - - edges[ - join_str( - (sides[0], "fore", "V0V", "diag", "to", sides[1], "hind", "RG", "F") - ) - ] = options.EdgeOptions( - source=join_str((sides[0], "fore", "V0V", "diag")), - target=join_str((sides[1], "hind", "RG", "F")), - weight=0.005, - type="excitatory", - visual=options.EdgeVisualOptions(), - ) - - edges[ - join_str( - (sides[0], "hind", "V3", "diag", "to", sides[1], "fore", "RG", "F") - ) - ] = options.EdgeOptions( - source=join_str((sides[0], "hind", "V3", "diag")), - target=join_str((sides[1], "fore", "RG", "F")), - weight=0.04, - type="excitatory", - visual=options.EdgeVisualOptions(), - ) - - return edges - - -def generate_network(): +def generate_network(n_iterations: int): """Generate network""" - N_ITERATIONS = int(1e4) - # Main network network_options = options.NetworkOptions( directed=True, multigraph=False, graph={"name": "mouse"}, + integration=options.IntegrationOptions.defaults(n_iterations=n_iterations), logs=options.NetworkLogOptions( - n_iterations=N_ITERATIONS, + n_iterations=n_iterations, ) ) + ############## + # MotorLayer # + ############## + # read muscle config file + muscles_config = read_yaml( + "/Users/tatarama/projects/work/research/neuromechanics/quadruped/mice/mouse-locomotion/data/config/muscles/quadruped_siggraph.yaml" + ) + + def update_muscle_name(name: str) -> str: + """ Update muscle name format """ + return name.replace("_", "-") + + muscles = { + "left": { + "hind": {"agonist": [], "antagonist": []}, + "fore": {"agonist": [], "antagonist": []} + }, + "right": { + "hind": {"agonist": [], "antagonist": []}, + "fore": {"agonist": [], "antagonist": []} + }, + } + + for name, muscle in muscles_config["muscles"].items(): + side = muscle["side"] + limb = muscle["limb"] + function = muscle.get("function", "agonist") + muscles[side][limb][function].append( + { + "name": name, + "type": muscle['type'], + "abbrev": muscle['abbrev'] + } + ) + # Generate rhythm centers scale = 1.0 for side in ("left", "right"): @@ -1118,6 +110,25 @@ def generate_network(): network_options.add_nodes((pattern.nodes()).values()) network_options.add_edges((pattern.edges()).values()) + # Motor Layer + motor_x = pf_x + 0.5*max( + len(muscles["left"]["fore"]["agonist"]), + len(muscles["left"]["fore"]["antagonist"]) + ) + motor_y = pf_y + 5.0 + left_fore_motor = MotorLayer( + muscles=muscles[side][limb], + name=f"{side}_{limb}_motor", + transform_mat=get_transform_mat( + angle=0, + off_x=off_x, + off_y=off_y, + mirror_x=mirror_x, + mirror_y=mirror_y, + ), + ) + network_options.add_nodes((left_fore_motor.nodes()).values()) + # Commissural comm_x, comm_y = rg_x - 7.0, rg_y + 0.0 off_x = -comm_x if side == "left" else comm_x @@ -1147,6 +158,7 @@ def generate_network(): ), ) network_options.add_nodes((commissural_drive.nodes()).values()) + # LPSN lpsn_x = rg_x - 9.0 lpsn_y = rg_y - 5.5 @@ -1235,20 +247,20 @@ def generate_network(): ) ) - data = NetworkData.from_options(network_options) + return network_options - network = PyNetwork.from_options(network_options) - # nnodes = len(network_options.nodes) - # integrator.set_initial_value(np.zeros(len(data.states.array),), 0.0) +def run_network(network_options: options.NetworkOptions): - # print("Data ------------", np.array(network.data.states.array)) - # data.to_file("/tmp/sim.hdf5") + data = NetworkData.from_options(network_options) + + network = PyNetwork.from_options(network_options) - # integrator.integrate(integrator.t + 1e-3) + # data.to_file("/tmp/sim.hdf5") # # Integrate + N_ITERATIONS = network_options.integration.n_iterations states = np.ones((len(data.states.array),)) * 1.0 # network_gui = NetworkGUI(data=data) @@ -1256,12 +268,11 @@ def generate_network(): # for index, node in enumerate(network_options.nodes): # print(index, node.name) + inputs_view = network.data.external_inputs.array for iteration in tqdm(range(0, N_ITERATIONS), colour="green", ascii=" >="): - network.data.external_inputs.array[:] = ( - np.ones((1,)) * (iteration / N_ITERATIONS) * 1.0 - ) + inputs_view[:] = (iteration / N_ITERATIONS) * 1.0 states = rk4(iteration * 1e-3, states, network.ode, step_size=1) - network.logging(iteration) + # network.logging(iteration) network.data.to_file("/tmp/network.h5") @@ -1352,13 +363,6 @@ def generate_network(): ) plt.show() - # generate_tikz_figure( - # graph, - # paths.get_project_data_path().joinpath("templates", "network",), - # "tikz-full-network.tex", - # paths.get_project_images_path().joinpath("quadruped_network.tex") - # ) - def generate_tikz_figure( network, template_env, template_name, export_path, add_axis=False, add_label=False @@ -1456,7 +460,9 @@ def main(): """Main.""" # Generate the network - generate_network() + network_options = generate_network(int(1e4)) + + run_network(network_options) # Run the network # run_network() From a37f965a002a32e081c31e852641c853e4195c9a Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Wed, 30 Oct 2024 16:47:05 -0400 Subject: [PATCH 101/316] [CORE] Added support for logging data --- farms_network/core/data.py | 12 ++++++ farms_network/core/network.pxd | 14 ++++-- farms_network/core/network.pyx | 78 ++++++++++++++++++++++++++++------ 3 files changed, 89 insertions(+), 15 deletions(-) diff --git a/farms_network/core/data.py b/farms_network/core/data.py index 8e6b4ff..fdc5b00 100644 --- a/farms_network/core/data.py +++ b/farms_network/core/data.py @@ -44,6 +44,7 @@ class NetworkData(NetworkDataCy): def __init__( self, + times, states, derivatives, connectivity, @@ -55,6 +56,7 @@ def __init__( """ Network data structure """ super().__init__() + self.times = times self.states = states self.derivatives = derivatives self.connectivity = connectivity @@ -67,6 +69,14 @@ def __init__( def from_options(cls, network_options: NetworkOptions): """ From options """ + buffer_size = network_options.logs.buffer_size + times = DoubleArray1D( + array=np.full( + shape=buffer_size, + fill_value=0, + dtype=NPDTYPE, + ) + ) states = NetworkStates.from_options(network_options) derivatives = NetworkStates.from_options(network_options) connectivity = NetworkConnectivity.from_options(network_options) @@ -83,6 +93,7 @@ def from_options(cls, network_options: NetworkOptions): dtype=NodeDataCy ) return cls( + times=times, states=states, derivatives=derivatives, connectivity=connectivity, @@ -94,6 +105,7 @@ def from_options(cls, network_options: NetworkOptions): def to_dict(self, iteration: int = None) -> Dict: """Convert data to dictionary""" return { + 'times': to_array(self.times.array), 'states': self.states.to_dict(), 'derivatives': self.derivatives.to_dict(), 'connectivity': self.connectivity.to_dict(), diff --git a/farms_network/core/network.pxd b/farms_network/core/network.pxd index 15b300b..cd386d4 100644 --- a/farms_network/core/network.pxd +++ b/farms_network/core/network.pxd @@ -1,6 +1,8 @@ -from .data_cy cimport NetworkDataCy -from .node cimport Node +cimport numpy as cnp + +from .data_cy cimport NetworkDataCy, NodeDataCy from .edge cimport Edge +from .node cimport Node cdef struct Network: @@ -27,5 +29,11 @@ cdef class PyNetwork: public NetworkDataCy data double[:] __tmp_node_outputs + Py_ssize_t iteration + # cpdef void step(self) - cpdef double[:] ode(self, double time, double[::1] states) + cpdef double[:] ode(self, double time, double[::1] states) noexcept + # cdef void odeint(self, double time, double[:] states, double[:] derivatives) noexcept + # cpdef void step(self) noexcept + + cpdef void logging(self, Py_ssize_t) noexcept diff --git a/farms_network/core/network.pyx b/farms_network/core/network.pyx index f2e1c14..16194b5 100644 --- a/farms_network/core/network.pyx +++ b/farms_network/core/network.pyx @@ -41,7 +41,7 @@ from .options import (EdgeOptions, IntegrationOptions, NetworkOptions, NodeOptions) -cpdef rk4(double time, cnp.ndarray[double, ndim=1] state, func, double step_size): +cpdef double[:] rk4(double time, double[:] state, func, double step_size): """ Runge-kutta order 4 integrator """ K1 = np.array(func(time, state)) @@ -57,7 +57,7 @@ cdef void ode( double time, NetworkDataCy data, Network* network, - double[:] tmp_node_outputs + double[:] node_outputs_tmp, ) noexcept: """ C Implementation to compute full network state """ cdef int j, step, steps, nnodes @@ -81,7 +81,7 @@ cdef void ode( cdef double* weights = &data.connectivity.weights[0] cdef unsigned int* input_neurons_indices = &data.connectivity.indices[0] - cdef double* tmp_node_outputs_ptr = &tmp_node_outputs[0] + cdef double* node_outputs_tmp_ptr = &node_outputs_tmp[0] for j in range(nnodes): __node = nodes[j][0] @@ -97,7 +97,7 @@ cdef void ode( nodes[j], edges + input_neurons_indices[j], ) - tmp_node_outputs_ptr[j] = __node.output( + node_outputs_tmp_ptr[j] = __node.output( time, states + states_indices[j], external_input[j], @@ -107,8 +107,6 @@ cdef void ode( nodes[j], edges + input_neurons_indices[j], ) - # Update all node outputs data for next iteration - data.outputs.array[:] = tmp_node_outputs[:] cdef class PyNetwork: @@ -139,6 +137,7 @@ cdef class PyNetwork: self.pyedges = [] self.__tmp_node_outputs = np.zeros((self.network.nnodes,)) self.setup_network(network_options, self.data) + self.iteration = 0 def __dealloc__(self): """ Deallocate any manual memory as part of clean up """ @@ -193,12 +192,6 @@ cdef class PyNetwork: edge = Edge.from_options(edge_options) return edge - cpdef double[:] ode(self, double time, double[::1] states): - """ ODE Wrapper for numerical integrators """ - self.data.states.array[:] = states[:] - ode(time, self.data, self.network, self.__tmp_node_outputs) - return self.data.derivatives.array - @property def nnodes(self): """ Number of nodes in the network """ @@ -213,3 +206,64 @@ cdef class PyNetwork: def nstates(self): """ Number of states in the network """ return self.network.nstates + + # cpdef void step(self) noexcept: + # """ Step the network state """ + # self.integrator.step(self.odeint, 0.0, self.data.states.array, &self) + # # self.data.states.array = rk4(0.0, self.data.states.array, self.odeint, step_size=1e-3) + + # cdef void odeint( + # self, + # double time, + # double[:] states, + # double[:] derivatives, + # ) noexcept: + # """ ODE function signature for integrators """ + # data.states.array[:] = curr_states[:] + # ode(time, data, network, node_outputs_tmp) + # # Update all node derivatives and outputs data for next iteration + # data.outputs.array[:] = self.__tmp_node_outputs + # derivatives[:] = self.data.derivatives.array + + cpdef void logging(self, Py_ssize_t iteration) noexcept: + """ Log network data """ + cdef Py_ssize_t j, nnodes + nnodes = self.network.nnodes + cdef NetworkDataCy data = self.data + + # cdef double[:] states = data.states.array + cdef double* states_ptr = &data.states.array[0] + cdef unsigned int[:] state_indices = data.states.indices + cdef Py_ssize_t state_idx, start_idx, end_idx, state_iteration + + # cdef double[:] derivatives = data.derivatives.array + cdef double* derivatives_ptr = &data.derivatives.array[0] + cdef unsigned int[:] derivatives_indices = data.derivatives.indices + + cdef double[:] outputs = data.outputs.array + + cdef double[:] external_inputs = data.external_inputs.array + + cdef NodeDataCy node_data + cdef NodeDataCy[:] nodes_data = self.data.nodes + for j in range(nnodes): + # Log states + node_data = nodes_data[j] + start_idx = state_indices[j] + end_idx = state_indices[j+1] + state_iteration = 0 + for state_idx in range(start_idx, end_idx): + node_data.states.array[iteration, state_iteration] = states_ptr[state_idx] + node_data.derivatives.array[iteration, state_iteration] = derivatives_ptr[state_idx] + state_iteration += 1 + node_data.output.array[iteration] = outputs[j] + node_data.external_input.array[iteration] = external_inputs[j] + + cpdef double[:] ode(self, double time, double[::1] states) noexcept: + """ ODE Wrapper for numerical integrators """ + + cdef NetworkDataCy data = self.data + data.states.array[:] = states[:] + ode(time, data, self.network, self.__tmp_node_outputs) + data.outputs.array[:] = self.__tmp_node_outputs + return data.derivatives.array From d7f9f63b887f5a79a4a27034fc7950b9bbc6d9dc Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Thu, 31 Oct 2024 10:35:58 -0400 Subject: [PATCH 102/316] [WORKFLOW] Added pip install to build to use cython files for docs --- .github/workflows/github-page-builder.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/github-page-builder.yml b/.github/workflows/github-page-builder.yml index 69d5fe8..922a863 100644 --- a/.github/workflows/github-page-builder.yml +++ b/.github/workflows/github-page-builder.yml @@ -27,6 +27,7 @@ jobs: sudo apt-get update sudo apt-get install -y make automake gcc g++ subversion python3-dev pip install -r ducks/source/requirements.txt + pip install . - name: Build HTML run: | From 8da51746f042e18aae819c6e255785df47394790 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Thu, 31 Oct 2024 10:39:01 -0400 Subject: [PATCH 103/316] [MAIN] Added farms-core to requirements --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index e69de29..6282e8b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -0,0 +1 @@ +git+https://github.com/farmsim/farms_core.git From e3a23b2c1a9b5a718ea23f1f002f0531bd747f8f Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Thu, 31 Oct 2024 10:39:22 -0400 Subject: [PATCH 104/316] [WORKFLOW] Added installation for repository requirements --- .github/workflows/github-page-builder.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/github-page-builder.yml b/.github/workflows/github-page-builder.yml index 922a863..7326471 100644 --- a/.github/workflows/github-page-builder.yml +++ b/.github/workflows/github-page-builder.yml @@ -27,6 +27,7 @@ jobs: sudo apt-get update sudo apt-get install -y make automake gcc g++ subversion python3-dev pip install -r ducks/source/requirements.txt + pip install -r requirements.txt pip install . - name: Build HTML From 9ed1919e3f121743479520c0fb9023688e631eac Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Thu, 31 Oct 2024 10:52:12 -0400 Subject: [PATCH 105/316] [WORKFLOW] Downgrading setuptools version to resolve fetch_build_eggs --- .github/workflows/github-page-builder.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/github-page-builder.yml b/.github/workflows/github-page-builder.yml index 7326471..537da6f 100644 --- a/.github/workflows/github-page-builder.yml +++ b/.github/workflows/github-page-builder.yml @@ -26,6 +26,8 @@ jobs: run: | sudo apt-get update sudo apt-get install -y make automake gcc g++ subversion python3-dev + pip show setuptools + pip install setuptools==70.3.0 pip install -r ducks/source/requirements.txt pip install -r requirements.txt pip install . From 0754e0eba5319bbf40ae3fa43962dadb6dfa8896 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Thu, 31 Oct 2024 11:01:35 -0400 Subject: [PATCH 106/316] [WORKFLOW] Attemp to resolve pip issue with --use-pep517 --- .github/workflows/github-page-builder.yml | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/.github/workflows/github-page-builder.yml b/.github/workflows/github-page-builder.yml index 537da6f..09341c1 100644 --- a/.github/workflows/github-page-builder.yml +++ b/.github/workflows/github-page-builder.yml @@ -26,11 +26,9 @@ jobs: run: | sudo apt-get update sudo apt-get install -y make automake gcc g++ subversion python3-dev - pip show setuptools - pip install setuptools==70.3.0 - pip install -r ducks/source/requirements.txt - pip install -r requirements.txt - pip install . + pip install --use-pep517 -r ducks/source/requirements.txt + pip install --use-pep517 -r requirements.txt + pip install --use-pep517 . - name: Build HTML run: | From be7ab0f720baa3162b3d11504e480a87a95dd14c Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Thu, 31 Oct 2024 11:16:59 -0400 Subject: [PATCH 107/316] [WORKFLOW] Added explicit cython and numpy pip installation steps --- .github/workflows/github-page-builder.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/github-page-builder.yml b/.github/workflows/github-page-builder.yml index 09341c1..8418c92 100644 --- a/.github/workflows/github-page-builder.yml +++ b/.github/workflows/github-page-builder.yml @@ -26,6 +26,8 @@ jobs: run: | sudo apt-get update sudo apt-get install -y make automake gcc g++ subversion python3-dev + pip install cython + pip install numpy pip install --use-pep517 -r ducks/source/requirements.txt pip install --use-pep517 -r requirements.txt pip install --use-pep517 . From 878a8cf09434acda493d1645d8d837e79a541775 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Thu, 31 Oct 2024 11:32:00 -0400 Subject: [PATCH 108/316] [WORKFLOW] Explicit installation of farms core --- .github/workflows/github-page-builder.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/github-page-builder.yml b/.github/workflows/github-page-builder.yml index 8418c92..f225b66 100644 --- a/.github/workflows/github-page-builder.yml +++ b/.github/workflows/github-page-builder.yml @@ -29,7 +29,10 @@ jobs: pip install cython pip install numpy pip install --use-pep517 -r ducks/source/requirements.txt - pip install --use-pep517 -r requirements.txt + git clone https://github.com/farmsim/farms_core.git + cd farms_core + python setup.py build_ext --inplace + cd .. pip install --use-pep517 . - name: Build HTML From b64d10c436aff8d31bf097d0a28414908d613eed Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Thu, 31 Oct 2024 13:47:58 -0400 Subject: [PATCH 109/316] [WORKFLOW] Replaced pip installation with setuptools for farms-network --- .github/workflows/github-page-builder.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/github-page-builder.yml b/.github/workflows/github-page-builder.yml index f225b66..3066664 100644 --- a/.github/workflows/github-page-builder.yml +++ b/.github/workflows/github-page-builder.yml @@ -32,8 +32,8 @@ jobs: git clone https://github.com/farmsim/farms_core.git cd farms_core python setup.py build_ext --inplace - cd .. - pip install --use-pep517 . + cd ../farms_network + python setup.py build_ext --inplace - name: Build HTML run: | From 4bb52ce4c2dd560e9bc7b4072aeac764dab9c1ea Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Thu, 31 Oct 2024 13:51:11 -0400 Subject: [PATCH 110/316] [WORKFLOW] Fixed dependencies installation path --- .github/workflows/github-page-builder.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/github-page-builder.yml b/.github/workflows/github-page-builder.yml index 3066664..a86df58 100644 --- a/.github/workflows/github-page-builder.yml +++ b/.github/workflows/github-page-builder.yml @@ -29,6 +29,7 @@ jobs: pip install cython pip install numpy pip install --use-pep517 -r ducks/source/requirements.txt + cd ../ git clone https://github.com/farmsim/farms_core.git cd farms_core python setup.py build_ext --inplace From 6d42daccadb86ca55f6d1bec00d9ed7472dbec5b Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Thu, 31 Oct 2024 13:54:24 -0400 Subject: [PATCH 111/316] [WORKFLOW] Added pip install to farms-core dependency --- .github/workflows/github-page-builder.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/github-page-builder.yml b/.github/workflows/github-page-builder.yml index a86df58..dff02f4 100644 --- a/.github/workflows/github-page-builder.yml +++ b/.github/workflows/github-page-builder.yml @@ -33,6 +33,7 @@ jobs: git clone https://github.com/farmsim/farms_core.git cd farms_core python setup.py build_ext --inplace + pip install . cd ../farms_network python setup.py build_ext --inplace From 27d2b4db2d04ef5a8be26a6db319d0f9f969301b Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Thu, 31 Oct 2024 14:34:37 -0400 Subject: [PATCH 112/316] [WORKFLOW] Added pip debug statements --- .github/workflows/github-page-builder.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/github-page-builder.yml b/.github/workflows/github-page-builder.yml index dff02f4..dcd38a3 100644 --- a/.github/workflows/github-page-builder.yml +++ b/.github/workflows/github-page-builder.yml @@ -33,7 +33,8 @@ jobs: git clone https://github.com/farmsim/farms_core.git cd farms_core python setup.py build_ext --inplace - pip install . + pip --version + pip list cd ../farms_network python setup.py build_ext --inplace From f14c3a1d8c5cff701ac199068e30f074113ba771 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Thu, 31 Oct 2024 17:49:47 -0400 Subject: [PATCH 113/316] [WORKFLOW] Added explicit python versions to fix installation --- .github/workflows/github-page-builder.yml | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/.github/workflows/github-page-builder.yml b/.github/workflows/github-page-builder.yml index dcd38a3..4c6c431 100644 --- a/.github/workflows/github-page-builder.yml +++ b/.github/workflows/github-page-builder.yml @@ -26,15 +26,18 @@ jobs: run: | sudo apt-get update sudo apt-get install -y make automake gcc g++ subversion python3-dev - pip install cython - pip install numpy - pip install --use-pep517 -r ducks/source/requirements.txt + python3 -m pip install pip==23.2.1 + python3 -m pip install distlib==0.3.8 + python3 -m pip install setuptools==65.5.0 + python3 -m pip install cython + python3 -m pip install numpy + python3 -m pip install --use-pep517 -r ducks/source/requirements.txt cd ../ git clone https://github.com/farmsim/farms_core.git cd farms_core - python setup.py build_ext --inplace - pip --version - pip list + python3 setup.py build_ext --inplace + python3 -m pip --version + python3 -m pip list cd ../farms_network python setup.py build_ext --inplace From 56f66e38cfbc97c737a2ff248e0ff32357677872 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Thu, 31 Oct 2024 17:56:49 -0400 Subject: [PATCH 114/316] [WORKFLOW] Test farms-core install failure again --- .github/workflows/github-page-builder.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/github-page-builder.yml b/.github/workflows/github-page-builder.yml index 4c6c431..d86ba26 100644 --- a/.github/workflows/github-page-builder.yml +++ b/.github/workflows/github-page-builder.yml @@ -35,7 +35,7 @@ jobs: cd ../ git clone https://github.com/farmsim/farms_core.git cd farms_core - python3 setup.py build_ext --inplace + python3 -m pip install . python3 -m pip --version python3 -m pip list cd ../farms_network From ede0c410becebe0ca499584147af3d8c616a2e79 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Thu, 31 Oct 2024 18:00:55 -0400 Subject: [PATCH 115/316] [WORKFLOW] Added --no-use-pep517 to force legacy behavior --- .github/workflows/github-page-builder.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/github-page-builder.yml b/.github/workflows/github-page-builder.yml index d86ba26..f6f84b7 100644 --- a/.github/workflows/github-page-builder.yml +++ b/.github/workflows/github-page-builder.yml @@ -26,16 +26,16 @@ jobs: run: | sudo apt-get update sudo apt-get install -y make automake gcc g++ subversion python3-dev - python3 -m pip install pip==23.2.1 - python3 -m pip install distlib==0.3.8 - python3 -m pip install setuptools==65.5.0 - python3 -m pip install cython - python3 -m pip install numpy - python3 -m pip install --use-pep517 -r ducks/source/requirements.txt + python3 -m pip install --no-use-pep517 pip==23.2.1 + python3 -m pip install --no-use-pep517 distlib==0.3.8 + python3 -m pip install --no-use-pep517 setuptools==65.5.0 + python3 -m pip install --no-use-pep517 cython + python3 -m pip install --no-use-pep517 numpy + python3 -m pip install --no-use-pep517 -r ducks/source/requirements.txt cd ../ git clone https://github.com/farmsim/farms_core.git cd farms_core - python3 -m pip install . + python3 -m pip --no-use-pep517 install . python3 -m pip --version python3 -m pip list cd ../farms_network From b9002051eed222e7d121fef32c9c5c3cf91c00c0 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Thu, 31 Oct 2024 18:02:40 -0400 Subject: [PATCH 116/316] [WORKFLOW] Added setuptools and wheel installation --- .github/workflows/github-page-builder.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/github-page-builder.yml b/.github/workflows/github-page-builder.yml index f6f84b7..f4be509 100644 --- a/.github/workflows/github-page-builder.yml +++ b/.github/workflows/github-page-builder.yml @@ -26,6 +26,7 @@ jobs: run: | sudo apt-get update sudo apt-get install -y make automake gcc g++ subversion python3-dev + python3 -m pip install setuptools wheel python3 -m pip install --no-use-pep517 pip==23.2.1 python3 -m pip install --no-use-pep517 distlib==0.3.8 python3 -m pip install --no-use-pep517 setuptools==65.5.0 From 012b4714085039f5b91658aed660ff3f98025841 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Thu, 31 Oct 2024 18:04:57 -0400 Subject: [PATCH 117/316] [WORKFLOW] Fixed --no-use-pep517 typo --- .github/workflows/github-page-builder.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/github-page-builder.yml b/.github/workflows/github-page-builder.yml index f4be509..d4fc159 100644 --- a/.github/workflows/github-page-builder.yml +++ b/.github/workflows/github-page-builder.yml @@ -36,7 +36,7 @@ jobs: cd ../ git clone https://github.com/farmsim/farms_core.git cd farms_core - python3 -m pip --no-use-pep517 install . + python3 -m pip install --no-use-pep517 . python3 -m pip --version python3 -m pip list cd ../farms_network From 1212ad66e867e42ff4dc43c63e924bcc428a0e9c Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Thu, 31 Oct 2024 18:20:11 -0400 Subject: [PATCH 118/316] [WORKFLOW] Added networkx as install dependency --- .github/workflows/github-page-builder.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/github-page-builder.yml b/.github/workflows/github-page-builder.yml index d4fc159..157fd8a 100644 --- a/.github/workflows/github-page-builder.yml +++ b/.github/workflows/github-page-builder.yml @@ -32,6 +32,7 @@ jobs: python3 -m pip install --no-use-pep517 setuptools==65.5.0 python3 -m pip install --no-use-pep517 cython python3 -m pip install --no-use-pep517 numpy + python3 -m pip install --no-use-pep517 networkx python3 -m pip install --no-use-pep517 -r ducks/source/requirements.txt cd ../ git clone https://github.com/farmsim/farms_core.git From bfa16e5fcdfaad268708501173cad0c7e4f50c62 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Fri, 1 Nov 2024 10:38:57 -0400 Subject: [PATCH 119/316] [PAGES] Added CNAME to use subdomains --- CNAME | 1 + 1 file changed, 1 insertion(+) create mode 100644 CNAME diff --git a/CNAME b/CNAME new file mode 100644 index 0000000..c598ed3 --- /dev/null +++ b/CNAME @@ -0,0 +1 @@ +network.farmsim.dev \ No newline at end of file From 69550e3d0efca232ad5cc781216ebf53da26007c Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Fri, 8 Nov 2024 15:14:43 -0500 Subject: [PATCH 120/316] [CORE][OPTIONS] Added feature to load saved network_options from disk --- farms_network/core/options.py | 253 ++++++++++++++++++++++++++++++++-- 1 file changed, 243 insertions(+), 10 deletions(-) diff --git a/farms_network/core/options.py b/farms_network/core/options.py index e9ffeb9..3c362f6 100644 --- a/farms_network/core/options.py +++ b/farms_network/core/options.py @@ -1,6 +1,6 @@ """ Options to configure the neural and network models """ -from typing import Iterable, List, Self +from typing import Dict, Iterable, List, Self, Union import matplotlib.pyplot as plt import networkx as nx @@ -23,9 +23,11 @@ def __init__(self, **kwargs): self.multigraph: bool = kwargs.pop("multigraph", False) self.graph: dict = kwargs.pop("graph", {"name": ""}) self.units = kwargs.pop("units", None) - self.logs = kwargs.pop("logs") + self.logs: NetworkLogOptions = kwargs.pop("logs") - self.integration = kwargs.pop("integration", IntegrationOptions.defaults()) + self.integration = kwargs.pop( + "integration", IntegrationOptions.defaults() + ) self.nodes: List[NodeOptions] = kwargs.pop("nodes", []) self.edges: List[EdgeOptions] = kwargs.pop("edges", []) @@ -33,6 +35,37 @@ def __init__(self, **kwargs): if kwargs: raise Exception(f'Unknown kwargs: {kwargs}') + @classmethod + def from_options(cls, kwargs): + """ From options """ + options = {} + options["directed"] = kwargs["directed"] + options["multigraph"] = kwargs["multigraph"] + options["graph"] = kwargs["graph"] + options["units"] = kwargs["units"] + # Log options + options["logs"] = NetworkLogOptions.from_options(kwargs["logs"]) + # Integration options + options["integration"] = IntegrationOptions.from_options(kwargs["integration"]) + # Nodes + node_types = { + "linear": LinearNodeOptions, + "external_relay": ExternalRelayNodeOptions, + "oscillator": OscillatorNodeOptions, + "li_danner": LIDannerNodeOptions, + "li_nap_danner": LINaPDannerNodeOptions, + } + options["nodes"] = [ + node_types[node["model"]].from_options(node) + for node in kwargs["nodes"] + ] + # Edges + options["edges"] = [ + EdgeOptions.from_options(edge) + for edge in kwargs["edges"] + ] + return cls(**options) + def add_node(self, options: "NodeOptions"): """ Add a node if it does not already exist in the list """ assert isinstance(options, NodeOptions), f"{type(options)} not an instance of NodeOptions" @@ -108,6 +141,11 @@ def defaults(cls, **kwargs): options["checks"] = kwargs.pop("checks", True) return cls(**options) + @classmethod + def from_options(cls, kwargs: Dict): + """ From options """ + return cls(**kwargs) + ################### # Logging Options # @@ -135,6 +173,12 @@ def __init__(self, n_iterations: int, **kwargs): if kwargs: raise Exception(f'Unknown kwargs: {kwargs}') + @classmethod + def from_options(cls, kwargs: Dict): + """ From options """ + n_iterations = kwargs.pop("n_iterations") + return cls(n_iterations, **kwargs) + ########################### # Node Base Class Options # @@ -147,7 +191,7 @@ def __init__(self, **kwargs): super().__init__() self.name: str = kwargs.pop("name") - self.model: str = kwargs.pop("model") + self.model: str = kwargs.pop("model", None) self.parameters: NodeParameterOptions = kwargs.pop("parameters") self.visual: NodeVisualOptions = kwargs.pop("visual") self.state: NodeStateOptions = kwargs.pop("state") @@ -167,6 +211,16 @@ def __eq__(self, other): def __hash__(self): return hash(self.name) # Hash based on the node name (or any unique property) + @classmethod + def from_options(cls, kwargs: Dict): + """ Load from options """ + options = {} + options["name"] = kwargs.pop("name") + options["parameters"] = kwargs.pop("parameters") + options["visual"] = kwargs.pop("visual") + options["state"] = kwargs.pop("state") + return cls(**options) + class NodeParameterOptions(Options): """ Base class for node specific parameters """ @@ -174,6 +228,11 @@ class NodeParameterOptions(Options): def __init__(self): super().__init__() + @classmethod + def from_options(cls, kwargs: Dict): + """ From options """ + return cls(**kwargs) + class NodeStateOptions(Options): """ Base class for node specific state options """ @@ -196,6 +255,11 @@ def from_kwargs(cls, **kwargs): raise Exception(f'Unknown kwargs: {kwargs}') return cls(initial=initial) + @classmethod + def from_options(cls, kwargs: Dict): + """ From options """ + return cls(**kwargs) + class NodeLogOptions(Options): """ Log options for the node level """ @@ -206,6 +270,11 @@ def __init__(self, buffer_size: int, enable: bool, **kwargs): if kwargs: raise Exception(f'Unknown kwargs: {kwargs}') + @classmethod + def from_options(cls, kwargs: Dict): + """ From options """ + return cls(**kwargs) + class NodeVisualOptions(Options): """ Base class for node visualization parameters """ @@ -221,6 +290,11 @@ def __init__(self, **kwargs): if kwargs: raise Exception(f'Unknown kwargs: {kwargs}') + @classmethod + def from_options(cls, kwargs: Dict): + """ From options """ + return cls(**kwargs) + ################ # Edge Options # @@ -252,6 +326,22 @@ def __eq__(self, other): ) return False + @classmethod + def from_options(cls, kwargs: Dict): + """ From options """ + options = {} + options["source"] = kwargs["source"] + options["target"] = kwargs["target"] + options["weight"] = kwargs["weight"] + options["type"] = kwargs["type"] + options["parameters"] = EdgeParameterOptions.from_options( + kwargs["parameters"] + ) + options["visual"] = EdgeVisualOptions.from_options( + kwargs["visual"] + ) + return cls(**options) + class EdgeParameterOptions(Options): """ Base class for edge specific parameters """ @@ -259,6 +349,11 @@ class EdgeParameterOptions(Options): def __init__(self): super().__init__() + @classmethod + def from_options(cls, kwargs: Dict): + """ From options """ + return cls(**kwargs) + class EdgeVisualOptions(Options): """ Base class for edge visualization parameters """ @@ -280,6 +375,11 @@ def __init__(self, **kwargs): if kwargs: raise Exception(f'Unknown kwargs: {kwargs}') + @classmethod + def from_options(cls, kwargs: Dict): + """ From options """ + return cls(**kwargs) + ################################ # External Relay Model Options # @@ -307,6 +407,15 @@ def __init__(self, **kwargs): if kwargs: raise Exception(f'Unknown kwargs: {kwargs}') + @classmethod + def from_options(cls, kwargs: Dict): + """ Load from options """ + options = {} + options["name"] = kwargs.pop("name") + options["parameters"] = NodeParameterOptions() + options["visual"] = kwargs.pop("visual") + return cls(**options) + ######################## # Linear Model Options # @@ -330,6 +439,20 @@ def __init__(self, **kwargs): if kwargs: raise Exception(f'Unknown kwargs: {kwargs}') + @classmethod + def from_options(cls, kwargs: Dict): + """ Load from options """ + options = {} + options["name"] = kwargs.pop("name") + options["parameters"] = LinearParameterOptions.from_options( + kwargs["parameters"] + ) + options["visual"] = NodeVisualOptions.from_options( + kwargs["visual"] + ) + options["state"] = None + return cls(**options) + class LinearParameterOptions(NodeParameterOptions): @@ -374,6 +497,22 @@ def __init__(self, **kwargs): if kwargs: raise Exception(f'Unknown kwargs: {kwargs}') + @classmethod + def from_options(cls, kwargs: Dict): + """ Load from options """ + options = {} + options["name"] = kwargs.pop("name") + options["parameters"] = OscillatorNodeParameterOptions.from_options( + kwargs["parameters"] + ) + options["visual"] = NodeVisualOptions.from_options( + kwargs["visual"] + ) + options["state"] = OscillatorStateOptions.from_options( + kwargs["state"] + ) + return cls(**options) + class OscillatorNodeParameterOptions(NodeParameterOptions): @@ -453,6 +592,22 @@ def __init__(self, **kwargs): if kwargs: raise Exception(f'Unknown kwargs: {kwargs}') + @classmethod + def from_options(cls, kwargs: Dict): + """ Load from options """ + options = {} + options["name"] = kwargs.pop("name") + options["parameters"] = LIDannerParameterOptions.from_options( + kwargs["parameters"] + ) + options["visual"] = NodeVisualOptions.from_options( + kwargs["visual"] + ) + options["state"] = LIDannerStateOptions.from_options( + kwargs["state"] + ) + return cls(**options) + class LIDannerParameterOptions(NodeParameterOptions): """ @@ -538,6 +693,22 @@ def __init__(self, **kwargs): if kwargs: raise Exception(f'Unknown kwargs: {kwargs}') + @classmethod + def from_options(cls, kwargs: Dict): + """ Load from options """ + options = {} + options["name"] = kwargs.pop("name") + options["parameters"] = LINaPDannerParameterOptions.from_options( + kwargs["parameters"] + ) + options["visual"] = NodeVisualOptions.from_options( + kwargs["visual"] + ) + options["state"] = LINaPDannerStateOptions.from_options( + kwargs["state"] + ) + return cls(**options) + class LINaPDannerParameterOptions(NodeParameterOptions): """ Class to define the parameters of Leaky integrator danner node model """ @@ -607,9 +778,71 @@ def __init__(self, **kwargs): assert len(self.initial) == 2, f"Number of initial states {len(self.initial)} should be 2" +#################### +# Izhikevich Model # +#################### +class IzhikevichNodeOptions(NodeOptions): + """ Class to define the properties of Leaky integrator danner node model """ + + def __init__(self, **kwargs): + """ Initialize """ + model = "izhikevich" + super().__init__( + name=kwargs.pop("name"), + model=model, + parameters=kwargs.pop("parameters"), + visual=kwargs.pop("visual"), + state=kwargs.pop("state"), + ) + self._nstates = 2 + self._nparameters = 5 + + if kwargs: + raise Exception(f'Unknown kwargs: {kwargs}') + + +class IzhikevichParameterOptions(NodeParameterOptions): + """ Class to define the parameters of Leaky integrator danner node model """ + + def __init__(self, **kwargs): + super().__init__() + + self.recovery_time = kwargs.pop("recovery_time") # pF + self.recovery_sensitivity = kwargs.pop("recovery_sensitivity") # nS + self.membrane_reset = kwargs.pop("membrane_reset") # mV + self.recovery_reset = kwargs.pop("recovery_reset") # mV + self.membrane_threshold = kwargs.pop("membrane_threshold") # mV + + @classmethod + def defaults(cls, **kwargs): + """ Get the default parameters for LI NaP Danner Node model """ + + options = {} + + options["recovery_time"] = kwargs.pop("recovery_time", 0.02) # pF + options["recovery_sensitivity"] = kwargs.pop("recovery_sensitivity", 0.2) # nS + options["membrane_reset"] = kwargs.pop("membrane_reset", -65.0) # mV + options["recovery_reset"] = kwargs.pop("recovery_reset", 2) # mV + options["membrane_threshold"] = kwargs.pop("membrane_threshold", 30.0) # mV + + return cls(**options) + + +class IzhikevichStateOptions(NodeStateOptions): + """ LI Danner node state options """ + + STATE_NAMES = ["v", "u"] + + def __init__(self, **kwargs): + super().__init__( + initial=kwargs.pop("initial"), + names=IzhikevichStateOptions.STATE_NAMES + ) + assert len(self.initial) == 2, f"Number of initial states {len(self.initial)} should be 2" + ######## -# MAIN # +# Main # ######## def main(): """ Main """ @@ -620,9 +853,9 @@ def main(): li_opts = LIDannerNodeOptions( name="li", - parameters=LINaPDannerParameterOptions.defaults(), + parameters=IzhikevichParameterOptions.defaults(), visual=NodeVisualOptions(), - state=LINaPDannerStateOptions.from_kwargs( + state=IzhikevichStateOptions.from_kwargs( v0=-60.0, h0=0.1 ), ) @@ -630,11 +863,11 @@ def main(): print(f"Is hashable {isinstance(li_opts, typing.Hashable)}") network_opts.add_node(li_opts) - li_opts = LINaPDannerNodeOptions( + li_opts = IzhikevichNodeOptions( name="li-2", - parameters=LINaPDannerParameterOptions.defaults(), + parameters=IzhikevichParameterOptions.defaults(), visual=NodeVisualOptions(), - state=LINaPDannerStateOptions.from_kwargs( + state=IzhikevichStateOptions.from_kwargs( v0=-60.0, h0=0.1 ), ) From 58dd90697cf5415e892b75a6e8a5ea4490943c94 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Mon, 11 Nov 2024 15:42:53 -0500 Subject: [PATCH 121/316] [IJSPEERT07] Added heatmap to visualize network connections and weights --- examples/ijspeert07/run.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/examples/ijspeert07/run.py b/examples/ijspeert07/run.py index ead852a..b79862b 100644 --- a/examples/ijspeert07/run.py +++ b/examples/ijspeert07/run.py @@ -18,6 +18,7 @@ NetworkStates) from farms_network.core.network import PyNetwork, rk4 from tqdm import tqdm +import seaborn as sns plt.rcParams['text.usetex'] = False @@ -270,6 +271,13 @@ def generate_network(): min_target_margin=5, connectionstyle="arc3,rad=-0.2", ) + plt.figure() + sparse_array = nx.to_scipy_sparse_array(graph) + sns.heatmap( + sparse_array.todense(), cbar=False, square=True, + linewidths=0.5, + annot=True + ) plt.show() # generate_tikz_figure( From 21e2a826192783dff04ae46c02f83c61ae0a8d55 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Tue, 12 Nov 2024 10:47:55 -0500 Subject: [PATCH 122/316] [NUMERIC] Added basic runge-kutta4 integration scheme --- farms_network/numeric/__init__.py | 0 farms_network/numeric/integrators_cy.pxd | 19 ++++++++ farms_network/numeric/integrators_cy.pyx | 57 ++++++++++++++++++++++++ farms_network/numeric/system.pxd | 3 ++ farms_network/numeric/system.pyx | 13 ++++++ 5 files changed, 92 insertions(+) create mode 100644 farms_network/numeric/__init__.py create mode 100644 farms_network/numeric/integrators_cy.pxd create mode 100644 farms_network/numeric/integrators_cy.pyx create mode 100644 farms_network/numeric/system.pxd create mode 100644 farms_network/numeric/system.pyx diff --git a/farms_network/numeric/__init__.py b/farms_network/numeric/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/farms_network/numeric/integrators_cy.pxd b/farms_network/numeric/integrators_cy.pxd new file mode 100644 index 0000000..a2e208d --- /dev/null +++ b/farms_network/numeric/integrators_cy.pxd @@ -0,0 +1,19 @@ +from farms_core.array.array_cy cimport DoubleArray1D + +from .system cimport ODESystem + +include 'types.pxd' + + +cdef class RK4Solver: + cdef: + DoubleArray1D k1 + DoubleArray1D k2 + DoubleArray1D k3 + DoubleArray1D k4 + DoubleArray1D states_tmp + + Py_ssize_t dim + double dt + + cdef void step(self, ODESystem sys, double time, double[:] state) noexcept diff --git a/farms_network/numeric/integrators_cy.pyx b/farms_network/numeric/integrators_cy.pyx new file mode 100644 index 0000000..a6f86e7 --- /dev/null +++ b/farms_network/numeric/integrators_cy.pyx @@ -0,0 +1,57 @@ +import numpy as np + +NPDTYPE = np.float64 + + +cdef class RK4Solver: + + def __init__(self, int dim, double dt): + self.dim = dim + self.dt = dt + self.k1 = DoubleArray1D( + array=np.full(shape=dim, fill_value=0.0, dtype=NPDTYPE,) + ) + self.k2 = DoubleArray1D( + array=np.full(shape=dim, fill_value=0.0, dtype=NPDTYPE,) + ) + self.k3 = DoubleArray1D( + array=np.full(shape=dim, fill_value=0.0, dtype=NPDTYPE,) + ) + self.k4 = DoubleArray1D( + array=np.full(shape=dim, fill_value=0.0, dtype=NPDTYPE,) + ) + self.states_tmp = DoubleArray1D( + array=np.full(shape=dim, fill_value=0.0, dtype=NPDTYPE,) + ) + + cdef void step(self, ODESystem sys, double time, double[:] state) noexcept: + cdef Py_ssize_t i + cdef double dt2 = 1.0 / 2.0 + cdef double dt6 = 1.0 / 6.0 + cdef double[:] k1 = self.k1.array + cdef double[:] k2 = self.k2.array + cdef double[:] k3 = self.k3.array + cdef double[:] k4 = self.k4.array + cdef double[:] states_tmp = self.states_tmp.array + + # Compute k1 + sys.evaluate(time, state, k1) + for i in range(self.dim): + self.states_tmp.array[i] = state[i] + (1.0 * k1[i])/2.0 + + # Compute k2 + sys.evaluate(time + dt2, self.states_tmp.array, k2) + for i in range(self.dim): + self.states_tmp.array[i] = state[i] + (1.0 * k2[i])/2.0 + + # Compute k3 + sys.evaluate(time + dt2, self.states_tmp.array, k3) + for i in range(self.dim): + self.states_tmp.array[i] = state[i] + 1.0*k3[i] + + # Compute k4 + sys.evaluate(time + 1.0, states_tmp, k4) + + # Update y: y = y + (k1 + 2*k2 + 2*k3 + k4) / 6 + for i in range(self.dim): + state[i] += dt6 * (k1[i] + 2.0 * k2[i] + 2.0 * k3[i] + k4[i]) diff --git a/farms_network/numeric/system.pxd b/farms_network/numeric/system.pxd new file mode 100644 index 0000000..70ade2d --- /dev/null +++ b/farms_network/numeric/system.pxd @@ -0,0 +1,3 @@ +cdef class ODESystem: + + cdef void evaluate(self, double time, double[:] states, double[:] derivatives) noexcept diff --git a/farms_network/numeric/system.pyx b/farms_network/numeric/system.pyx new file mode 100644 index 0000000..8f6b598 --- /dev/null +++ b/farms_network/numeric/system.pyx @@ -0,0 +1,13 @@ +""" Template for an ODE system """ + + +cdef class ODESystem: + """ ODE System """ + + def __init__(self): + """ Initialize """ + ... + + cdef void evaluate(self, double time, double[:] states, double[:] derivatives) noexcept: + """ Evaluate that needs to filled out by an ODE system """ + ... From 6a3c76919d656a987b20d1253a909af9dc560569 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Tue, 12 Nov 2024 22:05:14 -0500 Subject: [PATCH 123/316] [MAIN] Added Pyproject TOML for package setup --- pyproject.toml | 22 +++++++++++ setup.py | 105 +++++-------------------------------------------- 2 files changed, 32 insertions(+), 95 deletions(-) create mode 100644 pyproject.toml diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..cb2eb57 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,22 @@ +[build-system] +requires = [ + "setuptools", + "Cython >= 0.15.1", + "numpy", + "farms_core @ git+https://github.com/farmsim/farms_core.git" +] +build-backend = "setuptools.build_meta" + +[project] +name = "farms_network" +version = "0.1" +description = "Module to generate, develop and visualize neural networks" +license = {file = "LICENSE"} +dependencies = [ + "farms_pylog @ git+https://gitlab.com/FARMSIM/farms_pylog.git", + "tqdm", + "matplotlib", + "networkx", + "pydot", + "scipy" +] \ No newline at end of file diff --git a/setup.py b/setup.py index d885b15..ea3afba 100644 --- a/setup.py +++ b/setup.py @@ -1,16 +1,9 @@ -from setuptools import setup, dist, find_packages -from setuptools.extension import Extension - -dist.Distribution().fetch_build_eggs(['numpy']) import numpy - -dist.Distribution().fetch_build_eggs(['Cython>=0.15.1']) from Cython.Build import cythonize from Cython.Compiler import Options - -dist.Distribution().fetch_build_eggs(['farms_core']) -from farms_core import get_include_paths # pylint: disable=wrong-import-position - +from farms_core import get_include_paths +from setuptools import dist, find_packages, setup +from setuptools.extension import Extension DEBUG = False Options.docstrings = True @@ -33,86 +26,16 @@ # directive_defaults = Cython.Compiler.Options.get_directive_defaults() -# extensions = [ -# Extension( -# f"farms_network.{subpackage}.*", -# [f"farms_network/{subpackage}/*.pyx"], -# include_dirs=[numpy.get_include(),], -# # libraries=["c", "stdc++"], -# extra_compile_args=['-ffast-math', '-O3'], -# extra_link_args=['-O3'], -# ) -# for subpackage in ( -# 'core', -# # 'models' - -# ) -# ] - extensions = [ Extension( - "farms_network.core.network", - ["farms_network/core/network.pyx"], - include_dirs=[numpy.get_include()], - extra_compile_args=['-ffast-math', '-O3'], - extra_link_args=['-O3', '-lm'] - ), - Extension( - "farms_network.core.node", - ["farms_network/core/node.pyx"], - include_dirs=[numpy.get_include()], - extra_compile_args=['-ffast-math', '-O3'], - extra_link_args=['-O3', '-lm'] - ), - Extension( - "farms_network.core.edge", - ["farms_network/core/edge.pyx"], - include_dirs=[numpy.get_include()], - extra_compile_args=['-ffast-math', '-O3'], - extra_link_args=['-O3', '-lm'] - ), - Extension( - "farms_network.core.data_cy", - ["farms_network/core/data_cy.pyx"], - include_dirs=[numpy.get_include()], + f"farms_network.{subpackage}.*", + [f"farms_network/{subpackage}/*.pyx"], + include_dirs=[numpy.get_include(),], + # libraries=["c", "stdc++"], extra_compile_args=['-ffast-math', '-O3'], - extra_link_args=['-O3', '-lm'] - ), - Extension( - "farms_network.models.li_danner", - ["farms_network/models/li_danner.pyx"], - include_dirs=[numpy.get_include()], - extra_compile_args=['-ffast-math', '-O3'], - extra_link_args=['-O3', '-lm'] - ), - Extension( - "farms_network.models.li_nap_danner", - ["farms_network/models/li_nap_danner.pyx"], - include_dirs=[numpy.get_include()], - extra_compile_args=['-ffast-math', '-O3'], - extra_link_args=['-O3', '-lm'] - ), - Extension( - "farms_network.models.oscillator", - ["farms_network/models/oscillator.pyx"], - include_dirs=[numpy.get_include()], - extra_compile_args=['-ffast-math', '-O3'], - extra_link_args=['-O3', '-lm'] - ), - Extension( - "farms_network.models.linear", - ["farms_network/models/linear.pyx"], - include_dirs=[numpy.get_include()], - extra_compile_args=['-ffast-math', '-O3'], - extra_link_args=['-O3', '-lm'] - ), - Extension( - "farms_network.models.external_relay", - ["farms_network/models/external_relay.pyx"], - include_dirs=[numpy.get_include()], - extra_compile_args=['-ffast-math', '-O3'], - extra_link_args=['-O3', '-lm'] - ), + extra_link_args=['-O3'], + ) + for subpackage in ('core', 'models', 'numeric') ] setup( @@ -124,14 +47,6 @@ author_email='biorob-farms@groupes.epfl.ch', license='Apache-2.0', packages=find_packages(exclude=['tests*']), - install_requires=[ - 'farms_pylog @ git+https://gitlab.com/FARMSIM/farms_pylog.git', - 'tqdm', - 'matplotlib', - 'networkx', - 'pydot', - 'scipy' - ], zip_safe=False, ext_modules=cythonize( extensions, From 4f8675ee82c82837bbfb9d1b5312000cdf379254 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Tue, 12 Nov 2024 23:19:42 -0500 Subject: [PATCH 124/316] [CORE] Refactored network code to use numeric/integrators --- farms_network/core/network.pxd | 18 +++++-- farms_network/core/network.pyx | 90 +++++++++++++++------------------- 2 files changed, 53 insertions(+), 55 deletions(-) diff --git a/farms_network/core/network.pxd b/farms_network/core/network.pxd index cd386d4..9d9cc4e 100644 --- a/farms_network/core/network.pxd +++ b/farms_network/core/network.pxd @@ -1,9 +1,14 @@ cimport numpy as cnp +from ..numeric.integrators_cy cimport RK4Solver +from ..numeric.system cimport ODESystem from .data_cy cimport NetworkDataCy, NodeDataCy from .edge cimport Edge from .node cimport Node +# Typedef for a function signature: ODE system f(t, y) -> dydt +ctypedef void (*ode_func)(void*, double, double[:], double[:]) + cdef struct Network: @@ -19,7 +24,7 @@ cdef struct Network: Edge** edges -cdef class PyNetwork: +cdef class PyNetwork(ODESystem): """ Python interface to Network ODE """ cdef: @@ -30,10 +35,15 @@ cdef class PyNetwork: double[:] __tmp_node_outputs Py_ssize_t iteration + Py_ssize_t n_iterations + double timestep + + public RK4Solver integrator + + list nodes_output_data # cpdef void step(self) - cpdef double[:] ode(self, double time, double[::1] states) noexcept - # cdef void odeint(self, double time, double[:] states, double[:] derivatives) noexcept - # cpdef void step(self) noexcept + cdef void evaluate(self, double time, double[:] states, double[:] derivatives) noexcept + cpdef void step(self) noexcept cpdef void logging(self, Py_ssize_t) noexcept diff --git a/farms_network/core/network.pyx b/farms_network/core/network.pyx index 16194b5..06d9ce4 100644 --- a/farms_network/core/network.pyx +++ b/farms_network/core/network.pyx @@ -19,8 +19,6 @@ limitations under the License. include "types.pxd" -cimport numpy as cnp - import numpy as np from libc.stdio cimport printf @@ -33,7 +31,7 @@ from ..models.li_danner cimport LIDannerNodeParameters from .data import NetworkData, NetworkStates -from .data_cy cimport NetworkDataCy, NetworkStatesCy +from .data_cy cimport NetworkDataCy, NetworkStatesCy, NetworkConnectivityCy from .edge cimport Edge, PyEdge from .node cimport Node, PyNode @@ -41,27 +39,14 @@ from .options import (EdgeOptions, IntegrationOptions, NetworkOptions, NodeOptions) -cpdef double[:] rk4(double time, double[:] state, func, double step_size): - """ Runge-kutta order 4 integrator """ - - K1 = np.array(func(time, state)) - K2 = np.array(func(time + step_size/2, state + (step_size/2 * K1))) - K3 = np.array(func(time + step_size/2, state + (step_size/2 * K2))) - K4 = np.array(func(time + step_size, state + (step_size * K3))) - state = state + (K1 + 2*K2 + 2*K3 + K4)*(step_size/6) - time += step_size - return state - - -cdef void ode( +cdef inline void ode( double time, NetworkDataCy data, Network* network, double[:] node_outputs_tmp, ) noexcept: """ C Implementation to compute full network state """ - cdef int j, step, steps, nnodes - steps = 4 + cdef Py_ssize_t j, nnodes cdef Node __node cdef Node** nodes = network.nodes @@ -109,7 +94,7 @@ cdef void ode( ) -cdef class PyNetwork: +cdef class PyNetwork(ODESystem): """ Python interface to Network ODE """ def __cinit__(self, network_options: NetworkOptions): @@ -128,16 +113,21 @@ cdef class PyNetwork: if self.network.edges is NULL: raise MemoryError("Failed to allocate memory for Network edges") - def __init__(self, network_options): + def __init__(self, network_options: NetworkOptions): """ Initialize """ super().__init__() self.data = NetworkData.from_options(network_options) self.pynodes = [] self.pyedges = [] + self.nodes_output_data = [] self.__tmp_node_outputs = np.zeros((self.network.nnodes,)) self.setup_network(network_options, self.data) - self.iteration = 0 + + # Integration options + self.n_iterations: int = network_options.integration.n_iterations + self.timestep: int = network_options.integration.timestep + self.iteration: int = 0 def __dealloc__(self): """ Deallocate any manual memory as part of clean up """ @@ -151,9 +141,15 @@ cdef class PyNetwork: """ Initialize network from NetworkOptions """ return cls(options) - def setup_integrator(options: IntegrationOptions): + def to_options(self): + """ Return NetworkOptions from network """ + return self.options + + def setup_integrator(self, options: IntegrationOptions): """ Setup integrator for neural network """ - ... + cdef double timestep = options.timestep + print(timestep) + self.integrator = RK4Solver(self.network.nstates, timestep) def setup_network(self, options: NetworkOptions, data: NetworkData): """ Setup network """ @@ -161,6 +157,10 @@ cdef class PyNetwork: cdef Node* c_node connectivity = data.connectivity + cdef Py_ssize_t __nstates = 0 + cdef Py_ssize_t index + cdef PyNode pyn + cdef PyEdge pye for index, node_options in enumerate(options.nodes): self.pynodes.append(self.generate_node(node_options)) pyn = self.pynodes[index] @@ -169,6 +169,8 @@ cdef class PyNetwork: connectivity.sources[connectivity.indices[index]:connectivity.indices[index+1]] ) if connectivity.indices else 0 self.network.nodes[index] = c_node + __nstates += node_options._nstates + self.network.nstates = __nstates cdef Edge* c_edge for index, edge_options in enumerate(options.edges): @@ -207,23 +209,12 @@ cdef class PyNetwork: """ Number of states in the network """ return self.network.nstates - # cpdef void step(self) noexcept: - # """ Step the network state """ - # self.integrator.step(self.odeint, 0.0, self.data.states.array, &self) - # # self.data.states.array = rk4(0.0, self.data.states.array, self.odeint, step_size=1e-3) - - # cdef void odeint( - # self, - # double time, - # double[:] states, - # double[:] derivatives, - # ) noexcept: - # """ ODE function signature for integrators """ - # data.states.array[:] = curr_states[:] - # ode(time, data, network, node_outputs_tmp) - # # Update all node derivatives and outputs data for next iteration - # data.outputs.array[:] = self.__tmp_node_outputs - # derivatives[:] = self.data.derivatives.array + cpdef void step(self) noexcept: + """ Step the network state """ + self.iteration += 1 + self.integrator.step( + self, self.iteration*self.timestep, self.data.states.array + ) cpdef void logging(self, Py_ssize_t iteration) noexcept: """ Log network data """ @@ -238,17 +229,15 @@ cdef class PyNetwork: # cdef double[:] derivatives = data.derivatives.array cdef double* derivatives_ptr = &data.derivatives.array[0] - cdef unsigned int[:] derivatives_indices = data.derivatives.indices cdef double[:] outputs = data.outputs.array cdef double[:] external_inputs = data.external_inputs.array cdef NodeDataCy node_data - cdef NodeDataCy[:] nodes_data = self.data.nodes for j in range(nnodes): # Log states - node_data = nodes_data[j] + node_data = data.nodes[j] start_idx = state_indices[j] end_idx = state_indices[j+1] state_iteration = 0 @@ -259,11 +248,10 @@ cdef class PyNetwork: node_data.output.array[iteration] = outputs[j] node_data.external_input.array[iteration] = external_inputs[j] - cpdef double[:] ode(self, double time, double[::1] states) noexcept: - """ ODE Wrapper for numerical integrators """ - - cdef NetworkDataCy data = self.data - data.states.array[:] = states[:] - ode(time, data, self.network, self.__tmp_node_outputs) - data.outputs.array[:] = self.__tmp_node_outputs - return data.derivatives.array + cdef void evaluate(self, double time, double[:] states, double[:] derivatives) noexcept: + """ Evaluate the ODE """ + # cdef NetworkDataCy data = self.data + self.data.states.array[:] = states[:] + ode(time, self.data, self.network, self.__tmp_node_outputs) + self.data.outputs.array[:] = self.__tmp_node_outputs + derivatives[:] = self.data.derivatives.array From dd7bae4ce77436063778e9b61e77e7a94817432f Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Wed, 13 Nov 2024 09:32:18 -0500 Subject: [PATCH 125/316] [MAIN] Updated metadata and project dependencies in pyproject setup --- pyproject.toml | 37 +++++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index cb2eb57..15961e7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,8 +15,41 @@ license = {file = "LICENSE"} dependencies = [ "farms_pylog @ git+https://gitlab.com/FARMSIM/farms_pylog.git", "tqdm", +] +authors = [ + {name = "Jonathan Arreguit", email = ""}, + {name = "Shravan Tata Ramalingasetty", email = ""}, +] +classifiers = [ + "Development Status :: 3 - Beta", + + # Indicate who your project is intended for + "Intended Audience :: Science/Research", + "ScieTopic :: Scientific/Engineering", + "Topic :: Scientific/Engineering :: Artificial Intelligence" + "Topic :: Scientific/Engineering :: Artificial Life" + + # Specify the Python versions you support here. + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", +] + +[project.urls] +Homepage = "https://farmsim.dev" +Documentation = "https://farmsim.dev" +Repository = "https://github.com/farmsim/farms_network.git" +Issues = "https://github.com/farmsim/farms_network/issues" +Changelog = "https://github.com/me/spam/blob/master/CHANGELOG.md" + +[project.optional-dependencies] +gui = [ + "scipy", "matplotlib", + "PyQt5", "networkx", "pydot", - "scipy" -] \ No newline at end of file +] +cli = ["rich",] From e2e93318ef0e7c688db11cea3e5eb15989233243 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Wed, 13 Nov 2024 14:25:59 -0500 Subject: [PATCH 126/316] [SETUP] Fixed missing comma in pyproject.toml --- pyproject.toml | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 15961e7..9f3a978 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,19 +16,17 @@ dependencies = [ "farms_pylog @ git+https://gitlab.com/FARMSIM/farms_pylog.git", "tqdm", ] -authors = [ - {name = "Jonathan Arreguit", email = ""}, - {name = "Shravan Tata Ramalingasetty", email = ""}, -] +# authors = [ +# {name = "Jonathan Arreguit", email = ""}, +# {name = "Shravan Tata Ramalingasetty", email = ""}, +# ] classifiers = [ "Development Status :: 3 - Beta", - # Indicate who your project is intended for "Intended Audience :: Science/Research", "ScieTopic :: Scientific/Engineering", - "Topic :: Scientific/Engineering :: Artificial Intelligence" - "Topic :: Scientific/Engineering :: Artificial Life" - + "Topic :: Scientific/Engineering :: Artificial Intelligence", + "Topic :: Scientific/Engineering :: Artificial Life", # Specify the Python versions you support here. "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.6", From 30a4e415d4ffebd6fd8875dc553c24ffd9d5fc00 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Wed, 13 Nov 2024 14:31:57 -0500 Subject: [PATCH 127/316] [CORE][Network] changed Py_ssize_t to unsigned int --- farms_network/core/network.pxd | 10 ++++------ farms_network/core/network.pyx | 19 +++++++++---------- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/farms_network/core/network.pxd b/farms_network/core/network.pxd index 9d9cc4e..aca9369 100644 --- a/farms_network/core/network.pxd +++ b/farms_network/core/network.pxd @@ -6,9 +6,6 @@ from .data_cy cimport NetworkDataCy, NodeDataCy from .edge cimport Edge from .node cimport Node -# Typedef for a function signature: ODE system f(t, y) -> dydt -ctypedef void (*ode_func)(void*, double, double[:], double[:]) - cdef struct Network: @@ -34,8 +31,8 @@ cdef class PyNetwork(ODESystem): public NetworkDataCy data double[:] __tmp_node_outputs - Py_ssize_t iteration - Py_ssize_t n_iterations + unsigned int iteration + unsigned int n_iterations double timestep public RK4Solver integrator @@ -46,4 +43,5 @@ cdef class PyNetwork(ODESystem): cdef void evaluate(self, double time, double[:] states, double[:] derivatives) noexcept cpdef void step(self) noexcept - cpdef void logging(self, Py_ssize_t) noexcept + @staticmethod + cdef void logging(unsigned int iteration, NetworkDataCy data, Network* network) noexcept diff --git a/farms_network/core/network.pyx b/farms_network/core/network.pyx index 06d9ce4..2450833 100644 --- a/farms_network/core/network.pyx +++ b/farms_network/core/network.pyx @@ -46,7 +46,7 @@ cdef inline void ode( double[:] node_outputs_tmp, ) noexcept: """ C Implementation to compute full network state """ - cdef Py_ssize_t j, nnodes + cdef unsigned int j, nnodes cdef Node __node cdef Node** nodes = network.nodes @@ -148,7 +148,6 @@ cdef class PyNetwork(ODESystem): def setup_integrator(self, options: IntegrationOptions): """ Setup integrator for neural network """ cdef double timestep = options.timestep - print(timestep) self.integrator = RK4Solver(self.network.nstates, timestep) def setup_network(self, options: NetworkOptions, data: NetworkData): @@ -157,8 +156,8 @@ cdef class PyNetwork(ODESystem): cdef Node* c_node connectivity = data.connectivity - cdef Py_ssize_t __nstates = 0 - cdef Py_ssize_t index + cdef unsigned int __nstates = 0 + cdef unsigned int index cdef PyNode pyn cdef PyEdge pye for index, node_options in enumerate(options.nodes): @@ -215,17 +214,18 @@ cdef class PyNetwork(ODESystem): self.integrator.step( self, self.iteration*self.timestep, self.data.states.array ) + PyNetwork.logging(self.iteration, self.data, self.network) - cpdef void logging(self, Py_ssize_t iteration) noexcept: + @staticmethod + cdef void logging(int iteration, NetworkDataCy data, Network* network) noexcept: """ Log network data """ - cdef Py_ssize_t j, nnodes - nnodes = self.network.nnodes - cdef NetworkDataCy data = self.data + cdef int j, nnodes + nnodes = network.nnodes # cdef double[:] states = data.states.array cdef double* states_ptr = &data.states.array[0] cdef unsigned int[:] state_indices = data.states.indices - cdef Py_ssize_t state_idx, start_idx, end_idx, state_iteration + cdef int state_idx, start_idx, end_idx, state_iteration # cdef double[:] derivatives = data.derivatives.array cdef double* derivatives_ptr = &data.derivatives.array[0] @@ -233,7 +233,6 @@ cdef class PyNetwork(ODESystem): cdef double[:] outputs = data.outputs.array cdef double[:] external_inputs = data.external_inputs.array - cdef NodeDataCy node_data for j in range(nnodes): # Log states From d4bd9b78f3ae2ab6cf27e4a653218a40aa4ffad6 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Wed, 13 Nov 2024 14:34:01 -0500 Subject: [PATCH 128/316] [CORE][DATA] Change array creation to np.full --- farms_network/core/data.py | 53 ++++++++++++++++++++++++---------- farms_network/core/data_cy.pxd | 9 ++---- farms_network/core/data_cy.pyx | 12 +------- 3 files changed, 41 insertions(+), 33 deletions(-) diff --git a/farms_network/core/data.py b/farms_network/core/data.py index fdc5b00..ba7304a 100644 --- a/farms_network/core/data.py +++ b/farms_network/core/data.py @@ -80,8 +80,20 @@ def from_options(cls, network_options: NetworkOptions): states = NetworkStates.from_options(network_options) derivatives = NetworkStates.from_options(network_options) connectivity = NetworkConnectivity.from_options(network_options) - outputs = DoubleArray1D(array=np.zeros(len(network_options.nodes),)) - external_inputs = DoubleArray1D(array=np.zeros(len(network_options.nodes),)) + outputs = DoubleArray1D( + array=np.full( + shape=len(network_options.nodes), + fill_value=0, + dtype=NPDTYPE, + ) + ) + external_inputs = DoubleArray1D( + array=np.full( + shape=len(network_options.nodes), + fill_value=0, + dtype=NPDTYPE, + ) + ) nodes = np.array( [ NodeData.from_options( @@ -161,7 +173,11 @@ def from_options(cls, network_options: NetworkOptions): nodes = network_options.nodes edges = network_options.edges - connectivity = np.empty((len(edges), 3)) + connectivity = np.full( + shape=(len(edges), 3), + fill_value=0, + dtype=NPDTYPE, + ) node_names = [node.name for node in nodes] for index, edge in enumerate(edges): @@ -170,8 +186,16 @@ def from_options(cls, network_options: NetworkOptions): connectivity[index][2] = edge.weight connectivity = np.array(sorted(connectivity, key=lambda col: col[1])) - sources = np.empty((len(edges),)) - weights = np.empty((len(edges),)) + sources = np.full( + shape=len(edges), + fill_value=0, + dtype=NPDTYPE, + ) + weights = np.full( + shape=len(edges), + fill_value=0, + dtype=NPDTYPE, + ) nedges = 0 indices = [] if len(edges) > 0: @@ -211,13 +235,12 @@ def __init__( ): """ Node data initialization """ - super().__init__( - states=states, - derivatives=derivatives, - output=output, - external_input=external_input, - ) + super().__init__() self.name = name + self.states = states + self.derivatives = derivatives + self.output = output + self.external_input = external_input @classmethod def from_options(cls, options: NodeOptions, buffer_size: int): @@ -256,14 +279,14 @@ def from_options(cls, options: NodeOptions, buffer_size: int): array = np.full( shape=[buffer_size, nstates], fill_value=0, - dtype=NPDTYPE, + dtype=np.double, ) else: names = [] array = np.full( shape=[buffer_size, 0], fill_value=0, - dtype=NPDTYPE, + dtype=np.double, ) return cls(array=array, names=names) @@ -287,7 +310,7 @@ def from_options(cls, options: NodeOptions, buffer_size: int): array = np.full( shape=buffer_size, fill_value=0, - dtype=NPDTYPE, + dtype=np.double, ) return cls(array=array) @@ -304,7 +327,7 @@ def from_options(cls, options: NodeOptions, buffer_size: int): array = np.full( shape=buffer_size, fill_value=0, - dtype=NPDTYPE, + dtype=np.double, ) return cls(array=array) diff --git a/farms_network/core/data_cy.pxd b/farms_network/core/data_cy.pxd index c970e6c..72da5c9 100644 --- a/farms_network/core/data_cy.pxd +++ b/farms_network/core/data_cy.pxd @@ -33,16 +33,11 @@ cdef class NetworkDataCy: public DoubleArray1D outputs public NetworkConnectivityCy connectivity - public NodeDataCy[:] nodes - public DoubleArray1D states_noise + public DoubleArray1D derivatives_noise public DoubleArray1D outputs_noise - # _state_indices = None - # _state_index_skips = None - # _connectivity_indices = None - # _connectivity_index_skips = None - # _outputs = None + public NodeDataCy[:] nodes cdef class NetworkStatesCy(DoubleArray1D): diff --git a/farms_network/core/data_cy.pyx b/farms_network/core/data_cy.pyx index 72a025b..f5e8b97 100644 --- a/farms_network/core/data_cy.pyx +++ b/farms_network/core/data_cy.pyx @@ -69,17 +69,7 @@ cdef class NetworkConnectivityCy: cdef class NodeDataCy: """ Node data """ - def __init__( - self, - states: DoubleArray2D, - derivatives: DoubleArray2D, - output: DoubleArray1D, - external_input: DoubleArray1D, - ): + def __init__(self): """ nodes data initialization """ super().__init__() - self.states = states - self.derivatives = derivatives - self.output = output - self.external_input = external_input From e1778a2cfbcce5804f8818c875d229dd466c083f Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Wed, 13 Nov 2024 14:36:00 -0500 Subject: [PATCH 129/316] [NUMERIC] Fixed hard-coded timestep to be 1.0 --- farms_network/numeric/integrators_cy.pyx | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/farms_network/numeric/integrators_cy.pyx b/farms_network/numeric/integrators_cy.pyx index a6f86e7..209e99b 100644 --- a/farms_network/numeric/integrators_cy.pyx +++ b/farms_network/numeric/integrators_cy.pyx @@ -1,5 +1,7 @@ import numpy as np +from libc.stdio cimport printf + NPDTYPE = np.float64 @@ -26,8 +28,8 @@ cdef class RK4Solver: cdef void step(self, ODESystem sys, double time, double[:] state) noexcept: cdef Py_ssize_t i - cdef double dt2 = 1.0 / 2.0 - cdef double dt6 = 1.0 / 6.0 + cdef double dt2 = self.dt / 2.0 + cdef double dt6 = self.dt / 6.0 cdef double[:] k1 = self.k1.array cdef double[:] k2 = self.k2.array cdef double[:] k3 = self.k3.array @@ -37,17 +39,17 @@ cdef class RK4Solver: # Compute k1 sys.evaluate(time, state, k1) for i in range(self.dim): - self.states_tmp.array[i] = state[i] + (1.0 * k1[i])/2.0 + self.states_tmp.array[i] = state[i] + (self.dt * k1[i])/2.0 # Compute k2 sys.evaluate(time + dt2, self.states_tmp.array, k2) for i in range(self.dim): - self.states_tmp.array[i] = state[i] + (1.0 * k2[i])/2.0 + self.states_tmp.array[i] = state[i] + (self.dt * k2[i])/2.0 # Compute k3 sys.evaluate(time + dt2, self.states_tmp.array, k3) for i in range(self.dim): - self.states_tmp.array[i] = state[i] + 1.0*k3[i] + self.states_tmp.array[i] = state[i] + self.dt * k3[i] # Compute k4 sys.evaluate(time + 1.0, states_tmp, k4) From a11119a4876a51a9b01d56270954daff49476c4c Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Wed, 13 Nov 2024 14:36:40 -0500 Subject: [PATCH 130/316] [EX][IJSPEERT] Updated run script to changes in core --- examples/ijspeert07/run.py | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/examples/ijspeert07/run.py b/examples/ijspeert07/run.py index b79862b..1337edf 100644 --- a/examples/ijspeert07/run.py +++ b/examples/ijspeert07/run.py @@ -16,7 +16,7 @@ from farms_network.core import options from farms_network.core.data import (NetworkConnectivity, NetworkData, NetworkStates) -from farms_network.core.network import PyNetwork, rk4 +from farms_network.core.network import PyNetwork from tqdm import tqdm import seaborn as sns @@ -160,7 +160,7 @@ def nodes(self): return nodes -def generate_network(): +def generate_network(iterations=2000): """ Generate network """ # Main network @@ -168,18 +168,24 @@ def generate_network(): directed=True, multigraph=False, graph={"name": "ijspeert07"}, + integration=options.IntegrationOptions.defaults( + n_iterations=iterations, + timestep=float(1e-3), + ), logs=options.NetworkLogOptions( - n_iterations=10000, + n_iterations=iterations, + buffer_size=iterations, ) ) # Generate rhythm centers - n_oscillators = 20 + n_oscillators = 6 network_options = oscillator_double_chain(network_options, n_oscillators) data = NetworkData.from_options(network_options) network = PyNetwork.from_options(network_options) + network.setup_integrator(network_options.integration) # nnodes = len(network_options.nodes) # integrator.set_initial_value(np.zeros(len(data.states.array),), 0.0) @@ -191,7 +197,6 @@ def generate_network(): # integrator.integrate(integrator.t + 1e-3) # # Integrate - iterations = 20000 states = np.ones((iterations+1, len(data.states.array)))*0.0 outputs = np.ones((iterations, len(data.outputs.array)))*1.0 # states[0, 2] = -1.0 @@ -203,23 +208,22 @@ def generate_network(): # network.step(network.ode, iteration*1e-3, network.data.states.array) # network.step() # states[iteration+1, :] = network.data.states.array - states[iteration+1, :] = rk4(iteration*1e-3, states[iteration, :], network.ode, step_size=1e-3,) - outputs[iteration, :] = network.data.outputs.array + network.step() + network.data.times.array[iteration] = iteration*1e-3 # network.data.to_file("/tmp/network.h5") - plt.figure() for j in range(int(n_oscillators/2)+1): plt.fill_between( - np.linspace(0.0, iterations*1e-3, iterations), - j + (1 + np.sin(outputs[:, j])), + np.array(network.data.times.array), + j + (1 + np.sin(network.data.nodes[j].output.array)), j, alpha=0.2, lw=1.0, ) plt.plot( - np.linspace(0.0, iterations*1e-3, iterations), - j + (1 + np.sin(outputs[:, j])), + np.array(network.data.times.array), + j + (1 + np.sin(network.data.nodes[j].output.array)), label=f"{j}" ) plt.legend() @@ -238,10 +242,10 @@ def generate_network(): plt.figure() pos_circular = nx.circular_layout(graph) pos_spring = nx.spring_layout(graph) - # pos_graphviz = nx.nx_agraph.pygraphviz_layout(graph) + pos_graphviz = nx.nx_agraph.pygraphviz_layout(graph) _ = nx.draw_networkx_nodes( graph, - pos=pos_spring, + pos=pos_graphviz, node_color=[data["visual"]["color"] for node, data in graph.nodes.items()], alpha=0.25, edgecolors='k', @@ -249,7 +253,7 @@ def generate_network(): ) nx.draw_networkx_labels( graph, - pos=pos_spring, + pos=pos_graphviz, labels={node: data["visual"]["label"] for node, data in graph.nodes.items()}, font_size=11.0, font_weight='bold', @@ -258,7 +262,7 @@ def generate_network(): ) nx.draw_networkx_edges( graph, - pos=pos_spring, + pos=pos_graphviz, edge_color=[ [0.0, 1.0, 0.0] if data["type"] == "excitatory" else [1.0, 0.0, 0.0] for edge, data in graph.edges.items() From 729a3379c24448e6cbfacb0e3805327365e9882e Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Wed, 13 Nov 2024 15:43:56 -0500 Subject: [PATCH 131/316] [CORE][OPTIONS] Added support read/write different edge types --- farms_network/core/options.py | 52 +++++++++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/farms_network/core/options.py b/farms_network/core/options.py index 3c362f6..3c35889 100644 --- a/farms_network/core/options.py +++ b/farms_network/core/options.py @@ -60,8 +60,12 @@ def from_options(cls, kwargs): for node in kwargs["nodes"] ] # Edges + edge_types = { + "standard": EdgeOptions, + "oscillator": OscillatorEdgeOptions, + } options["edges"] = [ - EdgeOptions.from_options(edge) + edge_types[edge["model"]].from_options(edge) for edge in kwargs["edges"] ] return cls(**options) @@ -305,7 +309,8 @@ class EdgeOptions(Options): def __init__(self, **kwargs): """ Initialize """ super().__init__() - + model = "standard" + self.model: str = kwargs.pop("model", model) self.source: str = kwargs.pop("source") self.target: str = kwargs.pop("target") self.weight: float = kwargs.pop("weight") @@ -329,6 +334,7 @@ def __eq__(self, other): @classmethod def from_options(cls, kwargs: Dict): """ From options """ + options = {} options["source"] = kwargs["source"] options["target"] = kwargs["target"] @@ -551,6 +557,48 @@ def __init__(self, **kwargs): assert len(self.initial) == 3, f"Number of initial states {len(self.initial)} should be 3" +class OscillatorEdgeOptions(EdgeOptions): + """ Oscillator edge options """ + + def __init__(self, **kwargs): + """ Initialize """ + model = "oscillator" + super().__init__( + model=model, + source=kwargs.pop("source"), + target=kwargs.pop("target"), + weight=kwargs.pop("weight"), + type=kwargs.pop("type"), + parameters=kwargs.pop("parameters"), + visual=kwargs.pop("visual"), + ) + if kwargs: + raise Exception(f'Unknown kwargs: {kwargs}') + + def __eq__(self, other): + if isinstance(other, EdgeOptions): + return ( + (self.source == other.source) and + (self.target == other.target) + ) + return False + + @classmethod + def from_options(cls, kwargs: Dict): + """ From options """ + + options = {} + options["source"] = kwargs["source"] + options["target"] = kwargs["target"] + options["weight"] = kwargs["weight"] + options["type"] = kwargs["type"] + options["parameters"] = OscillatorEdgeParameterOptions.from_options( + kwargs["parameters"] + ) + options["visual"] = EdgeVisualOptions.from_options(kwargs["visual"]) + return cls(**options) + + class OscillatorEdgeParameterOptions(EdgeParameterOptions): """ Oscillator edge parameter options """ From f64053aa6174e162399a4e9f0e1d7a71bdb117e6 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Wed, 13 Nov 2024 15:46:29 -0500 Subject: [PATCH 132/316] [DUCKS] Renamed usage to introduction folder --- ducks/source/index.rst | 21 ++++++++++++------- ducks/source/introduction/index.rst | 6 ++++++ .../{usage => introduction}/installation.rst | 9 +++++--- .../{usage => introduction}/overview.rst | 0 4 files changed, 26 insertions(+), 10 deletions(-) create mode 100644 ducks/source/introduction/index.rst rename ducks/source/{usage => introduction}/installation.rst (93%) rename ducks/source/{usage => introduction}/overview.rst (100%) diff --git a/ducks/source/index.rst b/ducks/source/index.rst index e76284b..f6337ec 100644 --- a/ducks/source/index.rst +++ b/ducks/source/index.rst @@ -3,27 +3,34 @@ You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. -Welcome to FARMS Network's documentation! -========================================= +=========================================== + Welcome to FARMS Network's documentation! +=========================================== .. warning:: Farmers are currently busy! Documentation is work in progress!! farms network provides commonly used neural models for locomotion circuits. -A neural network simulation library designed for simulating neural models such as leaky integrators with efficient computing through Cython and future GPU integration. +A neural network simulation library designed for simulating sparse neural networks with models such as leaky integrators with efficient computing through Cython and future GPU integration. + +============= + Introduction +============= + +Contents .. toctree:: - :hidden: - usage/index.rst + introduction/index.rst core/index.rst models/index.rst .. sidebar-links:: :github: -Indices and tables -================== +==================== + Indices and tables +==================== * :ref:`genindex` * :ref:`modindex` diff --git a/ducks/source/introduction/index.rst b/ducks/source/introduction/index.rst new file mode 100644 index 0000000..b0aa207 --- /dev/null +++ b/ducks/source/introduction/index.rst @@ -0,0 +1,6 @@ +Introduction +============ + +.. toctree:: + + installation diff --git a/ducks/source/usage/installation.rst b/ducks/source/introduction/installation.rst similarity index 93% rename from ducks/source/usage/installation.rst rename to ducks/source/introduction/installation.rst index 1142943..0cf2322 100644 --- a/ducks/source/usage/installation.rst +++ b/ducks/source/introduction/installation.rst @@ -1,8 +1,11 @@ -Installation steps -------------------- +============== + Installation +============== + Requirements -^^^^^^^^^^^^ +============ + "Code is only currently tested with Python3" diff --git a/ducks/source/usage/overview.rst b/ducks/source/introduction/overview.rst similarity index 100% rename from ducks/source/usage/overview.rst rename to ducks/source/introduction/overview.rst From 7356ea4a1b0b2c0d126782ea8abed6901d150ef9 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Fri, 15 Nov 2024 00:54:32 -0500 Subject: [PATCH 133/316] [UTILS] Added console script to load and run from config file --- farms_network/utils/run.py | 43 ++++++++++++++++++++++++++++++++++++++ pyproject.toml | 3 +++ 2 files changed, 46 insertions(+) create mode 100644 farms_network/utils/run.py diff --git a/farms_network/utils/run.py b/farms_network/utils/run.py new file mode 100644 index 0000000..15c7d66 --- /dev/null +++ b/farms_network/utils/run.py @@ -0,0 +1,43 @@ +""" Run script """ + +from argparse import ArgumentParser + +from farms_core.io.yaml import read_yaml +from farms_network.core.network import PyNetwork +from farms_network.core.options import NetworkOptions +from tqdm import tqdm + + +def run_network(network_options): + + network = PyNetwork.from_options(network_options) + network.setup_integrator(network_options.integration) + + # data.to_file("/tmp/sim.hdf5") + + # Integrate + N_ITERATIONS = network_options.integration.n_iterations + TIMESTEP = network_options.integration.timestep + + inputs_view = network.data.external_inputs.array + for iteration in tqdm(range(0, N_ITERATIONS), colour="green", ascii=" >="): + inputs_view[:] = (iteration / N_ITERATIONS) * 1.0 + network.step() + network.data.times.array[iteration] = iteration*TIMESTEP + + +def main(): + """ Main """ + + parser = ArgumentParser() + parser.add_argument( + "--config_path", "-c", dest="config_path", type=str, required=True + ) + clargs = parser.parse_args() + # run network + options = NetworkOptions.from_options(read_yaml(clargs.config_path)) + run_network(options) + + +if __name__ == '__main__': + main() diff --git a/pyproject.toml b/pyproject.toml index 9f3a978..00f0be6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,3 +51,6 @@ gui = [ "pydot", ] cli = ["rich",] + +[project.scripts] +farms_network = "farms_network.utils.run:main" \ No newline at end of file From f9f5137eded902028a2b6aa71893a345a5336129 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Fri, 15 Nov 2024 00:55:27 -0500 Subject: [PATCH 134/316] [NUMERIC] Added outline for EulerMaruyamaSolver stochastic integrator --- farms_network/numeric/integrators_cy.pxd | 23 ++++++++++++++++- farms_network/numeric/integrators_cy.pyx | 33 ++++++++++++++++++++++-- 2 files changed, 53 insertions(+), 3 deletions(-) diff --git a/farms_network/numeric/integrators_cy.pxd b/farms_network/numeric/integrators_cy.pxd index a2e208d..8b15a53 100644 --- a/farms_network/numeric/integrators_cy.pxd +++ b/farms_network/numeric/integrators_cy.pxd @@ -1,4 +1,8 @@ +# distutils: language = c++ + from farms_core.array.array_cy cimport DoubleArray1D +from libc.math cimport sqrt as csqrt +from libcpp.random cimport mt19937, normal_distribution from .system cimport ODESystem @@ -13,7 +17,24 @@ cdef class RK4Solver: DoubleArray1D k4 DoubleArray1D states_tmp - Py_ssize_t dim + unsigned int dim double dt cdef void step(self, ODESystem sys, double time, double[:] state) noexcept + + +cdef struct OrnsteinUhlenbeckParameters: + double mu + double sigma + double tau + double dt + mt19937 random_generator + normal_distribution[double] distribution + + +cdef class EulerMaruyamaSolver: + cdef: + OrnsteinUhlenbeckParameters parameters + + cdef: + cdef void step(self, ODESystem sys, double time, double[:] state) noexcept diff --git a/farms_network/numeric/integrators_cy.pyx b/farms_network/numeric/integrators_cy.pyx index 209e99b..f8aa700 100644 --- a/farms_network/numeric/integrators_cy.pyx +++ b/farms_network/numeric/integrators_cy.pyx @@ -1,5 +1,9 @@ +# distutils: language = c++ + import numpy as np +from ..core.options import IntegrationOptions + from libc.stdio cimport printf NPDTYPE = np.float64 @@ -7,7 +11,9 @@ NPDTYPE = np.float64 cdef class RK4Solver: - def __init__(self, int dim, double dt): + def __init__ (self, unsigned int dim, double dt): + + super().__init__() self.dim = dim self.dt = dt self.k1 = DoubleArray1D( @@ -27,7 +33,7 @@ cdef class RK4Solver: ) cdef void step(self, ODESystem sys, double time, double[:] state) noexcept: - cdef Py_ssize_t i + cdef unsigned int i cdef double dt2 = self.dt / 2.0 cdef double dt6 = self.dt / 6.0 cdef double[:] k1 = self.k1.array @@ -57,3 +63,26 @@ cdef class RK4Solver: # Update y: y = y + (k1 + 2*k2 + 2*k3 + k4) / 6 for i in range(self.dim): state[i] += dt6 * (k1[i] + 2.0 * k2[i] + 2.0 * k3[i] + k4[i]) + + +cdef class EulerMaruyamaSolver: + + def __init__ (self, unsigned int dim, double dt): + + super().__init__() + self.dim = dim + self.dt = dt + self.parameters = OrnsteinUhlenbeckParameters() + + cdef void step(self, ODESystem sys, double time, double[:] state) noexcept: + """ Update stochastic noise process with Euler–Maruyama method (also called the + Euler method) is a method for the approximate numerical solution of a stochastic + differential equation (SDE) """ + + cdef unsigned int i + cdef double noise + + cdef OrnsteinUhlenbeckParameters params = self.parameters + for j in range(self.dim): + noise = params.distribution(params.random_generator) + state[i] += ((params.mu-state[j])*params.dt/params.tau) + params.sigma*(csqrt((2.0*params.dt)/params.tau))*noise From d51dd8934a50b6cc900d352e4152a9c827b06a22 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Fri, 15 Nov 2024 00:56:46 -0500 Subject: [PATCH 135/316] [CORE][DATA] Added network level noise data structures --- farms_network/core/data.py | 31 +++++++++++++++++++++++++++++++ farms_network/core/data_cy.pxd | 6 +++--- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/farms_network/core/data.py b/farms_network/core/data.py index ba7304a..785aa10 100644 --- a/farms_network/core/data.py +++ b/farms_network/core/data.py @@ -50,6 +50,9 @@ def __init__( connectivity, outputs, external_inputs, + noise_states, + noise_derivatives, + noise_outputs, nodes, **kwargs, ): @@ -63,6 +66,10 @@ def __init__( self.outputs = outputs self.external_inputs = external_inputs + self.noise_states = noise_states + self.noise_derivatives = noise_derivatives + self.noise_outputs = noise_outputs + self.nodes: np.ndarray[NodeDataCy] = nodes @classmethod @@ -94,6 +101,27 @@ def from_options(cls, network_options: NetworkOptions): dtype=NPDTYPE, ) ) + noise_states = DoubleArray1D( + array=np.full( + shape=len(network_options.nodes), + fill_value=0, + dtype=NPDTYPE, + ) + ) + noise_derivatives = DoubleArray1D( + array=np.full( + shape=len(network_options.nodes), + fill_value=0, + dtype=NPDTYPE, + ) + ) + noise_outputs = DoubleArray1D( + array=np.full( + shape=len(network_options.nodes), + fill_value=0, + dtype=NPDTYPE, + ) + ) nodes = np.array( [ NodeData.from_options( @@ -111,6 +139,9 @@ def from_options(cls, network_options: NetworkOptions): connectivity=connectivity, outputs=outputs, external_inputs=external_inputs, + noise_states=noise_states, + noise_derivatives=noise_derivatives, + noise_outputs=noise_outputs, nodes=nodes, ) diff --git a/farms_network/core/data_cy.pxd b/farms_network/core/data_cy.pxd index 72da5c9..ee2b365 100644 --- a/farms_network/core/data_cy.pxd +++ b/farms_network/core/data_cy.pxd @@ -33,9 +33,9 @@ cdef class NetworkDataCy: public DoubleArray1D outputs public NetworkConnectivityCy connectivity - public DoubleArray1D states_noise - public DoubleArray1D derivatives_noise - public DoubleArray1D outputs_noise + public DoubleArray1D noise_states + public DoubleArray1D noise_derivatives + public DoubleArray1D noise_outputs public NodeDataCy[:] nodes From cb1f9b867a7b4cffa57e5d6c09fccd5822fa6b37 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Fri, 15 Nov 2024 00:57:55 -0500 Subject: [PATCH 136/316] [CORE] Added stochastic noise system --- farms_network/core/stochastic_noise.pxd | 6 ++++++ farms_network/core/stochastic_noise.pyx | 9 +++++++++ 2 files changed, 15 insertions(+) create mode 100644 farms_network/core/stochastic_noise.pxd create mode 100644 farms_network/core/stochastic_noise.pyx diff --git a/farms_network/core/stochastic_noise.pxd b/farms_network/core/stochastic_noise.pxd new file mode 100644 index 0000000..fc121d2 --- /dev/null +++ b/farms_network/core/stochastic_noise.pxd @@ -0,0 +1,6 @@ +from ..numeric.system cimport ODESystem + + +cdef class OrnsteinUhlenbeck(ODESystem): + + cdef void evaluate(self, double time, double[:] states, double[:] derivatives) noexcept diff --git a/farms_network/core/stochastic_noise.pyx b/farms_network/core/stochastic_noise.pyx new file mode 100644 index 0000000..719ae60 --- /dev/null +++ b/farms_network/core/stochastic_noise.pyx @@ -0,0 +1,9 @@ +cdef class OrnsteinUhlenbeck(ODESystem): + """ """ + + def __init__(self): + super().__init__() + + + cdef void evaluate(self, double time, double[:] states, double[:] derivatives) noexcept: + ... From 94cc993f0495382d557980aba060150c7dd09c2f Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Sun, 17 Nov 2024 17:05:31 -0500 Subject: [PATCH 137/316] [CORE][OPTIONS] Fixed relay node bug to init visual opts --- farms_network/core/options.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/farms_network/core/options.py b/farms_network/core/options.py index 3c35889..3e10a7e 100644 --- a/farms_network/core/options.py +++ b/farms_network/core/options.py @@ -419,7 +419,7 @@ def from_options(cls, kwargs: Dict): options = {} options["name"] = kwargs.pop("name") options["parameters"] = NodeParameterOptions() - options["visual"] = kwargs.pop("visual") + options["visual"] = NodeVisualOptions.from_options(kwargs["visual"]) return cls(**options) From 20a7fb13e62fd903d34aab875b1a21cbe36b84c2 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Wed, 20 Nov 2024 23:01:55 -0500 Subject: [PATCH 138/316] [NUMERIC] Added stochastic differential equation system base class --- farms_network/numeric/system.pxd | 6 ++++++ farms_network/numeric/system.pyx | 16 ++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/farms_network/numeric/system.pxd b/farms_network/numeric/system.pxd index 70ade2d..1a660c6 100644 --- a/farms_network/numeric/system.pxd +++ b/farms_network/numeric/system.pxd @@ -1,3 +1,9 @@ cdef class ODESystem: cdef void evaluate(self, double time, double[:] states, double[:] derivatives) noexcept + + +cdef class SDESystem: + + cdef void evaluate_a(self, double time, double[:] states, double[:] derivatives) noexcept + cdef void evaluate_b(self, double time, double[:] states, double[:] derivatives) noexcept diff --git a/farms_network/numeric/system.pyx b/farms_network/numeric/system.pyx index 8f6b598..ac8b34d 100644 --- a/farms_network/numeric/system.pyx +++ b/farms_network/numeric/system.pyx @@ -11,3 +11,19 @@ cdef class ODESystem: cdef void evaluate(self, double time, double[:] states, double[:] derivatives) noexcept: """ Evaluate that needs to filled out by an ODE system """ ... + + +cdef class SDESystem: + """ SDE system of the form: dXt = a(Xt,t) dt + b(Xt,t) dW,""" + + def __init__(self): + """ Initialize """ + ... + + cdef void evaluate_a(self, double time, double[:] states, double[:] derivatives) noexcept: + """ a(Xt,t) """ + ... + + cdef void evaluate_b(self, double time, double[:] states, double[:] derivatives) noexcept: + """ b(Xt,t) """ + ... From 73ce5ff261a95fa2aeffea44e39e65116a165321 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Wed, 20 Nov 2024 23:02:45 -0500 Subject: [PATCH 139/316] [CORE][NUMERIC] Refactored ornstein uhlenbeck SDE system --- farms_network/core/stochastic_noise.pxd | 24 ++++++- farms_network/core/stochastic_noise.pyx | 89 ++++++++++++++++++++++-- farms_network/numeric/integrators_cy.pxd | 15 +--- farms_network/numeric/integrators_cy.pyx | 10 ++- 4 files changed, 112 insertions(+), 26 deletions(-) diff --git a/farms_network/core/stochastic_noise.pxd b/farms_network/core/stochastic_noise.pxd index fc121d2..8b35a8d 100644 --- a/farms_network/core/stochastic_noise.pxd +++ b/farms_network/core/stochastic_noise.pxd @@ -1,6 +1,24 @@ -from ..numeric.system cimport ODESystem +# distutils: language = c++ +from ..numeric.system cimport SDESystem +from libc.math cimport sqrt as csqrt +from libcpp.random cimport mt19937, normal_distribution -cdef class OrnsteinUhlenbeck(ODESystem): - cdef void evaluate(self, double time, double[:] states, double[:] derivatives) noexcept +cdef struct OrnsteinUhlenbeckParameters: + double* mu + double* sigma + double* tau + mt19937* random_generator + normal_distribution[double]* distribution + + +cdef class OrnsteinUhlenbeck(SDESystem): + + cdef: + double timestep + unsigned int n_dim + OrnsteinUhlenbeckParameters* parameters + + cdef void evaluate_a(self, double time, double[:] states, double[:] derivatives) noexcept + cdef void evaluate_b(self, double time, double[:] states, double[:] derivatives) noexcept diff --git a/farms_network/core/stochastic_noise.pyx b/farms_network/core/stochastic_noise.pyx index 719ae60..6a7fd99 100644 --- a/farms_network/core/stochastic_noise.pyx +++ b/farms_network/core/stochastic_noise.pyx @@ -1,9 +1,90 @@ -cdef class OrnsteinUhlenbeck(ODESystem): - """ """ +# distutils: language = c++ - def __init__(self): +""" +----------------------------------------------------------------------- +Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty +Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +----------------------------------------------------------------------- +""" + + +from libc.stdlib cimport free, malloc + + +cdef class OrnsteinUhlenbeck(SDESystem): + """ Ornstein Uhlenheck parameters """ + + def __cinit__(self, unsigned int n_dim): + """ C initialization for manual memory allocation """ + + self.parameters = malloc(sizeof(OrnsteinUhlenbeckParameters)) + if self.parameters is NULL: + raise MemoryError("Failed to allocate memory for OrnsteinUhlenbeck Parameters") + + self.parameters.mu = malloc(n_dim*sizeof(double)) + if self.parameters.mu is NULL: + raise MemoryError("Failed to allocate memory for OrnsteinUhlenbeck parameter MU") + + self.parameters.sigma = malloc(n_dim*sizeof(double)) + if self.parameters.sigma is NULL: + raise MemoryError("Failed to allocate memory for OrnsteinUhlenbeck parameter SIGMA") + + self.parameters.tau = malloc(n_dim*sizeof(double)) + if self.parameters.tau is NULL: + raise MemoryError("Failed to allocate memory for OrnsteinUhlenbeck parameter TAU") + + self.parameters.random_generator = malloc(n_dim*sizeof(mt19937)) + if self.parameters.random_generator is NULL: + raise MemoryError("Failed to allocate memory for OrnsteinUhlenbeck parameter RANDOM GENERATOR") + + self.parameters.distribution = malloc(n_dim*sizeof(normal_distribution[double])) + if self.parameters.distribution is NULL: + raise MemoryError("Failed to allocate memory for OrnsteinUhlenbeck parameter Distribution") + + def __dealloc__(self): + """ Deallocate any manual memory as part of clean up """ + + if self.parameters.mu is not NULL: + free(self.parameters.mu) + if self.parameters.sigma is not NULL: + free(self.parameters.sigma) + if self.parameters.tau is not NULL: + free(self.parameters.tau) + if self.parameters.random_generator is not NULL: + free(self.parameters.random_generator) + if self.parameters.distribution is not NULL: + free(self.parameters.distribution) + if self.parameters is not NULL: + free(self.parameters) + + def __init__(self, options): super().__init__() + cdef void evaluate_a(self, double time, double[:] states, double[:] derivatives) noexcept: + ... + # cdef unsigned int j + # cdef OrnsteinUhlenbeckParameters params = self.OrnsteinUhlenbeckParameters[0] + # for j range(self.n_dim): + # derivatives[j] = (params.mu[j]-states[j])/params.tau[0] - cdef void evaluate(self, double time, double[:] states, double[:] derivatives) noexcept: + cdef void evaluate_b(self, double time, double[:] states, double[:] derivatives) noexcept: ... + # cdef unsigned int j + # cdef OrnsteinUhlenbeckParameters params = ( + # self.OrnsteinUhlenbeckParameters[0] + # ) + # for j range(self.n_dim): + # derivatives[j] = (params.mu[j]-states[j])/params.tau[0] + # params.sigma[j]*(cppsqrt((2.0*params[0].dt)/params.tau[j])) diff --git a/farms_network/numeric/integrators_cy.pxd b/farms_network/numeric/integrators_cy.pxd index 8b15a53..4e42305 100644 --- a/farms_network/numeric/integrators_cy.pxd +++ b/farms_network/numeric/integrators_cy.pxd @@ -4,7 +4,7 @@ from farms_core.array.array_cy cimport DoubleArray1D from libc.math cimport sqrt as csqrt from libcpp.random cimport mt19937, normal_distribution -from .system cimport ODESystem +from .system cimport ODESystem, SDESystem include 'types.pxd' @@ -23,18 +23,7 @@ cdef class RK4Solver: cdef void step(self, ODESystem sys, double time, double[:] state) noexcept -cdef struct OrnsteinUhlenbeckParameters: - double mu - double sigma - double tau - double dt - mt19937 random_generator - normal_distribution[double] distribution - - cdef class EulerMaruyamaSolver: - cdef: - OrnsteinUhlenbeckParameters parameters cdef: - cdef void step(self, ODESystem sys, double time, double[:] state) noexcept + cdef void step(self, SDESystem sys, double time, double[:] state) noexcept diff --git a/farms_network/numeric/integrators_cy.pyx b/farms_network/numeric/integrators_cy.pyx index f8aa700..ae5b66c 100644 --- a/farms_network/numeric/integrators_cy.pyx +++ b/farms_network/numeric/integrators_cy.pyx @@ -72,9 +72,8 @@ cdef class EulerMaruyamaSolver: super().__init__() self.dim = dim self.dt = dt - self.parameters = OrnsteinUhlenbeckParameters() - cdef void step(self, ODESystem sys, double time, double[:] state) noexcept: + cdef void step(self, SDESystem sys, double time, double[:] state) noexcept: """ Update stochastic noise process with Euler–Maruyama method (also called the Euler method) is a method for the approximate numerical solution of a stochastic differential equation (SDE) """ @@ -82,7 +81,6 @@ cdef class EulerMaruyamaSolver: cdef unsigned int i cdef double noise - cdef OrnsteinUhlenbeckParameters params = self.parameters - for j in range(self.dim): - noise = params.distribution(params.random_generator) - state[i] += ((params.mu-state[j])*params.dt/params.tau) + params.sigma*(csqrt((2.0*params.dt)/params.tau))*noise + # for j in range(self.dim): + # noise = params.distribution(params.random_generator) + # state[i] += ((params.mu-state[j])*params.dt/params.tau) + params.sigma*(csqrt((2.0*params.dt)/params.tau))*noise From 47dc47b20d896f65639703b48b6400ed7eb19df8 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Wed, 20 Nov 2024 23:03:26 -0500 Subject: [PATCH 140/316] [CORE] Added int type assertion for iteration and buffer size --- farms_network/core/options.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/farms_network/core/options.py b/farms_network/core/options.py index 3e10a7e..55af0c6 100644 --- a/farms_network/core/options.py +++ b/farms_network/core/options.py @@ -168,10 +168,12 @@ class NetworkLogOptions(Options): def __init__(self, n_iterations: int, **kwargs): super().__init__(**kwargs) - self.n_iterations = n_iterations + self.n_iterations: int = n_iterations + assert isinstance(self.n_iterations, int), "iterations shoulde be an integer" self.buffer_size: int = kwargs.pop('buffer_size', self.n_iterations) if self.buffer_size == 0: self.buffer_size = self.n_iterations + assert isinstance(self.buffer_size, int), "buffer_size shoulde be an integer" self.nodes_all: bool = kwargs.pop("nodes_all", False) if kwargs: From ed2349ca3b02bd797b79f0a23718a4dfd7755e4e Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Wed, 20 Nov 2024 23:04:20 -0500 Subject: [PATCH 141/316] [CORE][NETWORK] Added buffer_size attribute to match FARMS updates --- farms_network/core/network.pxd | 1 + farms_network/core/network.pyx | 1 + 2 files changed, 2 insertions(+) diff --git a/farms_network/core/network.pxd b/farms_network/core/network.pxd index aca9369..33ec2b8 100644 --- a/farms_network/core/network.pxd +++ b/farms_network/core/network.pxd @@ -33,6 +33,7 @@ cdef class PyNetwork(ODESystem): unsigned int iteration unsigned int n_iterations + unsigned int buffer_size double timestep public RK4Solver integrator diff --git a/farms_network/core/network.pyx b/farms_network/core/network.pyx index 2450833..ec55b6a 100644 --- a/farms_network/core/network.pyx +++ b/farms_network/core/network.pyx @@ -128,6 +128,7 @@ cdef class PyNetwork(ODESystem): self.n_iterations: int = network_options.integration.n_iterations self.timestep: int = network_options.integration.timestep self.iteration: int = 0 + self.buffer_size: int = network_options.logs.buffer_size def __dealloc__(self): """ Deallocate any manual memory as part of clean up """ From c81379a2e9bec266dc7bdc86fec020067dc59279 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Wed, 20 Nov 2024 23:05:13 -0500 Subject: [PATCH 142/316] [CORE][NETWORK] Added support network state initialization --- farms_network/core/network.pyx | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/farms_network/core/network.pyx b/farms_network/core/network.pyx index ec55b6a..15452d2 100644 --- a/farms_network/core/network.pyx +++ b/farms_network/core/network.pyx @@ -166,7 +166,9 @@ cdef class PyNetwork(ODESystem): pyn = self.pynodes[index] c_node = (pyn.node) c_node.ninputs = len( - connectivity.sources[connectivity.indices[index]:connectivity.indices[index+1]] + connectivity.sources[ + connectivity.indices[index]:connectivity.indices[index+1] + ] ) if connectivity.indices else 0 self.network.nodes[index] = c_node __nstates += node_options._nstates @@ -179,6 +181,15 @@ cdef class PyNetwork(ODESystem): c_edge = (pye.edge) self.network.edges[index] = c_edge + # Initial states data + # Initialize states + for j, node in enumerate(options.nodes): + if node.state: + for state_index, index in enumerate( + range(data.states.indices[j], data.states.indices[j+1]) + ): + data.states.array[index] = node.state.initial[state_index] + @staticmethod def generate_node(node_options: NodeOptions): """ Generate a node from options """ From eb60959b5df03db8e949d6ec876400ddce84be6b Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Wed, 20 Nov 2024 23:06:10 -0500 Subject: [PATCH 143/316] [MOUSE] Working quadruped network model --- examples/mouse/components.py | 1238 ++++++++++++++++++++++++---------- examples/mouse/run.py | 580 +++++++++------- 2 files changed, 1212 insertions(+), 606 deletions(-) diff --git a/examples/mouse/components.py b/examples/mouse/components.py index 3c4594e..01b9fd1 100644 --- a/examples/mouse/components.py +++ b/examples/mouse/components.py @@ -9,18 +9,49 @@ import networkx as nx import numpy as np from farms_network.core import options +from farms_core.io.yaml import read_yaml from farms_network.core.data import NetworkData -from farms_network.core.network import PyNetwork, rk4 - -# from farms_network.gui.gui import NetworkGUI +from farms_network.core.network import PyNetwork from jinja2 import Environment, FileSystemLoader from tqdm import tqdm plt.rcParams["text.usetex"] = True +def calculate_arc_rad(source_pos, target_pos, base_rad=-0.1): + """Calculate arc3 radius for edge based on node positions.""" + dx = target_pos[0] - source_pos[0] + dy = target_pos[1] - source_pos[1] + + # Set curvature to zero if nodes are aligned horizontally or vertically + if dx == 0 or dy == 0: + return 0.0 + + # Decide on curvature based on position differences + if abs(dx) > abs(dy): + # Horizontal direction - positive rad for up, negative for down + return -base_rad if dy >= 0 else base_rad + else: + # Vertical direction - positive rad for right, negative for left + return base_rad if dx >= 0 else base_rad + + +def update_edge_visuals(network_options): + """ Update edge options """ + + nodes = network_options.nodes + edges = network_options.edges + for edge in edges: + base_rad = calculate_arc_rad( + nodes[nodes.index(edge.source)].visual.position, + nodes[nodes.index(edge.target)].visual.position, + ) + edge.visual.connectionstyle = f"arc3,rad={base_rad*0.0}" + return network_options + + def join_str(strings): - return "_".join(strings) + return "_".join(filter(None, strings)) def multiply_transform(vec: np.ndarray, transform_mat: np.ndarray) -> np.ndarray: @@ -52,7 +83,7 @@ def get_scale_matrix(scale: float) -> np.ndarray: return np.array([[scale, 0, 0], [0, scale, 0], [0, 0, 1]]) -def get_mirror_matrix(mirror_x: bool, mirror_y: bool) -> np.ndarray: +def get_mirror_matrix(mirror_x: bool = False, mirror_y: bool = False) -> np.ndarray: """Return a mirror matrix based on the mirror flags.""" mirror_matrix = np.identity(3) if mirror_x: @@ -133,6 +164,11 @@ def create_node( position = multiply_transform(position_vec, transform_mat).tolist() # Determine node type and state options + visual_options = options.NodeVisualOptions( + position=position, + label=label, + color=color, + ) if node_type == "LINaPDanner": state_options = options.LINaPDannerStateOptions.from_kwargs(**states) parameters = options.LINaPDannerParameterOptions.defaults(**parameters) @@ -148,6 +184,7 @@ def create_node( elif node_type == "ExternalRelay": state_options = None parameters = options.NodeParameterOptions() + visual_options.radius = 0.0 node_options_class = options.ExternalRelayNodeOptions else: raise ValueError(f"Unknown node type: {node_type}") @@ -156,7 +193,7 @@ def create_node( return node_options_class( name=full_name, parameters=parameters, - visual=options.NodeVisualOptions(position=position, label=label, color=color), + visual=visual_options, state=state_options, ) @@ -268,46 +305,10 @@ def nodes(self): {"v0": -60.0}, {}, ), - ] - - # Loop through the node specs to create each node using the create_node function - nodes = create_nodes(node_specs, self.name, self.transform_mat) - return nodes - - def edges(self): - """Add edges.""" - - # Define edge details in a list for easier iteration - edge_specs = [ - (("RG", "F"), ("RG", "In", "F"), 0.4, "excitatory"), - (("RG", "In", "F"), ("RG", "E"), -1.0, "inhibitory"), - (("RG", "E"), ("RG", "In", "E"), 0.4, "excitatory"), - (("RG", "In", "E"), ("RG", "F"), -0.08, "inhibitory"), - (("RG", "In", "E2"), ("RG", "F"), -0.04, "excitatory"), - ] - - # Loop through the edge specs to create each edge - edges = create_edges(edge_specs, self.name) - return edges - - -class RhythmDrive: - """Generate Drive Network""" - - def __init__(self, name="", transform_mat=np.identity(3)): - """Initialization.""" - self.name = name - self.transform_mat = transform_mat - - def nodes(self): - """Add nodes.""" - nodes = {} - - node_specs = [ ( join_str(("RG", "F", "DR")), "Linear", - np.array((3.0, 2.0)), # Assuming position is not important + np.array((3.0, 2.0)), "d", [0.5, 0.5, 0.5], # Default visual color if needed None, @@ -316,7 +317,7 @@ def nodes(self): ( join_str(("RG", "E", "DR")), "Linear", - np.array((-3.0, 2.0)), # Assuming position is not important + np.array((-3.0, 2.0)), "d", [0.5, 0.5, 0.5], # Default visual color if needed None, @@ -328,6 +329,24 @@ def nodes(self): nodes = create_nodes(node_specs, self.name, self.transform_mat) return nodes + def edges(self): + """Add edges.""" + + # Define edge details in a list for easier iteration + edge_specs = [ + (("RG", "F"), ("RG", "In", "F"), 0.4, "excitatory"), + (("RG", "In", "F"), ("RG", "E"), -1.0, "inhibitory"), + (("RG", "E"), ("RG", "In", "E"), 0.4, "excitatory"), + (("RG", "In", "E"), ("RG", "F"), -0.08, "inhibitory"), + (("RG", "In", "E2"), ("RG", "F"), -0.04, "inhibitory"), + (("RG", "F", "DR"), ("RG", "F"), 1.0, "excitatory"), + (("RG", "E", "DR"), ("RG", "E"), 1.0, "excitatory"), + ] + + # Loop through the edge specs to create each edge + edges = create_edges(edge_specs, self.name) + return edges + class PatternFormation: """Generate PatternFormation Network""" @@ -356,7 +375,7 @@ def nodes(self): join_str(("PF", "EA")), "LINaPDanner", np.array((-9.0, 0.0)), - "F\\textsubscript{A}", + "E\\textsubscript{A}", [0.0, 1.0, 0.0], {"v0": -60.0, "h0": np.random.uniform(0, 1)}, {"g_nap": 0.125, "e_leak": -67.5}, @@ -383,19 +402,19 @@ def nodes(self): join_str(("PF", "FB")), "LINaPDanner", np.array((9.0, 0.0)), - "F\\textsubscript{A}", + "F\\textsubscript{B}", [1.0, 0.0, 0.0], {"v0": -60.0, "h0": np.random.uniform(0, 1)}, - {"g_nap": 0.125, "e_leak": -67.5}, + {"g_nap": 0.125, "g_leak": 1.0, "e_leak": -67.5}, ), ( join_str(("PF", "EB")), "LINaPDanner", np.array((3.0, 0.0)), - "F\\textsubscript{A}", + "E\\textsubscript{B}", [0.0, 1.0, 0.0], {"v0": -60.0, "h0": np.random.uniform(0, 1)}, - {"g_nap": 0.125, "e_leak": -67.5}, + {"g_nap": 0.125, "g_leak": 1.0, "e_leak": -67.5}, ), ( join_str(("PF", "In", "FB")), @@ -433,6 +452,42 @@ def nodes(self): {"v0": -60.0}, {"g_leak": 5.0}, ), + ( + join_str(("PF", "FA", "DR")), + "Linear", + np.array((-3.0, 2.0)), + "d", + [0.5, 0.5, 0.5], # Default visual color if needed + None, + {"slope": 0.6, "bias": 0.0}, + ), + ( + join_str(("PF", "EA", "DR")), + "Linear", + np.array((-9.0, 2.0)), + "d", + [0.5, 0.5, 0.5], # Default visual color if needed + None, + {"slope": 0.6, "bias": 0.0}, + ), + ( + join_str(("PF", "EB", "DR")), + "Linear", + np.array((3.0, 2.0)), + "d", + [0.5, 0.5, 0.5], # Default visual color if needed + None, + {"slope": 0.07, "bias": 0.0}, + ), + ( + join_str(("PF", "In2", "E", "DR")), + "Linear", + np.array((4.0, -3.0)), + "d", + [0.5, 0.5, 0.5], # Default visual color if needed + None, + {"slope": 0.1, "bias": 0.0}, + ), ] # Loop through the node specs to create each node using the create_node function @@ -446,23 +501,32 @@ def edges(self): # Define edge details in a list for easier iteration edge_specs = [ (("PF", "FA"), ("PF", "In", "FA"), 0.8, "excitatory"), - (("PF", "In", "FA"), ("PF", "EA"), -1.5, "inhibitory"), (("PF", "EA"), ("PF", "In", "EA"), 1.0, "excitatory"), + (("PF", "In", "FA"), ("PF", "EA"), -1.5, "inhibitory"), (("PF", "In", "EA"), ("PF", "FA"), -1.0, "inhibitory"), + (("PF", "FB"), ("PF", "In", "FB"), 1.5, "excitatory"), - (("PF", "In", "FB"), ("PF", "EB"), -2.0, "inhibitory"), (("PF", "EB"), ("PF", "In", "EB"), 1.5, "excitatory"), + (("PF", "In", "FB"), ("PF", "EB"), -2.0, "inhibitory"), (("PF", "In", "EB"), ("PF", "FB"), -0.25, "inhibitory"), + (("PF", "In", "FA"), ("PF", "EB"), -0.5, "inhibitory"), (("PF", "In", "FA"), ("PF", "FB"), -0.1, "inhibitory"), (("PF", "In", "EA"), ("PF", "EB"), -0.5, "inhibitory"), (("PF", "In", "EA"), ("PF", "FB"), -0.25, "inhibitory"), + (("PF", "In", "FB"), ("PF", "EA"), -0.5, "inhibitory"), (("PF", "In", "FB"), ("PF", "FA"), -0.75, "inhibitory"), (("PF", "In", "EB"), ("PF", "EA"), -2.0, "inhibitory"), (("PF", "In", "EB"), ("PF", "FA"), -2.0, "inhibitory"), + (("PF", "In2", "F"), ("PF", "FB"), -3.0, "inhibitory"), (("PF", "In2", "E"), ("PF", "EB"), -3.0, "inhibitory"), + + (("PF", "FA", "DR"), ("PF", "FA"), 1.0, "excitatory"), + (("PF", "EA", "DR"), ("PF", "EA"), 1.0, "excitatory"), + (("PF", "EB", "DR"), ("PF", "EB"), 1.0, "excitatory"), + (("PF", "In2", "E", "DR"), ("PF", "In2", "E"), -1.0, "inhibitory"), ] # Loop through the edge specs to create each edge @@ -536,29 +600,10 @@ def nodes(self): {"v0": -60.0}, {}, ), - ] - - # Loop through the node specs to create each node using the create_node function - nodes = create_nodes(node_specs, self.name, self.transform_mat) - return nodes - - -class CommissuralDrive: - """Generate Drive Network""" - - def __init__(self, name="", transform_mat=np.identity(3)): - """Initialization.""" - self.name = name - self.transform_mat = transform_mat - - def nodes(self): - """Add nodes.""" - - node_specs = [ ( join_str(("V0V", "DR")), "Linear", - np.array((0.0, 0.0)), + np.array((3.0, 1.0)), "d", [0.5, 0.5, 0.5], None, @@ -567,7 +612,7 @@ def nodes(self): ( join_str(("V0D", "DR")), "Linear", - np.array((0.0, 0.0)), + np.array((3.0, -2.5)), "d", [0.5, 0.5, 0.5], None, @@ -579,6 +624,20 @@ def nodes(self): nodes = create_nodes(node_specs, self.name, self.transform_mat) return nodes + def edges(self): + """Add edges.""" + edges = {} + + # Define edge details in a list for easier iteration + edge_specs = [ + (("V0V", "DR"), ("V0V",), -1.0, "inhibitory"), + (("V0D", "DR"), ("V0D",), -1.0, "inhibitory"), + ] + + # Loop through the edge specs to create each edge + edges = create_edges(edge_specs, self.name) + return edges + class LPSN: """Generate Long Propriospinal Network""" @@ -648,29 +707,10 @@ def nodes(self): {"v0": -60.0}, {}, ), - ] - - # Loop through the node specs to create each node using the create_node function - nodes = create_nodes(node_specs, self.name, self.transform_mat) - return nodes - - -class LPSNDrive: - """Generate Drive Network""" - - def __init__(self, name="", transform_mat=np.identity(3)): - """Initialization.""" - self.name = name - self.transform_mat = transform_mat - - def nodes(self): - """Add nodes.""" - - node_specs = [ ( join_str(("V0D", "diag", "DR")), "Linear", - np.array((0.0, 0.0)), + np.array((1.0, 0.5)), "d", [0.5, 0.5, 0.5], None, @@ -682,6 +722,19 @@ def nodes(self): nodes = create_nodes(node_specs, self.name, self.transform_mat) return nodes + def edges(self): + """Add edges.""" + edges = {} + + # Define edge details in a list for easier iteration + edge_specs = [ + (("V0D", "diag", "DR"), ("V0D", "diag"), -1.0, "inhibitory") + ] + + # Loop through the edge specs to create each edge + edges = create_edges(edge_specs, self.name) + return edges + class MotorLayer: """Motorneurons and other associated interneurons.""" @@ -695,23 +748,77 @@ def __init__(self, muscles: List[str], name="", transform_mat=np.identity(3)): def nodes(self): """Add neurons for the motor layer.""" - node_specs = [] + spacing = 2.5 + max_muscles = max(len(self.muscles["agonist"]), len(self.muscles["antagonist"])) + node_specs = [] # Define neurons for the muscles for x_off, muscle in zip( - self._generate_positions(len(self.muscles["agonist"])), + self._generate_positions(len(self.muscles["agonist"]), spacing), self.muscles["agonist"], ): node_specs.extend(self._get_muscle_neurons(muscle, x_off, 0.0)) for x_off, muscle in zip( - self._generate_positions(len(self.muscles["antagonist"])), + self._generate_positions(len(self.muscles["antagonist"]), spacing), self.muscles["antagonist"], ): node_specs.extend( - self._get_muscle_neurons(muscle, x_off, 3.5, mirror_y=True) + self._get_muscle_neurons(muscle, x_off, 5.0, mirror_y=True) ) + # Calculate x positions for Ia inhibitory neurons + IaIn_x_positions = np.linspace(-spacing * max_muscles, spacing * max_muscles, 4) + y_off = 1.75 + + node_specs += [ + ( + join_str(("Ia", "In", "EA")), + "LIDanner", + np.array((IaIn_x_positions[0], y_off, 1.0)), + "Ia\\textsubscript{ea}", + [0.0, 0.0, 1.0], + {"v0": -60.0}, + {}, + ), + ( + join_str(("Ia", "In", "EB")), + "LIDanner", + np.array((IaIn_x_positions[1], y_off, 1.0)), + "Ia\\textsubscript{eb}", + [0.0, 0.0, 1.0], + {"v0": -60.0}, + {}, + ), + ( + join_str(("Ia", "In", "FA")), + "LIDanner", + np.array((IaIn_x_positions[2], y_off, 1.0)), + "Ia\\textsubscript{fa}", + [0.0, 0.0, 1.0], + {"v0": -60.0}, + {}, + ), + ( + join_str(("Ia", "In", "FB")), + "LIDanner", + np.array((IaIn_x_positions[3], y_off, 1.0)), + "Ia\\textsubscript{fb}", + [0.0, 0.0, 1.0], + {"v0": -60.0}, + {}, + ), + ( + join_str(("Ib", "In", "RG")), + "LIDanner", + np.array((np.mean(IaIn_x_positions), y_off - spacing, 1.0)), + "Ib\\textsubscript{rg}", + [0.0, 0.0, 1.0], + {"v0": -60.0}, + {}, + ), + ] + # Loop through the node specs to create each node using the create_node function nodes = create_nodes(node_specs, self.name, self.transform_mat) return nodes @@ -725,51 +832,15 @@ def edges(self): edges.update(self._generate_motor_connections(muscle)) for muscle in self.muscles["antagonist"]: edges.update(self._generate_motor_connections(muscle)) - return edges def _generate_motor_connections(self, muscle): """Generate the motor connections for a specific muscle.""" - edges = {} - edge_specs = [ - ( - f"{muscle}_Ia", - f"{muscle}_Mn", - 0.01, - "excitatory", - "Ia_monosynaptic_excitation", - ), - ( - f"{muscle}_Mn", - f"{muscle}_Rn", - 0.01, - "excitatory", - "Rn_reciprocal_inhibition", - ), - ( - f"{muscle}_Rn", - f"{muscle}_Mn", - -0.01, - "inhibitory", - "Rn_reciprocal_inhibition", - ), - ( - f"{muscle}_Ib", - f"{muscle}_IbIn_i", - 0.01, - "excitatory", - "Ib_disynaptic_inhibition", - ), - ( - f"{muscle}_IbIn_i", - f"{muscle}_Mn", - -0.01, - "inhibitory", - "Ib_disynaptic_inhibition", - ), + *MotorLayer.connect_Rn_reciprocal_inhibition(muscle), + *MotorLayer.connect_Ia_monosypatic_excitation(muscle), + *MotorLayer.connect_Ib_disynaptic_inhibition(muscle), ] - # Loop through the edge specs to create each edge edges = create_edges(edge_specs, self.name) return edges @@ -777,10 +848,9 @@ def _generate_motor_connections(self, muscle): def _get_muscle_neurons(self, muscle, x_off, y_off, mirror_y=False): """Return neuron specifications for a muscle.""" mirror_y_sign = -1 if mirror_y else 1 - return [ ( - f"{muscle}_Mn", + join_str((muscle["name"], "Mn")), "LIDanner", np.array((x_off, y_off, 1.0)), "Mn", @@ -789,7 +859,7 @@ def _get_muscle_neurons(self, muscle, x_off, y_off, mirror_y=False): {"e_leak": -52.5, "g_leak": 1.0}, ), ( - f"{muscle}_Ia", + join_str((muscle["name"], "Ia")), "ExternalRelay", np.array((x_off - 0.5, y_off + 0.75 * mirror_y_sign, 1.0)), "Ia", @@ -798,25 +868,25 @@ def _get_muscle_neurons(self, muscle, x_off, y_off, mirror_y=False): {}, ), ( - f"{muscle}_II", + join_str((muscle["name"], "II")), "ExternalRelay", np.array((x_off, y_off + 0.75 * mirror_y_sign, 1.0)), "II", [1.0, 0.0, 0.0], - {"v0": 0.0}, + {}, {}, ), ( - f"{muscle}_Ib", + join_str((muscle["name"], "Ib")), "ExternalRelay", np.array((x_off + 0.5, y_off + 0.75 * mirror_y_sign, 1.0)), "Ib", [1.0, 0.0, 0.0], - {"v0": 0.0}, + {}, {}, ), ( - f"{muscle}_Rn", + join_str((muscle["name"], "Rn")), "LIDanner", np.array((x_off + 0.5, y_off - 1.0 * mirror_y_sign, 1.0)), "Rn", @@ -825,7 +895,7 @@ def _get_muscle_neurons(self, muscle, x_off, y_off, mirror_y=False): {}, ), ( - f"{muscle}_IbIn_i", + join_str((muscle["name"], "Ib", "In", "i")), "LIDanner", np.array((x_off + 1.0, y_off, 1.0)), "Ib\\textsubscript{i}", @@ -834,7 +904,7 @@ def _get_muscle_neurons(self, muscle, x_off, y_off, mirror_y=False): {}, ), ( - f"{muscle}_IbIn_e", + join_str((muscle["name"], "Ib", "In", "e")), "LIDanner", np.array((x_off + 1.0, y_off + 1.5 * mirror_y_sign, 1.0)), "Ib\\textsubscript{e}", @@ -843,7 +913,7 @@ def _get_muscle_neurons(self, muscle, x_off, y_off, mirror_y=False): {}, ), ( - f"{muscle}_IIIn_RG", + join_str((muscle["name"], "II", "In", "RG")), "LIDanner", np.array((x_off - 1.0, y_off, 1.0)), "II\\textsubscript{RG}", @@ -853,280 +923,700 @@ def _get_muscle_neurons(self, muscle, x_off, y_off, mirror_y=False): ), ] - def _generate_positions(self, num_muscles): + def _generate_positions(self, num_muscles, spacing): """Generate positions for the neurons.""" - spacing = 1.25 return np.linspace(-spacing * num_muscles, spacing * num_muscles, num_muscles) + @staticmethod + def connect_Ia_monosypatic_excitation(muscle: str): + edge_specs = [ + ((muscle["name"], "Ia"), (muscle["name"], "Mn"), 0.01, "excitatory"), + ] + return edge_specs + + @staticmethod + def connect_Ib_disynaptic_inhibition(muscle: str): + edge_specs = [ + ((muscle["name"], "Ib"), (muscle["name"], "Ib", "In", "i"), 0.01, "excitatory"), + ((muscle["name"], "Ib", "In", "i"), (muscle["name"], "Mn"), -0.01, "inhibitory"), + ] + return edge_specs + + @staticmethod + def connect_Rn_reciprocal_inhibition(muscle: str): + """ Renshaw reciprocal inhibition """ + edge_specs = [ + ((muscle["name"], "Mn"), (muscle["name"], "Rn"), 0.01, "excitatory"), + ((muscle["name"], "Rn"), (muscle["name"], "Mn"), -0.01, "inhibitory"), + ] + return edge_specs + + +###################### +# CONNECT RG PATTERN # +###################### +def connect_rhythm_pattern(base_name): + """Connect RG's to pattern formation.""" + + edge_specs = [ + (("RG", "F"), ("PF", "FA"), 0.8, "excitatory"), + (("RG", "E"), ("PF", "EA"), 0.7, "excitatory"), + + (("RG", "F"), ("PF", "FB"), 0.6, "excitatory"), + (("RG", "E"), ("PF", "EB"), 0.5, "excitatory"), + + (("RG", "F"), ("PF", "In2", "F"), 0.4, "excitatory"), + (("RG", "E"), ("PF", "In2", "E"), 0.35, "excitatory"), + + (("RG", "In", "F"), ("PF", "EA"), -1.5, "inhibitory"), + (("RG", "In", "E"), ("PF", "FA"), -1.5, "inhibitory"), + ] + + # Use create_edges function to generate the edge options + return create_edges( + edge_specs, + base_name=base_name, + visual_options=options.EdgeVisualOptions() + ) + ########################## -# CONNECT RG COMMISSURAL # +# Connect RG COMMISSURAL # ########################## def connect_rg_commissural(): """Connect RG's to Commissural.""" - edges = {} + edge_specs = [] + for limb in ("hind", "fore"): for side in ("left", "right"): + edge_specs.extend([ + ((side, limb, "RG", "F"), (side, limb, "V2a"), 1.0, "excitatory"), + ((side, limb, "RG", "F"), (side, limb, "V0D"), 0.7, "excitatory"), + ((side, limb, "RG", "F"), (side, limb, "V3F"), 0.35, "excitatory"), + ((side, limb, "RG", "E"), (side, limb, "V3E"), 0.35, "excitatory"), + ((side, limb, "V2a"), (side, limb, "V0V"), 1.0, "excitatory"), + ((side, limb, "InV0V"), (side, limb, "RG", "F"), -0.07, "inhibitory") + ]) + + # Handle cross-limb connections + for sides in (("left", "right"), ("right", "left")): + edge_specs.extend([ + ((sides[0], limb, "V0V"), (sides[1], limb, "InV0V"), 0.6, "excitatory"), + ((sides[0], limb, "V0D"), (sides[1], limb, "RG", "F"), -0.07, "inhibitory"), + ((sides[0], limb, "V3F"), (sides[1], limb, "RG", "F"), 0.03, "excitatory"), + ((sides[0], limb, "V3E"), (sides[1], limb, "RG", "E"), 0.02, "excitatory"), + ((sides[0], limb, "V3E"), (sides[1], limb, "RG", "In", "E"), 0.0, "excitatory"), + ((sides[0], limb, "V3E"), (sides[1], limb, "RG", "In", "E2"), 0.8, "excitatory"), + ]) + + # Create the edges using create_edges + edges = create_edges( + edge_specs, base_name="", visual_options=options.EdgeVisualOptions() + ) + return edges - edges[join_str((side, limb, "RG", "F", "to", side, limb, "V2a"))] = ( - options.EdgeOptions( - source=join_str((side, limb, "RG", "F")), - target=join_str((side, limb, "V2a")), - weight=1.0, - type="excitatory", - visual=options.EdgeVisualOptions(), - ) - ) - # edges[join_str((side, limb, "RG" "F", "to", side, limb, "V2a", "diag"))] = options.EdgeOptions( - # source=join_str((side, limb, "RG" "F")), - # target=join_str((side, limb, "V2a", "diag")), - # weight=0.5, - # type="excitatory", - # visual=options.EdgeVisualOptions(), - - # ) - edges[join_str((side, limb, "RG", "F", "to", side, limb, "V0D"))] = ( - options.EdgeOptions( - source=join_str((side, limb, "RG", "F")), - target=join_str((side, limb, "V0D")), - weight=0.7, - type="excitatory", - visual=options.EdgeVisualOptions(), - ) - ) +############################## +# Connect Patter Commissural # +############################## +def connect_pattern_commissural(): - edges[join_str((side, limb, "RG", "F", "to", side, limb, "V3F"))] = ( - options.EdgeOptions( - source=join_str((side, limb, "RG", "F")), - target=join_str((side, limb, "V3F")), - weight=0.35, - type="excitatory", - visual=options.EdgeVisualOptions(), - ) - ) + edge_specs = [] - edges[join_str((side, limb, "RG", "E", "to", side, limb, "V3E"))] = ( - options.EdgeOptions( - source=join_str((side, limb, "RG", "E")), - target=join_str((side, limb, "V3E")), - weight=0.35, - type="excitatory", - visual=options.EdgeVisualOptions(), - ) - ) - - edges[join_str((side, limb, "V2a", "to", side, limb, "V0V"))] = ( - options.EdgeOptions( - source=join_str((side, limb, "V2a")), - target=join_str((side, limb, "V0V")), - weight=1.0, - type="excitatory", - visual=options.EdgeVisualOptions(), - ) - ) + for limb in ("hind",): + for side in ("left", "right"): + edge_specs.extend([ + ((side, limb, "V0D"), (side, limb, "PF", "FA"), -4.0, "inhibitory"), + ((side, limb, "InV0V"), (side, limb, "PF", "FA"), -3.0, "inhibitory"), + ]) + + # Create the edges using create_edges + edges = create_edges( + edge_specs, base_name="", visual_options=options.EdgeVisualOptions() + ) + return edges - edges[join_str((side, limb, "InV0V", "to", side, limb, "RG", "F"))] = ( - options.EdgeOptions( - source=join_str((side, limb, "InV0V")), - target=join_str((side, limb, "RG", "F")), - weight=-0.07, - type="inhibitory", - visual=options.EdgeVisualOptions(), - ) - ) - for sides in (("left", "right"), ("right", "left")): +def connect_fore_hind_circuits(): + """Connect CPG's to Interneurons.""" - edges[join_str((sides[0], limb, "V0V", "to", sides[1], limb, "InV0V"))] = ( - options.EdgeOptions( - source=join_str((sides[0], limb, "V0V")), - target=join_str((sides[1], limb, "InV0V")), - weight=0.6, - type="excitatory", - visual=options.EdgeVisualOptions(), - ) - ) + edge_specs = [] - edges[ - join_str((sides[0], limb, "V0D", "to", sides[1], limb, "RG", "F")) - ] = options.EdgeOptions( - source=join_str((sides[0], limb, "V0D")), - target=join_str((sides[1], limb, "RG", "F")), - weight=-0.07, - type="inhibitory", - visual=options.EdgeVisualOptions(), - ) + for side in ("left", "right"): + edge_specs.extend([ + ((side, "fore", "RG", "F"), (side, "fore", "Ini", "hom"), 0.70, "excitatory"), + ((side, "fore", "RG", "F"), (side, "V0D", "diag"), 0.50, "excitatory"), + ((side, "fore", "Ini", "hom"), (side, "hind", "RG", "F"), -0.01, "inhibitory"), + ((side, "fore", "Sh2", "hom"), (side, "hind", "RG", "F"), 0.01, "excitatory"), + ((side, "hind", "Sh2", "hom"), (side, "fore", "RG", "F"), 0.05, "excitatory"), + ((side, "fore", "RG", "F"), (side, "fore", "V0V", "diag"), 0.325, "excitatory"), + ((side, "hind", "RG", "F"), (side, "hind", "V3", "diag"), 0.325, "excitatory") + ]) + for limb in ("hind", "fore"): + edge_specs.extend([ + ((side, limb, "RG", "E"), (side, limb, "Sh2", "hom"), 0.50, "excitatory") + ]) + + # Handle cross-limb connections + for sides in (("left", "right"), ("right", "left")): + edge_specs.extend([ + ((sides[0], "V0D", "diag"), (sides[1], "hind", "RG", "F"), -0.01, "inhibitory"), + ((sides[0], "fore", "V0V", "diag"), (sides[1], "hind", "RG", "F"), 0.005, "excitatory"), + ((sides[0], "hind", "V3", "diag"), (sides[1], "fore", "RG", "F"), 0.04, "excitatory") + ]) + + # Create the edges using create_edges + edges = create_edges( + edge_specs, base_name="", visual_options=options.EdgeVisualOptions() + ) - edges[ - join_str((sides[0], limb, "V3F", "to", sides[1], limb, "RG", "F")) - ] = options.EdgeOptions( - source=join_str((sides[0], limb, "V3F")), - target=join_str((sides[1], limb, "RG", "F")), - weight=0.03, - type="excitatory", - visual=options.EdgeVisualOptions(), - ) + return edges - edges[ - join_str((sides[0], limb, "V3E", "to", sides[1], limb, "RG", "E")) - ] = options.EdgeOptions( - source=join_str((sides[0], limb, "V3E")), - target=join_str((sides[1], limb, "RG", "E")), - weight=0.02, - type="excitatory", - visual=options.EdgeVisualOptions(), - ) - edges[ - join_str((sides[0], limb, "V3E", "to", sides[1], limb, "RG", "In", "E")) - ] = options.EdgeOptions( - source=join_str((sides[0], limb, "V3E")), - target=join_str((sides[1], limb, "RG", "In", "E")), - weight=0.0, - type="excitatory", - visual=options.EdgeVisualOptions(), +def connect_pattern_motor_layer(base_name, muscle, patterns): + """Return edge specs for connecting pattern formation to motor neuron layer.""" + edge_specs = [ + (("PF", pattern), (muscle, "Mn"), 0.1, "excitatory",) + for pattern in patterns + ] + return create_edges(edge_specs, base_name=base_name) + + +def connect_pattern_to_IaIn(side, limb): + """Return edge specs for connecting pattern formation to motor neuron layer.""" + edge_specs = [ + (("PF", pattern), ("Ia", "In", pattern), 0.01, "excitatory") + for pattern in ("FA", "EA", "FB", "EB") + ] + return edge_specs + + +def connect_II_pattern_feedback(side, limb, muscle, patterns): + """Return edge specs for connecting group II feedback to pattern layer.""" + edge_specs = [ + ((muscle, "II"), ("PF", pattern), 0.01, "excitatory") + for pattern in patterns + ] + return edge_specs + + +def connect_Ia_reciprocal_inhibition_extensor2flexor(extensor: str, flexor: str): + """Return edge specs for Ia reciprocal inhibition from extensor to flexor.""" + edge_specs = [ + ((extensor, "Ia"), ("Ia", "In", "EA"), 0.01, "excitatory"), + ((extensor, "Ia"), ("Ia", "In", "EB"), 0.01, "excitatory"), + (("Ia", "In", "EA"), (flexor, "Mn"), -0.01, "inhibitory"), + (("Ia", "In", "EB"), (flexor, "Mn"), -0.01, "inhibitory"), + ] + return edge_specs + + +def connect_Ia_reciprocal_inhibition_flexor2extensor(flexor: str, extensor: str): + """Return edge specs for Ia reciprocal inhibition from flexor to extensor.""" + edge_specs = [ + ((flexor, "Ia"), ("Ia", "In", "FA"), 0.01, "excitatory"), + ((flexor, "Ia"), ("Ia", "In", "FB"), 0.01, "excitatory"), + (("Ia", "In", "FA"), (extensor, "Mn"), -0.01, "inhibitory"), + (("Ia", "In", "FB"), (extensor, "Mn"), -0.01, "inhibitory"), + ] + return edge_specs + + +def connect_Rn_reciprocal_facilitation_extensor2flexor(extensor: str, flexor: str): + """Return edge specs for Rn reciprocal facilitation from extensor to flexor.""" + edge_specs = [ + ((extensor, "Rn"), ("Ia", "In", "EA"), -0.01, "inhibitory"), + (("Ia", "In", "EA"), (flexor, "Mn"), -0.01, "inhibitory"), + ((extensor, "Rn"), ("Ia", "In", "EB"), -0.01, "inhibitory"), + (("Ia", "In", "EB"), (flexor, "Mn"), -0.01, "inhibitory"), + ] + return edge_specs + + +def connect_Rn_reciprocal_facilitation_flexor2extensor(flexor: str, extensor: str): + """Return edge specs for Rn reciprocal facilitation from flexor to extensor.""" + edge_specs = [ + ((flexor, "Rn"), ("Ia", "In", "FA"), -0.01, "inhibitory"), + (("Ia", "In", "FA"), (extensor, "Mn"), -0.01, "inhibitory"), + ((flexor, "Rn"), ("Ia", "In", "FB"), -0.01, "inhibitory"), + (("Ia", "In", "FB"), (extensor, "Mn"), -0.01, "inhibitory"), + ] + return edge_specs + + +def connect_Ib_disynaptic_extensor_excitation(extensor): + """Return edge specs for Ib disynaptic excitation in extensor.""" + edge_specs = [ + ((extensor, "Ib"), (extensor, "Ib", "In", "e"), 1.0, "excitatory"), + ((extensor, "Ib", "In", "e"), (extensor, "Mn"), 1.0, "excitatory"), + ] + return edge_specs + + +def connect_Ib_rg_feedback(extensor): + """Return edge specs for Ib rhythm feedback.""" + edge_specs = [ + ((extensor, "Ib"), ("Ib", "In", "RG"), 1.0, "excitatory"), + (("Ib", "In", "RG"), ("RG", "In", "E"), 1.0, "excitatory"), + (("Ib", "In", "RG"), ("RG", "E"), 1.0, "excitatory"), + ] + return edge_specs + + +def connect_II_rg_feedback(flexor): + """Return edge specs for II rhythm feedback.""" + edge_specs = [ + ((flexor, "II"), (flexor, "II", "In", "RG"), 1.0, "excitatory"), + ((flexor, "II", "In", "RG"), ("RG", "F"), 1.0, "excitatory"), + ((flexor, "II", "In", "RG"), ("RG", "In", "F"), 1.0, "excitatory"), + ] + return edge_specs + + +def connect_roll_vestibular_to_mn(side, fore_muscles, hind_muscles): + """Return edge specs for roll vestibular to motor neurons.""" + rates = ("position", "velocity") + weights = {"position": 0.01, "velocity": 0.01} + edge_specs = [] + + for rate in rates: + for muscle in fore_muscles: + name = "_".join(muscle["name"].split("_")[2:]) + edge_specs.append( + ( + (rate, "roll", "cclock", "In", "Vn"), + ("fore", name, "Mn"), + weights[rate], + "excitatory", + ) ) - edges[ - join_str( - (sides[0], limb, "V3E", "to", sides[1], limb, "RG", "In", "E2") + for muscle in hind_muscles: + name = "_".join(muscle["name"].split("_")[2:]) + edge_specs.append( + ( + (rate, "roll", "cclock", "In", "Vn"), + ("hind", name, "Mn"), + weights[rate], + "excitatory", ) - ] = options.EdgeOptions( - source=join_str((sides[0], limb, "V3E")), - target=join_str((sides[1], limb, "RG", "In", "E2")), - weight=0.8, - type="excitatory", - visual=options.EdgeVisualOptions(), ) - return edges + return edge_specs -def connect_fore_hind_circuits(): - """Connect CPG's to Interneurons.""" - edges = {} - for side in ("left", "right"): - edges[join_str((side, "fore", "RG", "F", "to", side, "fore", "Ini", "hom"))] = ( - options.EdgeOptions( - source=join_str((side, "fore", "RG", "F")), - target=join_str((side, "fore", "Ini", "hom")), - weight=0.70, - type="excitatory", - visual=options.EdgeVisualOptions(), - ) - ) +def connect_pitch_vestibular_to_mn( + side, rate, fore_muscles, hind_muscles, default_weight=0.01 +): + """Return edge specs for pitch vestibular to motor neurons.""" + edge_specs = [] - edges[join_str((side, "fore", "RG", "F", "to", side, "V0D", "diag"))] = ( - options.EdgeOptions( - source=join_str((side, "fore", "RG", "F")), - target=join_str((side, "V0D", "diag")), - weight=0.50, - type="excitatory", - visual=options.EdgeVisualOptions(), + for muscle in fore_muscles: + name = "_".join(muscle["name"].split("_")[2:]) + edge_specs.append( + ( + (rate, "pitch", "cclock", "In", "Vn"), + ("fore", name, "Mn"), + default_weight, + "excitatory", ) ) - edges[join_str((side, "fore", "Ini", "hom", "to", side, "hind", "RG", "F"))] = ( - options.EdgeOptions( - source=join_str((side, "fore", "Ini", "hom")), - target=join_str((side, "hind", "RG", "F")), - weight=-0.01, - type="excitatory", - visual=options.EdgeVisualOptions(), + for muscle in hind_muscles: + name = "_".join(muscle["name"].split("_")[2:]) + edge_specs.append( + ( + (rate, "pitch", "clock", "In", "Vn"), + ("hind", name, "Mn"), + default_weight, + "excitatory", ) ) - edges[join_str((side, "fore", "Sh2", "hom", "to", side, "hind", "RG", "F"))] = ( - options.EdgeOptions( - source=join_str((side, "fore", "Sh2", "hom")), - target=join_str((side, "hind", "RG", "F")), - weight=0.01, - type="excitatory", - visual=options.EdgeVisualOptions(), - ) + return edge_specs + + +########### +# Muscles # +########### +def define_muscle_patterns() -> dict: + muscles_patterns = { + "hind": { + "bfa": ["EA", "EB"], + "ip": ["FA", "FB"], + "bfpst": ["FA", "EA", "FB", "EB"], + "rf": ["EA", "FB", "EB"], + "va": ["EA", "FB", "EB"], + "mg": ["FA", "EA", "EB"], + "sol": ["EA", "EB"], + "ta": ["FA", "FB"], + "ab": ["FA", "EA", "FB", "EB"], + "gm_dorsal": ["FA", "EA", "FB", "EB"], + "edl": ["FA", "EA", "FB", "EB"], + "fdl": ["FA", "EA", "FB", "EB"], + }, + "fore": { + "spd": ["FA", "EA", "FB", "EB"], + "ssp": ["FA", "EA", "FB", "EB"], + "abd": ["FA", "EA", "FB", "EB"], + "add": ["FA", "EA", "FB", "EB"], + "tbl": ["FA", "EA", "FB", "EB"], + "tbo": ["FA", "EA", "FB", "EB"], + "bbs": ["FA", "FB"], + "bra": ["FA", "EA", "FB", "EB"], + "ecu": ["FA", "EA", "FB", "EB"], + "fcu": ["FA", "EA", "FB", "EB"], + } + } + return muscles_patterns + + +def generate_muscle_agonist_antagonist_pairs(muscle_config_path: str) -> dict: + # read muscle config file + muscles_config = read_yaml(muscle_config_path) + + sides = ("left", "right") + limbs = ("hind", "fore") + + muscles = { + sides[0]: { + limbs[0]: {"agonist": [], "antagonist": []}, + limbs[1]: {"agonist": [], "antagonist": []}, + }, + sides[1]: { + limbs[0]: {"agonist": [], "antagonist": []}, + limbs[1]: {"agonist": [], "antagonist": []}, + }, + } + + for _name, muscle in muscles_config["muscles"].items(): + _side = muscle["side"] + _limb = muscle["limb"] + function = muscle.get("function", "agonist") + muscles[_side][_limb][function].append( + { + "name": join_str(_name.split("_")[2:]), + "type": muscle["type"], + "abbrev": muscle["abbrev"], + } ) + return muscles + + +################ +# Limb Circuit # +################ +def limb_circuit( + network_options: options.NetworkOptions, + side: str, + limb: str, + transform_mat=np.identity(3) +): + + # TODO: Change the use of side and limb attr name in loops + # Base name + name = join_str((side, limb)) + + ##################### + # Rhythm generation # + ##################### + rhythm = RhythmGenerator(name=name, transform_mat=transform_mat) + network_options.add_nodes(rhythm.nodes().values()) + network_options.add_edges(rhythm.edges().values()) + + ##################### + # Pattern Formation # + ##################### + pattern_transformation_mat = transform_mat@( + get_translation_matrix(off_x=0.0, off_y=7.5) + ) - edges[join_str((side, "hind", "Sh2", "hom", "to", side, "fore", "RG", "F"))] = ( - options.EdgeOptions( - source=join_str((side, "hind", "Sh2", "hom")), - target=join_str((side, "fore", "RG", "F")), - weight=0.05, - type="excitatory", - visual=options.EdgeVisualOptions(), - ) + pattern = PatternFormation( + name=name, + transform_mat=pattern_transformation_mat + ) + network_options.add_nodes(pattern.nodes().values()) + network_options.add_edges(pattern.edges().values()) + + # Connect sub layers + rhythm_pattern_edges = connect_rhythm_pattern(name) + network_options.add_edges(rhythm_pattern_edges.values()) + + + ############## + # MotorLayer # + ############## + # read muscle config file + muscles_config_path = "/Users/tatarama/projects/work/research/neuromechanics/quadruped/mice/mouse-locomotion/data/config/muscles/quadruped_siggraph.yaml" + muscles = generate_muscle_agonist_antagonist_pairs(muscles_config_path) + + ################################### + # Connect patterns and motorlayer # + ################################### + muscles_patterns = define_muscle_patterns() + + motor_transformation_mat = pattern_transformation_mat@( + get_translation_matrix( + off_x=0.0, + off_y=5.0 ) + ) - edges[ - join_str((side, "fore", "RG", "F", "to", side, "fore", "V0V", "diag")) - ] = options.EdgeOptions( - source=join_str((side, "fore", "RG", "F")), - target=join_str((side, "fore", "V0V", "diag")), - weight=0.325, - type="excitatory", - visual=options.EdgeVisualOptions(), + motor = MotorLayer( + muscles=muscles[side][limb], + name=name, + transform_mat=motor_transformation_mat + ) + + network_options.add_nodes(motor.nodes().values()) + network_options.add_edges(motor.edges().values()) + + # Connect pattern formation to motor neurons + for muscle, patterns in muscles_patterns[limb].items(): + pattern_motor_edges = connect_pattern_motor_layer(name, muscle, patterns) + network_options.add_edges(pattern_motor_edges.values()) + + # Connect pattern formation to IaIn + edge_specs = connect_pattern_to_IaIn(side, limb) + network_options.add_edges((create_edges(edge_specs, name)).values()) + + ##################### + # Connect afferents # + ##################### + edge_specs = [] + Ib_feedback_to_rg = { + "hind": ["mg", "sol", "fdl"], + "fore": ["fcu"] + } + + Ib_feedback_disynaptic_excitation ={ + "hind": ["bfa", "bfpst", "va", "rf", "mg", "sol", "fdl"], + "fore": ["ssp", "tbl", "tbo", "fcu"] + } + + II_feedback_to_pattern = { + "hind": { + "ip": muscles_patterns["hind"]["ip"], + "ta": muscles_patterns["hind"]["ta"], + "edl": muscles_patterns["hind"]["edl"], + }, + "fore": { + "ssp": muscles_patterns["fore"]["ssp"], + "bra": muscles_patterns["fore"]["bra"], + } + } + + II_feedback_to_rg = { + "hind": ["ip", "ta",], + "fore": ["ssp", "bra"] + } + + Ia_reciprocal_inhibition_extensor2flexor = { + "hind": { + "extensors": ["bfa", "bfpst", "rf", "va", "mg", "sol", "fdl"], + "flexors": ["ip", "ta", "edl"], + }, + "fore": { + "extensors": ["ssp", "tbl", "tbo", "fcu"], + "flexors": ["spd", "bra", "bbs", "ecu"], + } + } + + Ia_reciprocal_inhibition_flexor2extensor = { + "hind": { + "extensors": ["bfa", "bfpst", "rf", "va", "mg", "sol", "fdl"], + "flexors": ["ip", "ta", "edl"], + }, + "fore": { + "extensors": ["ssp", "tbl", "tbo", "fcu"], + "flexors": ["spd", "bra", "bbs", "ecu"], + } + } + + renshaw_reciprocal_facilitation_extensor2flexor = { + "hind": { + "extensors": ["bfa", "bfpst", "rf", "va", "mg", "sol", "fdl"], + "flexors": ["ip", "ta", "edl"], + }, + "fore": { + "extensors": ["ssp", "tbl", "tbo", "fcu"], + "flexors": ["spd", "bra", "bbs", "ecu"], + } + } + + renshaw_reciprocal_facilitation_flexor2extensor = { + "hind": { + "extensors": ["bfa", "bfpst", "rf", "va", "mg", "sol", "fdl"], + "flexors": ["ip", "ta", "edl"], + }, + "fore": { + "extensors": ["ssp", "tbl", "tbo", "fcu"], + "flexors": ["spd", "bra", "bbs", "ecu"], + } + } + # Type II connections + # II to Pattern + for muscle, patterns in II_feedback_to_pattern[limb].items(): + edge_specs += connect_II_pattern_feedback( + side, limb=limb, muscle=muscle, patterns=patterns ) - edges[join_str((side, "hind", "RG", "F", "to", side, "hind", "V3", "diag"))] = ( - options.EdgeOptions( - source=join_str((side, "hind", "RG", "F")), - target=join_str((side, "hind", "V3", "diag")), - weight=0.325, - type="excitatory", - visual=options.EdgeVisualOptions(), + for flexor in II_feedback_to_rg[limb]: + edge_specs += connect_II_rg_feedback(flexor) + # Type Ib connections + # Ib to RG + for extensor in Ib_feedback_to_rg[limb]: + edge_specs += connect_Ib_rg_feedback(extensor) + # Ib Disynaptic extensor excitation + for extensor in Ib_feedback_disynaptic_excitation[limb]: + edge_specs += connect_Ib_disynaptic_extensor_excitation(extensor) + # Type Ia connections + # Ia reciprocal inhibition extensor to flexor + for extensor in Ia_reciprocal_inhibition_extensor2flexor[limb]["extensors"]: + for flexor in Ia_reciprocal_inhibition_extensor2flexor[limb]["flexors"]: + edge_specs += connect_Ia_reciprocal_inhibition_extensor2flexor( + extensor, flexor + ) + # Ia reciprocal inhibition flexor to extensor + for flexor in Ia_reciprocal_inhibition_flexor2extensor[limb]["flexors"]: + for extensor in Ia_reciprocal_inhibition_flexor2extensor[limb]["extensors"]: + edge_specs += connect_Ia_reciprocal_inhibition_flexor2extensor( + flexor, extensor + ) + # Renshaw recurrent connections + # renshaw reciprocal facilitation extensor to flexor + for extensor in renshaw_reciprocal_facilitation_extensor2flexor[limb]["extensors"]: + for flexor in renshaw_reciprocal_facilitation_extensor2flexor[limb]["flexors"]: + edge_specs += connect_Rn_reciprocal_facilitation_extensor2flexor(extensor, flexor) + # renshaw reciprocal facilitation flexor to extensor + for flexor in renshaw_reciprocal_facilitation_flexor2extensor[limb]["flexors"]: + for extensor in renshaw_reciprocal_facilitation_flexor2extensor[limb]["extensors"]: + edge_specs += connect_Rn_reciprocal_facilitation_flexor2extensor(flexor, extensor) + + edges = create_edges(edge_specs, name) + network_options.add_edges(edges.values()) + return network_options + + +##################### +# Interlimb Circuit # +##################### +def interlimb_circuit( + network_options: options.NetworkOptions, + sides: List[str], + limbs: List[str], + transform_mat=np.identity(3) +): + for side in sides: + for limb in limbs: + commissural_offset_x, commissural_offset_y = 5.0, 2.5 # Independent offsets + off_x = -commissural_offset_x if side == "left" else commissural_offset_x + off_y = (commissural_offset_y + 20) if limb == "fore" else commissural_offset_y + mirror_x = limb == "hind" + mirror_y = side == "right" + commissural = Commissural( + name=join_str((side, limb)), transform_mat=( + transform_mat@get_translation_matrix(off_x=off_x, off_y=off_y)@get_mirror_matrix( + mirror_x=mirror_y, mirror_y=mirror_y + ) + ) ) + network_options.add_nodes(commissural.nodes().values()) + network_options.add_edges(commissural.edges().values()) + + lpsn_x_offset = 25.0 + lpsn_y_offset = 20 - 5.5 # Adjusted relative to base fore_y_offset + off_x = -lpsn_x_offset if side == "left" else lpsn_x_offset + off_y = lpsn_y_offset + mirror_y = side == "right" + lpsn = LPSN( + name=side, + transform_mat=( + transform_mat@get_translation_matrix(off_x=off_x, off_y=off_y)@get_mirror_matrix( + mirror_x=False, mirror_y=mirror_y + ) + ) ) + network_options.add_nodes(lpsn.nodes().values()) + network_options.add_edges(lpsn.edges().values()) + + return network_options + + +##################### +# Quadruped Circuit # +##################### +def quadruped_circuit( + network_options: options.NetworkOptions, + transform_mat=np.identity(3) +): + """ Full Quadruped Circuit """ + + # Limb circuitry + network_options = limb_circuit( + network_options, + side="left", + limb="hind", + transform_mat=get_translation_matrix( + off_x=-25.0, off_y=0.0 + )@get_mirror_matrix( + mirror_x=True, mirror_y=False + )@get_rotation_matrix(angle=45) + ) - # edges[join_str((side, "fore", "V2a-diag", "to", side, "fore", "V0V", "diag"))] = options.EdgeOptions( - # source=join_str((side, "fore", "V2a-diag")), - # target=join_str((side, "fore", "V0V", "diag")), - # weight=0.9, - # type="excitatory", - # visual=options.EdgeVisualOptions(), - # ) - - # edges[join_str((side, "hind", "V2a-diag", "to", side, "hind", "V3", "diag"))] = options.EdgeOptions( - # source=join_str((side, "hind", "V2a-diag")), - # target=join_str((side, "hind", "V3", "diag")), - # weight=0.9, - # type="excitatory", - # visual=options.EdgeVisualOptions(), - # ) + network_options = limb_circuit( + network_options, + side="right", + limb="hind", + transform_mat=get_translation_matrix( + off_x=25.0, off_y=0.0 + )@get_mirror_matrix( + mirror_x=True, mirror_y=False + )@get_rotation_matrix(angle=-45) + ) - for sides in (("left", "right"), ("right", "left")): - edges[ - join_str((sides[0], "V0D", "diag", "to", sides[1], "hind", "RG", "F")) - ] = options.EdgeOptions( - source=join_str((sides[0], "V0D", "diag")), - target=join_str((sides[1], "hind", "RG", "F")), - weight=-0.01, - type="inhibitory", - visual=options.EdgeVisualOptions(), - ) + network_options = limb_circuit( + network_options, + side="left", + limb="fore", + transform_mat=get_translation_matrix( + off_x=-25.0, off_y=25.0 + )@get_rotation_matrix(angle=45) + ) - edges[ - join_str( - (sides[0], "fore", "V0V", "diag", "to", sides[1], "hind", "RG", "F") - ) - ] = options.EdgeOptions( - source=join_str((sides[0], "fore", "V0V", "diag")), - target=join_str((sides[1], "hind", "RG", "F")), - weight=0.005, - type="excitatory", - visual=options.EdgeVisualOptions(), - ) + network_options = limb_circuit( + network_options, + side="right", + limb="fore", + transform_mat=get_translation_matrix( + off_x=25.0, off_y=25.0 + )@get_rotation_matrix(angle=-45) + ) - edges[ - join_str( - (sides[0], "hind", "V3", "diag", "to", sides[1], "fore", "RG", "F") - ) - ] = options.EdgeOptions( - source=join_str((sides[0], "hind", "V3", "diag")), - target=join_str((sides[1], "fore", "RG", "F")), - weight=0.04, - type="excitatory", - visual=options.EdgeVisualOptions(), - ) + # Commisural + network_options = interlimb_circuit( + network_options, + sides=("left", "right"), + limbs=("hind", "fore",), + ) - return edges + ################################# + # Connect rhythm to commissural # + ################################# + rg_commissural_edges = connect_rg_commissural() + network_options.add_edges(rg_commissural_edges.values()) + + ################################## + # Connect pattern to commissural # + ################################## + pattern_commissural_edges = connect_pattern_commissural() + network_options.add_edges(pattern_commissural_edges.values()) + + ############################## + # Connect fore and hind lpsn # + ############################## + fore_hind_edges = connect_fore_hind_circuits() + network_options.add_edges(fore_hind_edges.values()) + + return network_options diff --git a/examples/mouse/run.py b/examples/mouse/run.py index de9dded..4714a91 100644 --- a/examples/mouse/run.py +++ b/examples/mouse/run.py @@ -1,11 +1,13 @@ """ Generate and reproduce Zhang, Shevtsova, et al. eLife 2022;11:e73424. DOI: https://doi.org/10.7554/eLife.73424 paper network """ - +import seaborn as sns from farms_core.io.yaml import read_yaml +from farms_core.utils import profile from farms_network.core import options from components import * +from components import limb_circuit def generate_network(n_iterations: int): @@ -16,10 +18,13 @@ def generate_network(n_iterations: int): directed=True, multigraph=False, graph={"name": "mouse"}, - integration=options.IntegrationOptions.defaults(n_iterations=n_iterations), + integration=options.IntegrationOptions.defaults( + n_iterations=n_iterations, + timestep=1.0, + ), logs=options.NetworkLogOptions( n_iterations=n_iterations, - ) + ), ) ############## @@ -31,17 +36,17 @@ def generate_network(n_iterations: int): ) def update_muscle_name(name: str) -> str: - """ Update muscle name format """ + """Update muscle name format""" return name.replace("_", "-") muscles = { "left": { "hind": {"agonist": [], "antagonist": []}, - "fore": {"agonist": [], "antagonist": []} + "fore": {"agonist": [], "antagonist": []}, }, "right": { "hind": {"agonist": [], "antagonist": []}, - "fore": {"agonist": [], "antagonist": []} + "fore": {"agonist": [], "antagonist": []}, }, } @@ -51,12 +56,43 @@ def update_muscle_name(name: str) -> str: function = muscle.get("function", "agonist") muscles[side][limb][function].append( { - "name": name, - "type": muscle['type'], - "abbrev": muscle['abbrev'] + "name": join_str(name.split("_")[2:]), + "type": muscle["type"], + "abbrev": muscle["abbrev"], } ) + ################################### + # Connect patterns and motorlayer # + ################################### + hind_muscle_patterns = { + "bfa": ["EA", "EB"], + "ip": ["FA", "FB"], + "bfpst": ["FA", "EA", "FB", "EB"], + "rf": ["EA", "FB", "EB"], + "va": ["EA", "FB", "EB"], + "mg": ["FA", "EA", "EB"], + "sol": ["EA", "EB"], + "ta": ["FA", "FB"], + "ab": ["FA", "EA", "FB", "EB"], + "gm_dorsal": ["FA", "EA", "FB", "EB"], + "edl": ["FA", "EA", "FB", "EB"], + "fdl": ["FA", "EA", "FB", "EB"], + } + + fore_muscle_patterns = { + "spd": ["FA", "EA", "FB", "EB"], + "ssp": ["FA", "EA", "FB", "EB"], + "abd": ["FA", "EA", "FB", "EB"], + "add": ["FA", "EA", "FB", "EB"], + "tbl": ["FA", "EA", "FB", "EB"], + "tbo": ["FA", "EA", "FB", "EB"], + "bbs": ["FA", "FB"], + "bra": ["FA", "EA", "FB", "EB"], + "ecu": ["FA", "EA", "FB", "EB"], + "fcu": ["FA", "EA", "FB", "EB"], + } + # Generate rhythm centers scale = 1.0 for side in ("left", "right"): @@ -79,8 +115,13 @@ def update_muscle_name(name: str) -> str: ) network_options.add_nodes((rhythm.nodes()).values()) network_options.add_edges((rhythm.edges()).values()) - # Rhtyhm Drive - rhythm_drive = RhythmDrive( + # Commissural + comm_x, comm_y = rg_x - 7.0, rg_y + 0.0 + off_x = -comm_x if side == "left" else comm_x + off_y = comm_y if limb == "fore" else -comm_y + mirror_x = limb == "hind" + mirror_y = side == "right" + commissural = Commissural( name=join_str((side, limb)), transform_mat=get_transform_mat( angle=0, @@ -90,7 +131,19 @@ def update_muscle_name(name: str) -> str: mirror_y=mirror_y, ), ) - network_options.add_nodes((rhythm_drive.nodes()).values()) + network_options.add_nodes((commissural.nodes()).values()) + # Drive + commissural_drive = CommissuralDrive( + name=join_str((side, limb)), + transform_mat=get_transform_mat( + angle=0, + off_x=off_x, + off_y=off_y, + mirror_x=mirror_x, + mirror_y=mirror_y, + ), + ) + network_options.add_nodes((commissural_drive.nodes()).values()) # Pattern pf_x, pf_y = rg_x + 0.0, rg_y + 7.5 off_x = -pf_x if side == "left" else pf_x @@ -110,55 +163,34 @@ def update_muscle_name(name: str) -> str: network_options.add_nodes((pattern.nodes()).values()) network_options.add_edges((pattern.edges()).values()) + rhythm_pattern_edges = connect_rhythm_pattern(base_name=join_str((side, limb))) + network_options.add_edges(rhythm_pattern_edges.values()) + # Motor Layer - motor_x = pf_x + 0.5*max( - len(muscles["left"]["fore"]["agonist"]), - len(muscles["left"]["fore"]["antagonist"]) + motor_x = pf_x + 0.5 * max( + len(muscles["left"][limb]["agonist"]), + len(muscles["left"][limb]["antagonist"]), ) motor_y = pf_y + 5.0 - left_fore_motor = MotorLayer( - muscles=muscles[side][limb], - name=f"{side}_{limb}_motor", - transform_mat=get_transform_mat( - angle=0, - off_x=off_x, - off_y=off_y, - mirror_x=mirror_x, - mirror_y=mirror_y, - ), - ) - network_options.add_nodes((left_fore_motor.nodes()).values()) - # Commissural - comm_x, comm_y = rg_x - 7.0, rg_y + 0.0 - off_x = -comm_x if side == "left" else comm_x - off_y = comm_y if limb == "fore" else -comm_y - mirror_x = limb == "hind" - mirror_y = side == "right" - commissural = Commissural( - name=join_str((side, limb)), - transform_mat=get_transform_mat( - angle=0, - off_x=off_x, - off_y=off_y, - mirror_x=mirror_x, - mirror_y=mirror_y, - ), - ) - network_options.add_nodes((commissural.nodes()).values()) - # Drive - commissural_drive = CommissuralDrive( + # Determine the mirror_x and mirror_y flags based on side and limb + mirror_x = True if limb == "hind" else False + mirror_y = True if side == "right" else False + + # Create MotorLayer for each side and limb + motor = MotorLayer( + muscles=muscles[side][limb], name=join_str((side, limb)), transform_mat=get_transform_mat( - angle=0, - off_x=off_x, - off_y=off_y, + angle=0.0, + off_x=motor_x if side == "right" else -motor_x, + off_y=motor_y if limb == "fore" else -motor_y, mirror_x=mirror_x, mirror_y=mirror_y, ), ) - network_options.add_nodes((commissural_drive.nodes()).values()) - + network_options.add_nodes((motor.nodes()).values()) + network_options.add_edges((motor.edges()).values()) # LPSN lpsn_x = rg_x - 9.0 lpsn_y = rg_y - 5.5 @@ -176,17 +208,29 @@ def update_muscle_name(name: str) -> str: ) network_options.add_nodes((lpsn.nodes()).values()) lpsn_drive = LPSNDrive( - name=join_str((side,)), - transform_mat=get_transform_mat( - angle=0, - off_x=off_x, - off_y=off_y, - mirror_x=mirror_x, - mirror_y=mirror_y, - ), - ) + name=side, + transform_mat=get_transform_mat( + angle=0, + off_x=off_x, + off_y=off_y, + mirror_x=mirror_x, + mirror_y=mirror_y, + ), + ) network_options.add_nodes((lpsn_drive.nodes()).values()) + # Connect pattern layer to motor layer + for muscle, patterns in hind_muscle_patterns.items(): + pattern_edges = connect_pattern_motor_layer( + base_name=join_str((side, "hind")), muscle=muscle, patterns=patterns + ) + network_options.add_edges(pattern_edges.values()) + for muscle, patterns in fore_muscle_patterns.items(): + pattern_edges = connect_pattern_motor_layer( + base_name=join_str((side, "fore")), muscle=muscle, patterns=patterns + ) + network_options.add_edges(pattern_edges.values()) + ################################# # Connect rhythm to commissural # ################################# @@ -199,110 +243,214 @@ def update_muscle_name(name: str) -> str: fore_hind_edges = connect_fore_hind_circuits() network_options.add_edges(fore_hind_edges.values()) + edge_specs = [] + for side in ("left", "right"): for limb in ("fore", "hind"): - network_options.add_edge( - options.EdgeOptions( - source=join_str((side, limb, "RG", "F", "DR")), - target=join_str((side, limb, "RG", "F")), - weight=1.0, - type="excitatory", - visual=options.EdgeVisualOptions(), - ) - ) - network_options.add_edge( - options.EdgeOptions( - source=join_str((side, limb, "RG", "E", "DR")), - target=join_str((side, limb, "RG", "E")), - weight=1.0, - type="excitatory", - visual=options.EdgeVisualOptions(), - ) - ) - network_options.add_edge( - options.EdgeOptions( - source=join_str((side, limb, "V0V", "DR")), - target=join_str((side, limb, "V0V")), - weight=-1.0, - type="inhibitory", - visual=options.EdgeVisualOptions(), - ) - ) - network_options.add_edge( - options.EdgeOptions( - source=join_str((side, limb, "V0D", "DR")), - target=join_str((side, limb, "V0D")), - weight=-1.0, - type="inhibitory", - visual=options.EdgeVisualOptions(), - ) - ) - network_options.add_edge( - options.EdgeOptions( - source=join_str((side, "V0D", "diag", "DR")), - target=join_str((side, "V0D", "diag")), - weight=-1.0, - type="inhibitory", - visual=options.EdgeVisualOptions(), - ) + edge_specs.extend([ + ((side, limb, "RG", "F", "DR"), (side, limb, "RG", "F"), 1.0, "excitatory"), + ((side, limb, "RG", "E", "DR"), (side, limb, "RG", "E"), 1.0, "excitatory"), + ((side, limb, "V0V", "DR"), (side, limb, "V0V"), -1.0, "inhibitory"), + ((side, limb, "V0D", "DR"), (side, limb, "V0D"), -1.0, "inhibitory"), + ]) + + # Add the diagonal V0D connection + edge_specs.append( + ((side, "V0D", "diag", "DR"), (side, "V0D", "diag"), -1.0, "inhibitory") + ) + + # Create the edges using create_edges + edges = create_edges( + edge_specs, + base_name="", + visual_options=options.EdgeVisualOptions() + ) + network_options.add_edges(edges.values()) + + return network_options + + +def generate_limb_circuit(n_iterations: int): + """ Generate limb circuit """ + # Main network + network_options = options.NetworkOptions( + directed=True, + multigraph=False, + graph={"name": "mouse"}, + integration=options.IntegrationOptions.defaults( + n_iterations=n_iterations, + timestep=1.0, + ), + logs=options.NetworkLogOptions( + n_iterations=n_iterations, + ), + ) + + ############## + # MotorLayer # + ############## + # read muscle config file + muscles_config = read_yaml( + "/Users/tatarama/projects/work/research/neuromechanics/quadruped/mice/mouse-locomotion/data/config/muscles/quadruped_siggraph.yaml" + ) + + ################################### + # Connect patterns and motorlayer # + ################################### + hind_muscle_patterns = { + "bfa": ["EA", "EB"], + "ip": ["FA", "FB"], + "bfpst": ["FA", "EA", "FB", "EB"], + "rf": ["EA", "FB", "EB"], + "va": ["EA", "FB", "EB"], + "mg": ["FA", "EA", "EB"], + "sol": ["EA", "EB"], + "ta": ["FA", "FB"], + "ab": ["FA", "EA", "FB", "EB"], + "gm_dorsal": ["FA", "EA", "FB", "EB"], + "edl": ["FA", "EA", "FB", "EB"], + "fdl": ["FA", "EA", "FB", "EB"], + } + + fore_muscle_patterns = { + "spd": ["FA", "EA", "FB", "EB"], + "ssp": ["FA", "EA", "FB", "EB"], + "abd": ["FA", "EA", "FB", "EB"], + "add": ["FA", "EA", "FB", "EB"], + "tbl": ["FA", "EA", "FB", "EB"], + "tbo": ["FA", "EA", "FB", "EB"], + "bbs": ["FA", "FB"], + "bra": ["FA", "EA", "FB", "EB"], + "ecu": ["FA", "EA", "FB", "EB"], + "fcu": ["FA", "EA", "FB", "EB"], + } + + def update_muscle_name(name: str) -> str: + """Update muscle name format""" + return name.replace("_", "-") + + muscles = { + "left": { + "hind": {"agonist": [], "antagonist": []}, + "fore": {"agonist": [], "antagonist": []}, + }, + "right": { + "hind": {"agonist": [], "antagonist": []}, + "fore": {"agonist": [], "antagonist": []}, + }, + } + + for name, muscle in muscles_config["muscles"].items(): + side = muscle["side"] + limb = muscle["limb"] + function = muscle.get("function", "agonist") + muscles[side][limb][function].append( + { + "name": join_str(name.split("_")[2:]), + "type": muscle["type"], + "abbrev": muscle["abbrev"], + } ) + network_options = limb_circuit( + network_options, + join_str(("left", "fore")), + muscles["left"]["fore"], + fore_muscle_patterns, + transform_mat=get_translation_matrix(off_x=-25.0, off_y=0.0) + ) + + # network_options = limb_circuit( + # network_options, + # join_str(("right", "fore")), + # muscles["right"]["fore"], + # fore_muscle_patterns, + # transform_mat=get_translation_matrix(off_x=25.0, off_y=0.0) + # ) + + # network_options = limb_circuit( + # network_options, + # join_str(("left", "hind")), + # muscles["left"]["hind"], + # hind_muscle_patterns, + # transform_mat=get_translation_matrix( + # off_x=-25.0, off_y=-25.0 + # ) @ get_mirror_matrix(mirror_x=True, mirror_y=False) + # ) + + # network_options = limb_circuit( + # network_options, + # join_str(("right", "hind")), + # muscles["right"]["hind"], + # hind_muscle_patterns, + # transform_mat=get_translation_matrix( + # off_x=25.0, off_y=-25.0 + # ) @ get_mirror_matrix(mirror_x=True, mirror_y=False) + # ) + + # rg_commissural_edges = connect_rg_commissural() + # network_options.add_edges(rg_commissural_edges.values()) + return network_options -def run_network(network_options: options.NetworkOptions): +def generate_quadruped_circuit( + n_iterations: int +): + # Main network + network_options = options.NetworkOptions( + directed=True, + multigraph=False, + graph={"name": "quadruped"}, + integration=options.IntegrationOptions.defaults( + n_iterations=int(n_iterations), + timestep=1.0, + ), + logs=options.NetworkLogOptions( + n_iterations=int(n_iterations), + buffer_size=int(n_iterations), + ), + ) + network_options = quadruped_circuit(network_options) + return network_options - data = NetworkData.from_options(network_options) +def run_network(*args): + network_options = args[0] network = PyNetwork.from_options(network_options) + network.setup_integrator(network_options.integration) # data.to_file("/tmp/sim.hdf5") - # # Integrate + # Integrate N_ITERATIONS = network_options.integration.n_iterations - states = np.ones((len(data.states.array),)) * 1.0 + states = np.ones((len(network.data.states.array),)) * 1.0 # network_gui = NetworkGUI(data=data) # network_gui.run() - # for index, node in enumerate(network_options.nodes): - # print(index, node.name) inputs_view = network.data.external_inputs.array for iteration in tqdm(range(0, N_ITERATIONS), colour="green", ascii=" >="): inputs_view[:] = (iteration / N_ITERATIONS) * 1.0 - states = rk4(iteration * 1e-3, states, network.ode, step_size=1) + # states = rk4(iteration * 1e-3, states, network.ode, step_size=1) + # states = network.integrator.step(network, iteration * 1e-3, states) + network.step() + # states = network.ode(iteration*1e-3, states) + # print(np.array(states)[0], network.data.states.array[0], network.data.derivatives.array[0]) + network.data.times.array[iteration] = iteration*1e-3 # network.logging(iteration) - network.data.to_file("/tmp/network.h5") + # network.data.to_file("/tmp/network.h5") + network_options.save("/tmp/network_options.yaml") + + return network - plt.figure() - plt.fill_between( - np.linspace(0.0, N_ITERATIONS * 1e-3, N_ITERATIONS), - np.array(network.data.nodes[15].output.array), - alpha=0.2, - lw=1.0, - ) - plt.plot( - np.linspace(0.0, N_ITERATIONS * 1e-3, N_ITERATIONS), - np.array(network.data.nodes[15].output.array), - label="RG-F" - ) - plt.fill_between( - np.linspace(0.0, N_ITERATIONS * 1e-3, N_ITERATIONS), - np.array(network.data.nodes[1].output.array), - alpha=0.2, - lw=1.0, - ) - plt.plot( - np.linspace(0.0, N_ITERATIONS * 1e-3, N_ITERATIONS), - np.array(network.data.nodes[1].output.array), - label="RG-E" - ) - plt.legend() - network_options.save("/tmp/netwok_options.yaml") +def plot_network(network_options): + """ Plot only network """ + network_options = update_edge_visuals(network_options) graph = nx.node_link_graph( network_options, directed=True, @@ -313,6 +461,13 @@ def run_network(network_options: options.NetworkOptions): target="target", ) + # plt.figure() + # sparse_array = nx.to_scipy_sparse_array(graph) + # sns.heatmap( + # sparse_array.todense()[50:75, 50:75], cbar=False, square=True, + # linewidths=0.5, + # annot=True + # ) plt.figure() pos_circular = nx.circular_layout(graph) pos_spring = nx.spring_layout(graph) @@ -327,6 +482,7 @@ def run_network(network_options: options.NetworkOptions): alpha=0.25, edgecolors="k", linewidths=2.0, + node_size=[300*data["visual"]["radius"] for node, data in graph.nodes.items()], ) nx.draw_networkx_labels( graph, @@ -342,17 +498,16 @@ def run_network(network_options: options.NetworkOptions): nx.draw_networkx_edges( graph, pos={ - node: data["visual"]["position"][:2] for node, data in graph.nodes.items() + node: data["visual"]["position"][:2] + for node, data in graph.nodes.items() }, edge_color=[ - [0.3, 1.0, 0.3] - if data["type"] == "excitatory" - else [0.7, 0.3, 0.3] + [0.3, 1.0, 0.3] if data["type"] == "excitatory" else [0.7, 0.3, 0.3] for edge, data in graph.edges.items() ], width=1.0, arrowsize=10, - style="dashed", + style="-", arrows=True, min_source_margin=5, min_target_margin=5, @@ -364,107 +519,68 @@ def run_network(network_options: options.NetworkOptions): plt.show() -def generate_tikz_figure( - network, template_env, template_name, export_path, add_axis=False, add_label=False -): - """Generate tikz network""" - - ########################################## - # Remove isolated nodes from the network # - ########################################## - network.remove_nodes_from(list(nx.isolates(network))) - - node_options = { - name: node.get("neuron_class", "interneuron") - for name, node in network.nodes.items() - } +def plot_data(network, network_options): + plot_nodes = [ + index + for index, node in enumerate(network.data.nodes) + if ("RG_F" in node.name) and ("DR" not in node.name) + ] - options = { - "flexor": "flexor-edge", - "extensor": "extensor-edge", - "excitatory": "excitatory-edge", - "inhibitory": "inhibitory-edge", - "interneuron": "inhibitory-edge", - } - edge_options = { - edge: "{}, opacity={}".format( - options.get( - network.nodes[edge[0]].get("neuron_class", "interneuron"), - "undefined-edge", - ), - # max(min(abs(data["weight"]), 1.0), 0.5) - 1.0, + plt.figure() + for index, node_index in enumerate(plot_nodes): + plt.fill_between( + np.array(network.data.times.array), + index + np.array(network.data.nodes[node_index].output.array), + index, + alpha=0.2, + lw=1.0, ) - for edge, data in network.edges.items() - } - - raw_latex = nx.to_latex_raw( - network, - pos={name: (node["x"], node["y"]) for name, node in network.nodes.items()}, - node_options=node_options, - # default_node_options="my-node", - node_label={name: node["label"] for name, node in network.nodes.items()}, - # edge_label={ - # name: np.round(edge['weight'], decimals=2) - # for name, edge in network.edges.items() - # }, - edge_label_options={ - name: "fill=white, font={\\tiny}, opacity=1.0" - for name, edge in network.edges.items() - }, - default_edge_options=( - "[color=black, ultra thick, -{Latex[scale=1.0]}, on background layer, opacity=1.0,]" # auto=mid - ), - edge_options=edge_options, - ) - - # Render the network - rhythm_groups = defaultdict(list) - pattern_groups = defaultdict(list) - commissural_groups = defaultdict(list) - lpsn_groups = defaultdict(list) - muscle_sensors_groups = defaultdict(list) - for name, node in network.nodes.items(): - if node["neuron_class"] == "sensory": - muscle_sensors_groups[node["neuron_group"]].append(name) - if node.get("neuron_group") == "rhythm": - rhythm_groups[node["layer"]].append(name) - if node.get("neuron_group") == "pattern": - pattern_groups[node["layer"]].append(name) - if node.get("neuron_group") == "commissural": - commissural_groups[node["layer"]].append(name) - if node.get("neuron_group") == "LPSN": - lpsn_groups[node["layer"]].append(name) - - environment = Environment(loader=FileSystemLoader(template_env)) - template = environment.get_template(template_name) - content = template.render( - network="\n".join(raw_latex.split("\n")[2:-2]), - rhythm_groups=list(rhythm_groups.values()), - pattern_groups=list(pattern_groups.values()), - commissural_groups=list(commissural_groups.values()), - lpsn_groups=list(lpsn_groups.values()), - muscle_sensors_groups=list(muscle_sensors_groups.values()), - add_axis=add_axis, - add_legend=add_label, - ) - with open(export_path, mode="w", encoding="utf-8") as message: - message.write(content) + plt.plot( + np.array(network.data.times.array), + index + np.array(network.data.nodes[node_index].output.array), + label=network.data.nodes[node_index].name, + ) + plt.legend() - result = os.system( - f"pdflatex --shell-escape -output-directory={str(Path(export_path).parents[0])} {export_path}" - ) + plot_nodes = [ + index + for index, node in enumerate(network.data.nodes) + if ("Mn" in node.name) + ] + plt.figure() + for index, node_index in enumerate(plot_nodes): + plt.fill_between( + np.array(network.data.times.array), + index + np.array(network.data.nodes[node_index].output.array), + index, + alpha=0.2, + lw=1.0, + ) + plt.plot( + np.array(network.data.times.array), + index + np.array(network.data.nodes[node_index].output.array), + label=network.data.nodes[node_index].name, + ) + plt.legend() + plt.show() def main(): """Main.""" # Generate the network - network_options = generate_network(int(1e4)) - - run_network(network_options) + # network_options = generate_network(int(1e4)) + # network_options = generate_limb_circuit(int(1e4)) + network_options = generate_quadruped_circuit((5e4)) + network_options.save("/tmp/network_options.yaml") # Run the network + # network = profile.profile(run_network, network_options) + # network = run_network(network_options) + + # Results + plot_network(network_options) + # run_network() From 21336f119a4814e2710c98b550110d260eee3b92 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Thu, 21 Nov 2024 00:13:19 -0500 Subject: [PATCH 144/316] [MODELS] Added new model files --- farms_network/models/fitzhugh_nagumo.pxd | 0 farms_network/models/fitzhugh_nagumo.pyx | 0 farms_network/models/hopf_oscillator.pxd | 0 farms_network/models/hopf_oscillator.pyx | 0 farms_network/models/izhikevich.pxd | 63 +++++++++++++++++ farms_network/models/izhikevich.pyx | 87 ++++++++++++++++++++++++ 6 files changed, 150 insertions(+) create mode 100644 farms_network/models/fitzhugh_nagumo.pxd create mode 100644 farms_network/models/fitzhugh_nagumo.pyx create mode 100644 farms_network/models/hopf_oscillator.pxd create mode 100644 farms_network/models/hopf_oscillator.pyx create mode 100644 farms_network/models/izhikevich.pxd create mode 100644 farms_network/models/izhikevich.pyx diff --git a/farms_network/models/fitzhugh_nagumo.pxd b/farms_network/models/fitzhugh_nagumo.pxd new file mode 100644 index 0000000..e69de29 diff --git a/farms_network/models/fitzhugh_nagumo.pyx b/farms_network/models/fitzhugh_nagumo.pyx new file mode 100644 index 0000000..e69de29 diff --git a/farms_network/models/hopf_oscillator.pxd b/farms_network/models/hopf_oscillator.pxd new file mode 100644 index 0000000..e69de29 diff --git a/farms_network/models/hopf_oscillator.pyx b/farms_network/models/hopf_oscillator.pyx new file mode 100644 index 0000000..e69de29 diff --git a/farms_network/models/izhikevich.pxd b/farms_network/models/izhikevich.pxd new file mode 100644 index 0000000..19f9c5e --- /dev/null +++ b/farms_network/models/izhikevich.pxd @@ -0,0 +1,63 @@ +""" +----------------------------------------------------------------------- +Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty +Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +----------------------------------------------------------------------- + +Izhikevich neuron model based on Izhikevich et.al. 2003 +""" + +from ..core.node cimport Node, PyNode +from ..core.edge cimport Edge, PyEdge + + +cdef enum: + #STATES + NSTATES = 0 + + +cdef packed struct IzhikevichNodeParameters: + double param + + +cdef: + void ode( + double time, + double* states, + double* derivatives, + double external_input, + double* network_outputs, + unsigned int* inputs, + double* weights, + Node* node, + Edge** edges, + ) noexcept + double output( + double time, + double* states, + double external_input, + double* network_outputs, + unsigned int* inputs, + double* weights, + Node* node, + Edge** edges, + ) noexcept + + +cdef class PyIzhikevichNode(PyNode): + """ Python interface to Leaky Integrator Node C-Structure """ + + cdef: + IzhikevichNodeParameters parameters diff --git a/farms_network/models/izhikevich.pyx b/farms_network/models/izhikevich.pyx new file mode 100644 index 0000000..e08fa86 --- /dev/null +++ b/farms_network/models/izhikevich.pyx @@ -0,0 +1,87 @@ +""" +----------------------------------------------------------------------- +Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty +Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +----------------------------------------------------------------------- + +Izhikevich model +""" + +from libc.stdio cimport printf +from libc.stdlib cimport free, malloc +from libc.string cimport strdup + + +cpdef enum STATE: + + #STATES + nstates = NSTATES + + +cdef void ode( + double time, + double* states, + double* derivatives, + double external_input, + double* network_outputs, + unsigned int* inputs, + double* weights, + Node* node, + Edge** edges, + ) noexcept: + """ Node ODE """ + ... + + +cdef double output( + double time, + double* states, + double external_input, + double* network_outputs, + unsigned int* inputs, + double* weights, + Node* node, + Edge** edges, +) noexcept: + """ Node output. """ + ... + + +cdef class PyIzhikevichNode(PyNode): + """ Python interface to Izhikevich Node C-Structure """ + + def __cinit__(self): + self.node.model_type = strdup("IZHIKEVICH".encode('UTF-8')) + # override default ode and out methods + self.node.is_statefull = True + self.node.output = output + # parameters + self.node.parameters = malloc(sizeof(IzhikevichNodeParameters)) + if self.node.parameters is NULL: + raise MemoryError("Failed to allocate memory for node parameters") + + def __init__(self, name: str, **kwargs): + super().__init__(name) + + # Set node parameters + cdef IzhikevichNodeParameters* param = (self.node.parameters) + if kwargs: + raise Exception(f'Unknown kwargs: {kwargs}') + + @property + def parameters(self): + """ Parameters in the network """ + cdef IzhikevichNodeParameters params = ( self.node.parameters)[0] + return params From 858f1b98b827d62803a8f989216bda1bddb8b1eb Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Thu, 21 Nov 2024 11:22:33 -0500 Subject: [PATCH 145/316] [CORE][OPTIONS] Fixed li danner node state option name --- farms_network/core/options.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/farms_network/core/options.py b/farms_network/core/options.py index 55af0c6..a02fc8e 100644 --- a/farms_network/core/options.py +++ b/farms_network/core/options.py @@ -711,7 +711,7 @@ def defaults(cls, **kwargs): class LIDannerStateOptions(NodeStateOptions): """ LI Danner node state options """ - STATE_NAMES = ["v0",] + STATE_NAMES = ["v",] def __init__(self, **kwargs): super().__init__( @@ -818,7 +818,7 @@ def defaults(cls, **kwargs): class LINaPDannerStateOptions(NodeStateOptions): """ LI Danner node state options """ - STATE_NAMES = ["v0", "h0"] + STATE_NAMES = ["v", "h"] def __init__(self, **kwargs): super().__init__( From 4b886b73d6f373d8caf5045acf661be235d923d3 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Thu, 21 Nov 2024 11:23:02 -0500 Subject: [PATCH 146/316] [EX][MOUSE] Adapted state initialization names to changes in core/options --- examples/mouse/components.py | 74 ++++++++++++++++++------------------ 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/examples/mouse/components.py b/examples/mouse/components.py index 01b9fd1..74a6345 100644 --- a/examples/mouse/components.py +++ b/examples/mouse/components.py @@ -266,7 +266,7 @@ def nodes(self): np.array((3.0, 0.0)), "F", [1.0, 0.0, 0.0], - {"v0": -62.5, "h0": np.random.uniform(0, 1)}, + {"v": -62.5, "h": np.random.uniform(0, 1)}, {}, ), ( @@ -275,7 +275,7 @@ def nodes(self): np.array((-3.0, 0.0)), "E", [0.0, 1.0, 0.0], - {"v0": -62.5, "h0": np.random.uniform(0, 1)}, + {"v": -62.5, "h": np.random.uniform(0, 1)}, {}, ), ( @@ -284,7 +284,7 @@ def nodes(self): np.array((1.0, -1.5)), "In", [0.2, 0.2, 0.2], - {"v0": -60.0}, + {"v": -60.0}, {}, ), ( @@ -293,7 +293,7 @@ def nodes(self): np.array((-1.0, 1.5)), "In", [0.2, 0.2, 0.2], - {"v0": -60.0}, + {"v": -60.0}, {}, ), ( @@ -302,7 +302,7 @@ def nodes(self): np.array((-5.0, 1.0)), "In", [0.2, 0.2, 0.2], - {"v0": -60.0}, + {"v": -60.0}, {}, ), ( @@ -368,7 +368,7 @@ def nodes(self): np.array((-3.0, 0.0)), "F\\textsubscript{A}", [1.0, 0.0, 0.0], - {"v0": -60.0, "h0": np.random.uniform(0, 1)}, + {"v": -60.0, "h": np.random.uniform(0, 1)}, {"g_nap": 0.125, "e_leak": -67.5}, ), ( @@ -377,7 +377,7 @@ def nodes(self): np.array((-9.0, 0.0)), "E\\textsubscript{A}", [0.0, 1.0, 0.0], - {"v0": -60.0, "h0": np.random.uniform(0, 1)}, + {"v": -60.0, "h": np.random.uniform(0, 1)}, {"g_nap": 0.125, "e_leak": -67.5}, ), ( @@ -386,7 +386,7 @@ def nodes(self): np.array((-5.0, -1.5)), "In\\textsubscript{A}", [0.2, 0.2, 0.2], - {"v0": -60.0}, + {"v": -60.0}, {}, ), ( @@ -395,7 +395,7 @@ def nodes(self): np.array((-7.0, 1.5)), "In\\textsubscript{A}", [0.2, 0.2, 0.2], - {"v0": -60.0}, + {"v": -60.0}, {}, ), ( @@ -404,7 +404,7 @@ def nodes(self): np.array((9.0, 0.0)), "F\\textsubscript{B}", [1.0, 0.0, 0.0], - {"v0": -60.0, "h0": np.random.uniform(0, 1)}, + {"v": -60.0, "h": np.random.uniform(0, 1)}, {"g_nap": 0.125, "g_leak": 1.0, "e_leak": -67.5}, ), ( @@ -413,7 +413,7 @@ def nodes(self): np.array((3.0, 0.0)), "E\\textsubscript{B}", [0.0, 1.0, 0.0], - {"v0": -60.0, "h0": np.random.uniform(0, 1)}, + {"v": -60.0, "h": np.random.uniform(0, 1)}, {"g_nap": 0.125, "g_leak": 1.0, "e_leak": -67.5}, ), ( @@ -422,7 +422,7 @@ def nodes(self): np.array((7.0, -1.5)), "In\\textsubscript{B}", [0.2, 0.2, 0.2], - {"v0": -60.0}, + {"v": -60.0}, {}, ), ( @@ -431,7 +431,7 @@ def nodes(self): np.array((5.0, 1.5)), "In\\textsubscript{B}", [0.2, 0.2, 0.2], - {"v0": -60.0}, + {"v": -60.0}, {}, ), ( @@ -440,7 +440,7 @@ def nodes(self): np.array((9.0, -3.0)), "In\\textsubscript{2F}", [0.2, 0.2, 0.2], - {"v0": -60.0}, + {"v": -60.0}, {"g_leak": 5.0}, ), ( @@ -449,7 +449,7 @@ def nodes(self): np.array((3.0, -3.0)), "In\\textsubscript{2E}", [0.2, 0.2, 0.2], - {"v0": -60.0}, + {"v": -60.0}, {"g_leak": 5.0}, ), ( @@ -552,7 +552,7 @@ def nodes(self): np.array((0.0, 2.0, 1.0)), "V2\\textsubscript{a}", [0.0, 1.0, 0.0], - {"v0": -60.0}, + {"v": -60.0}, {}, ), ( @@ -561,7 +561,7 @@ def nodes(self): np.array((0.0, 0.0, 1.0)), "In\\textsubscript{i}", [1.0, 0.0, 1.0], - {"v0": -60.0}, + {"v": -60.0}, {}, ), ( @@ -570,7 +570,7 @@ def nodes(self): np.array((2.0, 0.5, 1.0)), "V0\\textsubscript{V}", [0.0, 1.0, 0.0], - {"v0": -60.0}, + {"v": -60.0}, {}, ), ( @@ -579,7 +579,7 @@ def nodes(self): np.array((2.0, -2.0, 1.0)), "V0\\textsubscript{D}", [1.0, 0.0, 1.0], - {"v0": -60.0}, + {"v": -60.0}, {}, ), ( @@ -588,7 +588,7 @@ def nodes(self): np.array((2.0, 3.0, 1.0)), "V3\\textsubscript{E}", [0.0, 1.0, 0.0], - {"v0": -60.0}, + {"v": -60.0}, {}, ), ( @@ -597,7 +597,7 @@ def nodes(self): np.array((2.0, -4.0, 1.0)), "V3\\textsubscript{F}", [0.0, 1.0, 0.0], - {"v0": -60.0}, + {"v": -60.0}, {}, ), ( @@ -659,7 +659,7 @@ def nodes(self): np.array((0.0, 0.0, 1.0)), "V0\\textsubscript{D}", [0.5, 0.0, 0.5], - {"v0": -60.0}, + {"v": -60.0}, {}, ), ( @@ -668,7 +668,7 @@ def nodes(self): np.array((0.0, -1.25, 1.0)), "V0\\textsubscript{V}", [0.0, 1.0, 0.0], - {"v0": -60.0}, + {"v": -60.0}, {}, ), ( @@ -677,7 +677,7 @@ def nodes(self): np.array((0.0, -4.0, 1.0)), "V3\\textsubscript{a}", [0.0, 1.0, 0.0], - {"v0": -60.0}, + {"v": -60.0}, {}, ), ( @@ -686,7 +686,7 @@ def nodes(self): np.array((-4.0, 0.0, 1.0)), "LPN\\textsubscript{i}", [0.0, 1.0, 0.0], - {"v0": -60.0}, + {"v": -60.0}, {}, ), ( @@ -695,7 +695,7 @@ def nodes(self): np.array((-8.0, 0.0, 1.0)), "Sh\\textsubscript{2}", [0.0, 1.0, 0.0], - {"v0": -60.0}, + {"v": -60.0}, {}, ), ( @@ -704,7 +704,7 @@ def nodes(self): np.array((-8.0, -4.0, 1.0)), "Sh\\textsubscript{2}", [0.0, 1.0, 0.0], - {"v0": -60.0}, + {"v": -60.0}, {}, ), ( @@ -778,7 +778,7 @@ def nodes(self): np.array((IaIn_x_positions[0], y_off, 1.0)), "Ia\\textsubscript{ea}", [0.0, 0.0, 1.0], - {"v0": -60.0}, + {"v": -60.0}, {}, ), ( @@ -787,7 +787,7 @@ def nodes(self): np.array((IaIn_x_positions[1], y_off, 1.0)), "Ia\\textsubscript{eb}", [0.0, 0.0, 1.0], - {"v0": -60.0}, + {"v": -60.0}, {}, ), ( @@ -796,7 +796,7 @@ def nodes(self): np.array((IaIn_x_positions[2], y_off, 1.0)), "Ia\\textsubscript{fa}", [0.0, 0.0, 1.0], - {"v0": -60.0}, + {"v": -60.0}, {}, ), ( @@ -805,7 +805,7 @@ def nodes(self): np.array((IaIn_x_positions[3], y_off, 1.0)), "Ia\\textsubscript{fb}", [0.0, 0.0, 1.0], - {"v0": -60.0}, + {"v": -60.0}, {}, ), ( @@ -814,7 +814,7 @@ def nodes(self): np.array((np.mean(IaIn_x_positions), y_off - spacing, 1.0)), "Ib\\textsubscript{rg}", [0.0, 0.0, 1.0], - {"v0": -60.0}, + {"v": -60.0}, {}, ), ] @@ -855,7 +855,7 @@ def _get_muscle_neurons(self, muscle, x_off, y_off, mirror_y=False): np.array((x_off, y_off, 1.0)), "Mn", [1.0, 0.0, 1.0], - {"v0": -60.0}, + {"v": -60.0}, {"e_leak": -52.5, "g_leak": 1.0}, ), ( @@ -891,7 +891,7 @@ def _get_muscle_neurons(self, muscle, x_off, y_off, mirror_y=False): np.array((x_off + 0.5, y_off - 1.0 * mirror_y_sign, 1.0)), "Rn", [1.0, 0.0, 1.0], - {"v0": -60.0}, + {"v": -60.0}, {}, ), ( @@ -900,7 +900,7 @@ def _get_muscle_neurons(self, muscle, x_off, y_off, mirror_y=False): np.array((x_off + 1.0, y_off, 1.0)), "Ib\\textsubscript{i}", [0.0, 0.0, 1.0], - {"v0": -60.0}, + {"v": -60.0}, {}, ), ( @@ -909,7 +909,7 @@ def _get_muscle_neurons(self, muscle, x_off, y_off, mirror_y=False): np.array((x_off + 1.0, y_off + 1.5 * mirror_y_sign, 1.0)), "Ib\\textsubscript{e}", [0.0, 0.0, 1.0], - {"v0": -60.0}, + {"v": -60.0}, {}, ), ( @@ -918,7 +918,7 @@ def _get_muscle_neurons(self, muscle, x_off, y_off, mirror_y=False): np.array((x_off - 1.0, y_off, 1.0)), "II\\textsubscript{RG}", [0.0, 0.0, 1.0], - {"v0": -60.0}, + {"v": -60.0}, {}, ), ] From 8ae3b57204d6e49fb8c34cb574a33e6e381098d9 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Thu, 21 Nov 2024 14:30:09 -0500 Subject: [PATCH 147/316] [SCRATCH] Added test_gui script --- scratch/test_gui.py | 531 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 531 insertions(+) create mode 100644 scratch/test_gui.py diff --git a/scratch/test_gui.py b/scratch/test_gui.py new file mode 100644 index 0000000..4665a7d --- /dev/null +++ b/scratch/test_gui.py @@ -0,0 +1,531 @@ +import time +from argparse import ArgumentParser + +import numpy as np +from farms_core.io.yaml import read_yaml +from farms_network.core.network import PyNetwork +from farms_network.core.options import NetworkOptions +from farms_network.gui.gui import NetworkGUI +from imgui_bundle import imgui, imgui_ctx, implot +from tqdm import tqdm + + +# From farms_amphibious. To be replaced! +def rotate(vector, theta): + """Rotate vector""" + cos_t, sin_t = np.cos(theta), np.sin(theta) + rotation = np.array(((cos_t, -sin_t), (sin_t, cos_t))) + return np.dot(rotation, vector) + + +def direction(vector1, vector2): + """Unit direction""" + return (vector2-vector1)/np.linalg.norm(vector2-vector1) + + +def connect_positions(source, destination, dir_shift, perp_shift): + """Connect positions""" + connection_direction = direction(source, destination) + connection_perp = rotate(connection_direction, 0.5*np.pi) + new_source = ( + source + + dir_shift*connection_direction + + perp_shift*connection_perp + ) + new_destination = ( + destination + - dir_shift*connection_direction + + perp_shift*connection_perp + ) + return new_source, new_destination + + +def compute_phases(times, data): + phases = (np.array(data) > 0.1).astype(np.int16) + phases = np.logical_not(phases).astype(np.int16) + phases_xs = [] + phases_ys = [] + for j in range(len(data)): + phases_start = np.where(np.diff(phases[j, :], prepend=0) == 1.0)[0] + phases_ends = np.where(np.diff(phases[j, :], append=0) == -1.0)[0] + phases_xs.append(np.vstack( + (times[phases_start], times[phases_start], times[phases_ends], times[phases_ends]) + ).T) + phases_ys.append(np.ones(np.shape(phases_xs[j]))*j) + if np.all(len(phases_start) > 3): + phases_ys[j][:, 1] += 1 + phases_ys[j][:, 2] += 1 + return phases_xs, phases_ys + + +def add_plot(iteration, data): + """ """ + # times = data.times.array[iteration%1000:] + side = "right" + limb = "fore" + plot_names = [ + f"{side}_{limb}_RG_E", + f"{side}_{limb}_RG_F", + f"left_fore_RG_F", + f"right_hind_RG_F", + f"left_hind_RG_F", + f"{side}_{limb}_PF_FA", + f"{side}_{limb}_PF_EA", + f"{side}_{limb}_PF_FB", + f"{side}_{limb}_PF_EB", + f"{side}_{limb}_RG_F_DR", + ] + + nodes_names = [ + node.name + for node in data.nodes + ] + + plot_nodes = [ + nodes_names.index(name) + for name in plot_names + ] + if not plot_nodes: + return + + outputs = np.vstack( + ( + *[ + data.nodes[plot_nodes[j]].output.array + for j in range(len(plot_names)) + ], + data.nodes[plot_nodes[-1]].external_input.array, + ) + ) + if iteration < 1000: + plot_data = np.array(outputs[:, :iteration]) + else: + plot_data = np.array(outputs[:, iteration-1000:iteration]) + # plot_data = np.vstack((outputs[iteration%1000:], outputs[:iteration%1000])) + + times = np.array((np.linspace(0.0, 1.0, 1000)*-1.0)[::-1]) + + phases_xs, phases_ys = compute_phases(times, plot_data[1:5, :]) + + # phases = (np.array(plot_data[0, :]) > 0.1).astype(np.int16) + # phases = np.logical_not(phases).astype(np.int16) + # phases_start = np.where(np.diff(phases, prepend=0) == 1.0)[0] + # phases_ends = np.where(np.diff(phases, append=0) == -1.0)[0] + # phases_xs = np.vstack( + # (times[phases_start], times[phases_start], times[phases_ends], times[phases_ends]) + # ).T + # phases_ys = np.ones(np.shape(phases_xs)) + # if len(phases_start) > 3: + # phases_ys[:, 1] += 1.0 + # phases_ys[:, 2] += 1.0 + row_col_ratios = implot.SubplotsRowColRatios(row_ratios=[0.1, 0.6, 0.3], col_ratios=[1]) + colors = { + "RF": imgui.IM_COL32(28, 107, 180, 255), + "LF": imgui.IM_COL32(23, 163, 74, 255), + "RH": imgui.IM_COL32(200, 38, 39, 255), + "LH": imgui.IM_COL32(0, 0, 0, 255), # imgui.IM_COL32(255, 252, 212, 255) + "right_fore_RG_F": imgui.IM_COL32(28, 107, 180, 255), + "left_fore_RG_F": imgui.IM_COL32(23, 163, 74, 255), + "right_hind_RG_F": imgui.IM_COL32(200, 38, 39, 255), + "left_hind_RG_F": imgui.IM_COL32(0, 0, 0, 255), # imgui.IM_COL32(255, 252, 212, 255) + } + with imgui_ctx.begin("States"): + if implot.begin_subplots( + "Network Activity", + 3, + 1, + imgui.ImVec2(-1, -1), + row_col_ratios=implot.SubplotsRowColRatios(row_ratios=[0.1, 0.6, 0.3], col_ratios=[1]) + ): + if implot.begin_plot(""): + flags = ( + implot.AxisFlags_.no_label | implot.AxisFlags_.no_tick_labels | implot.AxisFlags_.no_tick_marks + ) + implot.setup_axis(implot.ImAxis_.y1, "Drive") + implot.setup_axis(implot.ImAxis_.x1, flags=flags) + implot.setup_axis_links(implot.ImAxis_.x1, -1.0, 0.0) + implot.setup_axis_limits(implot.ImAxis_.x1, -1.0, 0.0) + implot.setup_axis_limits(implot.ImAxis_.y1, 0.0, 1.5) + implot.setup_axis_limits_constraints(implot.ImAxis_.x1, -1.0, 0.0) + implot.setup_axis_limits_constraints(implot.ImAxis_.y1, 0.0, 1.5) + implot.plot_line("RG-F-Dr", times, plot_data[-1, :]) + implot.end_plot() + if implot.begin_plot(""): + implot.setup_axis(implot.ImAxis_.x1, "Time") + implot.setup_axis(implot.ImAxis_.y1, "Activity") + implot.setup_axis_links(implot.ImAxis_.x1, -1.0, 0.0) + implot.setup_axis_limits(implot.ImAxis_.y1, -1*len(plot_names), 1.0) + implot.setup_axis_limits_constraints(implot.ImAxis_.x1, -1.0, 0.0) + implot.setup_axis_limits_constraints(implot.ImAxis_.y1, -1*len(plot_names), 1.0) + for j, name in enumerate(plot_names[:-1]): + if plot_names[j] in colors: + implot.push_style_color(implot.Col_.line, colors.get(plot_names[j])) + implot.plot_line(plot_names[j], times, plot_data[j, :] - j) + implot.pop_style_color() + else: + implot.plot_line(plot_names[j], times, plot_data[j, :] - j) + implot.end_plot() + if implot.begin_plot(""): + implot.setup_axis_limits(implot.ImAxis_.x1, -1.0, 0.0) + implot.setup_axis_limits(implot.ImAxis_.y1, -2.0, 6.0) + implot.setup_axis_limits_constraints(implot.ImAxis_.x1, -1.0, 0.0) + implot.setup_axis_limits_constraints(implot.ImAxis_.y1, -2.0, 4.0) + implot.setup_axis(implot.ImAxis_.y1, flags=implot.AxisFlags_.invert) + for j, limb in enumerate(("RF", "LF", "RH", "LH")): + if len(phases_xs[j]) > 3: + implot.push_style_color( + implot.Col_.fill, + colors[limb] + ) + implot.plot_shaded( + limb, + phases_xs[j].flatten(), + phases_ys[j].flatten(), + yref=j + ) + implot.pop_style_color() + implot.end_plot() + implot.end_subplots() + + +def draw_muscle_activity(iteration, data): + """ Draw muscle activity """ + side = "left" + limb = "hind" + + muscle_names = [ + "bfa", + "ip", + "bfpst", + "rf", + "va", + "mg", + "sol", + "ta", + "ab", + "gm_dorsal", + "edl", + "fdl", + ] + + nodes_names = [ + node.name + for node in data.nodes + ] + + plot_nodes = [ + nodes_names.index(f"{side}_{limb}_{name}_Mn") + for name in muscle_names + ] + if not plot_nodes: + return + outputs = np.vstack( + [ + data.nodes[plot_nodes[j]].output.array + for j in range(len(plot_nodes)) + ] + ) + if iteration < 1000: + plot_data = np.array(outputs[:, :iteration]) + else: + plot_data = np.array(outputs[:, iteration-1000:iteration]) + + times = np.array((np.linspace(0.0, 1.0, 1000)*-1.0)[::-1]) + + with imgui_ctx.begin("Muscle activity"): + if implot.begin_plot("Muscle Activity", imgui.ImVec2(-1, -1)): + implot.setup_axis(implot.ImAxis_.x1, "Time") + implot.setup_axis(implot.ImAxis_.y1, "Activity") + implot.setup_axis_links(implot.ImAxis_.x1, -1.0, 0.0) + implot.setup_axis_limits(implot.ImAxis_.x1, -1.0, 0.0) + implot.setup_axis_limits(implot.ImAxis_.y1, -1*len(plot_nodes), 1.0) + implot.setup_axis_limits_constraints(implot.ImAxis_.x1, -1.0, 0.0) + # implot.setup_axis_limits_constraints(implot.ImAxis_.y1, -5.2, 1.0) + for j in range(len(plot_nodes)): + implot.plot_line(muscle_names[j], times, plot_data[j, :] - j) + implot.end_plot() + + # plot_nodes = [ + # nodes_names.index(f"{side}_{limb}_{name}_Rn") + # for name in muscle_names + # ] + # if not plot_nodes: + # return + # outputs = np.vstack( + # [ + # data.nodes[plot_nodes[j]].output.array + # for j in range(len(plot_nodes)) + # ] + # ) + # if iteration < 1000: + # plot_data = np.array(outputs[:, :iteration]) + # else: + # plot_data = np.array(outputs[:, iteration-1000:iteration]) + + # with imgui_ctx.begin("Renshaw activity"): + # if implot.begin_plot("Renshaw Activity", imgui.ImVec2(-1, -1)): + # implot.setup_axis(implot.ImAxis_.x1, "Time") + # implot.setup_axis(implot.ImAxis_.y1, "Activity") + # implot.setup_axis_links(implot.ImAxis_.x1, -1.0, 0.0) + # implot.setup_axis_limits(implot.ImAxis_.x1, -1.0, 0.0) + # implot.setup_axis_limits(implot.ImAxis_.y1, -1*len(plot_nodes), 1.0) + # implot.setup_axis_limits_constraints(implot.ImAxis_.x1, -1.0, 0.0) + # # implot.setup_axis_limits_constraints(implot.ImAxis_.y1, -5.2, 1.0) + # for j in range(len(plot_nodes)): + # implot.plot_line(muscle_names[j], times, plot_data[j, :] - j) + # implot.end_plot() + + Ia_In_names = ("EA", "EB", "FA", "FB") + plot_nodes = [ + nodes_names.index(f"{side}_{limb}_Ia_In_{name}") + for name in ("EA", "EB", "FA", "FB") + ] + plot_nodes = [ + nodes_names.index(name) + for name in nodes_names + if "Ib_In_e" in name + ] + plot_labels = [ + name + for name in nodes_names + if "Ib_In_e" in name + ] + if not plot_nodes: + return + outputs = np.vstack( + [ + data.nodes[plot_nodes[j]].output.array + for j in range(len(plot_nodes)) + ] + ) + if iteration < 1000: + plot_data = np.array(outputs[:, :iteration]) + else: + plot_data = np.array(outputs[:, iteration-1000:iteration]) + + with imgui_ctx.begin("Sensory interneuron activity"): + if implot.begin_plot("Sensory interneuron Activity", imgui.ImVec2(-1, -1)): + implot.setup_axis(implot.ImAxis_.x1, "Time") + implot.setup_axis(implot.ImAxis_.y1, "Activity") + implot.setup_axis_links(implot.ImAxis_.x1, -1.0, 0.0) + implot.setup_axis_limits(implot.ImAxis_.x1, -1.0, 0.0) + implot.setup_axis_limits(implot.ImAxis_.y1, -1*len(plot_nodes), 1.0) + implot.setup_axis_limits_constraints(implot.ImAxis_.x1, -1.0, 0.0) + # implot.setup_axis_limits_constraints(implot.ImAxis_.y1, -5.2, 1.0) + for j in range(len(plot_nodes)): + implot.plot_line(plot_labels[j], times, plot_data[j, :] - j) + implot.end_plot() + + +def draw_network(network_options, data, iteration, edges_x, edges_y): + """ Draw network """ + + nodes = network_options.nodes + + with imgui_ctx.begin("Full-Network"): + if implot.begin_plot("vis", imgui.ImVec2((-1, -1))): + radius = implot.plot_to_pixels(implot.Point((7.5, 7.5))) + + implot.plot_line( + "", + xs=edges_x, + ys=edges_y, + flags=implot.LineFlags_.segments + ) + for index, node in enumerate(nodes): + implot.set_next_marker_style( + size=7.5# *node.visual.radius + ) + implot.push_style_var( + implot.StyleVar_.fill_alpha, + 0.05+data.nodes[index].output.array[iteration]*2.0 + ) + implot.plot_scatter( + "##", + xs=np.array((node.visual.position[0],)), + ys=np.array((node.visual.position[1],)), + ) + implot.pop_style_var() + # implot.push_plot_clip_rect() + # position = implot.plot_to_pixels(implot.Point(node.visual.position[:2])) + # color = imgui.IM_COL32( + # 0, 0, 0, 255 + # ) + # implot.get_plot_draw_list().add_circle(position, 7.5, color) + # implot.pop_plot_clip_rect() + + # implot.push_plot_clip_rect() + # color = imgui.IM_COL32( + # 100, 185, 0, + # int(255*(data.nodes[index].output.array[iteration])) + # ) + # implot.get_plot_draw_list().add_circle_filled(position, 7.5, color) + # implot.pop_plot_clip_rect() + implot.plot_text( + node.visual.label.replace("\\textsubscript", "")[0], + node.visual.position[0], + node.visual.position[1], + ) + + implot.end_plot() + + +def draw_slider( + label: str, + name: str, + values: list, + min_value: float = 0.0, + max_value: float = 1.0 +): + with imgui_ctx.begin(name): + clicked, values[0] = imgui.slider_float( + label="drive", + v=values[0], + v_min=min_value, + v_max=max_value, + ) + clicked, values[1] = imgui.slider_float( + label="Ia", + v=values[1], + v_min=min_value, + v_max=max_value, + ) + clicked, values[2] = imgui.slider_float( + label="II", + v=values[2], + v_min=min_value, + v_max=max_value, + ) + clicked, values[3] = imgui.slider_float( + label="Ib", + v=values[3], + v_min=min_value, + v_max=max_value, + ) + return values + + +def draw_table(): + """ Draw table """ + with imgui_ctx.begin("Table"): + if imgui.begin_table("table", 5): + for row in range(6): + imgui.table_next_row() + for column in range(5): + imgui.table_set_column_index(column) + imgui.text(f"Row {row} Column {column}") + imgui.end_table() + + +def draw_polar(): + """ Draw polar """ + with imgui_ctx.begin("Polar"): + if imgui.begin_plot("polar"): + implot.plot_line + imgui.end_plot() + + + +def main(): + """ Main """ + + parser = ArgumentParser() + parser.add_argument( + "--config_path", "-c", dest="config_path", type=str, required=True + ) + clargs = parser.parse_args() + # run network + network_options = NetworkOptions.from_options(read_yaml(clargs.config_path)) + + network = PyNetwork.from_options(network_options) + network.setup_integrator(network_options.integration) + + # Integrate + N_ITERATIONS = network_options.integration.n_iterations + TIMESTEP = network_options.integration.timestep + BUFFER_SIZE = network_options.logs.buffer_size + + gui = NetworkGUI() + gui.create_context() + + inputs_view = network.data.external_inputs.array + drive_input = 0.0 + imgui.style_colors_light() + implot.style_colors_light() + + edges_xy = np.array( + [ + network_options.nodes[node_idx].visual.position[:2] + for edge in network_options.edges + for node_idx in ( + network_options.nodes.index(edge.source), + network_options.nodes.index(edge.target), + ) + ] + ) + # for index in range(len(edges_xy) - 1): + # edges_xy[index], edges_xy[index + 1] = connect_positions( + # edges_xy[index+1], edges_xy[index], 10.0, 0.0 + # ) + edges_x = np.array(edges_xy[:, 0]) + edges_y = np.array(edges_xy[:, 1]) + + fps = 30.0 + _time_draw = time.time() + _time_draw_last = _time_draw + _realtime = 0.1 + + imgui.get_io().config_flags |= imgui.ConfigFlags_.docking_enable + + drive_input_indices = [ + index + for index, node in enumerate(network_options.nodes) + if "DR" in node.name + ] + Ia_input_indices = [ + index + for index, node in enumerate(network_options.nodes) + if "Ia" == node.name[-2:] + ] + II_input_indices = [ + index + for index, node in enumerate(network_options.nodes) + if "II" == node.name[-2:] + ] + Ib_input_indices = [ + index + for index, node in enumerate(network_options.nodes) + if "Ib" == node.name[-2:] + ] + slider_values = np.zeros((4,)) + slider_values[0] = 0.25 + input_array = np.zeros(np.shape(inputs_view)) + input_array[drive_input_indices] = 0.25 + input_array[drive_input_indices[0]] *= 1.05 + for iteration in tqdm(range(0, N_ITERATIONS), colour="green", ascii=" >="): + input_array[drive_input_indices] = slider_values[0] + input_array[Ia_input_indices] = slider_values[1] + input_array[II_input_indices] = slider_values[2] + input_array[Ib_input_indices] = slider_values[3] + + inputs_view[:] = input_array + network.step() + buffer_iteration = iteration%BUFFER_SIZE + network.data.times.array[buffer_iteration] = (iteration)*TIMESTEP + _time_draw_last = _time_draw + _time_draw = time.time() + fps = _realtime*1/(_time_draw-_time_draw_last)+(1-_realtime)*fps + # print(imgui.get_io().delta_time, imgui.get_io().framerate) + time.sleep(1e-4) + if not (iteration % 2): + gui.new_frame() + slider_values = draw_slider(label="d", name="Drive", values=slider_values) + add_plot(buffer_iteration, network.data) + draw_table() + draw_network(network_options, network.data, buffer_iteration, edges_x, edges_y) + draw_muscle_activity(buffer_iteration, network.data) + gui.render_frame() + + +if __name__ == '__main__': + main() From d7112707093477bd97888abbab22194ef0191b01 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Thu, 21 Nov 2024 14:35:56 -0500 Subject: [PATCH 148/316] [CORE][DATA] Added network noise feature --- farms_network/core/data.py | 82 +++++++++++++++++++++------------- farms_network/core/data_cy.pxd | 14 ++++-- farms_network/core/data_cy.pyx | 15 +++++++ 3 files changed, 76 insertions(+), 35 deletions(-) diff --git a/farms_network/core/data.py b/farms_network/core/data.py index 785aa10..fcf988d 100644 --- a/farms_network/core/data.py +++ b/farms_network/core/data.py @@ -32,7 +32,8 @@ NDARRAY_V3_D) from farms_core.io.hdf5 import dict_to_hdf5, hdf5_to_dict -from .data_cy import NetworkConnectivityCy, NetworkDataCy, NetworkStatesCy, NodeDataCy +from .data_cy import (NetworkConnectivityCy, NetworkDataCy, NetworkNoiseCy, + NetworkStatesCy, NodeDataCy) from .options import NetworkOptions, NodeOptions, NodeStateOptions NPDTYPE = np.float64 @@ -50,9 +51,7 @@ def __init__( connectivity, outputs, external_inputs, - noise_states, - noise_derivatives, - noise_outputs, + noise, nodes, **kwargs, ): @@ -66,9 +65,7 @@ def __init__( self.outputs = outputs self.external_inputs = external_inputs - self.noise_states = noise_states - self.noise_derivatives = noise_derivatives - self.noise_outputs = noise_outputs + self.noise = noise self.nodes: np.ndarray[NodeDataCy] = nodes @@ -87,6 +84,7 @@ def from_options(cls, network_options: NetworkOptions): states = NetworkStates.from_options(network_options) derivatives = NetworkStates.from_options(network_options) connectivity = NetworkConnectivity.from_options(network_options) + noise = NetworkNoise.from_options(network_options) outputs = DoubleArray1D( array=np.full( shape=len(network_options.nodes), @@ -101,27 +99,6 @@ def from_options(cls, network_options: NetworkOptions): dtype=NPDTYPE, ) ) - noise_states = DoubleArray1D( - array=np.full( - shape=len(network_options.nodes), - fill_value=0, - dtype=NPDTYPE, - ) - ) - noise_derivatives = DoubleArray1D( - array=np.full( - shape=len(network_options.nodes), - fill_value=0, - dtype=NPDTYPE, - ) - ) - noise_outputs = DoubleArray1D( - array=np.full( - shape=len(network_options.nodes), - fill_value=0, - dtype=NPDTYPE, - ) - ) nodes = np.array( [ NodeData.from_options( @@ -139,9 +116,7 @@ def from_options(cls, network_options: NetworkOptions): connectivity=connectivity, outputs=outputs, external_inputs=external_inputs, - noise_states=noise_states, - noise_derivatives=noise_derivatives, - noise_outputs=noise_outputs, + noise=noise, nodes=nodes, ) @@ -154,6 +129,7 @@ def to_dict(self, iteration: int = None) -> Dict: 'connectivity': self.connectivity.to_dict(), 'outputs': to_array(self.outputs.array), 'external_inputs': to_array(self.external_inputs.array), + 'noise': self.noise.to_dict(), 'nodes': {node.name: node.to_dict() for node in self.nodes}, } @@ -253,6 +229,50 @@ def to_dict(self, iteration: int = None) -> Dict: } +class NetworkNoise(NetworkNoiseCy): + """ Data for network noise modeling """ + + def __init__(self, states, derivatives, outputs): + super().__init__(states, derivatives, outputs) + + @classmethod + def from_options(cls, network_options: NetworkOptions): + + nodes = network_options.nodes + n_noise_states = 0 + n_nodes = len(nodes) + + for index, node in enumerate(nodes): + if node.noise and node.noise.is_stochastic: + n_noise_states += 1 + + return cls( + states=np.full( + shape=n_noise_states, + fill_value=0.0, + dtype=NPDTYPE, + ), + derivatives=np.full( + shape=n_noise_states, + fill_value=0.0, + dtype=NPDTYPE, + ), + outputs=np.full( + shape=n_nodes, + fill_value=0.0, + dtype=NPDTYPE, + ) + ) + + def to_dict(self, iteration: int = None) -> Dict: + """Convert data to dictionary""" + return { + 'states': to_array(self.states), + 'derivatives': to_array(self.derivatives), + 'outputs': to_array(self.outputs), + } + + class NodeData(NodeDataCy): """ Base class for representing an arbitrary node data """ diff --git a/farms_network/core/data_cy.pxd b/farms_network/core/data_cy.pxd index ee2b365..1e1c39e 100644 --- a/farms_network/core/data_cy.pxd +++ b/farms_network/core/data_cy.pxd @@ -32,10 +32,7 @@ cdef class NetworkDataCy: public DoubleArray1D external_inputs public DoubleArray1D outputs public NetworkConnectivityCy connectivity - - public DoubleArray1D noise_states - public DoubleArray1D noise_derivatives - public DoubleArray1D noise_outputs + public NetworkNoiseCy noise public NodeDataCy[:] nodes @@ -55,6 +52,15 @@ cdef class NetworkConnectivityCy: public UITYPEv1 indices +cdef class NetworkNoiseCy: + """ Noise data array """ + + cdef: + public DTYPEv1 states + public DTYPEv1 derivatives + public DTYPEv1 outputs + + cdef class NodeDataCy: """ Node data """ cdef: diff --git a/farms_network/core/data_cy.pyx b/farms_network/core/data_cy.pyx index f5e8b97..bbc3a75 100644 --- a/farms_network/core/data_cy.pyx +++ b/farms_network/core/data_cy.pyx @@ -63,6 +63,21 @@ cdef class NetworkConnectivityCy: self.indices = np.array(indices, dtype=np.uintc) +cdef class NetworkNoiseCy: + """ Noise data """ + + def __init__( + self, + states: NDArray[(Any,), np.double], + derivatives: NDArray[(Any,), np.double], + outputs: NDArray[(Any,), np.double], + ): + super().__init__() + self.states = np.array(states, dtype=np.double) + self.derivatives = np.array(derivatives, dtype=np.double) + self.outputs = np.array(outputs, dtype=np.double) + + ############# # Node Data # ############# From a02ab46a193551dd6322bf06aa0ccc472223d5d5 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Thu, 21 Nov 2024 23:52:34 -0500 Subject: [PATCH 149/316] [NOISE] Moved stochastic model to noise sub-module --- farms_network/noise/__init__.py | 0 .../ornstein_uhlenbeck.pxd} | 4 ++-- .../ornstein_uhlenbeck.pyx} | 18 ++++++++++++------ 3 files changed, 14 insertions(+), 8 deletions(-) create mode 100644 farms_network/noise/__init__.py rename farms_network/{core/stochastic_noise.pxd => noise/ornstein_uhlenbeck.pxd} (91%) rename farms_network/{core/stochastic_noise.pyx => noise/ornstein_uhlenbeck.pyx} (89%) diff --git a/farms_network/noise/__init__.py b/farms_network/noise/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/farms_network/core/stochastic_noise.pxd b/farms_network/noise/ornstein_uhlenbeck.pxd similarity index 91% rename from farms_network/core/stochastic_noise.pxd rename to farms_network/noise/ornstein_uhlenbeck.pxd index 8b35a8d..710a6a3 100644 --- a/farms_network/core/stochastic_noise.pxd +++ b/farms_network/noise/ornstein_uhlenbeck.pxd @@ -20,5 +20,5 @@ cdef class OrnsteinUhlenbeck(SDESystem): unsigned int n_dim OrnsteinUhlenbeckParameters* parameters - cdef void evaluate_a(self, double time, double[:] states, double[:] derivatives) noexcept - cdef void evaluate_b(self, double time, double[:] states, double[:] derivatives) noexcept + cdef void evaluate_a(self, double time, double[:] states, double[:] drift) noexcept + cdef void evaluate_b(self, double time, double[:] states, double[:] diffusion) noexcept diff --git a/farms_network/core/stochastic_noise.pyx b/farms_network/noise/ornstein_uhlenbeck.pyx similarity index 89% rename from farms_network/core/stochastic_noise.pyx rename to farms_network/noise/ornstein_uhlenbeck.pyx index 6a7fd99..dfb625a 100644 --- a/farms_network/core/stochastic_noise.pyx +++ b/farms_network/noise/ornstein_uhlenbeck.pyx @@ -26,9 +26,10 @@ from libc.stdlib cimport free, malloc cdef class OrnsteinUhlenbeck(SDESystem): """ Ornstein Uhlenheck parameters """ - def __cinit__(self, unsigned int n_dim): + def __cinit__(self, network_options): """ C initialization for manual memory allocation """ + n_dim = 1 self.parameters = malloc(sizeof(OrnsteinUhlenbeckParameters)) if self.parameters is NULL: raise MemoryError("Failed to allocate memory for OrnsteinUhlenbeck Parameters") @@ -69,17 +70,22 @@ cdef class OrnsteinUhlenbeck(SDESystem): if self.parameters is not NULL: free(self.parameters) - def __init__(self, options): + def __init__(self, network_options): super().__init__() + self.n_dim = 1 + self.timestep = 0.001 + self.parameters.mu[0] = 1.0 - cdef void evaluate_a(self, double time, double[:] states, double[:] derivatives) noexcept: + cdef void evaluate_a(self, double time, double[:] states, double[:] drift) noexcept: ... # cdef unsigned int j - # cdef OrnsteinUhlenbeckParameters params = self.OrnsteinUhlenbeckParameters[0] - # for j range(self.n_dim): + # cdef OrnsteinUhlenbeckParameters params = ( + # self.OrnsteinUhlenbeckParameters[0] + # ) + # for j in range(self.n_dim): # derivatives[j] = (params.mu[j]-states[j])/params.tau[0] - cdef void evaluate_b(self, double time, double[:] states, double[:] derivatives) noexcept: + cdef void evaluate_b(self, double time, double[:] states, double[:] diffusion) noexcept: ... # cdef unsigned int j # cdef OrnsteinUhlenbeckParameters params = ( From b78034ec552611064278f909f0423418a7ed26e3 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Sat, 23 Nov 2024 17:50:26 -0500 Subject: [PATCH 150/316] [NOISE] Added equations for ornstein uhlenbeck model --- farms_network/noise/ornstein_uhlenbeck.pxd | 4 +- farms_network/noise/ornstein_uhlenbeck.pyx | 117 ++++++++++++++------- 2 files changed, 82 insertions(+), 39 deletions(-) diff --git a/farms_network/noise/ornstein_uhlenbeck.pxd b/farms_network/noise/ornstein_uhlenbeck.pxd index 710a6a3..2f9a91b 100644 --- a/farms_network/noise/ornstein_uhlenbeck.pxd +++ b/farms_network/noise/ornstein_uhlenbeck.pxd @@ -20,5 +20,5 @@ cdef class OrnsteinUhlenbeck(SDESystem): unsigned int n_dim OrnsteinUhlenbeckParameters* parameters - cdef void evaluate_a(self, double time, double[:] states, double[:] drift) noexcept - cdef void evaluate_b(self, double time, double[:] states, double[:] diffusion) noexcept + cdef void evaluate_a(self, double time, double timestep, double[:] states, double[:] drift) noexcept + cdef void evaluate_b(self, double time, double timestep, double[:] states, double[:] diffusion) noexcept diff --git a/farms_network/noise/ornstein_uhlenbeck.pyx b/farms_network/noise/ornstein_uhlenbeck.pyx index dfb625a..678d857 100644 --- a/farms_network/noise/ornstein_uhlenbeck.pyx +++ b/farms_network/noise/ornstein_uhlenbeck.pyx @@ -21,38 +21,60 @@ limitations under the License. from libc.stdlib cimport free, malloc +from libcpp.cmath cimport sqrt as cppsqrt + +from typing import List + +from ..core.options import OrnsteinUhlenbeckOptions cdef class OrnsteinUhlenbeck(SDESystem): """ Ornstein Uhlenheck parameters """ - def __cinit__(self, network_options): + def __cinit__(self, noise_options: List[OrnsteinUhlenbeckOptions]): """ C initialization for manual memory allocation """ - n_dim = 1 - self.parameters = malloc(sizeof(OrnsteinUhlenbeckParameters)) + self.n_dim = len(noise_options) + + self.parameters = malloc( + sizeof(OrnsteinUhlenbeckParameters) + ) if self.parameters is NULL: - raise MemoryError("Failed to allocate memory for OrnsteinUhlenbeck Parameters") + raise MemoryError( + "Failed to allocate memory for OrnsteinUhlenbeck Parameters" + ) - self.parameters.mu = malloc(n_dim*sizeof(double)) + self.parameters.mu = malloc(self.n_dim*sizeof(double)) if self.parameters.mu is NULL: - raise MemoryError("Failed to allocate memory for OrnsteinUhlenbeck parameter MU") + raise MemoryError( + "Failed to allocate memory for OrnsteinUhlenbeck parameter MU" + ) - self.parameters.sigma = malloc(n_dim*sizeof(double)) + self.parameters.sigma = malloc(self.n_dim*sizeof(double)) if self.parameters.sigma is NULL: - raise MemoryError("Failed to allocate memory for OrnsteinUhlenbeck parameter SIGMA") + raise MemoryError( + "Failed to allocate memory for OrnsteinUhlenbeck parameter SIGMA" + ) - self.parameters.tau = malloc(n_dim*sizeof(double)) + self.parameters.tau = malloc(self.n_dim*sizeof(double)) if self.parameters.tau is NULL: - raise MemoryError("Failed to allocate memory for OrnsteinUhlenbeck parameter TAU") + raise MemoryError( + "Failed to allocate memory for OrnsteinUhlenbeck parameter TAU" + ) - self.parameters.random_generator = malloc(n_dim*sizeof(mt19937)) + self.parameters.random_generator = malloc(self.n_dim*sizeof(mt19937)) if self.parameters.random_generator is NULL: - raise MemoryError("Failed to allocate memory for OrnsteinUhlenbeck parameter RANDOM GENERATOR") + raise MemoryError( + "Failed to allocate memory for OrnsteinUhlenbeck parameter RANDOM GENERATOR" + ) - self.parameters.distribution = malloc(n_dim*sizeof(normal_distribution[double])) + self.parameters.distribution = malloc( + self.n_dim*sizeof(normal_distribution[double]) + ) if self.parameters.distribution is NULL: - raise MemoryError("Failed to allocate memory for OrnsteinUhlenbeck parameter Distribution") + raise MemoryError( + "Failed to allocate memory for OrnsteinUhlenbeck parameter Distribution" + ) def __dealloc__(self): """ Deallocate any manual memory as part of clean up """ @@ -70,27 +92,48 @@ cdef class OrnsteinUhlenbeck(SDESystem): if self.parameters is not NULL: free(self.parameters) - def __init__(self, network_options): + def __init__(self, noise_options: List[OrnsteinUhlenbeckOptions]): super().__init__() - self.n_dim = 1 - self.timestep = 0.001 - self.parameters.mu[0] = 1.0 - - cdef void evaluate_a(self, double time, double[:] states, double[:] drift) noexcept: - ... - # cdef unsigned int j - # cdef OrnsteinUhlenbeckParameters params = ( - # self.OrnsteinUhlenbeckParameters[0] - # ) - # for j in range(self.n_dim): - # derivatives[j] = (params.mu[j]-states[j])/params.tau[0] - - cdef void evaluate_b(self, double time, double[:] states, double[:] diffusion) noexcept: - ... - # cdef unsigned int j - # cdef OrnsteinUhlenbeckParameters params = ( - # self.OrnsteinUhlenbeckParameters[0] - # ) - # for j range(self.n_dim): - # derivatives[j] = (params.mu[j]-states[j])/params.tau[0] - # params.sigma[j]*(cppsqrt((2.0*params[0].dt)/params.tau[j])) + self.initialize_parameters_from_options(noise_options) + + cdef void evaluate_a( + self, double time, double timestep, double[:] states, double[:] drift + ) noexcept: + cdef unsigned int j + cdef OrnsteinUhlenbeckParameters params = ( + self.parameters[0] + ) + for j in range(self.n_dim): + drift[j] = (params.mu[j]-states[j])/params.tau[j] + + cdef void evaluate_b( + self, double time, double timestep, double[:] states, double[:] diffusion + ) noexcept: + cdef unsigned int j + cdef OrnsteinUhlenbeckParameters params = ( + self.parameters[0] + ) + cdef double noise + for j in range(self.n_dim): + noise = params.distribution[j](params.random_generator[j]) + diffusion[j] = (params.sigma[j]/cppsqrt(params.tau[j]))*noise + + def py_evaluate_a(self, time, states, drift): + self.evaluate_a(time, 0.0, states, drift) + return drift + + def py_evaluate_b(self, time, states, diffusion): + self.evaluate_b(time, 0.0, states, diffusion) + return diffusion + + def initialize_parameters_from_options(self, noise_options): + """ """ + for index in range(self.n_dim): + noise_option = noise_options[index] + self.parameters.mu[index] = noise_option.mu + self.parameters.sigma[index] = noise_option.sigma + self.parameters.tau[index] = noise_option.tau + self.parameters.random_generator[index] = mt19937(12131) + self.parameters.distribution[index] = normal_distribution[double]( + self.parameters.mu[index], self.parameters.sigma[index] + ) From 962c9918e8dd5fb35b6798fc26236e0e0ea4bee0 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Sat, 23 Nov 2024 17:52:24 -0500 Subject: [PATCH 151/316] [NUMERIC] Added support for EulerMaruyama Solver --- farms_network/numeric/integrators_cy.pxd | 7 +++++++ farms_network/numeric/integrators_cy.pyx | 16 ++++++++++++---- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/farms_network/numeric/integrators_cy.pxd b/farms_network/numeric/integrators_cy.pxd index 4e42305..8c47196 100644 --- a/farms_network/numeric/integrators_cy.pxd +++ b/farms_network/numeric/integrators_cy.pxd @@ -25,5 +25,12 @@ cdef class RK4Solver: cdef class EulerMaruyamaSolver: + cdef: + DoubleArray1D drift + DoubleArray1D diffusion + + unsigned int dim + double dt + cdef: cdef void step(self, SDESystem sys, double time, double[:] state) noexcept diff --git a/farms_network/numeric/integrators_cy.pyx b/farms_network/numeric/integrators_cy.pyx index ae5b66c..05cf221 100644 --- a/farms_network/numeric/integrators_cy.pyx +++ b/farms_network/numeric/integrators_cy.pyx @@ -72,6 +72,12 @@ cdef class EulerMaruyamaSolver: super().__init__() self.dim = dim self.dt = dt + self.drift = DoubleArray1D( + array=np.full(shape=self.dim, fill_value=0.0, dtype=NPDTYPE,) + ) + self.diffusion = DoubleArray1D( + array=np.full(shape=self.dim, fill_value=0.0, dtype=NPDTYPE,) + ) cdef void step(self, SDESystem sys, double time, double[:] state) noexcept: """ Update stochastic noise process with Euler–Maruyama method (also called the @@ -79,8 +85,10 @@ cdef class EulerMaruyamaSolver: differential equation (SDE) """ cdef unsigned int i - cdef double noise + cdef double[:] drift = self.drift.array + cdef double[:] diffusion = self.diffusion.array - # for j in range(self.dim): - # noise = params.distribution(params.random_generator) - # state[i] += ((params.mu-state[j])*params.dt/params.tau) + params.sigma*(csqrt((2.0*params.dt)/params.tau))*noise + sys.evaluate_a(time, self.dt, state, drift) + sys.evaluate_b(time, self.dt, state, diffusion) + for i in range(self.dim): + state[i] += drift[i]*self.dt + csqrt((2.0*self.dt))*diffusion[i] From 1a4b81dc7810407be3b30b9a94f9f032eacde2a1 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Sat, 23 Nov 2024 17:53:25 -0500 Subject: [PATCH 152/316] [NUMERIC] Added timestep to SDE system base class --- farms_network/numeric/system.pxd | 8 ++++++-- farms_network/numeric/system.pyx | 8 ++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/farms_network/numeric/system.pxd b/farms_network/numeric/system.pxd index 1a660c6..ca942d0 100644 --- a/farms_network/numeric/system.pxd +++ b/farms_network/numeric/system.pxd @@ -5,5 +5,9 @@ cdef class ODESystem: cdef class SDESystem: - cdef void evaluate_a(self, double time, double[:] states, double[:] derivatives) noexcept - cdef void evaluate_b(self, double time, double[:] states, double[:] derivatives) noexcept + cdef void evaluate_a( + self, double time, double timestep, double[:] states, double[:] drift + ) noexcept + cdef void evaluate_b( + self, double time, double timestep, double[:] states, double[:] diffusion + ) noexcept diff --git a/farms_network/numeric/system.pyx b/farms_network/numeric/system.pyx index ac8b34d..002ddc1 100644 --- a/farms_network/numeric/system.pyx +++ b/farms_network/numeric/system.pyx @@ -20,10 +20,14 @@ cdef class SDESystem: """ Initialize """ ... - cdef void evaluate_a(self, double time, double[:] states, double[:] derivatives) noexcept: + cdef void evaluate_a( + self, double time, double timestep, double[:] states, double[:] drift + ) noexcept: """ a(Xt,t) """ ... - cdef void evaluate_b(self, double time, double[:] states, double[:] derivatives) noexcept: + cdef void evaluate_b( + self, double time, double timestep, double[:] states, double[:] diffusion + ) noexcept: """ b(Xt,t) """ ... From 3b7356e40319d4503fa3f93a65dbfb9bd5a85a7d Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Sat, 23 Nov 2024 17:53:54 -0500 Subject: [PATCH 153/316] [DATA] Added drift and diffusion arrays to NoiseData --- farms_network/core/data.py | 14 ++++++++++---- farms_network/core/data_cy.pxd | 3 ++- farms_network/core/data_cy.pyx | 6 ++++-- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/farms_network/core/data.py b/farms_network/core/data.py index fcf988d..5fb66cb 100644 --- a/farms_network/core/data.py +++ b/farms_network/core/data.py @@ -232,8 +232,8 @@ def to_dict(self, iteration: int = None) -> Dict: class NetworkNoise(NetworkNoiseCy): """ Data for network noise modeling """ - def __init__(self, states, derivatives, outputs): - super().__init__(states, derivatives, outputs) + def __init__(self, states, drift, diffusion, outputs): + super().__init__(states, drift, diffusion, outputs) @classmethod def from_options(cls, network_options: NetworkOptions): @@ -252,7 +252,12 @@ def from_options(cls, network_options: NetworkOptions): fill_value=0.0, dtype=NPDTYPE, ), - derivatives=np.full( + drift=np.full( + shape=n_noise_states, + fill_value=0.0, + dtype=NPDTYPE, + ), + diffusion=np.full( shape=n_noise_states, fill_value=0.0, dtype=NPDTYPE, @@ -268,7 +273,8 @@ def to_dict(self, iteration: int = None) -> Dict: """Convert data to dictionary""" return { 'states': to_array(self.states), - 'derivatives': to_array(self.derivatives), + 'drift': to_array(self.drift), + 'diffusion': to_array(self.diffusion), 'outputs': to_array(self.outputs), } diff --git a/farms_network/core/data_cy.pxd b/farms_network/core/data_cy.pxd index 1e1c39e..d6174f4 100644 --- a/farms_network/core/data_cy.pxd +++ b/farms_network/core/data_cy.pxd @@ -57,7 +57,8 @@ cdef class NetworkNoiseCy: cdef: public DTYPEv1 states - public DTYPEv1 derivatives + public DTYPEv1 drift + public DTYPEv1 diffusion public DTYPEv1 outputs diff --git a/farms_network/core/data_cy.pyx b/farms_network/core/data_cy.pyx index bbc3a75..a39cad7 100644 --- a/farms_network/core/data_cy.pyx +++ b/farms_network/core/data_cy.pyx @@ -69,12 +69,14 @@ cdef class NetworkNoiseCy: def __init__( self, states: NDArray[(Any,), np.double], - derivatives: NDArray[(Any,), np.double], + drift: NDArray[(Any,), np.double], + diffusion: NDArray[(Any,), np.double], outputs: NDArray[(Any,), np.double], ): super().__init__() self.states = np.array(states, dtype=np.double) - self.derivatives = np.array(derivatives, dtype=np.double) + self.drift = np.array(drift, dtype=np.double) + self.diffusion = np.array(diffusion, dtype=np.double) self.outputs = np.array(outputs, dtype=np.double) From cabb3bdc2dd540b44d71802c82f62e327ee38858 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Sat, 23 Nov 2024 17:54:18 -0500 Subject: [PATCH 154/316] [MAIN][SETUP] Added numeric and noise sub-modules to cython extension --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index ea3afba..e521c50 100644 --- a/setup.py +++ b/setup.py @@ -35,7 +35,7 @@ extra_compile_args=['-ffast-math', '-O3'], extra_link_args=['-O3'], ) - for subpackage in ('core', 'models', 'numeric') + for subpackage in ('core', 'models', 'numeric', 'noise') ] setup( From 53dbb9e4ec9c7a8556913f5522d1a587cd519fb2 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Sat, 23 Nov 2024 17:55:07 -0500 Subject: [PATCH 155/316] [CORE] Added noise options to all node classes --- farms_network/core/options.py | 78 +++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/farms_network/core/options.py b/farms_network/core/options.py index a02fc8e..7702535 100644 --- a/farms_network/core/options.py +++ b/farms_network/core/options.py @@ -1,5 +1,6 @@ """ Options to configure the neural and network models """ +from enum import IntEnum from typing import Dict, Iterable, List, Self, Union import matplotlib.pyplot as plt @@ -201,6 +202,7 @@ def __init__(self, **kwargs): self.parameters: NodeParameterOptions = kwargs.pop("parameters") self.visual: NodeVisualOptions = kwargs.pop("visual") self.state: NodeStateOptions = kwargs.pop("state") + self.noise: NoiseOptions = kwargs.pop("noise", None) self._nstates: int = 0 self._nparameters: int = 0 @@ -225,6 +227,7 @@ def from_options(cls, kwargs: Dict): options["parameters"] = kwargs.pop("parameters") options["visual"] = kwargs.pop("visual") options["state"] = kwargs.pop("state") + options["noise"] = kwargs.pop("noise") return cls(**options) @@ -389,6 +392,68 @@ def from_options(cls, kwargs: Dict): return cls(**kwargs) +################# +# Noise Options # +################# +class NoiseOptions(Options): + """ Base class for node noise options """ + + NOISE_TYPES = ("additive",) + NOISE_MODELS = ("white", "ornstein_uhlenbeck") + + def __init__(self, **kwargs): + super().__init__() + self.type = kwargs.pop("type", NoiseOptions.NOISE_TYPES[0]) + assert self.type.lower() in NoiseOptions.NOISE_TYPES + self.model = kwargs.pop("model", None) + assert self.model.lower() in NoiseOptions.NOISE_MODELS + self.is_stochastic = kwargs.pop("is_stochastic") + + @classmethod + def from_options(cls, kwargs: Dict): + """ From options """ + noise_models = { + cls.NOISE_MODELS[0]: NoiseOptions, + cls.NOISE_MODELS[1]: OrnsteinUhlenbeckOptions + } + noise_model = kwargs.pop("model") + return noise_models[noise_model].from_options(kwargs) + + +class OrnsteinUhlenbeckOptions(NoiseOptions): + """ Options to OrnsteinUhlenbeckOptions """ + + def __init__(self, **kwargs): + """ Initialize """ + model = NoiseOptions.NOISE_MODELS[1] + is_stochastic = True + super().__init__(model=model, is_stochastic=is_stochastic) + self.mu: float = kwargs.pop("mu") + self.sigma: float = kwargs.pop("sigma") + self.tau: float = kwargs.pop("tau") + self.timestep: float = kwargs.pop("timestep") + + @classmethod + def from_options(cls, kwargs: Dict): + """ From options """ + options = {} + options["mu"] = kwargs.pop("mu") + options["sigma"] = kwargs.pop("sigma") + options["tau"] = kwargs.pop("tau") + options["timestep"] = kwargs.pop("timestep") + return cls(**options) + + @classmethod + def defaults(cls, **kwargs: Dict): + """ From options """ + options = {} + options["mu"] = kwargs.pop("mu", 0.0) + options["sigma"] = kwargs.pop("sigma", 0.005) + options["tau"] = kwargs.pop("tau", 10.0) + options["timestep"] = kwargs.pop("timestep", 1/2.0) + return cls(**options) + + ################################ # External Relay Model Options # ################################ @@ -408,6 +473,7 @@ def __init__(self, **kwargs): parameters=kwargs.pop("parameters"), visual=kwargs.pop("visual"), state=kwargs.pop("state", None), + noise=kwargs.pop("noise"), ) self._nstates = 0 self._nparameters = 0 @@ -422,6 +488,7 @@ def from_options(cls, kwargs: Dict): options["name"] = kwargs.pop("name") options["parameters"] = NodeParameterOptions() options["visual"] = NodeVisualOptions.from_options(kwargs["visual"]) + options["noise"] = kwargs.pop("noise", None) return cls(**options) @@ -440,6 +507,7 @@ def __init__(self, **kwargs): parameters=kwargs.pop("parameters"), visual=kwargs.pop("visual"), state=kwargs.pop("state", None), + noise=kwargs.pop("noise"), ) self._nstates = 0 self._nparameters = 2 @@ -459,6 +527,7 @@ def from_options(cls, kwargs: Dict): kwargs["visual"] ) options["state"] = None + options["noise"] = kwargs.pop("noise", None) return cls(**options) @@ -498,6 +567,7 @@ def __init__(self, **kwargs): parameters=kwargs.pop("parameters"), visual=kwargs.pop("visual"), state=kwargs.pop("state"), + noise=kwargs.pop("noise"), ) self._nstates = 3 self._nparameters = 3 @@ -519,6 +589,7 @@ def from_options(cls, kwargs: Dict): options["state"] = OscillatorStateOptions.from_options( kwargs["state"] ) + options["noise"] = NoiseOptions.from_options(kwargs["noise"]) return cls(**options) @@ -635,6 +706,7 @@ def __init__(self, **kwargs): parameters=kwargs.pop("parameters"), visual=kwargs.pop("visual"), state=kwargs.pop("state"), + noise=kwargs.pop("noise"), ) self._nstates = 1 self._nparameters = 13 @@ -656,6 +728,7 @@ def from_options(cls, kwargs: Dict): options["state"] = LIDannerStateOptions.from_options( kwargs["state"] ) + options["noise"] = NoiseOptions.from_options(kwargs["noise"]) return cls(**options) @@ -736,6 +809,7 @@ def __init__(self, **kwargs): parameters=kwargs.pop("parameters"), visual=kwargs.pop("visual"), state=kwargs.pop("state"), + noise=kwargs.pop("noise"), ) self._nstates = 2 self._nparameters = 19 @@ -757,6 +831,9 @@ def from_options(cls, kwargs: Dict): options["state"] = LINaPDannerStateOptions.from_options( kwargs["state"] ) + options["noise"] = NoiseOptions.from_options( + kwargs["noise"] + ) return cls(**options) @@ -843,6 +920,7 @@ def __init__(self, **kwargs): parameters=kwargs.pop("parameters"), visual=kwargs.pop("visual"), state=kwargs.pop("state"), + noise=kwargs.pop("noise"), ) self._nstates = 2 self._nparameters = 5 From fb30b8847c0355895857e951fee833d238c363fb Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Mon, 25 Nov 2024 11:36:26 -0500 Subject: [PATCH 156/316] [SCRATCH][GUI] Added support for changes values in table --- scratch/test_gui.py | 79 ++++++++++++++++++++++++++++----------------- 1 file changed, 49 insertions(+), 30 deletions(-) diff --git a/scratch/test_gui.py b/scratch/test_gui.py index 4665a7d..68899db 100644 --- a/scratch/test_gui.py +++ b/scratch/test_gui.py @@ -52,9 +52,9 @@ def compute_phases(times, data): (times[phases_start], times[phases_start], times[phases_ends], times[phases_ends]) ).T) phases_ys.append(np.ones(np.shape(phases_xs[j]))*j) - if np.all(len(phases_start) > 3): - phases_ys[j][:, 1] += 1 - phases_ys[j][:, 2] += 1 + # if np.all(len(phases_start) > 3): + phases_ys[j][:, 1] += 1 + phases_ys[j][:, 2] += 1 return phases_xs, phases_ys @@ -123,11 +123,11 @@ def add_plot(iteration, data): "RF": imgui.IM_COL32(28, 107, 180, 255), "LF": imgui.IM_COL32(23, 163, 74, 255), "RH": imgui.IM_COL32(200, 38, 39, 255), - "LH": imgui.IM_COL32(0, 0, 0, 255), # imgui.IM_COL32(255, 252, 212, 255) + "LH": imgui.IM_COL32(255, 252, 212, 255), # imgui.IM_COL32(0, 0, 0, 255), "right_fore_RG_F": imgui.IM_COL32(28, 107, 180, 255), "left_fore_RG_F": imgui.IM_COL32(23, 163, 74, 255), "right_hind_RG_F": imgui.IM_COL32(200, 38, 39, 255), - "left_hind_RG_F": imgui.IM_COL32(0, 0, 0, 255), # imgui.IM_COL32(255, 252, 212, 255) + "left_hind_RG_F": imgui.IM_COL32(255, 252, 212, 255), # imgui.IM_COL32(0, 0, 0, 255), } with imgui_ctx.begin("States"): if implot.begin_subplots( @@ -167,23 +167,23 @@ def add_plot(iteration, data): implot.end_plot() if implot.begin_plot(""): implot.setup_axis_limits(implot.ImAxis_.x1, -1.0, 0.0) - implot.setup_axis_limits(implot.ImAxis_.y1, -2.0, 6.0) + implot.setup_axis_limits(implot.ImAxis_.y1, 0.0, 4.0) implot.setup_axis_limits_constraints(implot.ImAxis_.x1, -1.0, 0.0) - implot.setup_axis_limits_constraints(implot.ImAxis_.y1, -2.0, 4.0) + implot.setup_axis_limits_constraints(implot.ImAxis_.y1, 0.0, 4.0) implot.setup_axis(implot.ImAxis_.y1, flags=implot.AxisFlags_.invert) for j, limb in enumerate(("RF", "LF", "RH", "LH")): - if len(phases_xs[j]) > 3: - implot.push_style_color( - implot.Col_.fill, - colors[limb] - ) - implot.plot_shaded( - limb, - phases_xs[j].flatten(), - phases_ys[j].flatten(), - yref=j - ) - implot.pop_style_color() + # if len(phases_xs[j]) > 3: + implot.push_style_color( + implot.Col_.fill, + colors[limb] + ) + implot.plot_shaded( + limb, + phases_xs[j].flatten(), + phases_ys[j].flatten(), + yref=j + ) + implot.pop_style_color() implot.end_plot() implot.end_subplots() @@ -322,6 +322,7 @@ def draw_network(network_options, data, iteration, edges_x, edges_y): nodes = network_options.nodes + imgui.WindowFlags_ with imgui_ctx.begin("Full-Network"): if implot.begin_plot("vis", imgui.ImVec2((-1, -1))): radius = implot.plot_to_pixels(implot.Point((7.5, 7.5))) @@ -405,15 +406,31 @@ def draw_slider( return values -def draw_table(): +def draw_table(network_options, network_data): """ Draw table """ + flags = ( + imgui.TableFlags_.borders | imgui.TableFlags_.row_bg | imgui.TableFlags_.resizable | \ + imgui.TableFlags_.sortable + ) with imgui_ctx.begin("Table"): - if imgui.begin_table("table", 5): - for row in range(6): + edges = network_options.edges + nodes = network_options.nodes + n_edges = len(edges) + if imgui.begin_table("Edges", 3, flags): + weights = network_data.connectivity.weights + for col in ("Source", "Target", "Weight"): + imgui.table_setup_column(col) + imgui.table_headers_row() + for row in range(n_edges): imgui.table_next_row() - for column in range(5): - imgui.table_set_column_index(column) - imgui.text(f"Row {row} Column {column}") + imgui.table_set_column_index(0) + imgui.text(edges[row].source) + imgui.table_set_column_index(1) + imgui.text(edges[row].target) + imgui.table_set_column_index(2) + imgui.push_id(row) + _, weights[row] = imgui.input_float("##row", weights[row]) + imgui.pop_id() imgui.end_table() @@ -438,7 +455,7 @@ def main(): network_options = NetworkOptions.from_options(read_yaml(clargs.config_path)) network = PyNetwork.from_options(network_options) - network.setup_integrator(network_options.integration) + network.setup_integrator(network_options) # Integrate N_ITERATIONS = network_options.integration.n_iterations @@ -450,8 +467,8 @@ def main(): inputs_view = network.data.external_inputs.array drive_input = 0.0 - imgui.style_colors_light() - implot.style_colors_light() + imgui.style_colors_dark() + implot.style_colors_dark() edges_xy = np.array( [ @@ -476,6 +493,9 @@ def main(): _realtime = 0.1 imgui.get_io().config_flags |= imgui.ConfigFlags_.docking_enable + imgui.get_style().anti_aliased_lines = True + imgui.get_style().anti_aliased_lines_use_tex = True + imgui.get_style().anti_aliased_fill = True drive_input_indices = [ index @@ -516,12 +536,11 @@ def main(): _time_draw = time.time() fps = _realtime*1/(_time_draw-_time_draw_last)+(1-_realtime)*fps # print(imgui.get_io().delta_time, imgui.get_io().framerate) - time.sleep(1e-4) if not (iteration % 2): gui.new_frame() slider_values = draw_slider(label="d", name="Drive", values=slider_values) add_plot(buffer_iteration, network.data) - draw_table() + draw_table(network_options, network.data) draw_network(network_options, network.data, buffer_iteration, edges_x, edges_y) draw_muscle_activity(buffer_iteration, network.data) gui.render_frame() From b6268279287ed1604f0489c254d4e5bab0dbbaaa Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Tue, 26 Nov 2024 11:45:38 -0500 Subject: [PATCH 157/316] [NUMERIC] Removed timestep argument in SDE evaluate arguments --- farms_network/noise/ornstein_uhlenbeck.pxd | 4 ++-- farms_network/noise/ornstein_uhlenbeck.pyx | 12 ++++-------- farms_network/numeric/system.pxd | 8 ++------ farms_network/numeric/system.pyx | 8 ++------ 4 files changed, 10 insertions(+), 22 deletions(-) diff --git a/farms_network/noise/ornstein_uhlenbeck.pxd b/farms_network/noise/ornstein_uhlenbeck.pxd index 2f9a91b..710a6a3 100644 --- a/farms_network/noise/ornstein_uhlenbeck.pxd +++ b/farms_network/noise/ornstein_uhlenbeck.pxd @@ -20,5 +20,5 @@ cdef class OrnsteinUhlenbeck(SDESystem): unsigned int n_dim OrnsteinUhlenbeckParameters* parameters - cdef void evaluate_a(self, double time, double timestep, double[:] states, double[:] drift) noexcept - cdef void evaluate_b(self, double time, double timestep, double[:] states, double[:] diffusion) noexcept + cdef void evaluate_a(self, double time, double[:] states, double[:] drift) noexcept + cdef void evaluate_b(self, double time, double[:] states, double[:] diffusion) noexcept diff --git a/farms_network/noise/ornstein_uhlenbeck.pyx b/farms_network/noise/ornstein_uhlenbeck.pyx index 678d857..160acbd 100644 --- a/farms_network/noise/ornstein_uhlenbeck.pyx +++ b/farms_network/noise/ornstein_uhlenbeck.pyx @@ -96,9 +96,7 @@ cdef class OrnsteinUhlenbeck(SDESystem): super().__init__() self.initialize_parameters_from_options(noise_options) - cdef void evaluate_a( - self, double time, double timestep, double[:] states, double[:] drift - ) noexcept: + cdef void evaluate_a(self, double time, double[:] states, double[:] drift) noexcept: cdef unsigned int j cdef OrnsteinUhlenbeckParameters params = ( self.parameters[0] @@ -106,9 +104,7 @@ cdef class OrnsteinUhlenbeck(SDESystem): for j in range(self.n_dim): drift[j] = (params.mu[j]-states[j])/params.tau[j] - cdef void evaluate_b( - self, double time, double timestep, double[:] states, double[:] diffusion - ) noexcept: + cdef void evaluate_b(self, double time, double[:] states, double[:] diffusion) noexcept: cdef unsigned int j cdef OrnsteinUhlenbeckParameters params = ( self.parameters[0] @@ -119,11 +115,11 @@ cdef class OrnsteinUhlenbeck(SDESystem): diffusion[j] = (params.sigma[j]/cppsqrt(params.tau[j]))*noise def py_evaluate_a(self, time, states, drift): - self.evaluate_a(time, 0.0, states, drift) + self.evaluate_a(time, states, drift) return drift def py_evaluate_b(self, time, states, diffusion): - self.evaluate_b(time, 0.0, states, diffusion) + self.evaluate_b(time, states, diffusion) return diffusion def initialize_parameters_from_options(self, noise_options): diff --git a/farms_network/numeric/system.pxd b/farms_network/numeric/system.pxd index ca942d0..a6d3306 100644 --- a/farms_network/numeric/system.pxd +++ b/farms_network/numeric/system.pxd @@ -5,9 +5,5 @@ cdef class ODESystem: cdef class SDESystem: - cdef void evaluate_a( - self, double time, double timestep, double[:] states, double[:] drift - ) noexcept - cdef void evaluate_b( - self, double time, double timestep, double[:] states, double[:] diffusion - ) noexcept + cdef void evaluate_a(self, double time, double[:] states, double[:] drift) noexcept + cdef void evaluate_b(self, double time, double[:] states, double[:] diffusion) noexcept diff --git a/farms_network/numeric/system.pyx b/farms_network/numeric/system.pyx index 002ddc1..8a01b2a 100644 --- a/farms_network/numeric/system.pyx +++ b/farms_network/numeric/system.pyx @@ -20,14 +20,10 @@ cdef class SDESystem: """ Initialize """ ... - cdef void evaluate_a( - self, double time, double timestep, double[:] states, double[:] drift - ) noexcept: + cdef void evaluate_a(self, double time, double[:] states, double[:] drift) noexcept: """ a(Xt,t) """ ... - cdef void evaluate_b( - self, double time, double timestep, double[:] states, double[:] diffusion - ) noexcept: + cdef void evaluate_b(self, double time, double[:] states, double[:] diffusion) noexcept: """ b(Xt,t) """ ... From 7d35bb3fc20a4f426ce338a5fe2b4753a1d71f53 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Tue, 26 Nov 2024 11:46:44 -0500 Subject: [PATCH 158/316] [NOISE] Fixed ornstein uhlenbeck computation --- farms_network/noise/ornstein_uhlenbeck.pyx | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/farms_network/noise/ornstein_uhlenbeck.pyx b/farms_network/noise/ornstein_uhlenbeck.pyx index 160acbd..ceeff4f 100644 --- a/farms_network/noise/ornstein_uhlenbeck.pyx +++ b/farms_network/noise/ornstein_uhlenbeck.pyx @@ -112,7 +112,7 @@ cdef class OrnsteinUhlenbeck(SDESystem): cdef double noise for j in range(self.n_dim): noise = params.distribution[j](params.random_generator[j]) - diffusion[j] = (params.sigma[j]/cppsqrt(params.tau[j]))*noise + diffusion[j] = params.sigma[j]*cppsqrt(2.0/params.tau[j])*noise def py_evaluate_a(self, time, states, drift): self.evaluate_a(time, states, drift) @@ -123,13 +123,12 @@ cdef class OrnsteinUhlenbeck(SDESystem): return diffusion def initialize_parameters_from_options(self, noise_options): - """ """ + """ Initialize the parameters from noise options """ for index in range(self.n_dim): noise_option = noise_options[index] self.parameters.mu[index] = noise_option.mu self.parameters.sigma[index] = noise_option.sigma self.parameters.tau[index] = noise_option.tau - self.parameters.random_generator[index] = mt19937(12131) - self.parameters.distribution[index] = normal_distribution[double]( - self.parameters.mu[index], self.parameters.sigma[index] - ) + self.parameters.random_generator[index] = mt19937(self.parameters.seed[index]) + # The distribution should always be mean=0.0 and std=1.0 + self.parameters.distribution[index] = normal_distribution[double](0.0, 1.0) From f3d8b145224becfcdd48dcd7f576de279902b8ee Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Tue, 26 Nov 2024 11:52:34 -0500 Subject: [PATCH 159/316] [SCRATCH] Added test script for ornstein noise model --- scratch/test_numeric.py | 49 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 scratch/test_numeric.py diff --git a/scratch/test_numeric.py b/scratch/test_numeric.py new file mode 100644 index 0000000..d8a43de --- /dev/null +++ b/scratch/test_numeric.py @@ -0,0 +1,49 @@ +import matplotlib.pyplot as plt +import numpy as np +from farms_core.io.yaml import read_yaml +from farms_network.core import options +from farms_network.noise.ornstein_uhlenbeck import OrnsteinUhlenbeck + +network_options = options.NetworkOptions.from_options( + read_yaml("/tmp/network_options.yaml") +) + +n_dim = 0 +for node in network_options.nodes: + if node.noise is not None: + if (node.noise.model == "ornstein_uhlenbeck") and node.noise.is_stochastic: + n_dim += 1 + + + +timestep = 1e-3 +tau = 1.0 +sigma = 1# np.sqrt(2.0) + +noise_options = [network_options.nodes[1].noise,] + +oo = OrnsteinUhlenbeck(noise_options) + +times = np.linspace(0, 100000*timestep, int(10000)) +print(np.sqrt(2.0*timestep)) + +for initial, mean in zip((10.0, 0.0, -10.0, 0.0), (0.0, 0.0, 0.0, -10.0)): + states = np.zeros((len(times), 1)) + states[0, 0] = initial + noise_options[0].seed = np.random.randint(low=0, high=10000) + noise_options[0].mu = mean + noise_options[0].tau = tau + noise_options[0].sigma = sigma + print(noise_options) + oo = OrnsteinUhlenbeck(noise_options) + drift = np.zeros((len(times), 1)) + diffusion = np.zeros((len(times), 1)) + for index, time in enumerate(times[:-1]): + drift[index, :] = oo.py_evaluate_a(time, states[index, :], drift[index, :]) + diffusion[index, :] = oo.py_evaluate_b(time, states[index, :], diffusion[index, :]) + states[index+1, :] = states[index, :] + drift[index, :]*timestep + np.sqrt(timestep)*diffusion[index, :] + print(np.std(states[500:, 0]), np.mean(states[500:, 0])) + plt.plot(times, states[:, 0]) +plt.xlim([0, times[-1]]) +plt.ylim([-15.0, 15.0]) +plt.show() From f81274b37cf236c112d9c0c0e9e4ab66d80de9a4 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Tue, 26 Nov 2024 11:53:15 -0500 Subject: [PATCH 160/316] [NUMERIC] Fixed Euler Maruyama Solver integration equation --- farms_network/numeric/integrators_cy.pxd | 1 - farms_network/numeric/integrators_cy.pyx | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/farms_network/numeric/integrators_cy.pxd b/farms_network/numeric/integrators_cy.pxd index 8c47196..dfb3cf8 100644 --- a/farms_network/numeric/integrators_cy.pxd +++ b/farms_network/numeric/integrators_cy.pxd @@ -2,7 +2,6 @@ from farms_core.array.array_cy cimport DoubleArray1D from libc.math cimport sqrt as csqrt -from libcpp.random cimport mt19937, normal_distribution from .system cimport ODESystem, SDESystem diff --git a/farms_network/numeric/integrators_cy.pyx b/farms_network/numeric/integrators_cy.pyx index 05cf221..3839ef8 100644 --- a/farms_network/numeric/integrators_cy.pyx +++ b/farms_network/numeric/integrators_cy.pyx @@ -88,7 +88,7 @@ cdef class EulerMaruyamaSolver: cdef double[:] drift = self.drift.array cdef double[:] diffusion = self.diffusion.array - sys.evaluate_a(time, self.dt, state, drift) - sys.evaluate_b(time, self.dt, state, diffusion) + sys.evaluate_a(time, state, drift) + sys.evaluate_b(time, state, diffusion) for i in range(self.dim): - state[i] += drift[i]*self.dt + csqrt((2.0*self.dt))*diffusion[i] + state[i] += drift[i]*self.dt + csqrt(self.dt)*diffusion[i] From 01bd3a43b0fd01f645ebbbc7c7682f4f311bc608 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Tue, 26 Nov 2024 11:54:26 -0500 Subject: [PATCH 161/316] [CORE][NOISE] Added basic support random number seed --- farms_network/core/options.py | 10 +++++++--- farms_network/noise/ornstein_uhlenbeck.pxd | 1 + farms_network/noise/ornstein_uhlenbeck.pyx | 8 ++++++++ 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/farms_network/core/options.py b/farms_network/core/options.py index 7702535..c755808 100644 --- a/farms_network/core/options.py +++ b/farms_network/core/options.py @@ -1,10 +1,12 @@ """ Options to configure the neural and network models """ +import time from enum import IntEnum from typing import Dict, Iterable, List, Self, Union import matplotlib.pyplot as plt import networkx as nx +import numpy as np from farms_core import pylog from farms_core.options import Options @@ -25,6 +27,7 @@ def __init__(self, **kwargs): self.graph: dict = kwargs.pop("graph", {"name": ""}) self.units = kwargs.pop("units", None) self.logs: NetworkLogOptions = kwargs.pop("logs") + self.random_seed: int = kwargs.pop("random_seed", time.time_ns()) self.integration = kwargs.pop( "integration", IntegrationOptions.defaults() @@ -408,6 +411,7 @@ def __init__(self, **kwargs): self.model = kwargs.pop("model", None) assert self.model.lower() in NoiseOptions.NOISE_MODELS self.is_stochastic = kwargs.pop("is_stochastic") + self.seed = kwargs.pop("seed", None) @classmethod def from_options(cls, kwargs: Dict): @@ -431,7 +435,7 @@ def __init__(self, **kwargs): self.mu: float = kwargs.pop("mu") self.sigma: float = kwargs.pop("sigma") self.tau: float = kwargs.pop("tau") - self.timestep: float = kwargs.pop("timestep") + self.seed: float = kwargs.pop("seed", None) @classmethod def from_options(cls, kwargs: Dict): @@ -440,7 +444,7 @@ def from_options(cls, kwargs: Dict): options["mu"] = kwargs.pop("mu") options["sigma"] = kwargs.pop("sigma") options["tau"] = kwargs.pop("tau") - options["timestep"] = kwargs.pop("timestep") + options["seed"] = kwargs.pop("seed", None) return cls(**options) @classmethod @@ -450,7 +454,7 @@ def defaults(cls, **kwargs: Dict): options["mu"] = kwargs.pop("mu", 0.0) options["sigma"] = kwargs.pop("sigma", 0.005) options["tau"] = kwargs.pop("tau", 10.0) - options["timestep"] = kwargs.pop("timestep", 1/2.0) + options["seed"] = kwargs.pop("seed", None) return cls(**options) diff --git a/farms_network/noise/ornstein_uhlenbeck.pxd b/farms_network/noise/ornstein_uhlenbeck.pxd index 710a6a3..29e0ce4 100644 --- a/farms_network/noise/ornstein_uhlenbeck.pxd +++ b/farms_network/noise/ornstein_uhlenbeck.pxd @@ -9,6 +9,7 @@ cdef struct OrnsteinUhlenbeckParameters: double* mu double* sigma double* tau + unsigned int* seed mt19937* random_generator normal_distribution[double]* distribution diff --git a/farms_network/noise/ornstein_uhlenbeck.pyx b/farms_network/noise/ornstein_uhlenbeck.pyx index ceeff4f..f66cf1d 100644 --- a/farms_network/noise/ornstein_uhlenbeck.pyx +++ b/farms_network/noise/ornstein_uhlenbeck.pyx @@ -25,6 +25,8 @@ from libcpp.cmath cimport sqrt as cppsqrt from typing import List +import numpy as np + from ..core.options import OrnsteinUhlenbeckOptions @@ -62,6 +64,12 @@ cdef class OrnsteinUhlenbeck(SDESystem): "Failed to allocate memory for OrnsteinUhlenbeck parameter TAU" ) + self.parameters.seed = malloc(self.n_dim*sizeof(unsigned int)) + if self.parameters.seed is NULL: + raise MemoryError( + "Failed to allocate memory for OrnsteinUhlenbeck parameter SEED" + ) + self.parameters.random_generator = malloc(self.n_dim*sizeof(mt19937)) if self.parameters.random_generator is NULL: raise MemoryError( From 1684eccb6564703fe638c4d71abc0aa2672601a9 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Tue, 26 Nov 2024 14:12:01 -0500 Subject: [PATCH 162/316] [CORE][WIP] Changed network logging function signature --- farms_network/core/network.pxd | 7 ++++--- farms_network/core/network.pyx | 26 ++++++++++---------------- 2 files changed, 14 insertions(+), 19 deletions(-) diff --git a/farms_network/core/network.pxd b/farms_network/core/network.pxd index 33ec2b8..97aa2a5 100644 --- a/farms_network/core/network.pxd +++ b/farms_network/core/network.pxd @@ -1,7 +1,7 @@ cimport numpy as cnp -from ..numeric.integrators_cy cimport RK4Solver -from ..numeric.system cimport ODESystem +from ..numeric.integrators_cy cimport EulerMaruyamaSolver, RK4Solver +from ..numeric.system cimport ODESystem, SDESystem from .data_cy cimport NetworkDataCy, NodeDataCy from .edge cimport Edge from .node cimport Node @@ -29,6 +29,7 @@ cdef class PyNetwork(ODESystem): public list pynodes public list pyedges public NetworkDataCy data + NodeDataCy[:] nodes_data double[:] __tmp_node_outputs unsigned int iteration @@ -45,4 +46,4 @@ cdef class PyNetwork(ODESystem): cpdef void step(self) noexcept @staticmethod - cdef void logging(unsigned int iteration, NetworkDataCy data, Network* network) noexcept + cdef void logging(unsigned int iteration, NetworkDataCy data, NodeDataCy[:] nodes_data, Network* network) noexcept diff --git a/farms_network/core/network.pyx b/farms_network/core/network.pyx index 15452d2..ac6592a 100644 --- a/farms_network/core/network.pyx +++ b/farms_network/core/network.pyx @@ -26,12 +26,10 @@ from libc.stdlib cimport free, malloc from libc.string cimport strdup from ..models.factory import EdgeFactory, NodeFactory - -from ..models.li_danner cimport LIDannerNodeParameters - +from ..noise.ornstein_uhlenbeck import OrnsteinUhlenbeck from .data import NetworkData, NetworkStates -from .data_cy cimport NetworkDataCy, NetworkStatesCy, NetworkConnectivityCy +from .data_cy cimport NetworkConnectivityCy, NetworkDataCy, NetworkStatesCy from .edge cimport Edge, PyEdge from .node cimport Node, PyNode @@ -118,6 +116,7 @@ cdef class PyNetwork(ODESystem): super().__init__() self.data = NetworkData.from_options(network_options) + self.nodes_data = self.data.nodes self.pynodes = [] self.pyedges = [] self.nodes_output_data = [] @@ -226,29 +225,24 @@ cdef class PyNetwork(ODESystem): self.integrator.step( self, self.iteration*self.timestep, self.data.states.array ) - PyNetwork.logging(self.iteration, self.data, self.network) + PyNetwork.logging((self.iteration%self.buffer_size), self.data, self.nodes_data, self.network) @staticmethod - cdef void logging(int iteration, NetworkDataCy data, Network* network) noexcept: + cdef void logging(int iteration, NetworkDataCy data, NodeDataCy[:] nodes_data, Network* network) noexcept: """ Log network data """ - cdef int j, nnodes - nnodes = network.nnodes - # cdef double[:] states = data.states.array + cdef unsigned int nnodes = network.nnodes + cdef unsigned int j cdef double* states_ptr = &data.states.array[0] - cdef unsigned int[:] state_indices = data.states.indices - cdef int state_idx, start_idx, end_idx, state_iteration - - # cdef double[:] derivatives = data.derivatives.array cdef double* derivatives_ptr = &data.derivatives.array[0] - + cdef unsigned int[:] state_indices = data.states.indices cdef double[:] outputs = data.outputs.array - cdef double[:] external_inputs = data.external_inputs.array cdef NodeDataCy node_data + cdef int state_idx, start_idx, end_idx, state_iteration for j in range(nnodes): # Log states - node_data = data.nodes[j] + node_data = nodes_data[j] start_idx = state_indices[j] end_idx = state_indices[j+1] state_iteration = 0 From 5e2b8111faa413f1d97f235ed138e82f7a22f599 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Tue, 26 Nov 2024 14:13:22 -0500 Subject: [PATCH 163/316] [EX][MOUSE] Added noise options to network generation --- examples/mouse/components.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/examples/mouse/components.py b/examples/mouse/components.py index 74a6345..8521f32 100644 --- a/examples/mouse/components.py +++ b/examples/mouse/components.py @@ -172,18 +172,22 @@ def create_node( if node_type == "LINaPDanner": state_options = options.LINaPDannerStateOptions.from_kwargs(**states) parameters = options.LINaPDannerParameterOptions.defaults(**parameters) + noise = options.OrnsteinUhlenbeckOptions.defaults() node_options_class = options.LINaPDannerNodeOptions elif node_type == "LIDanner": state_options = options.LIDannerStateOptions.from_kwargs(**states) parameters = options.LIDannerParameterOptions.defaults(**parameters) + noise = options.OrnsteinUhlenbeckOptions.defaults() node_options_class = options.LIDannerNodeOptions elif node_type == "Linear": state_options = None parameters = options.LinearParameterOptions.defaults(**parameters) + noise = None node_options_class = options.LinearNodeOptions elif node_type == "ExternalRelay": state_options = None parameters = options.NodeParameterOptions() + noise = None visual_options.radius = 0.0 node_options_class = options.ExternalRelayNodeOptions else: @@ -195,6 +199,7 @@ def create_node( parameters=parameters, visual=visual_options, state=state_options, + noise=noise, ) From 0d8cb4caa576fb412dc59df4d407a454d0884366 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Sun, 1 Dec 2024 10:44:30 -0500 Subject: [PATCH 164/316] [DUCKS] Removed contents from li danner page --- ducks/source/models/li_danner.rst | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/ducks/source/models/li_danner.rst b/ducks/source/models/li_danner.rst index e7397dc..db06627 100644 --- a/ducks/source/models/li_danner.rst +++ b/ducks/source/models/li_danner.rst @@ -1,9 +1,8 @@ -Models ------- +Leaky Integrator Danner +======================= .. toctree:: - :maxdepth: 1 - :caption: Contents: + :hidden: .. automodule:: farms_network.models.li_danner :members: From d0d28382a8ea5a7303825d4ceaac4537ad59c02c Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Sun, 1 Dec 2024 11:19:00 -0500 Subject: [PATCH 165/316] [NUMERIC] Removed distutils langauge c++ directive No cpp code is used here so it has been removed --- farms_network/numeric/integrators_cy.pxd | 2 -- farms_network/numeric/integrators_cy.pyx | 2 -- 2 files changed, 4 deletions(-) diff --git a/farms_network/numeric/integrators_cy.pxd b/farms_network/numeric/integrators_cy.pxd index dfb3cf8..c4c98d6 100644 --- a/farms_network/numeric/integrators_cy.pxd +++ b/farms_network/numeric/integrators_cy.pxd @@ -1,5 +1,3 @@ -# distutils: language = c++ - from farms_core.array.array_cy cimport DoubleArray1D from libc.math cimport sqrt as csqrt diff --git a/farms_network/numeric/integrators_cy.pyx b/farms_network/numeric/integrators_cy.pyx index 3839ef8..24f350f 100644 --- a/farms_network/numeric/integrators_cy.pyx +++ b/farms_network/numeric/integrators_cy.pyx @@ -1,5 +1,3 @@ -# distutils: language = c++ - import numpy as np from ..core.options import IntegrationOptions From 3c38674a7e1fec630535ce3a04f9a1e75a267127 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Sun, 1 Dec 2024 16:21:47 -0500 Subject: [PATCH 166/316] [NOISE] Added wrappers for cpp random class in ornstein ulhenbeck process --- farms_network/noise/ornstein_uhlenbeck.pxd | 48 ++++++++++++++++++--- farms_network/noise/ornstein_uhlenbeck.pyx | 50 +++++++--------------- 2 files changed, 59 insertions(+), 39 deletions(-) diff --git a/farms_network/noise/ornstein_uhlenbeck.pxd b/farms_network/noise/ornstein_uhlenbeck.pxd index 29e0ce4..41a4b6e 100644 --- a/farms_network/noise/ornstein_uhlenbeck.pxd +++ b/farms_network/noise/ornstein_uhlenbeck.pxd @@ -1,17 +1,53 @@ # distutils: language = c++ -from ..numeric.system cimport SDESystem from libc.math cimport sqrt as csqrt -from libcpp.random cimport mt19937, normal_distribution +from libc.stdint cimport uint_fast32_t, uint_fast64_t + +from ..numeric.system cimport SDESystem + + +cdef extern from "" namespace "std" nogil: + cdef cppclass random_device: + ctypedef uint_fast32_t result_type + random_device() + result_type operator()() + + cdef cppclass mt19937: + ctypedef uint_fast32_t result_type + mt19937() + mt19937(result_type seed) + result_type operator()() + result_type min() + result_type max() + void discard(size_t z) + void seed(result_type seed) + + cdef cppclass mt19937_64: + ctypedef uint_fast64_t result_type + + mt19937_64() + mt19937_64(result_type seed) + result_type operator()() + result_type min() + result_type max() + void discard(size_t z) + void seed(result_type seed) + + cdef cppclass normal_distribution[T]: + ctypedef T result_type + normal_distribution() + normal_distribution(result_type, result_type) + result_type operator()[Generator](Generator&) + result_type min() + result_type max() cdef struct OrnsteinUhlenbeckParameters: double* mu double* sigma double* tau - unsigned int* seed - mt19937* random_generator - normal_distribution[double]* distribution + mt19937_64 random_generator + normal_distribution[double] distribution cdef class OrnsteinUhlenbeck(SDESystem): @@ -20,6 +56,8 @@ cdef class OrnsteinUhlenbeck(SDESystem): double timestep unsigned int n_dim OrnsteinUhlenbeckParameters* parameters + normal_distribution[double] distribution + mt19937_64 random_generator cdef void evaluate_a(self, double time, double[:] states, double[:] drift) noexcept cdef void evaluate_b(self, double time, double[:] states, double[:] diffusion) noexcept diff --git a/farms_network/noise/ornstein_uhlenbeck.pyx b/farms_network/noise/ornstein_uhlenbeck.pyx index f66cf1d..6b5469a 100644 --- a/farms_network/noise/ornstein_uhlenbeck.pyx +++ b/farms_network/noise/ornstein_uhlenbeck.pyx @@ -20,8 +20,10 @@ limitations under the License. """ +from libc.math cimport sqrt as csqrt from libc.stdlib cimport free, malloc -from libcpp.cmath cimport sqrt as cppsqrt + +from .ornstein_uhlenbeck cimport mt19937, mt19937_64, normal_distribution from typing import List @@ -33,7 +35,7 @@ from ..core.options import OrnsteinUhlenbeckOptions cdef class OrnsteinUhlenbeck(SDESystem): """ Ornstein Uhlenheck parameters """ - def __cinit__(self, noise_options: List[OrnsteinUhlenbeckOptions]): + def __cinit__(self, noise_options: List[OrnsteinUhlenbeckOptions], random_seed: int=None): """ C initialization for manual memory allocation """ self.n_dim = len(noise_options) @@ -64,26 +66,6 @@ cdef class OrnsteinUhlenbeck(SDESystem): "Failed to allocate memory for OrnsteinUhlenbeck parameter TAU" ) - self.parameters.seed = malloc(self.n_dim*sizeof(unsigned int)) - if self.parameters.seed is NULL: - raise MemoryError( - "Failed to allocate memory for OrnsteinUhlenbeck parameter SEED" - ) - - self.parameters.random_generator = malloc(self.n_dim*sizeof(mt19937)) - if self.parameters.random_generator is NULL: - raise MemoryError( - "Failed to allocate memory for OrnsteinUhlenbeck parameter RANDOM GENERATOR" - ) - - self.parameters.distribution = malloc( - self.n_dim*sizeof(normal_distribution[double]) - ) - if self.parameters.distribution is NULL: - raise MemoryError( - "Failed to allocate memory for OrnsteinUhlenbeck parameter Distribution" - ) - def __dealloc__(self): """ Deallocate any manual memory as part of clean up """ @@ -93,15 +75,13 @@ cdef class OrnsteinUhlenbeck(SDESystem): free(self.parameters.sigma) if self.parameters.tau is not NULL: free(self.parameters.tau) - if self.parameters.random_generator is not NULL: - free(self.parameters.random_generator) - if self.parameters.distribution is not NULL: - free(self.parameters.distribution) if self.parameters is not NULL: free(self.parameters) - def __init__(self, noise_options: List[OrnsteinUhlenbeckOptions]): + def __init__(self, noise_options: List[OrnsteinUhlenbeckOptions], random_seed: int=None): super().__init__() + # if random_seed is None: + # random_seed = np.random.rand self.initialize_parameters_from_options(noise_options) cdef void evaluate_a(self, double time, double[:] states, double[:] drift) noexcept: @@ -117,10 +97,12 @@ cdef class OrnsteinUhlenbeck(SDESystem): cdef OrnsteinUhlenbeckParameters params = ( self.parameters[0] ) - cdef double noise + cdef normal_distribution[double] distribution = self.distribution + cdef mt19937_64 random_generator = self.random_generator for j in range(self.n_dim): - noise = params.distribution[j](params.random_generator[j]) - diffusion[j] = params.sigma[j]*cppsqrt(2.0/params.tau[j])*noise + diffusion[j] = params.sigma[j]*csqrt(2.0/params.tau[j])*( + distribution(random_generator) + ) def py_evaluate_a(self, time, states, drift): self.evaluate_a(time, states, drift) @@ -130,13 +112,13 @@ cdef class OrnsteinUhlenbeck(SDESystem): self.evaluate_b(time, states, diffusion) return diffusion - def initialize_parameters_from_options(self, noise_options): + def initialize_parameters_from_options(self, noise_options, random_seed=123124): """ Initialize the parameters from noise options """ for index in range(self.n_dim): noise_option = noise_options[index] self.parameters.mu[index] = noise_option.mu self.parameters.sigma[index] = noise_option.sigma self.parameters.tau[index] = noise_option.tau - self.parameters.random_generator[index] = mt19937(self.parameters.seed[index]) - # The distribution should always be mean=0.0 and std=1.0 - self.parameters.distribution[index] = normal_distribution[double](0.0, 1.0) + self.random_generator = mt19937_64(random_seed) + # The distribution should always be mean=0.0 and std=1.0 + self.distribution = normal_distribution[double](0.0, 1.0) From cbdff9eb790f9fe56a199d3b16fc1895b5566b37 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Sun, 1 Dec 2024 16:49:52 -0500 Subject: [PATCH 167/316] [CORE][MODELS] Added feature to support noise in nodes --- farms_network/core/data.py | 28 +++--- farms_network/core/data_cy.pxd | 1 + farms_network/core/data_cy.pyx | 2 + farms_network/core/network.pxd | 10 +-- farms_network/core/network.pyx | 117 ++++++++++++++++++------- farms_network/core/node.pxd | 2 + farms_network/core/node.pyx | 6 +- farms_network/models/izhikevich.pxd | 1 + farms_network/models/izhikevich.pyx | 21 ++--- farms_network/models/li_danner.pxd | 1 + farms_network/models/li_danner.pyx | 5 +- farms_network/models/li_nap_danner.pxd | 1 + farms_network/models/li_nap_danner.pyx | 3 +- farms_network/models/linear.pxd | 2 +- farms_network/models/linear.pyx | 2 +- farms_network/models/oscillator.pxd | 1 + farms_network/models/oscillator.pyx | 1 + 17 files changed, 139 insertions(+), 65 deletions(-) diff --git a/farms_network/core/data.py b/farms_network/core/data.py index 5fb66cb..b21261b 100644 --- a/farms_network/core/data.py +++ b/farms_network/core/data.py @@ -64,7 +64,6 @@ def __init__( self.connectivity = connectivity self.outputs = outputs self.external_inputs = external_inputs - self.noise = noise self.nodes: np.ndarray[NodeDataCy] = nodes @@ -157,7 +156,7 @@ def from_options(cls, network_options: NetworkOptions): nstates += node._nstates indices.append(nstates) return cls( - array=np.array(np.zeros((nstates,)), dtype=np.double), + array=np.array(np.zeros((nstates,)), dtype=NPDTYPE), indices=np.array(indices) ) @@ -215,9 +214,9 @@ def from_options(cls, network_options: NetworkOptions): sources[indices[index]:indices[index+1]] = node_sources weights[indices[index]:indices[index+1]] = node_weights return cls( - sources=np.array(sources, dtype=np.uintc), - weights=np.array(weights, dtype=np.double), - indices=np.array(indices, dtype=np.uintc) + sources=np.array(sources, dtype=NPUITYPE), + weights=np.array(weights, dtype=NPDTYPE), + indices=np.array(indices, dtype=NPUITYPE) ) def to_dict(self, iteration: int = None) -> Dict: @@ -232,8 +231,8 @@ def to_dict(self, iteration: int = None) -> Dict: class NetworkNoise(NetworkNoiseCy): """ Data for network noise modeling """ - def __init__(self, states, drift, diffusion, outputs): - super().__init__(states, drift, diffusion, outputs) + def __init__(self, states, indices,drift, diffusion, outputs): + super().__init__(states, indices, drift, diffusion, outputs) @classmethod def from_options(cls, network_options: NetworkOptions): @@ -242,9 +241,11 @@ def from_options(cls, network_options: NetworkOptions): n_noise_states = 0 n_nodes = len(nodes) + indices = [] for index, node in enumerate(nodes): if node.noise and node.noise.is_stochastic: n_noise_states += 1 + indices.append(index) return cls( states=np.full( @@ -252,6 +253,10 @@ def from_options(cls, network_options: NetworkOptions): fill_value=0.0, dtype=NPDTYPE, ), + indices=np.array( + indices, + dtype=NPUITYPE, + ), drift=np.full( shape=n_noise_states, fill_value=0.0, @@ -273,6 +278,7 @@ def to_dict(self, iteration: int = None) -> Dict: """Convert data to dictionary""" return { 'states': to_array(self.states), + 'indices': to_array(self.indices), 'drift': to_array(self.drift), 'diffusion': to_array(self.diffusion), 'outputs': to_array(self.outputs), @@ -336,14 +342,14 @@ def from_options(cls, options: NodeOptions, buffer_size: int): array = np.full( shape=[buffer_size, nstates], fill_value=0, - dtype=np.double, + dtype=NPDTYPE, ) else: names = [] array = np.full( shape=[buffer_size, 0], fill_value=0, - dtype=np.double, + dtype=NPDTYPE, ) return cls(array=array, names=names) @@ -367,7 +373,7 @@ def from_options(cls, options: NodeOptions, buffer_size: int): array = np.full( shape=buffer_size, fill_value=0, - dtype=np.double, + dtype=NPDTYPE, ) return cls(array=array) @@ -384,7 +390,7 @@ def from_options(cls, options: NodeOptions, buffer_size: int): array = np.full( shape=buffer_size, fill_value=0, - dtype=np.double, + dtype=NPDTYPE, ) return cls(array=array) diff --git a/farms_network/core/data_cy.pxd b/farms_network/core/data_cy.pxd index d6174f4..cc3b24d 100644 --- a/farms_network/core/data_cy.pxd +++ b/farms_network/core/data_cy.pxd @@ -57,6 +57,7 @@ cdef class NetworkNoiseCy: cdef: public DTYPEv1 states + public UITYPEv1 indices public DTYPEv1 drift public DTYPEv1 diffusion public DTYPEv1 outputs diff --git a/farms_network/core/data_cy.pyx b/farms_network/core/data_cy.pyx index a39cad7..f7e9d70 100644 --- a/farms_network/core/data_cy.pyx +++ b/farms_network/core/data_cy.pyx @@ -69,12 +69,14 @@ cdef class NetworkNoiseCy: def __init__( self, states: NDArray[(Any,), np.double], + indices: NDArray[(Any,), np.uintc], drift: NDArray[(Any,), np.double], diffusion: NDArray[(Any,), np.double], outputs: NDArray[(Any,), np.double], ): super().__init__() self.states = np.array(states, dtype=np.double) + self.indices = np.array(indices, dtype=np.uintc) self.drift = np.array(drift, dtype=np.double) self.diffusion = np.array(diffusion, dtype=np.double) self.outputs = np.array(outputs, dtype=np.double) diff --git a/farms_network/core/network.pxd b/farms_network/core/network.pxd index 97aa2a5..024283f 100644 --- a/farms_network/core/network.pxd +++ b/farms_network/core/network.pxd @@ -37,13 +37,13 @@ cdef class PyNetwork(ODESystem): unsigned int buffer_size double timestep - public RK4Solver integrator + public RK4Solver ode_integrator + public EulerMaruyamaSolver sde_integrator + + SDESystem sde_system list nodes_output_data # cpdef void step(self) cdef void evaluate(self, double time, double[:] states, double[:] derivatives) noexcept - cpdef void step(self) noexcept - - @staticmethod - cdef void logging(unsigned int iteration, NetworkDataCy data, NodeDataCy[:] nodes_data, Network* network) noexcept + cpdef void step(self) diff --git a/farms_network/core/network.pyx b/farms_network/core/network.pyx index ac6592a..45778df 100644 --- a/farms_network/core/network.pyx +++ b/farms_network/core/network.pyx @@ -29,7 +29,8 @@ from ..models.factory import EdgeFactory, NodeFactory from ..noise.ornstein_uhlenbeck import OrnsteinUhlenbeck from .data import NetworkData, NetworkStates -from .data_cy cimport NetworkConnectivityCy, NetworkDataCy, NetworkStatesCy +from .data_cy cimport (NetworkConnectivityCy, NetworkDataCy, NetworkNoiseCy, + NetworkStatesCy) from .edge cimport Edge, PyEdge from .node cimport Node, PyNode @@ -60,6 +61,8 @@ cdef inline void ode( cdef double* external_input = &data.external_inputs.array[0] cdef double* outputs = &data.outputs.array[0] + cdef double* noise = &data.noise.outputs[0] + cdef unsigned int* input_neurons = &data.connectivity.sources[0] cdef double* weights = &data.connectivity.weights[0] cdef unsigned int* input_neurons_indices = &data.connectivity.indices[0] @@ -77,6 +80,7 @@ cdef inline void ode( outputs, input_neurons + input_neurons_indices[j], weights + input_neurons_indices[j], + noise[j], nodes[j], edges + input_neurons_indices[j], ) @@ -92,6 +96,49 @@ cdef inline void ode( ) +cdef inline void logger( + int iteration, + NetworkDataCy data, + NodeDataCy[:] nodes_data, + Network* network +) noexcept: + cdef unsigned int nnodes = network.nnodes + cdef unsigned int j + cdef double* states_ptr = &data.states.array[0] + cdef double* derivatives_ptr = &data.derivatives.array[0] + cdef unsigned int[:] state_indices = data.states.indices + cdef double[:] outputs = data.outputs.array + cdef double* outputs_ptr = &data.outputs.array[0] + cdef double[:] external_inputs = data.external_inputs.array + cdef NodeDataCy node_data + cdef double[:] node_states + cdef int state_idx, start_idx, end_idx, state_iteration + for j in range(nnodes): + # Log states + start_idx = state_indices[j] + end_idx = state_indices[j+1] + state_iteration = 0 + node_states = nodes_data[j].states.array[iteration] + for state_idx in range(start_idx, end_idx): + node_states[state_iteration] = states_ptr[state_idx] + # nodes_data[j].derivatives.array[iteration, state_iteration] = derivatives_ptr[state_idx] + state_iteration += 1 + nodes_data[j].output.array[iteration] = outputs_ptr[j] + nodes_data[j].external_input.array[iteration] = external_inputs[j] + + +cdef inline void _noise_states_to_output( + double[:] states, + unsigned int[:] indices, + double[:] outputs, +) noexcept: + """ Copy noise states data to noise outputs """ + cdef int n_indices = indices.shape[0] + cdef int index + for index in range(n_indices): + outputs[indices[index]] = states[index] + + cdef class PyNetwork(ODESystem): """ Python interface to Network ODE """ @@ -129,6 +176,10 @@ cdef class PyNetwork(ODESystem): self.iteration: int = 0 self.buffer_size: int = network_options.logs.buffer_size + # Set the seed for random number generation + random_seed = network_options.random_seed + # np.random.seed(random_seed) + def __dealloc__(self): """ Deallocate any manual memory as part of clean up """ if self.network.nodes is not NULL: @@ -145,10 +196,21 @@ cdef class PyNetwork(ODESystem): """ Return NetworkOptions from network """ return self.options - def setup_integrator(self, options: IntegrationOptions): + def setup_integrator(self, network_options: NetworkOptions): """ Setup integrator for neural network """ - cdef double timestep = options.timestep - self.integrator = RK4Solver(self.network.nstates, timestep) + # Setup ODE numerical integrator + integration_options = network_options.integration + cdef double timestep = integration_options.timestep + self.ode_integrator = RK4Solver(self.network.nstates, timestep) + # Setup SDE numerical integrator for noise models if any + noise_options = [] + for node in network_options.nodes: + if node.noise is not None: + if node.noise.is_stochastic: + noise_options.append(node.noise) + + self.sde_system = OrnsteinUhlenbeck(noise_options) + self.sde_integrator = EulerMaruyamaSolver(len(noise_options), timestep) def setup_network(self, options: NetworkOptions, data: NetworkData): """ Setup network """ @@ -219,43 +281,30 @@ cdef class PyNetwork(ODESystem): """ Number of states in the network """ return self.network.nstates - cpdef void step(self) noexcept: + cpdef void step(self): """ Step the network state """ + cdef NetworkDataCy data = self.data self.iteration += 1 - self.integrator.step( - self, self.iteration*self.timestep, self.data.states.array + self.ode_integrator.step( + self, (self.iteration%self.buffer_size)*self.timestep, data.states.array ) - PyNetwork.logging((self.iteration%self.buffer_size), self.data, self.nodes_data, self.network) - - @staticmethod - cdef void logging(int iteration, NetworkDataCy data, NodeDataCy[:] nodes_data, Network* network) noexcept: - """ Log network data """ - - cdef unsigned int nnodes = network.nnodes - cdef unsigned int j - cdef double* states_ptr = &data.states.array[0] - cdef double* derivatives_ptr = &data.derivatives.array[0] - cdef unsigned int[:] state_indices = data.states.indices - cdef double[:] outputs = data.outputs.array - cdef double[:] external_inputs = data.external_inputs.array - cdef NodeDataCy node_data - cdef int state_idx, start_idx, end_idx, state_iteration - for j in range(nnodes): - # Log states - node_data = nodes_data[j] - start_idx = state_indices[j] - end_idx = state_indices[j+1] - state_iteration = 0 - for state_idx in range(start_idx, end_idx): - node_data.states.array[iteration, state_iteration] = states_ptr[state_idx] - node_data.derivatives.array[iteration, state_iteration] = derivatives_ptr[state_idx] - state_iteration += 1 - node_data.output.array[iteration] = outputs[j] - node_data.external_input.array[iteration] = external_inputs[j] + logger((self.iteration%self.buffer_size), data, self.nodes_data, self.network) cdef void evaluate(self, double time, double[:] states, double[:] derivatives) noexcept: """ Evaluate the ODE """ # cdef NetworkDataCy data = self.data + # Update noise model + cdef SDESystem sde_system = self.sde_system + self.sde_integrator.step( + sde_system, + (self.iteration%self.buffer_size)*self.timestep, + self.data.noise.states + ) + _noise_states_to_output( + self.data.noise.states, + self.data.noise.indices, + self.data.noise.outputs + ) self.data.states.array[:] = states[:] ode(time, self.data, self.network, self.__tmp_node_outputs) self.data.outputs.array[:] = self.__tmp_node_outputs diff --git a/farms_network/core/node.pxd b/farms_network/core/node.pxd index 6bec773..fec2554 100644 --- a/farms_network/core/node.pxd +++ b/farms_network/core/node.pxd @@ -47,6 +47,7 @@ cdef struct Node: double* network_outputs, unsigned int* inputs, double* weights, + double noise, Node* node, Edge** edges, ) noexcept @@ -72,6 +73,7 @@ cdef: double* network_outputs, unsigned int* inputs, double* weights, + double noise, Node* node, Edge** edges, ) noexcept diff --git a/farms_network/core/node.pyx b/farms_network/core/node.pyx index 0aaf3ce..12c33a0 100644 --- a/farms_network/core/node.pyx +++ b/farms_network/core/node.pyx @@ -32,6 +32,7 @@ cdef void ode( double* network_outputs, unsigned int* inputs, double* weights, + double noise, Node* node, Edge** edges, ) noexcept: @@ -135,12 +136,14 @@ cdef class PyNode: double[:] network_outputs, unsigned int[:] inputs, double[:] weights, + double noise, ): cdef double* states_ptr = &states[0] cdef double* derivatives_ptr = &derivatives[0] cdef double* network_outputs_ptr = &network_outputs[0] cdef unsigned int* inputs_ptr = &inputs[0] cdef double* weights_ptr = &weights[0] + cdef Edge** edges = NULL # Call the C function directly @@ -152,6 +155,7 @@ cdef class PyNode: network_outputs_ptr, inputs_ptr, weights_ptr, + noise, self.node, edges ) @@ -163,7 +167,7 @@ cdef class PyNode: double external_input, double[:] network_outputs, unsigned int[:] inputs, - double[:] weights + double[:] weights, ): # Call the C function and return its result cdef double* states_ptr = &states[0] diff --git a/farms_network/models/izhikevich.pxd b/farms_network/models/izhikevich.pxd index 19f9c5e..add7c2d 100644 --- a/farms_network/models/izhikevich.pxd +++ b/farms_network/models/izhikevich.pxd @@ -41,6 +41,7 @@ cdef: double* network_outputs, unsigned int* inputs, double* weights, + double noise, Node* node, Edge** edges, ) noexcept diff --git a/farms_network/models/izhikevich.pyx b/farms_network/models/izhikevich.pyx index e08fa86..0e1028e 100644 --- a/farms_network/models/izhikevich.pyx +++ b/farms_network/models/izhikevich.pyx @@ -31,16 +31,17 @@ cpdef enum STATE: cdef void ode( - double time, - double* states, - double* derivatives, - double external_input, - double* network_outputs, - unsigned int* inputs, - double* weights, - Node* node, - Edge** edges, - ) noexcept: + double time, + double* states, + double* derivatives, + double external_input, + double* network_outputs, + unsigned int* inputs, + double* weights, + double noise, + Node* node, + Edge** edges, +) noexcept: """ Node ODE """ ... diff --git a/farms_network/models/li_danner.pxd b/farms_network/models/li_danner.pxd index 12a06a3..cd2b584 100644 --- a/farms_network/models/li_danner.pxd +++ b/farms_network/models/li_danner.pxd @@ -52,6 +52,7 @@ cdef: double* network_outputs, unsigned int* inputs, double* weights, + double noise, Node* node, Edge** edges, ) noexcept diff --git a/farms_network/models/li_danner.pyx b/farms_network/models/li_danner.pyx index d2188dc..df8b950 100644 --- a/farms_network/models/li_danner.pyx +++ b/farms_network/models/li_danner.pyx @@ -42,6 +42,7 @@ cdef void ode( double* network_outputs, unsigned int* inputs, double* weights, + double noise, Node* node, Edge** edges, ) noexcept: @@ -72,8 +73,10 @@ cdef void ode( # Inhibitory Synapse _sum += params.g_syn_i*cfabs(_weight)*_input*(state_v - params.e_syn_i) + # noise current + cdef double i_noise = noise + # dV - cdef double i_noise = 0.0 derivatives[STATE.v] = (-(i_leak + i_noise + _sum)/params.c_m) diff --git a/farms_network/models/li_nap_danner.pxd b/farms_network/models/li_nap_danner.pxd index 1ce051b..e86fed5 100644 --- a/farms_network/models/li_nap_danner.pxd +++ b/farms_network/models/li_nap_danner.pxd @@ -63,6 +63,7 @@ cdef: double* network_outputs, unsigned int* inputs, double* weights, + double noise, Node* node, Edge** edges, ) noexcept diff --git a/farms_network/models/li_nap_danner.pyx b/farms_network/models/li_nap_danner.pyx index b7ba64d..07bf2ee 100644 --- a/farms_network/models/li_nap_danner.pyx +++ b/farms_network/models/li_nap_danner.pyx @@ -45,6 +45,7 @@ cdef void ode( double* network_outputs, unsigned int* inputs, double* weights, + double noise, Node* node, Edge** edges, ) noexcept: @@ -90,7 +91,7 @@ cdef void ode( _sum += params.g_syn_i*cfabs(_weight)*_input*(state_v - params.e_syn_i) # noise current - cdef double i_noise = 0.0 + cdef double i_noise = noise # Slow inactivation derivatives[STATE.h] = (h_inf - state_h) / tau_h diff --git a/farms_network/models/linear.pxd b/farms_network/models/linear.pxd index e44a40b..5371a7b 100644 --- a/farms_network/models/linear.pxd +++ b/farms_network/models/linear.pxd @@ -16,7 +16,7 @@ See the License for the specific language governing permissions and limitations under the License. ----------------------------------------------------------------------- -Oscillator model +Linear model """ diff --git a/farms_network/models/linear.pyx b/farms_network/models/linear.pyx index 5927fe1..df92085 100644 --- a/farms_network/models/linear.pyx +++ b/farms_network/models/linear.pyx @@ -16,7 +16,7 @@ See the License for the specific language governing permissions and limitations under the License. ----------------------------------------------------------------------- -Oscillator model +Linear model """ from libc.stdio cimport printf diff --git a/farms_network/models/oscillator.pxd b/farms_network/models/oscillator.pxd index 58ccde5..bad1953 100644 --- a/farms_network/models/oscillator.pxd +++ b/farms_network/models/oscillator.pxd @@ -54,6 +54,7 @@ cdef: double* network_outputs, unsigned int* inputs, double* weights, + double noise, Node* node, Edge** edges, ) noexcept diff --git a/farms_network/models/oscillator.pyx b/farms_network/models/oscillator.pyx index 7140061..f98a884 100644 --- a/farms_network/models/oscillator.pyx +++ b/farms_network/models/oscillator.pyx @@ -44,6 +44,7 @@ cdef void ode( double* network_outputs, unsigned int* inputs, double* weights, + double noise, Node* node, Edge** edges, ) noexcept: From f2061728afda1826908fc775c269f5f8018af138 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Sun, 1 Dec 2024 16:50:49 -0500 Subject: [PATCH 168/316] [MODELS] Added new ReLU neuron type --- farms_network/core/options.py | 73 +++++++++++++++++++++++--- farms_network/models/factory.py | 9 ++-- farms_network/models/relu.pxd | 55 ++++++++++++++++++++ farms_network/models/relu.pyx | 90 +++++++++++++++++++++++++++++++++ 4 files changed, 217 insertions(+), 10 deletions(-) create mode 100644 farms_network/models/relu.pxd create mode 100644 farms_network/models/relu.pyx diff --git a/farms_network/core/options.py b/farms_network/core/options.py index c755808..8937a4b 100644 --- a/farms_network/core/options.py +++ b/farms_network/core/options.py @@ -6,7 +6,6 @@ import matplotlib.pyplot as plt import networkx as nx -import numpy as np from farms_core import pylog from farms_core.options import Options @@ -55,6 +54,7 @@ def from_options(cls, kwargs): node_types = { "linear": LinearNodeOptions, "external_relay": ExternalRelayNodeOptions, + "relu": ReLUNodeOptions, "oscillator": OscillatorNodeOptions, "li_danner": LIDannerNodeOptions, "li_nap_danner": LINaPDannerNodeOptions, @@ -141,7 +141,7 @@ def defaults(cls, **kwargs): options["timestep"] = kwargs.pop("timestep", 1e-3) options["n_iterations"] = int(kwargs.pop("n_iterations", 1e3)) - options["integrator"] = kwargs.pop("integrator", "dopri5") + options["integrator"] = kwargs.pop("integrator", "rk4") options["method"] = kwargs.pop("method", "adams") options["atol"] = kwargs.pop("atol", 1e-12) options["rtol"] = kwargs.pop("rtol", 1e-6) @@ -155,6 +155,7 @@ def from_options(cls, kwargs: Dict): return cls(**kwargs) + ################### # Logging Options # ################### @@ -411,7 +412,6 @@ def __init__(self, **kwargs): self.model = kwargs.pop("model", None) assert self.model.lower() in NoiseOptions.NOISE_MODELS self.is_stochastic = kwargs.pop("is_stochastic") - self.seed = kwargs.pop("seed", None) @classmethod def from_options(cls, kwargs: Dict): @@ -435,7 +435,6 @@ def __init__(self, **kwargs): self.mu: float = kwargs.pop("mu") self.sigma: float = kwargs.pop("sigma") self.tau: float = kwargs.pop("tau") - self.seed: float = kwargs.pop("seed", None) @classmethod def from_options(cls, kwargs: Dict): @@ -444,7 +443,6 @@ def from_options(cls, kwargs: Dict): options["mu"] = kwargs.pop("mu") options["sigma"] = kwargs.pop("sigma") options["tau"] = kwargs.pop("tau") - options["seed"] = kwargs.pop("seed", None) return cls(**options) @classmethod @@ -454,7 +452,6 @@ def defaults(cls, **kwargs: Dict): options["mu"] = kwargs.pop("mu", 0.0) options["sigma"] = kwargs.pop("sigma", 0.005) options["tau"] = kwargs.pop("tau", 10.0) - options["seed"] = kwargs.pop("seed", None) return cls(**options) @@ -556,6 +553,70 @@ def defaults(cls, **kwargs): return cls(**options) + +###################### +# ReLU Model Options # +###################### +class ReLUNodeOptions(NodeOptions): + """ Class to define the properties of ReLU node model """ + + def __init__(self, **kwargs): + """ Initialize """ + model = "relu" + super().__init__( + name=kwargs.pop("name"), + model=model, + parameters=kwargs.pop("parameters"), + visual=kwargs.pop("visual"), + state=kwargs.pop("state", None), + noise=kwargs.pop("noise"), + ) + self._nstates = 0 + self._nparameters = 3 + + if kwargs: + raise Exception(f'Unknown kwargs: {kwargs}') + + @classmethod + def from_options(cls, kwargs: Dict): + """ Load from options """ + options = {} + options["name"] = kwargs.pop("name") + options["parameters"] = ReLUParameterOptions.from_options( + kwargs["parameters"] + ) + options["visual"] = NodeVisualOptions.from_options( + kwargs["visual"] + ) + options["state"] = None + options["noise"] = kwargs.pop("noise", None) + return cls(**options) + + +class ReLUParameterOptions(NodeParameterOptions): + + def __init__(self, **kwargs): + super().__init__() + self.gain = kwargs.pop("gain") + self.sign = kwargs.pop("sign") + self.offset = kwargs.pop("offset") + + if kwargs: + raise Exception(f'Unknown kwargs: {kwargs}') + + @classmethod + def defaults(cls, **kwargs): + """ Get the default parameters for ReLU Node model """ + + options = {} + + options["gain"] = kwargs.pop("gain", 1.0) + options["sign"] = kwargs.pop("sign", 1) + options["offset"] = kwargs.pop("offset", 0.0) + + return cls(**options) + + ############################################ # Phase-Amplitude Oscillator Model Options # ############################################ diff --git a/farms_network/models/factory.py b/farms_network/models/factory.py index 477d41f..388e89c 100644 --- a/farms_network/models/factory.py +++ b/farms_network/models/factory.py @@ -25,15 +25,16 @@ # from farms_network.models.hopf_oscillator import HopfOscillator # from farms_network.models.leaky_integrator import LeakyIntegrator from farms_network.models.external_relay import PyExternalRelayNode -from farms_network.models.linear import PyLinearNode from farms_network.models.li_danner import PyLIDannerNode from farms_network.models.li_nap_danner import PyLINaPDannerNode +from farms_network.models.linear import PyLinearNode # from farms_network.models.lif_daun_interneuron import LIFDaunInterneuron # from farms_network.models.matsuoka_node import MatsuokaNode # from farms_network.models.morphed_oscillator import MorphedOscillator # from farms_network.models.morris_lecar import MorrisLecarNode -from farms_network.models.oscillator import PyOscillatorNode, PyOscillatorEdge -# from farms_network.models.relu import ReLUNode +from farms_network.models.oscillator import PyOscillatorEdge, PyOscillatorNode +from farms_network.models.relu import PyReLUNode + # from farms_network.models.sensory_node import SensoryNode @@ -55,7 +56,7 @@ class NodeFactory: # 'fitzhugh_nagumo': FitzhughNagumo, # 'matsuoka_node': MatsuokaNode, # 'morris_lecar': MorrisLecarNode, - # 'relu': ReLUNode, + 'relu': PyReLUNode, } def __init__(self): diff --git a/farms_network/models/relu.pxd b/farms_network/models/relu.pxd new file mode 100644 index 0000000..e0eb110 --- /dev/null +++ b/farms_network/models/relu.pxd @@ -0,0 +1,55 @@ +""" +----------------------------------------------------------------------- +Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty +Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +----------------------------------------------------------------------- + +Rectified Linear Unit +""" + + +from ..core.node cimport Node, PyNode +from ..core.edge cimport Edge, PyEdge + + +cdef enum: + #STATES + NSTATES = 0 + + +cdef packed struct ReLUNodeParameters: + double gain + double sign + double offset + + +cdef: + double output( + double time, + double* states, + double external_input, + double* network_outputs, + unsigned int* inputs, + double* weights, + Node* node, + Edge** edges, + ) noexcept + + +cdef class PyReLUNode(PyNode): + """ Python interface to ReLU Node C-Structure """ + + cdef: + ReLUNodeParameters parameters diff --git a/farms_network/models/relu.pyx b/farms_network/models/relu.pyx new file mode 100644 index 0000000..f145fa8 --- /dev/null +++ b/farms_network/models/relu.pyx @@ -0,0 +1,90 @@ +""" +----------------------------------------------------------------------- +Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty +Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +----------------------------------------------------------------------- + +Rectified Linear Unit +""" + + +from libc.stdio cimport printf +from libc.stdlib cimport free, malloc +from libc.string cimport strdup + + +cpdef enum STATE: + + #STATES + nstates = NSTATES + + +cdef double output( + double time, + double* states, + double external_input, + double* network_outputs, + unsigned int* inputs, + double* weights, + Node* node, + Edge** edges, +) noexcept: + """ Node output. """ + cdef ReLUNodeParameters params = ( node[0].parameters)[0] + + cdef: + double _sum = 0.0 + unsigned int j + double _input, _weight + cdef unsigned int ninputs = node.ninputs + _sum += external_input + for j in range(ninputs): + _input = network_outputs[inputs[j]] + _weight = weights[j] + _sum += _weight*_input + + cdef double res = max(0.0, params.gain*(params.sign*_sum + params.offset)) + return res + + +cdef class PyReLUNode(PyNode): + """ Python interface to ReLU Node C-Structure """ + + def __cinit__(self): + self.node.model_type = strdup("RELU".encode('UTF-8')) + # override default ode and out methods + self.node.is_statefull = False + self.node.output = output + # parameters + self.node.parameters = malloc(sizeof(ReLUNodeParameters)) + if self.node.parameters is NULL: + raise MemoryError("Failed to allocate memory for node parameters") + + def __init__(self, name: str, **kwargs): + super().__init__(name) + + # Set node parameters + cdef ReLUNodeParameters* param = (self.node.parameters) + param.gain = kwargs.pop("gain") + param.sign = kwargs.pop("sign") + param.offset = kwargs.pop("offset") + if kwargs: + raise Exception(f'Unknown kwargs: {kwargs}') + + @property + def parameters(self): + """ Parameters in the network """ + cdef ReLUNodeParameters params = ( self.node.parameters)[0] + return params From c03e81c517fb1ba3d125dd319911253276a86eed Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Mon, 2 Dec 2024 10:02:53 -0500 Subject: [PATCH 169/316] [MAIN] Removed unused dist import in setuptools --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index e521c50..0a27394 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ from Cython.Build import cythonize from Cython.Compiler import Options from farms_core import get_include_paths -from setuptools import dist, find_packages, setup +from setuptools import find_packages, setup from setuptools.extension import Extension DEBUG = False From 78000d0ebb5335458beded00dfcc1db3d8b8aaec Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Mon, 2 Dec 2024 15:07:34 -0500 Subject: [PATCH 170/316] [NETWORK] Refactored evaluate func with explicit types --- farms_network/core/network.pyx | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/farms_network/core/network.pyx b/farms_network/core/network.pyx index 45778df..63319c2 100644 --- a/farms_network/core/network.pyx +++ b/farms_network/core/network.pyx @@ -292,20 +292,22 @@ cdef class PyNetwork(ODESystem): cdef void evaluate(self, double time, double[:] states, double[:] derivatives) noexcept: """ Evaluate the ODE """ - # cdef NetworkDataCy data = self.data # Update noise model + cdef NetworkDataCy data = self.data cdef SDESystem sde_system = self.sde_system - self.sde_integrator.step( + cdef EulerMaruyamaSolver sde_integrator = self.sde_integrator + + sde_integrator.step( sde_system, (self.iteration%self.buffer_size)*self.timestep, - self.data.noise.states + data.noise.states ) _noise_states_to_output( - self.data.noise.states, - self.data.noise.indices, - self.data.noise.outputs + data.noise.states, + data.noise.indices, + data.noise.outputs ) - self.data.states.array[:] = states[:] - ode(time, self.data, self.network, self.__tmp_node_outputs) - self.data.outputs.array[:] = self.__tmp_node_outputs - derivatives[:] = self.data.derivatives.array + data.states.array[:] = states[:] + ode(time, data, self.network, self.__tmp_node_outputs) + data.outputs.array[:] = self.__tmp_node_outputs + derivatives[:] = data.derivatives.array From 7dd280385a1bbfe4ef6215f9a84af9f8752e4758 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Mon, 2 Dec 2024 15:08:07 -0500 Subject: [PATCH 171/316] [EX][MOUSE] Added vestibular and somatosensory systems --- examples/mouse/components.py | 328 +++++++++++++++++++++++++++++++---- 1 file changed, 294 insertions(+), 34 deletions(-) diff --git a/examples/mouse/components.py b/examples/mouse/components.py index 8521f32..7bc4feb 100644 --- a/examples/mouse/components.py +++ b/examples/mouse/components.py @@ -8,8 +8,8 @@ import matplotlib.pyplot as plt import networkx as nx import numpy as np -from farms_network.core import options from farms_core.io.yaml import read_yaml +from farms_network.core import options from farms_network.core.data import NetworkData from farms_network.core.network import PyNetwork from jinja2 import Environment, FileSystemLoader @@ -184,6 +184,11 @@ def create_node( parameters = options.LinearParameterOptions.defaults(**parameters) noise = None node_options_class = options.LinearNodeOptions + elif node_type == "ReLU": + state_options = None + parameters = options.ReLUParameterOptions.defaults(**parameters) + noise = None + node_options_class = options.ReLUNodeOptions elif node_type == "ExternalRelay": state_options = None parameters = options.NodeParameterOptions() @@ -957,6 +962,147 @@ def connect_Rn_reciprocal_inhibition(muscle: str): return edge_specs +##################### +# Vestibular System # +##################### +class VestibularSystem: + """Generate Vestibular System Network""" + + def __init__(self, side="", rate="", axis="", transform_mat=np.identity(3)): + """Initialization.""" + self.side = side + self.rate = rate + self.axis = axis + self.transform_mat = transform_mat + + def nodes(self): + """Define nodes.""" + directions = ("cclock", "clock") + node_specs = [ + ( + join_str((self.side, self.rate, self.axis, "Vn")), + "ExternalRelay", + np.array((0.0, 0.0)), + "Vn", + [1.0, 1.0, 0.0], # Yellow for sensory neuron + None, + {}, + ) + ] + + for direction, x_offset in zip(directions, (-1.0, 1.0)): + # ReLU Interneurons + node_specs.append( + ( + join_str((self.side, self.rate, self.axis, direction, "ReLU", "Vn")), + "ReLU", + np.array((0.0 - x_offset, 1.0)), + "ReLU", + [0.2, 0.2, 1.0], # Blue for interneurons + None, + {"sign": 1.0 if direction == "cclock" else -1.0, "offset": np.deg2rad(10), "gain": 0.5}, + ) + ) + # Inhibitory Interneurons + node_specs.append( + ( + join_str((self.side, self.rate, self.axis, direction, "In", "Vn")), + "LIDanner", + np.array((0.0 - x_offset, 2.0)), + "In", + [0.5, 0.5, 0.5], # Gray for inhibitory interneurons + {"v": -60.0}, + {}, + ) + ) + + # Create nodes using the `create_nodes` utility + return create_nodes(node_specs, base_name="", transform_mat=self.transform_mat) + + def edges(self): + """Define edges.""" + directions = ("cclock", "clock") + edge_specs = [] + + for direction in directions: + edge_specs.append( + ( + (self.side, self.rate, self.axis, "Vn"), + (self.side, self.rate, self.axis, direction, "ReLU", "Vn"), + 1.0, + "excitatory", + ) + ) + edge_specs.append( + ( + (self.side, self.rate, self.axis, direction, "ReLU", "Vn"), + (self.side, self.rate, self.axis, direction, "In", "Vn"), + 1.0, + "excitatory", + ) + ) + + # Create edges using the `create_edges` utility + return create_edges(edge_specs, base_name="") + + +######################## +# SomatoSensory System # +######################## +class SomatoSensory: + """Generate Afferents Network""" + + def __init__(self, contacts, name="", transform_mat=np.identity(3)): + """Initialization.""" + self.contacts = contacts + self.name = name + self.transform_mat = transform_mat + + def nodes(self): + """Define nodes.""" + n_contacts = len(self.contacts) + node_y_pos = np.linspace(-1 * n_contacts, 1 * n_contacts, n_contacts) + node_specs = [] + + for y_pos, contact in zip(node_y_pos, self.contacts): + node_specs.append( + ( + join_str((contact, "cut")), + "ExternalRelay", + np.array((0.0, y_pos)), + "C", + [1.0, 0.0, 0.0], + {}, + {} + ) + ) + + node_specs.append( + ( + join_str(("In", "cut")), + "LIDanner", + np.array((-1.0, np.mean(node_y_pos))), + "In\\textsubscript{C}", + [1.0, 0.0, 0.0], + {"v": -60.0}, + {} + ) + ) + + return create_nodes(node_specs, base_name=self.name, transform_mat=self.transform_mat) + + def edges(self): + """Define edges.""" + edge_specs = [] + + for contact in self.contacts: + edge_specs.append( + ((contact, "cut"), ("In", "cut"), 1.0, "excitatory") + ) + + return create_edges(edge_specs, base_name=self.name) + + ###################### # CONNECT RG PATTERN # ###################### @@ -1179,67 +1325,77 @@ def connect_II_rg_feedback(flexor): return edge_specs -def connect_roll_vestibular_to_mn(side, fore_muscles, hind_muscles): +def connect_roll_vestibular_to_mn(side, fore_muscles, hind_muscles, default_weight=0.01): """Return edge specs for roll vestibular to motor neurons.""" rates = ("position", "velocity") - weights = {"position": 0.01, "velocity": 0.01} + weights = {"position": default_weight, "velocity": default_weight} edge_specs = [] for rate in rates: for muscle in fore_muscles: - name = "_".join(muscle["name"].split("_")[2:]) edge_specs.append( ( (rate, "roll", "cclock", "In", "Vn"), - ("fore", name, "Mn"), + ("fore", muscle["name"], "Mn"), weights[rate], "excitatory", ) ) for muscle in hind_muscles: - name = "_".join(muscle["name"].split("_")[2:]) edge_specs.append( ( (rate, "roll", "cclock", "In", "Vn"), - ("hind", name, "Mn"), + ("hind", muscle["name"], "Mn"), weights[rate], "excitatory", ) ) - - return edge_specs + edges = create_edges(edge_specs, base_name=side) + return edges def connect_pitch_vestibular_to_mn( - side, rate, fore_muscles, hind_muscles, default_weight=0.01 + side, fore_muscles, hind_muscles, default_weight=0.01 ): """Return edge specs for pitch vestibular to motor neurons.""" - edge_specs = [] + rates = ("position", "velocity") - for muscle in fore_muscles: - name = "_".join(muscle["name"].split("_")[2:]) - edge_specs.append( - ( - (rate, "pitch", "cclock", "In", "Vn"), - ("fore", name, "Mn"), - default_weight, - "excitatory", + edge_specs = [] + for rate in rates: + for muscle in fore_muscles: + edge_specs.append( + ( + (rate, "pitch", "cclock", "In", "Vn"), + ("fore", muscle["name"], "Mn"), + default_weight, + "excitatory", + ) ) - ) - for muscle in hind_muscles: - name = "_".join(muscle["name"].split("_")[2:]) - edge_specs.append( - ( - (rate, "pitch", "clock", "In", "Vn"), - ("hind", name, "Mn"), - default_weight, - "excitatory", + for muscle in hind_muscles: + edge_specs.append( + ( + (rate, "pitch", "clock", "In", "Vn"), + ("hind", muscle["name"], "Mn"), + default_weight, + "excitatory", + ) ) - ) + edges = create_edges(edge_specs, base_name=side) + return edges - return edge_specs + +def connect_somatosensory_to_rhythm( + side, limb, contacts, default_weight=0.01 +): + """ Connect somatosensory to Rhythm generation """ + edge_specs = [ + (("In", "cut"), ("RG", "In", "E"), default_weight, "excitatory"), + (("In", "cut"), ("RG", "E"), default_weight, "excitatory"), + ] + edges = create_edges(edge_specs, base_name=join_str((side, limb))) + return edges ########### @@ -1316,6 +1472,8 @@ def limb_circuit( network_options: options.NetworkOptions, side: str, limb: str, + muscles: dict, + contacts: Iterable[str] = None, transform_mat=np.identity(3) ): @@ -1352,10 +1510,6 @@ def limb_circuit( ############## # MotorLayer # ############## - # read muscle config file - muscles_config_path = "/Users/tatarama/projects/work/research/neuromechanics/quadruped/mice/mouse-locomotion/data/config/muscles/quadruped_siggraph.yaml" - muscles = generate_muscle_agonist_antagonist_pairs(muscles_config_path) - ################################### # Connect patterns and motorlayer # ################################### @@ -1501,6 +1655,25 @@ def limb_circuit( edges = create_edges(edge_specs, name) network_options.add_edges(edges.values()) + + # somatosensory system + if contacts: + somatosensory_transformation_mat = ( + motor_transformation_mat@get_translation_matrix(off_x=1.0, off_y=10.0) + ) + somatosensory = SomatoSensory( + contacts=contacts, + name=join_str((side, limb)), + transform_mat=somatosensory_transformation_mat + ) + network_options.add_nodes(somatosensory.nodes().values()) + network_options.add_edges(somatosensory.edges().values()) + # Connect somatosensory to Rhythm + edges = connect_somatosensory_to_rhythm( + side, limb, contacts, default_weight=1.0 + ) + network_options.add_edges(edges.values()) + return network_options @@ -1549,6 +1722,48 @@ def interlimb_circuit( return network_options +###################### +# Vestibular Circuit # +###################### +def vestibular_circuit( + network_options: options.NetworkLogOptions, + position=True, + velocity=True, + transform_mat=np.identity(3), +): + rates = ("position", "velocity",) + axes = ("roll", "pitch",) + # Define base offsets for positioning + base_x_offset = -15.0 + base_y_offset = 0.0 + spacing_x = 10.0 # Horizontal spacing between systems + spacing_y = 5.0 # Vertical spacing between systems + for side_index, side in enumerate(("left", "right",)): + for rate_index, rate in enumerate(rates): + for axis_index, axis in enumerate(axes): + # Calculate unique offsets for each system + off_x = base_x_offset + axis_index * spacing_x # Axes placed next to each other + off_y = base_y_offset + rate_index * spacing_y # Rates placed below each other + + mirror_x = False + if rate == "position": + mirror_x = True + if side == "right": + off_x = -1*off_x + vestibular = VestibularSystem( + side=side, + rate=rate, + axis=axis, + transform_mat=transform_mat@get_translation_matrix( + off_x=off_x, off_y=off_y + )@get_mirror_matrix(mirror_x=mirror_x, mirror_y=False) + ) + network_options.add_nodes(vestibular.nodes().values()) + network_options.add_edges(vestibular.edges().values()) + + return network_options + + ##################### # Quadruped Circuit # ##################### @@ -1558,11 +1773,17 @@ def quadruped_circuit( ): """ Full Quadruped Circuit """ + # read muscle config file + muscles_config_path = "/Users/tatarama/projects/work/research/neuromechanics/quadruped/mice/mouse-locomotion/data/config/muscles/quadruped_siggraph.yaml" + muscles = generate_muscle_agonist_antagonist_pairs(muscles_config_path) + # Limb circuitry network_options = limb_circuit( network_options, side="left", limb="hind", + muscles=muscles, + contacts=("PHALANGE",), transform_mat=get_translation_matrix( off_x=-25.0, off_y=0.0 )@get_mirror_matrix( @@ -1574,6 +1795,8 @@ def quadruped_circuit( network_options, side="right", limb="hind", + muscles=muscles, + contacts=("PHALANGE",), transform_mat=get_translation_matrix( off_x=25.0, off_y=0.0 )@get_mirror_matrix( @@ -1585,6 +1808,8 @@ def quadruped_circuit( network_options, side="left", limb="fore", + muscles=muscles, + contacts=("PHALANGE",), transform_mat=get_translation_matrix( off_x=-25.0, off_y=25.0 )@get_rotation_matrix(angle=45) @@ -1594,6 +1819,8 @@ def quadruped_circuit( network_options, side="right", limb="fore", + muscles=muscles, + contacts=("PHALANGE",), transform_mat=get_translation_matrix( off_x=25.0, off_y=25.0 )@get_rotation_matrix(angle=-45) @@ -1624,4 +1851,37 @@ def quadruped_circuit( fore_hind_edges = connect_fore_hind_circuits() network_options.add_edges(fore_hind_edges.values()) + #################### + # VestibularSystem # + #################### + network_options = vestibular_circuit(network_options) + for side in ('left', 'right'): + vn_edges = connect_pitch_vestibular_to_mn( + side=side, + fore_muscles=[ + *muscles[side]['fore']['agonist'], + *muscles[side]['fore']['antagonist'] + ], + hind_muscles=[ + *muscles[side]['hind']['agonist'], + *muscles[side]['hind']['antagonist'] + ], + default_weight=0.1 + ) + network_options.add_edges(vn_edges.values()) + + vn_edges = connect_roll_vestibular_to_mn( + side=side, + fore_muscles=[ + *muscles['left']['fore']['agonist'], + *muscles['left']['fore']['antagonist'] + ], + hind_muscles=[ + *muscles['left']['hind']['agonist'], + *muscles['left']['hind']['antagonist'] + ], + default_weight=0.1 + ) + network_options.add_edges(vn_edges.values()) + return network_options From 19f924c041a7674e4ca05815a95d804e6fe1cc5e Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Fri, 6 Dec 2024 10:15:59 -0500 Subject: [PATCH 172/316] [CORE][NODE] Fixed function docstring --- farms_network/core/node.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/farms_network/core/node.pyx b/farms_network/core/node.pyx index 12c33a0..c7823b3 100644 --- a/farms_network/core/node.pyx +++ b/farms_network/core/node.pyx @@ -123,7 +123,7 @@ cdef class PyNode: @property def parameters(self): - """ Number of states in the network """ + """ Return a view of parameters in the network """ return self.node.parameters[0] # Methods to wrap the ODE and output functions From f5cd48e2986d6913af51822faaaefda2cc97b1e6 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Fri, 6 Dec 2024 10:31:10 -0500 Subject: [PATCH 173/316] [EX][IJSPEERT07] Updated to the changes in the core functions --- examples/ijspeert07/run.py | 35 ++++++++++++++++------------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/examples/ijspeert07/run.py b/examples/ijspeert07/run.py index 1337edf..dc30d8a 100644 --- a/examples/ijspeert07/run.py +++ b/examples/ijspeert07/run.py @@ -2,23 +2,16 @@ https://doi.org/10.7554/eLife.73424 paper network """ -import itertools -import os -from copy import deepcopy -from pprint import pprint - import farms_pylog as pylog import matplotlib.pyplot as plt import networkx as nx import numpy as np -from farms_core.io.yaml import read_yaml, write_yaml +import seaborn as sns from farms_core.utils import profile from farms_network.core import options -from farms_network.core.data import (NetworkConnectivity, NetworkData, - NetworkStates) +from farms_network.core.data import NetworkData from farms_network.core.network import PyNetwork from tqdm import tqdm -import seaborn as sns plt.rcParams['text.usetex'] = False @@ -58,6 +51,7 @@ def oscillator_chain(network_options, n_oscillators, name_prefix, **kwargs): amplitude=np.random.uniform(0, 1), amplitude_0=np.random.uniform(0, 1) ), + noise=None, ) ) # Connect @@ -68,7 +62,7 @@ def oscillator_chain(network_options, n_oscillators, name_prefix, **kwargs): np.roll(np.arange(n_oscillators), -1)))[:, :-1] for j in np.arange(n_oscillators-1): network_options.add_edge( - options.EdgeOptions( + options.OscillatorEdgeOptions( source=oscillator_names[connections[0, j]], target=oscillator_names[connections[1, j]], weight=weight, @@ -81,7 +75,7 @@ def oscillator_chain(network_options, n_oscillators, name_prefix, **kwargs): ) network_options.add_edge( - options.EdgeOptions( + options.OscillatorEdgeOptions( source=oscillator_names[connections[1, j]], target=oscillator_names[connections[0, j]], weight=weight, @@ -107,7 +101,7 @@ def oscillator_double_chain(network_options, n_oscillators, **kwargs): weight = kwargs.get('anti_w', 50) for n in range(n_oscillators): network_options.add_edge( - options.EdgeOptions( + options.OscillatorEdgeOptions( source=f'left_{n}', target=f'right_{n}', weight=weight, @@ -119,7 +113,7 @@ def oscillator_double_chain(network_options, n_oscillators, **kwargs): ) ) network_options.add_edge( - options.EdgeOptions( + options.OscillatorEdgeOptions( source=f'right_{n}', target=f'left_{n}', weight=weight, @@ -185,7 +179,7 @@ def generate_network(iterations=2000): data = NetworkData.from_options(network_options) network = PyNetwork.from_options(network_options) - network.setup_integrator(network_options.integration) + network.setup_integrator(network_options) # nnodes = len(network_options.nodes) # integrator.set_initial_value(np.zeros(len(data.states.array),), 0.0) @@ -216,20 +210,18 @@ def generate_network(iterations=2000): for j in range(int(n_oscillators/2)+1): plt.fill_between( np.array(network.data.times.array), - j + (1 + np.sin(network.data.nodes[j].output.array)), - j, + 2*j + (1 + np.sin(network.data.nodes[j].output.array)), + 2*j, alpha=0.2, lw=1.0, ) plt.plot( np.array(network.data.times.array), - j + (1 + np.sin(network.data.nodes[j].output.array)), + 2*j + (1 + np.sin(network.data.nodes[j].output.array)), label=f"{j}" ) plt.legend() - network_options.save("/tmp/netwok_options.yaml") - graph = nx.node_link_graph( network_options, directed=True, @@ -243,6 +235,11 @@ def generate_network(iterations=2000): pos_circular = nx.circular_layout(graph) pos_spring = nx.spring_layout(graph) pos_graphviz = nx.nx_agraph.pygraphviz_layout(graph) + for index, node in enumerate(network_options.nodes): + node.visual.position[:2] = pos_graphviz[node.name] + + network_options.save("/tmp/network_options.yaml") + _ = nx.draw_networkx_nodes( graph, pos=pos_graphviz, From 09ab4fb7443d2c038e431f2db8edd42d65045b75 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Fri, 6 Dec 2024 10:31:42 -0500 Subject: [PATCH 174/316] [CORE][OPTIONS] Updated oscillator node to handle noise options --- farms_network/core/options.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/farms_network/core/options.py b/farms_network/core/options.py index 8937a4b..4901a04 100644 --- a/farms_network/core/options.py +++ b/farms_network/core/options.py @@ -654,7 +654,11 @@ def from_options(cls, kwargs: Dict): options["state"] = OscillatorStateOptions.from_options( kwargs["state"] ) - options["noise"] = NoiseOptions.from_options(kwargs["noise"]) + options["noise"] = None + if kwargs["noise"] is not None: + options["noise"] = NoiseOptions.from_options( + kwargs["noise"] + ) return cls(**options) From 960ee3ba9fb44b03c09edf1688d1556c92a7068e Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Fri, 6 Dec 2024 10:32:41 -0500 Subject: [PATCH 175/316] [CORE][DATA] Removed derivatives from node logs Derivatives at a certain time-step can be computed as difference between the states. This is redundant information and hence will not be supported for logs --- farms_network/core/data.py | 4 ---- farms_network/core/network.pxd | 1 - farms_network/core/network.pyx | 2 -- 3 files changed, 7 deletions(-) diff --git a/farms_network/core/data.py b/farms_network/core/data.py index b21261b..e75379b 100644 --- a/farms_network/core/data.py +++ b/farms_network/core/data.py @@ -292,7 +292,6 @@ def __init__( self, name: str, states: "NodeStatesArray", - derivatives: "NodeStatesArray", output: "NodeOutputArray", external_input: "NodeExternalInputArray", ): @@ -301,7 +300,6 @@ def __init__( super().__init__() self.name = name self.states = states - self.derivatives = derivatives self.output = output self.external_input = external_input @@ -311,7 +309,6 @@ def from_options(cls, options: NodeOptions, buffer_size: int): return cls( name=options.name, states=NodeStatesArray.from_options(options, buffer_size), - derivatives=NodeStatesArray.from_options(options, buffer_size), output=NodeOutputArray.from_options(options, buffer_size), external_input=NodeExternalInputArray.from_options(options, buffer_size), ) @@ -320,7 +317,6 @@ def to_dict(self, iteration: int = None) -> Dict: """ Concert data to dictionary """ return { 'states': self.states.to_dict(iteration), - 'derivatives': self.derivatives.to_dict(iteration), 'output': to_array(self.output.array), 'external_input': to_array(self.output.array), } diff --git a/farms_network/core/network.pxd b/farms_network/core/network.pxd index 024283f..949faeb 100644 --- a/farms_network/core/network.pxd +++ b/farms_network/core/network.pxd @@ -29,7 +29,6 @@ cdef class PyNetwork(ODESystem): public list pynodes public list pyedges public NetworkDataCy data - NodeDataCy[:] nodes_data double[:] __tmp_node_outputs unsigned int iteration diff --git a/farms_network/core/network.pyx b/farms_network/core/network.pyx index 63319c2..4d3e529 100644 --- a/farms_network/core/network.pyx +++ b/farms_network/core/network.pyx @@ -105,7 +105,6 @@ cdef inline void logger( cdef unsigned int nnodes = network.nnodes cdef unsigned int j cdef double* states_ptr = &data.states.array[0] - cdef double* derivatives_ptr = &data.derivatives.array[0] cdef unsigned int[:] state_indices = data.states.indices cdef double[:] outputs = data.outputs.array cdef double* outputs_ptr = &data.outputs.array[0] @@ -121,7 +120,6 @@ cdef inline void logger( node_states = nodes_data[j].states.array[iteration] for state_idx in range(start_idx, end_idx): node_states[state_iteration] = states_ptr[state_idx] - # nodes_data[j].derivatives.array[iteration, state_iteration] = derivatives_ptr[state_idx] state_iteration += 1 nodes_data[j].output.array[iteration] = outputs_ptr[j] nodes_data[j].external_input.array[iteration] = external_inputs[j] From 9bcc7e3dcaaf906c5e91b2d07b222351a606e817 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Fri, 6 Dec 2024 10:34:50 -0500 Subject: [PATCH 176/316] [CORE][NETWORK] Removed saving nodes_data as a network attribute --- farms_network/core/network.pyx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/farms_network/core/network.pyx b/farms_network/core/network.pyx index 4d3e529..e273b66 100644 --- a/farms_network/core/network.pyx +++ b/farms_network/core/network.pyx @@ -99,7 +99,6 @@ cdef inline void ode( cdef inline void logger( int iteration, NetworkDataCy data, - NodeDataCy[:] nodes_data, Network* network ) noexcept: cdef unsigned int nnodes = network.nnodes @@ -112,6 +111,7 @@ cdef inline void logger( cdef NodeDataCy node_data cdef double[:] node_states cdef int state_idx, start_idx, end_idx, state_iteration + cdef NodeDataCy[:] nodes_data = data.nodes for j in range(nnodes): # Log states start_idx = state_indices[j] @@ -161,7 +161,7 @@ cdef class PyNetwork(ODESystem): super().__init__() self.data = NetworkData.from_options(network_options) - self.nodes_data = self.data.nodes + self.pynodes = [] self.pyedges = [] self.nodes_output_data = [] @@ -281,12 +281,12 @@ cdef class PyNetwork(ODESystem): cpdef void step(self): """ Step the network state """ - cdef NetworkDataCy data = self.data + # cdef NetworkDataCy data = self.data self.iteration += 1 self.ode_integrator.step( - self, (self.iteration%self.buffer_size)*self.timestep, data.states.array + self, (self.iteration%self.buffer_size)*self.timestep, self.data.states.array ) - logger((self.iteration%self.buffer_size), data, self.nodes_data, self.network) + logger((self.iteration%self.buffer_size), self.data, self.network) cdef void evaluate(self, double time, double[:] states, double[:] derivatives) noexcept: """ Evaluate the ODE """ From 37d691836ef5fe07c00aeb0680bdb45a1e7def67 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Fri, 6 Dec 2024 10:38:00 -0500 Subject: [PATCH 177/316] [EX][IJSPEERT07] Fixed module docstring DOI paper reference --- examples/ijspeert07/run.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/ijspeert07/run.py b/examples/ijspeert07/run.py index dc30d8a..62a4377 100644 --- a/examples/ijspeert07/run.py +++ b/examples/ijspeert07/run.py @@ -1,5 +1,5 @@ -""" Generate and reproduce Zhang, Shevtsova, et al. eLife 2022;11:e73424. DOI: -https://doi.org/10.7554/eLife.73424 paper network """ +""" Generate and reproduce Ijspeert 07 Science paper +DOI: 10.1126/science.1138353 """ import farms_pylog as pylog From d0db66ee246c87780b110b5ff6573f5e153b65c8 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Fri, 6 Dec 2024 10:53:49 -0500 Subject: [PATCH 178/316] [EX][MOUSE] Minor code clean up in run file --- examples/mouse/run.py | 48 ++++++++----------------------------------- 1 file changed, 9 insertions(+), 39 deletions(-) diff --git a/examples/mouse/run.py b/examples/mouse/run.py index 4714a91..d9716f8 100644 --- a/examples/mouse/run.py +++ b/examples/mouse/run.py @@ -286,7 +286,7 @@ def generate_limb_circuit(n_iterations: int): ), ) - ############## + ############## # MotorLayer # ############## # read muscle config file @@ -354,43 +354,13 @@ def update_muscle_name(name: str) -> str: network_options = limb_circuit( network_options, - join_str(("left", "fore")), - muscles["left"]["fore"], - fore_muscle_patterns, + side="right", + limb="hind", + muscles=muscles, + contacts=("PHALANGE",), transform_mat=get_translation_matrix(off_x=-25.0, off_y=0.0) ) - # network_options = limb_circuit( - # network_options, - # join_str(("right", "fore")), - # muscles["right"]["fore"], - # fore_muscle_patterns, - # transform_mat=get_translation_matrix(off_x=25.0, off_y=0.0) - # ) - - # network_options = limb_circuit( - # network_options, - # join_str(("left", "hind")), - # muscles["left"]["hind"], - # hind_muscle_patterns, - # transform_mat=get_translation_matrix( - # off_x=-25.0, off_y=-25.0 - # ) @ get_mirror_matrix(mirror_x=True, mirror_y=False) - # ) - - # network_options = limb_circuit( - # network_options, - # join_str(("right", "hind")), - # muscles["right"]["hind"], - # hind_muscle_patterns, - # transform_mat=get_translation_matrix( - # off_x=25.0, off_y=-25.0 - # ) @ get_mirror_matrix(mirror_x=True, mirror_y=False) - # ) - - # rg_commissural_edges = connect_rg_commissural() - # network_options.add_edges(rg_commissural_edges.values()) - return network_options @@ -419,7 +389,7 @@ def run_network(*args): network_options = args[0] network = PyNetwork.from_options(network_options) - network.setup_integrator(network_options.integration) + network.setup_integrator(network_options) # data.to_file("/tmp/sim.hdf5") @@ -570,16 +540,16 @@ def main(): # Generate the network # network_options = generate_network(int(1e4)) - # network_options = generate_limb_circuit(int(1e4)) + # network_options = generate_limb_circuit(int(5e4)) network_options = generate_quadruped_circuit((5e4)) network_options.save("/tmp/network_options.yaml") # Run the network - # network = profile.profile(run_network, network_options) + network = profile.profile(run_network, network_options) # network = run_network(network_options) # Results - plot_network(network_options) + # plot_network(network_options) # run_network() From 97d0dde55a7e160aedf3ea9aa457b9eac5d2f7b1 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Fri, 6 Dec 2024 13:24:29 -0500 Subject: [PATCH 179/316] [SCRATCH] Refactored test_gui --- scratch/test_gui.py | 338 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 288 insertions(+), 50 deletions(-) diff --git a/scratch/test_gui.py b/scratch/test_gui.py index 68899db..382bf5b 100644 --- a/scratch/test_gui.py +++ b/scratch/test_gui.py @@ -76,6 +76,19 @@ def add_plot(iteration, data): f"{side}_{limb}_RG_F_DR", ] + plot_labels = [ + "RH_RG_E", + "RH_RG_F", + "LF_RG_F", + "RH_RG_F", + "LH_RG_F", + "RH_PF_FA", + "RH_PF_EA", + "RH_PF_FB", + "RH_PF_EB", + "RH_RG_F_DR", + ] + nodes_names = [ node.name for node in data.nodes @@ -84,6 +97,7 @@ def add_plot(iteration, data): plot_nodes = [ nodes_names.index(name) for name in plot_names + if name in nodes_names ] if not plot_nodes: return @@ -92,7 +106,7 @@ def add_plot(iteration, data): ( *[ data.nodes[plot_nodes[j]].output.array - for j in range(len(plot_names)) + for j in range(len(plot_nodes)) ], data.nodes[plot_nodes[-1]].external_input.array, ) @@ -123,11 +137,11 @@ def add_plot(iteration, data): "RF": imgui.IM_COL32(28, 107, 180, 255), "LF": imgui.IM_COL32(23, 163, 74, 255), "RH": imgui.IM_COL32(200, 38, 39, 255), - "LH": imgui.IM_COL32(255, 252, 212, 255), # imgui.IM_COL32(0, 0, 0, 255), + "LH": imgui.IM_COL32(255, 252, 212, 255), # imgui.IM_COL32(0, 0, 0, 255), "right_fore_RG_F": imgui.IM_COL32(28, 107, 180, 255), "left_fore_RG_F": imgui.IM_COL32(23, 163, 74, 255), "right_hind_RG_F": imgui.IM_COL32(200, 38, 39, 255), - "left_hind_RG_F": imgui.IM_COL32(255, 252, 212, 255), # imgui.IM_COL32(0, 0, 0, 255), + "left_hind_RG_F": imgui.IM_COL32(255, 252, 212, 255), # imgui.IM_COL32(0, 0, 0, 255), } with imgui_ctx.begin("States"): if implot.begin_subplots( @@ -151,13 +165,27 @@ def add_plot(iteration, data): implot.plot_line("RG-F-Dr", times, plot_data[-1, :]) implot.end_plot() if implot.begin_plot(""): - implot.setup_axis(implot.ImAxis_.x1, "Time") implot.setup_axis(implot.ImAxis_.y1, "Activity") + implot.setup_axis( + implot.ImAxis_.x1, + flags=( + implot.AxisFlags_.no_tick_labels | + implot.AxisFlags_.no_tick_marks + ) + ) implot.setup_axis_links(implot.ImAxis_.x1, -1.0, 0.0) implot.setup_axis_limits(implot.ImAxis_.y1, -1*len(plot_names), 1.0) implot.setup_axis_limits_constraints(implot.ImAxis_.x1, -1.0, 0.0) - implot.setup_axis_limits_constraints(implot.ImAxis_.y1, -1*len(plot_names), 1.0) - for j, name in enumerate(plot_names[:-1]): + implot.setup_axis_limits_constraints(implot.ImAxis_.y1, -8.2, 1.0) + implot.setup_axis_ticks( + axis=implot.ImAxis_.y1, + v_min=-8.0, + v_max=0.0, + n_ticks=int(len(plot_names[:-1])), + labels=(plot_labels[:-1])[::-1], + keep_default=False + ) + for j in range(len(plot_nodes[:-1])): if plot_names[j] in colors: implot.push_style_color(implot.Col_.line, colors.get(plot_names[j])) implot.plot_line(plot_names[j], times, plot_data[j, :] - j) @@ -165,30 +193,139 @@ def add_plot(iteration, data): else: implot.plot_line(plot_names[j], times, plot_data[j, :] - j) implot.end_plot() - if implot.begin_plot(""): - implot.setup_axis_limits(implot.ImAxis_.x1, -1.0, 0.0) - implot.setup_axis_limits(implot.ImAxis_.y1, 0.0, 4.0) - implot.setup_axis_limits_constraints(implot.ImAxis_.x1, -1.0, 0.0) - implot.setup_axis_limits_constraints(implot.ImAxis_.y1, 0.0, 4.0) - implot.setup_axis(implot.ImAxis_.y1, flags=implot.AxisFlags_.invert) - for j, limb in enumerate(("RF", "LF", "RH", "LH")): - # if len(phases_xs[j]) > 3: - implot.push_style_color( - implot.Col_.fill, - colors[limb] - ) - implot.plot_shaded( - limb, - phases_xs[j].flatten(), - phases_ys[j].flatten(), - yref=j + if len(plot_nodes) > 7: + if implot.begin_plot(""): + implot.setup_axis_limits(implot.ImAxis_.x1, -1.0, 0.0) + implot.setup_axis_limits(implot.ImAxis_.y1, 0.0, 4.0) + implot.setup_axis_limits_constraints(implot.ImAxis_.x1, -1.0, 0.0) + implot.setup_axis_limits_constraints(implot.ImAxis_.y1, 0.0, 4.0) + implot.setup_axis(implot.ImAxis_.y1, flags=implot.AxisFlags_.invert) + implot.setup_axis_ticks( + axis=implot.ImAxis_.y1, + v_min=0.5, + v_max=3.5, + n_ticks=int(4), + labels=("RF", "LF", "RH", "LH"), + keep_default=False ) - implot.pop_style_color() - implot.end_plot() + for j, limb in enumerate(("RF", "LF", "RH", "LH")): + # if len(phases_xs[j]) > 3: + implot.push_style_color( + implot.Col_.fill, + colors[limb] + ) + implot.plot_shaded( + limb, + phases_xs[j].flatten(), + phases_ys[j].flatten(), + yref=j + ) + implot.pop_style_color() + implot.end_plot() implot.end_subplots() -def draw_muscle_activity(iteration, data): +def draw_muscle_activity(iteration, data, plot_nodes, plot_names, title): + + outputs = np.vstack( + [ + data.nodes[plot_nodes[j]].output.array + for j in range(len(plot_nodes)) + ] + ) + if iteration < 1000: + plot_data = np.array(outputs[:, :iteration]) + else: + plot_data = np.array(outputs[:, iteration-1000:iteration]) + + times = np.array((np.linspace(0.0, 1.0, 1000)*-1.0)[::-1]) + + with imgui_ctx.begin(title): + if implot.begin_plot("Muscle Activity", imgui.ImVec2(-1, -1)): + implot.setup_axis(implot.ImAxis_.x1, "Time") + implot.setup_axis(implot.ImAxis_.y1, "Activity") + implot.setup_axis_links(implot.ImAxis_.x1, -1.0, 0.0) + implot.setup_axis_limits(implot.ImAxis_.x1, -1.0, 0.0) + implot.setup_axis_limits(implot.ImAxis_.y1, -1*(len(plot_nodes)-1), 1.0) + implot.setup_axis_limits_constraints(implot.ImAxis_.x1, -1.0, 0.0) + implot.setup_axis_ticks( + axis=implot.ImAxis_.y1, + v_min=-1*(len(plot_nodes)-1), + v_max=0.0, + n_ticks=int(len(plot_names)), + labels=plot_names[::-1], + keep_default=False + ) + # implot.setup_axis_limits_constraints(implot.ImAxis_.y1, -5.2, 1.0) + for j in range(len(plot_nodes)): + implot.plot_line(plot_names[j], times, plot_data[j, :] - j) + implot.end_plot() + + +def plot_hind_motor_activity(iteration, data, side="right"): + side = "right" + limb = "hind" + + muscle_names = [ + "bfa", + "ip", + "bfpst", + "rf", + "va", + "mg", + "sol", + "ta", + "ab", + "gm_dorsal", + "edl", + "fdl", + ] + + nodes_names = [ + node.name + for node in data.nodes + ] + + plot_nodes = [ + nodes_names.index(f"{side}_{limb}_{name}_Mn") + for name in muscle_names + if f"{side}_{limb}_{name}_Mn" in nodes_names + ] + draw_muscle_activity(iteration, data, plot_nodes, muscle_names, title="Hindlimb muscles") + + +def plot_fore_motor_activity(iteration, data, side="right"): + side = "right" + limb = "fore" + + muscle_names = [ + "spd", + "ssp", + "abd", + "add", + "tbl", + "tbo", + "bbs", + "bra", + "eip", + "fcu", + ] + + nodes_names = [ + node.name + for node in data.nodes + ] + + plot_nodes = [ + nodes_names.index(f"{side}_{limb}_{name}_Mn") + for name in muscle_names + if f"{side}_{limb}_{name}_Mn" in nodes_names + ] + + draw_muscle_activity(iteration, data, plot_nodes, muscle_names, title="Forelimb muscles") + + +def __draw_muscle_activity(iteration, data): """ Draw muscle activity """ side = "left" limb = "hind" @@ -221,7 +358,7 @@ def draw_muscle_activity(iteration, data): return outputs = np.vstack( [ - data.nodes[plot_nodes[j]].output.array + data.nodes[plot_nodes[j]].output for j in range(len(plot_nodes)) ] ) @@ -253,7 +390,7 @@ def draw_muscle_activity(iteration, data): # return # outputs = np.vstack( # [ - # data.nodes[plot_nodes[j]].output.array + # data.nodes[plot_nodes[j]].output # for j in range(len(plot_nodes)) # ] # ) @@ -294,7 +431,7 @@ def draw_muscle_activity(iteration, data): return outputs = np.vstack( [ - data.nodes[plot_nodes[j]].output.array + data.nodes[plot_nodes[j]].output for j in range(len(plot_nodes)) ] ) @@ -317,48 +454,115 @@ def draw_muscle_activity(iteration, data): implot.end_plot() +def draw_vn_activity(iteration, data): + """ Draw muscle activity """ + side = "left" + limb = "hind" + + vn_names = [ + f"{side}_{rate}_{axis}_{direction}_In_Vn" + for rate in ("position", "velocity") + for direction in ("clock", "cclock") + for axis in ("pitch", "roll") + for side in ("left", "right") + ] + + nodes_names = [ + node.name + for node in data.nodes + ] + + plot_nodes = [ + nodes_names.index(name) + for name in vn_names + ] + if not plot_nodes: + return + outputs = np.vstack( + + [ + data.nodes[plot_nodes[j]].output.array + for j in range(len(plot_nodes)) + ] + ) + if iteration < 1000: + plot_data = np.array(outputs[:, :iteration]) + else: + plot_data = np.array(outputs[:, iteration-1000:iteration]) + + times = np.array((np.linspace(0.0, 1.0, 1000)*-1.0)[::-1]) + + with imgui_ctx.begin("Vestibular"): + if implot.begin_plot("Vestibular Activity", imgui.ImVec2(-1, -1)): + implot.setup_axis(implot.ImAxis_.x1, "Time") + implot.setup_axis(implot.ImAxis_.y1, "Activity") + implot.setup_axis_links(implot.ImAxis_.x1, -1.0, 0.0) + implot.setup_axis_limits(implot.ImAxis_.x1, -1.0, 0.0) + implot.setup_axis_limits(implot.ImAxis_.y1, -1*len(plot_nodes), 1.0) + implot.setup_axis_limits_constraints(implot.ImAxis_.x1, -1.0, 0.0) + # implot.setup_axis_limits_constraints(implot.ImAxis_.y1, -5.2, 1.0) + for j in range(len(plot_nodes)): + implot.plot_line(vn_names[j], times, plot_data[j, :] - j) + implot.end_plot() + + def draw_network(network_options, data, iteration, edges_x, edges_y): """ Draw network """ nodes = network_options.nodes + edges = network_options.edges imgui.WindowFlags_ with imgui_ctx.begin("Full-Network"): - if implot.begin_plot("vis", imgui.ImVec2((-1, -1))): - radius = implot.plot_to_pixels(implot.Point((7.5, 7.5))) - + flags = ( + implot.AxisFlags_.no_label | + implot.AxisFlags_.no_tick_labels | + implot.AxisFlags_.no_tick_marks + ) + if implot.begin_plot( + "vis", imgui.ImVec2((-1, -1)), implot.Flags_.equal + ): + implot.setup_axis(implot.ImAxis_.x1, flags=flags) + implot.setup_axis(implot.ImAxis_.y1, flags=flags) implot.plot_line( "", xs=edges_x, ys=edges_y, flags=implot.LineFlags_.segments ) + radius = 0.1 + circ_x = radius*np.cos(np.linspace(-np.pi, np.pi, 50)) + circ_y = radius*np.sin(np.linspace(-np.pi, np.pi, 50)) for index, node in enumerate(nodes): implot.set_next_marker_style( - size=7.5# *node.visual.radius + size=10.0 # *node.visual.radius ) implot.push_style_var( implot.StyleVar_.fill_alpha, - 0.05+data.nodes[index].output.array[iteration]*2.0 + 0.05+data.nodes[index].output.array[iteration]*3.0 ) implot.plot_scatter( "##", xs=np.array((node.visual.position[0],)), ys=np.array((node.visual.position[1],)), ) + # implot.plot_line( + # "##", + # node.visual.position[0]+circ_x, + # node.visual.position[1]+circ_y + # ) implot.pop_style_var() # implot.push_plot_clip_rect() # position = implot.plot_to_pixels(implot.Point(node.visual.position[:2])) - # color = imgui.IM_COL32( - # 0, 0, 0, 255 - # ) - # implot.get_plot_draw_list().add_circle(position, 7.5, color) + # radius = implot.plot_to_pixels(0.001, 0.001) + # color = imgui.IM_COL32(255, 0, 0, 255) + # implot.get_plot_draw_list().add_circle(position, radius[0], color) # implot.pop_plot_clip_rect() # implot.push_plot_clip_rect() # color = imgui.IM_COL32( # 100, 185, 0, - # int(255*(data.nodes[index].output.array[iteration])) + # int(255*(data.nodes[index].output[iteration])) # ) # implot.get_plot_draw_list().add_circle_filled(position, 7.5, color) # implot.pop_plot_clip_rect() @@ -403,6 +607,18 @@ def draw_slider( v_min=min_value, v_max=max_value, ) + clicked, values[4] = imgui.slider_float( + label="Vn", + v=values[4], + v_min=-1.0, + v_max=max_value, + ) + clicked, values[5] = imgui.slider_float( + label="Cut", + v=values[5], + v_min=min_value, + v_max=max_value, + ) return values @@ -434,13 +650,15 @@ def draw_table(network_options, network_data): imgui.end_table() -def draw_polar(): - """ Draw polar """ - with imgui_ctx.begin("Polar"): - if imgui.begin_plot("polar"): - implot.plot_line - imgui.end_plot() +def draw_play_pause_button(button_state): + """ Draw button """ + button_title = "Pause" if button_state else "Play" + with imgui_ctx.begin("Controls"): + if imgui.button(button_title): + button_state = not button_state + print(button_state) + return button_state def main(): @@ -482,7 +700,7 @@ def main(): ) # for index in range(len(edges_xy) - 1): # edges_xy[index], edges_xy[index + 1] = connect_positions( - # edges_xy[index+1], edges_xy[index], 10.0, 0.0 + # edges_xy[index+1], edges_xy[index], 0.1, 0.0 # ) edges_x = np.array(edges_xy[:, 0]) edges_y = np.array(edges_xy[:, 1]) @@ -517,16 +735,30 @@ def main(): for index, node in enumerate(network_options.nodes) if "Ib" == node.name[-2:] ] - slider_values = np.zeros((4,)) + Vn_input_indices = [ + index + for index, node in enumerate(network_options.nodes) + if "Vn" == node.name[-2:] and node.model == "external_relay" + ] + Cut_input_indices = [ + index + for index, node in enumerate(network_options.nodes) + if "cut" == node.name[-3:] and node.model == "external_relay" + ] + slider_values = np.zeros((6,)) slider_values[0] = 0.25 input_array = np.zeros(np.shape(inputs_view)) input_array[drive_input_indices] = 0.25 - input_array[drive_input_indices[0]] *= 1.05 + button_state = False + # input_array[drive_input_indices[0]] *= 1.05 + for iteration in tqdm(range(0, N_ITERATIONS), colour="green", ascii=" >="): input_array[drive_input_indices] = slider_values[0] input_array[Ia_input_indices] = slider_values[1] input_array[II_input_indices] = slider_values[2] input_array[Ib_input_indices] = slider_values[3] + input_array[Vn_input_indices] = slider_values[4] + input_array[Cut_input_indices] = slider_values[5] inputs_view[:] = input_array network.step() @@ -536,14 +768,20 @@ def main(): _time_draw = time.time() fps = _realtime*1/(_time_draw-_time_draw_last)+(1-_realtime)*fps # print(imgui.get_io().delta_time, imgui.get_io().framerate) - if not (iteration % 2): + implot.push_style_var(implot.StyleVar_.line_weight, 2.0) + if not (iteration % 1): + time.sleep(1e-4) gui.new_frame() slider_values = draw_slider(label="d", name="Drive", values=slider_values) add_plot(buffer_iteration, network.data) + # button_state = draw_play_pause_button(button_state) draw_table(network_options, network.data) draw_network(network_options, network.data, buffer_iteration, edges_x, edges_y) - draw_muscle_activity(buffer_iteration, network.data) + plot_hind_motor_activity(buffer_iteration, network.data) + plot_fore_motor_activity(buffer_iteration, network.data) + # draw_vn_activity(buffer_iteration, network.data) gui.render_frame() + implot.pop_style_var() if __name__ == '__main__': From 1b52a89b14f333c1fb940cf0357fef9965fac2b8 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Fri, 6 Dec 2024 13:25:53 -0500 Subject: [PATCH 180/316] [SCRATCH] Udpated test_network script --- scratch/test_network.py | 160 ++++++++++++++++++++++++++++++++++------ 1 file changed, 138 insertions(+), 22 deletions(-) diff --git a/scratch/test_network.py b/scratch/test_network.py index 35a5724..2f9b061 100644 --- a/scratch/test_network.py +++ b/scratch/test_network.py @@ -3,6 +3,7 @@ from copy import deepcopy from pprint import pprint +import matplotlib.pyplot as plt import networkx as nx import numpy as np from farms_core.io.yaml import read_yaml, write_yaml @@ -10,44 +11,146 @@ from farms_network.core import options from farms_network.core.data import (NetworkConnectivity, NetworkData, NetworkStates) -from farms_network.core.network import PyNetwork +from farms_network.core.network import PyNetwork, rk4 from farms_network.core.options import NetworkOptions from scipy.integrate import ode from tqdm import tqdm -param_opts = options.LIDannerParameterOptions.defaults() -state_opts = options.LIDannerNaPStateOptions.from_kwargs(v0=0.0, h0=-70.0) -vis_opts = options.NodeVisualOptions() -danner_network = nx.read_graphml("/Users/tatarama/projects/work/research/neuromechanics/quadruped/mice/mouse-locomotion/data/config/network/siggraph_network.graphml") +def linear_network(): + """ Linear stateless network """ + param_opts = options.LinearParameterOptions.defaults() + vis_opts = options.NodeVisualOptions() -network_options = options.NetworkOptions( - directed=True, - multigraph=False, - graph={"name": "network"}, -) + network_options = options.NetworkOptions( + directed=True, + multigraph=False, + graph={"name": "network"}, + ) -for node in danner_network.nodes: network_options.add_node( - options.LIDannerNodeOptions( - name=node, + options.LinearNodeOptions( + name="node1", + parameters=param_opts, + visual=vis_opts, + ) + ) + return network_options + + +def quadruped_network(): + """ Quadruped network """ + param_opts = options.LIDannerParameterOptions.defaults() + state_opts = options.LINaPDannerStateOptions.from_kwargs(v0=0.0, h0=-70.0) + vis_opts = options.NodeVisualOptions() + + danner_network = nx.read_graphml( + "/Users/tatarama/projects/work/research/neuromechanics/quadruped/mice/mouse-locomotion/data/config/network/siggraph_network.graphml" + ) + + network_options = options.NetworkOptions( + directed=True, + multigraph=False, + graph={"name": "network"}, + ) + + for node, data in danner_network.nodes.items(): + if data["model"] == "li_nap_danner": + network_options.add_node( + options.LIDannerNodeOptions( + name=node, + parameters=param_opts, + visual=vis_opts, + state=state_opts, + ) + ) + else: + network_options.add_node( + options.LIDannerNodeOptions( + name=node, + parameters=param_opts, + visual=vis_opts, + state=state_opts, + ) + ) + + for edge, data in danner_network.edges.items(): + network_options.add_edge( + options.EdgeOptions( + source=edge[0], + target=edge[1], + weight=data["weight"], + type=data.get("type", "excitatory"), + visual=options.EdgeVisualOptions(), + ) + ) + return network_options + + +def oscillator_network(): + """ Oscillator network """ + + param_opts = options.OscillatorNodeParameterOptions.defaults() + state_opts = options.OscillatorStateOptions.from_kwargs(phase=0.0, amplitude=0.0) + vis_opts = options.NodeVisualOptions() + + network_options = options.NetworkOptions( + directed=True, + multigraph=False, + graph={"name": "network"}, + ) + + network_options.add_node( + options.OscillatorNodeOptions( + name="O1", + parameters=param_opts, + visual=vis_opts, + state=state_opts, + ) + ) + + network_options.add_node( + options.OscillatorNodeOptions( + name="O2", parameters=param_opts, visual=vis_opts, state=state_opts, ) ) -for edge, data in danner_network.edges.items(): network_options.add_edge( options.EdgeOptions( - source=edge[0], - target=edge[1], - weight=data["weight"], - type=data.get("type", "excitatory"), + source="O1", + target="O2", + weight=1.0, + type="excitatory", + visual=options.EdgeVisualOptions(), + parameters=options.OscillatorEdgeParameterOptions( + phase_difference=np.pi/2 + ) + ) + ) + + network_options.add_edge( + options.EdgeOptions( + source="O2", + target="O1", + weight=1.0, + type="excitatory", visual=options.EdgeVisualOptions(), + parameters=options.OscillatorEdgeParameterOptions( + phase_difference=-np.pi/4 + ) ) ) + return network_options + +network_options = linear_network() + +network_options = oscillator_network() +# network_options = quadruped_network() + data = NetworkData.from_options(network_options) network = PyNetwork.from_options(network_options) @@ -68,9 +171,22 @@ # integrator.integrate(integrator.t + 1e-3) # # Integrate -states = np.array(np.arange(0, len(data.states.array)), dtype=np.double) -network.ode(0.0, states) -for iteration in tqdm(range(0, 100000), colour='green'): +iterations = 10000 +states = np.ones((iterations+1, len(data.states.array)))*1.0 +outputs = np.ones((iterations, len(data.outputs.array)))*1.0 +# states[0, 2] = -1.0 + +for iteration in tqdm(range(0, iterations), colour='green', ascii=' >='): # integrator.set_initial_value(integrator.y, integrator.t) - network.ode(0.0, states) + # states[iteration+1, :] = states[iteration, :] + np.array(network.ode(iteration*1e-3, states[iteration, :]))*1e-3 + # network.ode(0.0, states) + # network.ode(0.0, states) + # network.ode(0.0, states) + network.data.external_inputs.array[:] = np.ones((1,))*np.sin(iteration*1e-3) + states[iteration+1, :] = rk4(iteration*1e-3, states[iteration, :], network.ode, step_size=1) + outputs[iteration, :] = network.data.outputs.array + # integrator.integrate(integrator.t+(iteration*1e-3)) + +plt.plot(np.linspace(0.0, iterations*1e-3, iterations), outputs[:, :]) +plt.show() From 1c81f5c81d2a4d401dfb64ff1f1022df9ceff7b3 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Tue, 10 Dec 2024 17:34:01 -0500 Subject: [PATCH 181/316] [NUMERIC] Minor refactoring of rk4 integrator Using locally declared states_tmp pointer for better readability --- farms_network/numeric/integrators_cy.pyx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/farms_network/numeric/integrators_cy.pyx b/farms_network/numeric/integrators_cy.pyx index 24f350f..27f5604 100644 --- a/farms_network/numeric/integrators_cy.pyx +++ b/farms_network/numeric/integrators_cy.pyx @@ -42,25 +42,25 @@ cdef class RK4Solver: # Compute k1 sys.evaluate(time, state, k1) - for i in range(self.dim): - self.states_tmp.array[i] = state[i] + (self.dt * k1[i])/2.0 # Compute k2 - sys.evaluate(time + dt2, self.states_tmp.array, k2) for i in range(self.dim): - self.states_tmp.array[i] = state[i] + (self.dt * k2[i])/2.0 + states_tmp[i] = state[i] + (dt2 * k1[i]) + sys.evaluate(time + dt2, states_tmp, k2) # Compute k3 - sys.evaluate(time + dt2, self.states_tmp.array, k3) for i in range(self.dim): - self.states_tmp.array[i] = state[i] + self.dt * k3[i] + states_tmp[i] = state[i] + (dt2 * k2[i]) + sys.evaluate(time + dt2, states_tmp, k3) # Compute k4 - sys.evaluate(time + 1.0, states_tmp, k4) + for i in range(self.dim): + states_tmp[i] = state[i] + self.dt * k3[i] + sys.evaluate(time + self.dt, states_tmp, k4) # Update y: y = y + (k1 + 2*k2 + 2*k3 + k4) / 6 for i in range(self.dim): - state[i] += dt6 * (k1[i] + 2.0 * k2[i] + 2.0 * k3[i] + k4[i]) + state[i] = state[i] + dt6 * (k1[i] + 2.0 * k2[i] + 2.0 * k3[i] + k4[i]) cdef class EulerMaruyamaSolver: From 998195b17f22296467ec30c98ab1060dde38866c Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Tue, 10 Dec 2024 17:36:40 -0500 Subject: [PATCH 182/316] [MODELS] Minor refactoring in li danner models output functions --- farms_network/models/li_danner.pyx | 9 +++++---- farms_network/models/li_nap_danner.pyx | 9 +++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/farms_network/models/li_danner.pyx b/farms_network/models/li_danner.pyx index df8b950..7081d5f 100644 --- a/farms_network/models/li_danner.pyx +++ b/farms_network/models/li_danner.pyx @@ -95,11 +95,12 @@ cdef double output( cdef LIDannerNodeParameters params = ( node.parameters)[0] cdef double _n_out = 0.0 - if states[STATE.v] >= params.v_max: + cdef double state_v = states[STATE.v] + if state_v >= params.v_max: _n_out = 1.0 - elif (params.v_thr <= states[STATE.v]) and (states[STATE.v] < params.v_max): - _n_out = (states[STATE.v] - params.v_thr) / (params.v_max - params.v_thr) - elif states[STATE.v] < params.v_thr: + elif (params.v_thr <= state_v) and (state_v < params.v_max): + _n_out = (state_v - params.v_thr) / (params.v_max - params.v_thr) + elif state_v < params.v_thr: _n_out = 0.0 return _n_out diff --git a/farms_network/models/li_nap_danner.pyx b/farms_network/models/li_nap_danner.pyx index 07bf2ee..220c1a8 100644 --- a/farms_network/models/li_nap_danner.pyx +++ b/farms_network/models/li_nap_danner.pyx @@ -114,11 +114,12 @@ cdef double output( cdef LINaPDannerNodeParameters params = ( node.parameters)[0] cdef double _n_out = 0.0 - if states[STATE.v] >= params.v_max: + cdef double state_v = states[STATE.v] + if state_v >= params.v_max: _n_out = 1.0 - elif (params.v_thr <= states[STATE.v]) and (states[STATE.v] < params.v_max): - _n_out = (states[STATE.v] - params.v_thr) / (params.v_max - params.v_thr) - elif states[0] < params.v_thr: + elif (params.v_thr <= state_v) and (state_v < params.v_max): + _n_out = (state_v - params.v_thr) / (params.v_max - params.v_thr) + elif state_v < params.v_thr: _n_out = 0.0 return _n_out From bc27dd394146254fd78af951f5cb67a5995a4613 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Tue, 10 Dec 2024 17:37:08 -0500 Subject: [PATCH 183/316] [MODELS] Minor refactoring li_danner model ode function --- farms_network/models/li_danner.pyx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/farms_network/models/li_danner.pyx b/farms_network/models/li_danner.pyx index 7081d5f..bafbd9c 100644 --- a/farms_network/models/li_danner.pyx +++ b/farms_network/models/li_danner.pyx @@ -48,7 +48,9 @@ cdef void ode( ) noexcept: """ ODE """ # Parameters - cdef LIDannerNodeParameters params = ( node[0].parameters)[0] + cdef LIDannerNodeParameters params = ( + node[0].parameters + )[0] # States cdef double state_v = states[STATE.v] @@ -77,7 +79,7 @@ cdef void ode( cdef double i_noise = noise # dV - derivatives[STATE.v] = (-(i_leak + i_noise + _sum)/params.c_m) + derivatives[STATE.v] = -(i_leak + i_noise + _sum)/params.c_m cdef double output( From 57aa2473f81b7797c1ca0f8397162238e396cc56 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Wed, 11 Dec 2024 09:58:17 -0500 Subject: [PATCH 184/316] [NOISE] Added random_samples vector to separate computation loops in OU To improve the performance, it proved to be benefecial to loop the compuation of random samples outside the main computation loop and then reuse the results --- farms_network/noise/ornstein_uhlenbeck.pxd | 1 + farms_network/noise/ornstein_uhlenbeck.pyx | 12 +++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/farms_network/noise/ornstein_uhlenbeck.pxd b/farms_network/noise/ornstein_uhlenbeck.pxd index 41a4b6e..c332cf2 100644 --- a/farms_network/noise/ornstein_uhlenbeck.pxd +++ b/farms_network/noise/ornstein_uhlenbeck.pxd @@ -58,6 +58,7 @@ cdef class OrnsteinUhlenbeck(SDESystem): OrnsteinUhlenbeckParameters* parameters normal_distribution[double] distribution mt19937_64 random_generator + double* random_samples cdef void evaluate_a(self, double time, double[:] states, double[:] drift) noexcept cdef void evaluate_b(self, double time, double[:] states, double[:] diffusion) noexcept diff --git a/farms_network/noise/ornstein_uhlenbeck.pyx b/farms_network/noise/ornstein_uhlenbeck.pyx index 6b5469a..0acdbc4 100644 --- a/farms_network/noise/ornstein_uhlenbeck.pyx +++ b/farms_network/noise/ornstein_uhlenbeck.pyx @@ -66,6 +66,12 @@ cdef class OrnsteinUhlenbeck(SDESystem): "Failed to allocate memory for OrnsteinUhlenbeck parameter TAU" ) + self.random_samples = malloc(self.n_dim*sizeof(double)) + if self.random_samples is NULL: + raise MemoryError( + "Failed to allocate memory for OrnsteinUhlenbeck random samples" + ) + def __dealloc__(self): """ Deallocate any manual memory as part of clean up """ @@ -77,6 +83,8 @@ cdef class OrnsteinUhlenbeck(SDESystem): free(self.parameters.tau) if self.parameters is not NULL: free(self.parameters) + if self.random_samples is not NULL: + free(self.random_samples) def __init__(self, noise_options: List[OrnsteinUhlenbeckOptions], random_seed: int=None): super().__init__() @@ -99,9 +107,11 @@ cdef class OrnsteinUhlenbeck(SDESystem): ) cdef normal_distribution[double] distribution = self.distribution cdef mt19937_64 random_generator = self.random_generator + for j in range(self.n_dim): + self.random_samples[j] = distribution(random_generator) for j in range(self.n_dim): diffusion[j] = params.sigma[j]*csqrt(2.0/params.tau[j])*( - distribution(random_generator) + self.random_samples[j] ) def py_evaluate_a(self, time, states, drift): From dc7e5efe5f3cad7468baa7d7336884927c2215cc Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Wed, 11 Dec 2024 10:15:04 -0500 Subject: [PATCH 185/316] [CORE][BUG][NETWORK] Fixed issue with states data pointer --- farms_network/core/network.pyx | 40 +++++++++++++++++++--------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/farms_network/core/network.pyx b/farms_network/core/network.pyx index e273b66..6ba4ff2 100644 --- a/farms_network/core/network.pyx +++ b/farms_network/core/network.pyx @@ -40,6 +40,7 @@ from .options import (EdgeOptions, IntegrationOptions, NetworkOptions, cdef inline void ode( double time, + double[:] states_arr, NetworkDataCy data, Network* network, double[:] node_outputs_tmp, @@ -52,7 +53,8 @@ cdef inline void ode( cdef Edge** edges = network.edges nnodes = network.nnodes - cdef double* states = &data.states.array[0] + # It is important to use the states passed to the function and not from the data.states + cdef double* states = &states_arr[0] cdef unsigned int* states_indices = &data.states.indices[0] cdef double* derivatives = &data.derivatives.array[0] @@ -282,30 +284,34 @@ cdef class PyNetwork(ODESystem): cpdef void step(self): """ Step the network state """ # cdef NetworkDataCy data = self.data - self.iteration += 1 - self.ode_integrator.step( - self, (self.iteration%self.buffer_size)*self.timestep, self.data.states.array - ) - logger((self.iteration%self.buffer_size), self.data, self.network) - - cdef void evaluate(self, double time, double[:] states, double[:] derivatives) noexcept: - """ Evaluate the ODE """ - # Update noise model - cdef NetworkDataCy data = self.data cdef SDESystem sde_system = self.sde_system cdef EulerMaruyamaSolver sde_integrator = self.sde_integrator sde_integrator.step( sde_system, (self.iteration%self.buffer_size)*self.timestep, - data.noise.states + self.data.noise.states ) _noise_states_to_output( - data.noise.states, - data.noise.indices, - data.noise.outputs + self.data.noise.states, + self.data.noise.indices, + self.data.noise.outputs + ) + self.ode_integrator.step( + self, + (self.iteration%self.buffer_size)*self.timestep, + self.data.states.array ) - data.states.array[:] = states[:] - ode(time, data, self.network, self.__tmp_node_outputs) + # Logging + # TODO: Use network options to check global logging flag + logger((self.iteration%self.buffer_size), self.data, self.network) + self.iteration += 1 + + cdef void evaluate(self, double time, double[:] states, double[:] derivatives) noexcept: + """ Evaluate the ODE """ + # Update noise model + cdef NetworkDataCy data = self.data + + ode(time, states, data, self.network, self.__tmp_node_outputs) data.outputs.array[:] = self.__tmp_node_outputs derivatives[:] = data.derivatives.array From d78ce2f49ed1c8813c2da285f1e52ea9c0c3d27f Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Wed, 11 Dec 2024 10:20:37 -0500 Subject: [PATCH 186/316] [NUMERIC] Renamed state to states in rk4 step method for readability --- farms_network/numeric/integrators_cy.pyx | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/farms_network/numeric/integrators_cy.pyx b/farms_network/numeric/integrators_cy.pyx index 27f5604..9f4abbb 100644 --- a/farms_network/numeric/integrators_cy.pyx +++ b/farms_network/numeric/integrators_cy.pyx @@ -30,7 +30,7 @@ cdef class RK4Solver: array=np.full(shape=dim, fill_value=0.0, dtype=NPDTYPE,) ) - cdef void step(self, ODESystem sys, double time, double[:] state) noexcept: + cdef void step(self, ODESystem sys, double time, double[:] states) noexcept: cdef unsigned int i cdef double dt2 = self.dt / 2.0 cdef double dt6 = self.dt / 6.0 @@ -41,26 +41,28 @@ cdef class RK4Solver: cdef double[:] states_tmp = self.states_tmp.array # Compute k1 - sys.evaluate(time, state, k1) + sys.evaluate(time, states, k1) # Compute k2 for i in range(self.dim): - states_tmp[i] = state[i] + (dt2 * k1[i]) + states_tmp[i] = states[i] + (dt2 * k1[i]) sys.evaluate(time + dt2, states_tmp, k2) # Compute k3 for i in range(self.dim): - states_tmp[i] = state[i] + (dt2 * k2[i]) + states_tmp[i] = states[i] + (dt2 * k2[i]) sys.evaluate(time + dt2, states_tmp, k3) # Compute k4 for i in range(self.dim): - states_tmp[i] = state[i] + self.dt * k3[i] + states_tmp[i] = states[i] + self.dt * k3[i] sys.evaluate(time + self.dt, states_tmp, k4) # Update y: y = y + (k1 + 2*k2 + 2*k3 + k4) / 6 for i in range(self.dim): - state[i] = state[i] + dt6 * (k1[i] + 2.0 * k2[i] + 2.0 * k3[i] + k4[i]) + states[i] = states[i] + dt6 * ( + k1[i] + 2.0 * k2[i] + 2.0 * k3[i] + k4[i] + ) cdef class EulerMaruyamaSolver: From 8377ded32def157fef87f3e2e60e591d9717266f Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Wed, 11 Dec 2024 12:15:42 -0500 Subject: [PATCH 187/316] [EX][WIP] Refactoring zhang22 paper model --- examples/zhang22/run.py | 376 +++++++++++++++++++++++++--------------- 1 file changed, 238 insertions(+), 138 deletions(-) diff --git a/examples/zhang22/run.py b/examples/zhang22/run.py index 939b795..3cdbf6b 100644 --- a/examples/zhang22/run.py +++ b/examples/zhang22/run.py @@ -2,76 +2,100 @@ https://doi.org/10.7554/eLife.73424 paper network """ -import networkx as nx -import numpy as np -import farms_pylog as pylog import os +from copy import deepcopy +from pprint import pprint - -class RhythmGenerator(nx.DiGraph): +import farms_pylog as pylog +import matplotlib.pyplot as plt +import networkx as nx +import numpy as np +from farms_core.io.yaml import read_yaml, write_yaml +from farms_core.options import Options +from farms_network.core import options +from farms_network.core.data import (NetworkConnectivity, NetworkData, + NetworkStates) +from farms_network.core.network import PyNetwork, rk4 +from farms_network.core.options import NetworkOptions +from scipy.integrate import ode +from tqdm import tqdm + + +class RhythmGenerator: """Generate RhythmGenerator Network""" def __init__(self, name="", anchor_x=0.0, anchor_y=0.0): """Initialization.""" - super().__init__(name=name) - - @classmethod - def generate_nodes_edges(cls, name, anchor_x=0.0, anchor_y=0.0): - obj = cls(name, anchor_x, anchor_y) - obj.add_neurons(anchor_x, anchor_y) - obj.add_connections() - return obj - - def add_neurons(self, anchor_x, anchor_y): - """Add neurons.""" - self.add_node( - self.name + "-RG-F", - label="F", - model="lif_danner_nap", - x=1.0 + anchor_x, - y=0.0 + anchor_y, - color="r", - m_e=0.1, - b_e=0.0, - v0=-62.5, - h0=np.random.uniform(0, 1), - ) - self.add_node( - self.name + "-RG-E", - label="E", - model="lif_danner_nap", - x=1.0 + anchor_x, - y=4.0 + anchor_y, - color="b", - m_e=0.0, - b_e=0.1, - v0=-62.5, - h0=np.random.uniform(0, 1), - ) - self.add_node( - self.name + "-In-F", - label="In", - model="lif_danner", - x=0.0 + anchor_x, - y=2.0 + anchor_y, - color="m", - v0=-60.0, - ) - self.add_node( - self.name + "-In-E", - label="In", - model="lif_danner", - x=2.0 + anchor_x, - y=2.0 + anchor_y, - color="m", - v0=-60.0, - ) - - def add_connections(self): - self.add_edge(self.name + "-RG-F", self.name + "-In-F", weight=0.4) - self.add_edge(self.name + "-In-F", self.name + "-RG-E", weight=-1.0) - self.add_edge(self.name + "-RG-E", self.name + "-In-E", weight=0.4) - self.add_edge(self.name + "-In-E", self.name + "-RG-F", weight=-0.08) + super().__init__() + self.name = name + + def nodes(self): + """Add nodes.""" + nodes = {} + nodes["RG-F"] = options.LINaPDannerNodeOptions( + name=self.name + "-RG-F", + parameters=options.LINaPDannerParameterOptions.defaults(), + visual=options.NodeVisualOptions(label="F", color=[1.0, 0.0, 0.0]), + state=options.LINaPDannerStateOptions.from_kwargs( + v0=-62.5, h0=np.random.uniform(0, 1) + ), + ) + + nodes["RG-E"] = options.LINaPDannerNodeOptions( + name=self.name + "-RG-E", + parameters=options.LINaPDannerParameterOptions.defaults(), + visual=options.NodeVisualOptions(label="E", color=[0.0, 1.0, 0.0]), + state=options.LINaPDannerStateOptions.from_kwargs( + v0=-62.5, h0=np.random.uniform(0, 1) + ), + ) + + nodes["In-F"] = options.LIDannerNodeOptions( + name=self.name + "-In-F", + parameters=options.LIDannerParameterOptions.defaults(), + visual=options.NodeVisualOptions(label="In", color=[0.2, 0.2, 0.2]), + state=options.LIDannerStateOptions.from_kwargs(v0=-60.0,), + ) + + nodes["In-E"] = options.LIDannerNodeOptions( + name=self.name + "-In-E", + parameters=options.LIDannerParameterOptions.defaults(), + visual=options.NodeVisualOptions(label="In", color=[0.2, 0.2, 0.2]), + state=options.LIDannerStateOptions.from_kwargs(v0=-60.0,), + ) + return nodes + + def edges(self): + edges = {} + edges["RG-F-to-In-F"] = options.EdgeOptions( + source=self.name + "-RG-F", + target=self.name + "-In-F", + weight=0.4, + type="excitatory", + visual=options.EdgeVisualOptions(), + ) + edges["In-F-to-RG-E"] = options.EdgeOptions( + source=self.name + "-In-F", + target=self.name + "-RG-E", + weight=-1.0, + type="inhibitory", + visual=options.EdgeVisualOptions(), + ) + edges["RG-E-to-In-E"] = options.EdgeOptions( + source=self.name + "-RG-E", + target=self.name + "-In-E", + weight=0.4, + type="excitatory", + visual=options.EdgeVisualOptions(), + ) + edges["In-E-to-RG-F"] = options.EdgeOptions( + source=self.name + "-In-E", + target=self.name + "-RG-F", + weight=-0.08, + type="inhibitory", + visual=options.EdgeVisualOptions(), + ) + return edges class Commissural(nx.DiGraph): @@ -82,27 +106,24 @@ def __init__(self, name="", anchor_x=0.0, anchor_y=0.0): """Initialization.""" super().__init__(name=name) - @classmethod - def generate_nodes_edges(cls, name, anchor_x=0.0, anchor_y=0.0): - obj = cls(name, anchor_x, anchor_y) - obj.add_neurons(anchor_x, anchor_y) - obj.add_connections() - return obj - - def add_neurons(self, anchor_x, anchor_y,): - """Add neurons.""" - + def nodes(self): + """Add nodes.""" + nodes = {} # V3 - # for side in ("LEFT", "RIGHT"): - # for leg in ("FORE", "HIND"): - self.add_node( - self.name + "-V3-E-Left-Fore", - label="V3-E", - model="lif_danner", - x=-1.0 + anchor_x, - y=8.0 + anchor_y, - color="g", - v0=-60.0, + node[] = options.LINaPDannerNodeOptions( + name=self.name + "-RG-F", + parameters=options.LINaPDannerParameterOptions.defaults(), + visual=options.NodeVisualOptions(label="F", color=[1.0, 0.0, 0.0]), + state=options.LINaPDannerStateOptions.from_kwargs( + v0=-62.5, h0=np.random.uniform(0, 1) + ), + ) + + nodes[self.name + "-V3-E-Left-Fore"] = options.LIDannerNodeOptions( + name=self.name + "-V3-E-Left-Fore", + parameters=options.LIDannerParameterOptions.defaults(), + visual=options.NodeVisualOptions(label="In", color=[0.2, 0.2, 0.2]), + state=options.LIDannerStateOptions.from_kwargs(v0=-60.0,), ) self.add_node( @@ -419,64 +440,143 @@ def generate_network(): """ Generate network """ # Main network - network = nx.DiGraph() + network_options = options.NetworkOptions( + directed=True, + multigraph=False, + graph={"name": "zhang2022"}, + ) # Generate rhythm centers - rhythm1 = RhythmGenerator.generate_nodes_edges(name="LEFT-FORE", anchor_x=-5.0, anchor_y=4.0) - rhythm2 = RhythmGenerator.generate_nodes_edges(name="LEFT-HIND", anchor_x=-5.0, anchor_y=-6.0) - rhythm3 = RhythmGenerator.generate_nodes_edges(name="RIGHT-FORE", anchor_x=5.0, anchor_y=4.0) - rhythm4 = RhythmGenerator.generate_nodes_edges(name="RIGHT-HIND", anchor_x=5.0, anchor_y=-6.0) - - # Commissural - commissural = Commissural.generate_nodes_edges(name="commissural") - - # LSPN - lpsn = LPSN.generate_nodes_edges(name="lspn") - - network = nx.compose_all( - [rhythm1, rhythm2, rhythm3, rhythm4, commissural, lpsn] + for side in ("LEFT", "RIGHT"): + for limb in ("FORE", "HIND"): + rhythm = RhythmGenerator(name=f"{side}-{limb}") + network_options.add_nodes((rhythm.nodes()).values()) + network_options.add_edges((rhythm.edges()).values()) + + flexor_drive = options.LinearNodeOptions( + name="FD", + parameters=options.LinearParameterOptions.defaults(slope=0.1, bias=0.0), + visual=options.NodeVisualOptions(), ) - - colors = { - "RG-F": "fill=flex!40", - "RG-E": "fill=ext!40", - "In-E": "fill=inf!40", - "In-F": "fill=inf!40", - "V2a-diag": "fill=red!40", - "commissural-CINi1": "fill=green!40", - "commissural-V0V": "fill=red!40", - "commissural-V0D": "fill=green!40", - "commissural-V3": "fill=red!40", - "commissural-V2a": "fill=green!40", - "commissural-IniV0V": "fill=red!40", - } - - node_options = { - name: colors.get("-".join(name.split("-")[-2:]), "fill=yellow!40") - for name, node in network.nodes.items() - } - - nx.write_graphml(network, "./config/auto_zhang_2022.graphml") - nx.write_latex( - network, - "zhang2022_figure.tex", - pos={name: (node['x'], node['y']) for name, node in network.nodes.items()}, - as_document=True, - caption="A path graph", - latex_label="fig1", - node_options=node_options, - default_node_options="my-node", - node_label={ - name: node["label"] - for name, node in network.nodes.items() - }, - default_edge_options="[color=black, thick, -{Latex[scale=1.0]}, bend left, looseness=0.75]", - document_wrapper=_DOC_WRAPPER_TIKZ, + extensor_drive = options.LinearNodeOptions( + name="ED", + parameters=options.LinearParameterOptions.defaults(slope=0.0, bias=0.1), + visual=options.NodeVisualOptions(), ) - - latex_code = nx.to_latex(network) # a string rather than a file - - os.system("pdflatex zhang2022_figure.tex") + network_options.add_node(flexor_drive) + network_options.add_node(extensor_drive) + for side in ("LEFT", "RIGHT"): + for limb in ("FORE", "HIND"): + network_options.add_edge( + options.EdgeOptions( + source="FD", + target=f"{side}-{limb}-RG-F", + weight=1.0, + type="excitatory", + visual=options.EdgeVisualOptions(), + ) + ) + network_options.add_edge( + options.EdgeOptions( + source="ED", + target=f"{side}-{limb}-RG-E", + weight=1.0, + type="excitatory", + visual=options.EdgeVisualOptions(), + ) + ) + + data = NetworkData.from_options(network_options) + + network = PyNetwork.from_options(network_options) + + # nnodes = len(network_options.nodes) + # integrator.set_initial_value(np.zeros(len(data.states.array),), 0.0) + + # print("Data ------------", np.array(network.data.states.array)) + + # data.to_file("/tmp/sim.hdf5") + + # integrator.integrate(integrator.t + 1e-3) + + # # Integrate + iterations = 10000 + states = np.ones((iterations+1, len(data.states.array)))*1.0 + outputs = np.ones((iterations, len(data.outputs.array)))*1.0 + # states[0, 2] = -1.0 + + for iteration in tqdm(range(0, iterations), colour='green', ascii=' >='): + network.data.external_inputs.array[:] = np.ones((1,))*(iteration/iterations)*1.0 + states[iteration+1, :] = rk4(iteration*1e-3, states[iteration, :], network.ode, step_size=1) + outputs[iteration, :] = network.data.outputs.array + + plt.figure() + plt.fill_between( + np.linspace(0.0, iterations*1e-3, iterations), outputs[:, 0], + alpha=0.2, lw=1.0, + ) + plt.plot( + np.linspace(0.0, iterations*1e-3, iterations), outputs[:, 0], + label="RG-F" + ) + plt.fill_between( + np.linspace(0.0, iterations*1e-3, iterations), outputs[:, 1], + alpha=0.2, lw=1.0, + ) + plt.plot(np.linspace(0.0, iterations*1e-3, iterations), outputs[:, 1], label="RG-E") + plt.legend() + + network_options.save("/tmp/netwok_options.yaml") + + graph = nx.node_link_graph( + network_options, + directed=True, + multigraph=False, + link="edges", + name="name", + source="source", + target="target" + ) + plt.figure() + nx.draw( + graph, pos=nx.nx_agraph.graphviz_layout(graph), + node_shape="o", + connectionstyle="arc3,rad=-0.2", + with_labels=True, + ) + plt.show() + + # # Commissural + # commissural = Commissural.generate_nodes_edges(name="commissural") + + # # LSPN + # lpsn = LPSN.generate_nodes_edges(name="lspn") + + # network = nx.compose_all( + # [rhythm1, rhythm2, rhythm3, rhythm4, commissural, lpsn] + # ) + + # nx.write_graphml(network, "./config/auto_zhang_2022.graphml") + # nx.write_latex( + # network, + # "zhang2022_figure.tex", + # pos={name: (node['x'], node['y']) for name, node in network.nodes.items()}, + # as_document=True, + # caption="A path graph", + # latex_label="fig1", + # node_options=node_options, + # default_node_options="my-node", + # node_label={ + # name: node["label"] + # for name, node in network.nodes.items() + # }, + # default_edge_options="[color=black, thick, -{Latex[scale=1.0]}, bend left, looseness=0.75]", + # document_wrapper=_DOC_WRAPPER_TIKZ, + # ) + + # latex_code = nx.to_latex(network) # a string rather than a file + + # os.system("pdflatex zhang2022_figure.tex") def main(): From 4db791ea02a183ce4315dabce25703ba739ab745 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Wed, 11 Dec 2024 13:48:30 -0500 Subject: [PATCH 188/316] [GIT] Added cython cpp files to ignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 56da4a3..b85fb08 100644 --- a/.gitignore +++ b/.gitignore @@ -36,8 +36,9 @@ build/ *.so *.dylib -# Generated cython c-files +# Generated cython c/cpp-files *.c +*.cpp # Generated cython perf html files *.html From 70269ca88e0f61bd3c1a3e6a73d5d78d091c632a Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Wed, 11 Dec 2024 13:48:56 -0500 Subject: [PATCH 189/316] [EX][MOUSE] Added brainstem circuit --- examples/mouse/components.py | 101 ++++++++++++++++++++++++++++++----- examples/mouse/run.py | 58 ++++++++++++++++++-- 2 files changed, 141 insertions(+), 18 deletions(-) diff --git a/examples/mouse/components.py b/examples/mouse/components.py index 7bc4feb..79edd95 100644 --- a/examples/mouse/components.py +++ b/examples/mouse/components.py @@ -258,6 +258,44 @@ def create_edges( return edges +class BrainStemDrive: + """ Generate Brainstem drive network """ + + def __init__(self, name="", transform_mat=np.identity(3)): + """Initialization.""" + super().__init__() + self.name = name + self.transform_mat = transform_mat + + def nodes(self): + """Add nodes.""" + node_specs = [ + ( + join_str(("BS", "DR")), + "ExternalRelay", + np.array((3.0, 0.0)), + "A", + [0.0, 0.0, 0.0], + {}, + {}, + ), + ] + + # Loop through the node specs to create each node using the create_node function + nodes = create_nodes(node_specs, self.name, self.transform_mat) + return nodes + + def edges(self): + """Add edges.""" + + # Define edge details in a list for easier iteration + edge_specs = [] + + # Loop through the edge specs to create each edge + edges = create_edges(edge_specs, self.name) + return edges + + class RhythmGenerator: """Generate RhythmGenerator Network""" @@ -1422,12 +1460,12 @@ def define_muscle_patterns() -> dict: "ssp": ["FA", "EA", "FB", "EB"], "abd": ["FA", "EA", "FB", "EB"], "add": ["FA", "EA", "FB", "EB"], - "tbl": ["FA", "EA", "FB", "EB"], - "tbo": ["FA", "EA", "FB", "EB"], + "tbl": ["EA", "EB"], + "tbo": ["EA", "EB"], "bbs": ["FA", "FB"], - "bra": ["FA", "EA", "FB", "EB"], - "ecu": ["FA", "EA", "FB", "EB"], - "fcu": ["FA", "EA", "FB", "EB"], + "bra": ["FA", "FB"], + "eip": ["FA", "FB"], + "fcu": ["EA", "EB"], } } return muscles_patterns @@ -1529,7 +1567,7 @@ def limb_circuit( ) network_options.add_nodes(motor.nodes().values()) - network_options.add_edges(motor.edges().values()) + # network_options.add_edges(motor.edges().values()) # Connect pattern formation to motor neurons for muscle, patterns in muscles_patterns[limb].items(): @@ -1561,14 +1599,14 @@ def limb_circuit( "edl": muscles_patterns["hind"]["edl"], }, "fore": { - "ssp": muscles_patterns["fore"]["ssp"], - "bra": muscles_patterns["fore"]["bra"], + "spd": muscles_patterns["fore"]["spd"], + "eip": muscles_patterns["fore"]["eip"], } } II_feedback_to_rg = { "hind": ["ip", "ta",], - "fore": ["ssp", "bra"] + "fore": ["ssp", "bra", "eip"] } Ia_reciprocal_inhibition_extensor2flexor = { @@ -1578,7 +1616,7 @@ def limb_circuit( }, "fore": { "extensors": ["ssp", "tbl", "tbo", "fcu"], - "flexors": ["spd", "bra", "bbs", "ecu"], + "flexors": ["spd", "bra", "bbs", "eip"], } } @@ -1589,7 +1627,7 @@ def limb_circuit( }, "fore": { "extensors": ["ssp", "tbl", "tbo", "fcu"], - "flexors": ["spd", "bra", "bbs", "ecu"], + "flexors": ["spd", "bra", "bbs", "eip"], } } @@ -1600,7 +1638,7 @@ def limb_circuit( }, "fore": { "extensors": ["ssp", "tbl", "tbo", "fcu"], - "flexors": ["spd", "bra", "bbs", "ecu"], + "flexors": ["spd", "bra", "bbs", "eip"], } } @@ -1611,7 +1649,7 @@ def limb_circuit( }, "fore": { "extensors": ["ssp", "tbl", "tbo", "fcu"], - "flexors": ["spd", "bra", "bbs", "ecu"], + "flexors": ["spd", "bra", "bbs", "eip"], } } # Type II connections @@ -1726,7 +1764,7 @@ def interlimb_circuit( # Vestibular Circuit # ###################### def vestibular_circuit( - network_options: options.NetworkLogOptions, + network_options: options.NetworkOptions, position=True, velocity=True, transform_mat=np.identity(3), @@ -1764,6 +1802,35 @@ def vestibular_circuit( return network_options +################## +# BrainStemDrive # +################## +def brain_stem_circuit( + network_options: options.NetworkOptions, + transform_mat=np.identity(3), +): + # Brain stem global drive(alpha) + brain_stem = BrainStemDrive( + transform_mat=transform_mat + ) + network_options.add_nodes(brain_stem.nodes().values()) + network_options.add_edges(brain_stem.edges().values()) + + edge_specs = [] + for node in network_options.nodes: + if ("DR" in node.name) and node.model == "linear": + edge_specs.append( + ( + ("BS", "DR"), + (node.name,), + 1.0, + "excitatory", + ) + ) + edges = create_edges(edge_specs, base_name="") + return edges + + ##################### # Quadruped Circuit # ##################### @@ -1884,4 +1951,10 @@ def quadruped_circuit( ) network_options.add_edges(vn_edges.values()) + edges = brain_stem_circuit( + network_options, + transform_mat=get_translation_matrix(off_x=0.0, off_y=40.0) + ) + network_options.add_edges(edges.values()) + return network_options diff --git a/examples/mouse/run.py b/examples/mouse/run.py index d9716f8..07a3566 100644 --- a/examples/mouse/run.py +++ b/examples/mouse/run.py @@ -395,14 +395,21 @@ def run_network(*args): # Integrate N_ITERATIONS = network_options.integration.n_iterations - states = np.ones((len(network.data.states.array),)) * 1.0 + # states = np.ones((len(network.data.states.array),)) * 1.0 # network_gui = NetworkGUI(data=data) # network_gui.run() inputs_view = network.data.external_inputs.array + drive_input_indices = [ + index + for index, node in enumerate(network_options.nodes) + if "DR" in node.name and node.model == "linear" + ] + inputs = np.zeros((len(inputs_view),)) for iteration in tqdm(range(0, N_ITERATIONS), colour="green", ascii=" >="): - inputs_view[:] = (iteration / N_ITERATIONS) * 1.0 + inputs[drive_input_indices] = 0.02 + inputs_view[:] = inputs # states = rk4(iteration * 1e-3, states, network.ode, step_size=1) # states = network.integrator.step(network, iteration * 1e-3, states) network.step() @@ -544,13 +551,56 @@ def main(): network_options = generate_quadruped_circuit((5e4)) network_options.save("/tmp/network_options.yaml") + graph = nx.node_link_graph( + network_options, + directed=True, + multigraph=False, + link="edges", + name="name", + source="source", + target="target", + ) + + # Run the network - network = profile.profile(run_network, network_options) + # network = profile.profile(run_network, network_options) # network = run_network(network_options) - # Results + # nodes_names = [ + # node.name + # for node in network.data.nodes + # ] + # print(nodes_names) + + # plot_nodes = [ + # nodes_names.index(name) + # for name in ["left_hind_PF_FA",] + # if name in nodes_names + # ] + + # plt.plot( + # # np.array(network.data.times.array), + # np.array(network.data.nodes[0].output.array) + # ) + # plt.figure() + # plt.plot( + # np.array(network.data.times.array), + # np.array(network.data.nodes[plot_nodes[0]].states.array), + # ) + # plt.show() + # print(network.data.nodes[5].name) + # plt.plot( + # np.array(network.data.times.array), + # np.array(network.data.nodes[5].output.array) + # ) + # plt.plot( + # np.array(network.data.times.array), + # np.array(network.data.nodes[6].output.array) + # ) # plot_network(network_options) + # Results + # run_network() From 120255d4a018c1e54f10e8defff6735f526a504e Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Thu, 19 Dec 2024 00:30:32 -0500 Subject: [PATCH 190/316] [GIT] Added new workflow for testing cross platform installation --- .../python-cross-platform-installer.yml | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 .github/workflows/python-cross-platform-installer.yml diff --git a/.github/workflows/python-cross-platform-installer.yml b/.github/workflows/python-cross-platform-installer.yml new file mode 100644 index 0000000..72c21e9 --- /dev/null +++ b/.github/workflows/python-cross-platform-installer.yml @@ -0,0 +1,45 @@ +# This workflow will install Python dependencies, run tests and lint with a single version of Python +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python + +name: Python application + +on: + push: + branches: [ "main", "farms-core-integration" ] + pull_request: + branches: [ "main" ] + +permissions: + contents: read + +jobs: + build: + + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + python-version: ["3.11", "3.13"] + exclude: + - os: macos-latest + python-version: "3.13" + - os: windows-latest + python-version: "3.13" + - os: ubuntu-latest + python-version: "3.13" + + steps: + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + # You can test your matrix by printing the current Python version + - name: Display Python version + run: python -c "import sys; print(sys.version)" + - name: Install dependencies + run: | + python -m pip install --upgrade pip + # if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + - name: Run installation + run: pip install . --verbose From 9baa506ba8222e90033f308d8a2e9a274bbd0a9c Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Thu, 19 Dec 2024 00:31:27 -0500 Subject: [PATCH 191/316] [GIT][CI] Disabled building pages for farms-core-integration branch --- .github/workflows/github-page-builder.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/github-page-builder.yml b/.github/workflows/github-page-builder.yml index 157fd8a..dd32a0c 100644 --- a/.github/workflows/github-page-builder.yml +++ b/.github/workflows/github-page-builder.yml @@ -5,7 +5,7 @@ on: push: branches: - main - - farms-core-integration + # - farms-core-integration jobs: build: From 64b0633a5834f2660610491734080f7b90ebb52f Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Thu, 19 Dec 2024 00:34:09 -0500 Subject: [PATCH 192/316] [GIT][CI] Fixed run command python pip error --- .github/workflows/python-cross-platform-installer.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-cross-platform-installer.yml b/.github/workflows/python-cross-platform-installer.yml index 72c21e9..ef4bb6a 100644 --- a/.github/workflows/python-cross-platform-installer.yml +++ b/.github/workflows/python-cross-platform-installer.yml @@ -42,4 +42,4 @@ jobs: python -m pip install --upgrade pip # if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - name: Run installation - run: pip install . --verbose + run: python -m pip install . --verbose From 856b26b21a803c6db6d89db10526e24b4d31f409 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Thu, 19 Dec 2024 01:04:57 -0500 Subject: [PATCH 193/316] [MAIN] Changed farms-core dependency to pyproject toml setup branch --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 00f0be6..f1b630f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,7 @@ requires = [ "setuptools", "Cython >= 0.15.1", "numpy", - "farms_core @ git+https://github.com/farmsim/farms_core.git" + "farms_core @ git+https://github.com/ShravanTata/farms_core.git@pyproject-toml-setup", ] build-backend = "setuptools.build_meta" From 93b2425157efe4e58ae713031a8ee9a087adc7aa Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Thu, 19 Dec 2024 01:15:52 -0500 Subject: [PATCH 194/316] [MAIN] Added package dir and data for setup.py --- setup.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index 0a27394..236957b 100644 --- a/setup.py +++ b/setup.py @@ -47,6 +47,11 @@ author_email='biorob-farms@groupes.epfl.ch', license='Apache-2.0', packages=find_packages(exclude=['tests*']), + package_dir={'farms_network': 'farms_network'}, + package_data={'farms_network': [ + f'{folder}*.pxd' + for folder in ['', 'core/', 'models/', 'numeric/', 'noise/'] + ]}, zip_safe=False, ext_modules=cythonize( extensions, @@ -81,7 +86,4 @@ 'warn.multiple_declarators': True, } ), - package_data={ - 'farms_network': ['*.pxd'], - }, ) From 922548b3ee359f599959f4a23ba050d845850f93 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Thu, 19 Dec 2024 01:36:57 -0500 Subject: [PATCH 195/316] [CORE] Fixed base class parameter property access On Windows with MSVC compiler the following error was raised error C2036: 'void *': unknown size. The issue stems from how void* is being handled in your Cython code, particularly the way parameters is accessed. On Windows, the MSVC compiler enforces stricter type-checking and does not allow dereferencing a void* directly, which causes errors like unknown size or invalid access. --- farms_network/core/edge.pyx | 11 +++++++++-- farms_network/core/node.pyx | 12 ++++++++++-- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/farms_network/core/edge.pyx b/farms_network/core/edge.pyx index c0d5a4c..62649d6 100644 --- a/farms_network/core/edge.pyx +++ b/farms_network/core/edge.pyx @@ -31,6 +31,7 @@ cdef class PyEdge: self.edge = malloc(sizeof(Edge)) if self.edge is NULL: raise MemoryError("Failed to allocate memory for Edge") + self.edge.nparameters = 0 def __dealloc__(self): if self.edge.source is not NULL: @@ -84,5 +85,11 @@ cdef class PyEdge: @property def parameters(self): - """ Number of states in the network """ - return self.edge.parameters[0] + """Generic accessor for parameters.""" + if not self.edge.parameters: + raise ValueError("Edge parameters are NULL") + if self.edge.nparameters == 0: + raise ValueError("No parameters available") + + # The derived class should override this method to provide specific behavior + raise NotImplementedError("Base class does not define parameter handling") diff --git a/farms_network/core/node.pyx b/farms_network/core/node.pyx index c7823b3..494eb2e 100644 --- a/farms_network/core/node.pyx +++ b/farms_network/core/node.pyx @@ -66,6 +66,8 @@ cdef class PyNode: self.node.model_type = strdup("base".encode('UTF-8')) self.node.ode = ode self.node.output = output + self.edge.nparameters = 0 + self.edge.ninputs = 0 def __dealloc__(self): if self.node.name is not NULL: @@ -123,8 +125,14 @@ cdef class PyNode: @property def parameters(self): - """ Return a view of parameters in the network """ - return self.node.parameters[0] + """Generic accessor for parameters.""" + if not self.node.parameters: + raise ValueError("Node parameters are NULL") + if self.node.nparameters == 0: + raise ValueError("No parameters available") + + # The derived class should override this method to provide specific behavior + raise NotImplementedError("Base class does not define parameter handling") # Methods to wrap the ODE and output functions def ode( From d83436e30a30f9fab82e7ea14c30b33167b6346d Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Fri, 20 Dec 2024 14:33:37 -0500 Subject: [PATCH 196/316] [CORE][NODE] Fixed wrong attribute name edge in cinit --- farms_network/core/node.pyx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/farms_network/core/node.pyx b/farms_network/core/node.pyx index 494eb2e..dfbef32 100644 --- a/farms_network/core/node.pyx +++ b/farms_network/core/node.pyx @@ -66,8 +66,8 @@ cdef class PyNode: self.node.model_type = strdup("base".encode('UTF-8')) self.node.ode = ode self.node.output = output - self.edge.nparameters = 0 - self.edge.ninputs = 0 + self.node.nparameters = 0 + self.node.ninputs = 0 def __dealloc__(self): if self.node.name is not NULL: From a2978edfb07e597a19f712d877959f0595a94985 Mon Sep 17 00:00:00 2001 From: Orren Ravid Date: Fri, 20 Dec 2024 20:11:25 -0800 Subject: [PATCH 197/316] Check for top level struct being null before trying to check members --- farms_network/core/edge.pyx | 16 ++++++++-------- farms_network/core/node.pyx | 12 ++++++------ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/farms_network/core/edge.pyx b/farms_network/core/edge.pyx index 62649d6..303651f 100644 --- a/farms_network/core/edge.pyx +++ b/farms_network/core/edge.pyx @@ -34,15 +34,15 @@ cdef class PyEdge: self.edge.nparameters = 0 def __dealloc__(self): - if self.edge.source is not NULL: - free(self.edge.source) - if self.edge.target is not NULL: - free(self.edge.target) - if self.edge.type is not NULL: - free(self.edge.type) - if self.edge.parameters is not NULL: - free(self.edge.parameters) if self.edge is not NULL: + if self.edge.source is not NULL: + free(self.edge.source) + if self.edge.target is not NULL: + free(self.edge.target) + if self.edge.type is not NULL: + free(self.edge.type) + if self.edge.parameters is not NULL: + free(self.edge.parameters) free(self.edge) def __init__(self, source: str, target: str, edge_type: str, **kwargs): diff --git a/farms_network/core/node.pyx b/farms_network/core/node.pyx index dfbef32..1730025 100644 --- a/farms_network/core/node.pyx +++ b/farms_network/core/node.pyx @@ -70,13 +70,13 @@ cdef class PyNode: self.node.ninputs = 0 def __dealloc__(self): - if self.node.name is not NULL: - free(self.node.name) - if self.node.model_type is not NULL: - free(self.node.model_type) - if self.node.parameters is not NULL: - free(self.node.parameters) if self.node is not NULL: + if self.node.name is not NULL: + free(self.node.name) + if self.node.model_type is not NULL: + free(self.node.model_type) + if self.node.parameters is not NULL: + free(self.node.parameters) free(self.node) def __init__(self, name: str, **kwargs): From 42f8b4fe1a233cfc219b3c8f379495fdf9ce7d6e Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Thu, 9 Jan 2025 22:35:50 -0500 Subject: [PATCH 198/316] [MODELS] Added hopf oscillator --- farms_network/core/options.py | 84 +++++++++++++++ farms_network/models/factory.py | 8 +- farms_network/models/hopf_oscillator.pxd | 72 +++++++++++++ farms_network/models/hopf_oscillator.pyx | 132 +++++++++++++++++++++++ 4 files changed, 292 insertions(+), 4 deletions(-) diff --git a/farms_network/core/options.py b/farms_network/core/options.py index 4901a04..13e19f4 100644 --- a/farms_network/core/options.py +++ b/farms_network/core/options.py @@ -760,6 +760,90 @@ def defaults(cls, **kwargs): return cls(**options) +################################# +# Hopf-Oscillator Model Options # +################################# +class HopfOscillatorNodeOptions(NodeOptions): + """ Class to define the properties of HopfOscillator node model """ + + def __init__(self, **kwargs): + """ Initialize """ + model = "hopf_oscillator" + super().__init__( + name=kwargs.pop("name"), + model=model, + parameters=kwargs.pop("parameters"), + visual=kwargs.pop("visual"), + state=kwargs.pop("state"), + noise=kwargs.pop("noise"), + ) + self._nstates = 2 + self._nparameters = 4 + + if kwargs: + raise Exception(f'Unknown kwargs: {kwargs}') + + @classmethod + def from_options(cls, kwargs: Dict): + """ Load from options """ + options = {} + options["name"] = kwargs.pop("name") + options["parameters"] = HopfOscillatorNodeParameterOptions.from_options( + kwargs["parameters"] + ) + options["visual"] = NodeVisualOptions.from_options( + kwargs["visual"] + ) + options["state"] = HopfOscillatorStateOptions.from_options( + kwargs["state"] + ) + options["noise"] = None + if kwargs["noise"] is not None: + options["noise"] = NoiseOptions.from_options( + kwargs["noise"] + ) + return cls(**options) + + +class HopfOscillatorNodeParameterOptions(NodeParameterOptions): + + def __init__(self, **kwargs): + super().__init__() + self.mu = kwargs.pop("mu") + self.omega = kwargs.pop("omega") + self.alpha = kwargs.pop("alpha") + self.beta = kwargs.pop("beta") + + if kwargs: + raise Exception(f'Unknown kwargs: {kwargs}') + + @classmethod + def defaults(cls, **kwargs): + """ Get the default parameters for HopfOscillator Node model """ + + options = {} + + options["mu"] = kwargs.pop("mu", 0.1) + options["omega"] = kwargs.pop("omega", 0.1) + options["alpha"] = kwargs.pop("alpha", 1.0) + options["beta"] = kwargs.pop("beta", 1.0) + + return cls(**options) + + +class HopfOscillatorStateOptions(NodeStateOptions): + """ HopfOscillator node state options """ + + STATE_NAMES = ["x", "y"] + + def __init__(self, **kwargs): + super().__init__( + initial=kwargs.pop("initial"), + names=HopfOscillatorStateOptions.STATE_NAMES + ) + assert len(self.initial) == 2, f"Number of initial states {len(self.initial)} should be 2" + + ######################################### # Leaky Integrator Danner Model Options # ######################################### diff --git a/farms_network/models/factory.py b/farms_network/models/factory.py index 388e89c..7ea1ab3 100644 --- a/farms_network/models/factory.py +++ b/farms_network/models/factory.py @@ -20,11 +20,11 @@ """ from farms_network.core.edge import PyEdge -# from farms_network.models.fitzhugh_nagumo import FitzhughNagumo -# from farms_network.models.hh_daun_motoneuron import HHDaunMotoneuron -# from farms_network.models.hopf_oscillator import HopfOscillator # from farms_network.models.leaky_integrator import LeakyIntegrator from farms_network.models.external_relay import PyExternalRelayNode +# from farms_network.models.fitzhugh_nagumo import FitzhughNagumo +# from farms_network.models.hh_daun_motoneuron import HHDaunMotoneuron +from farms_network.models.hopf_oscillator import PyHopfOscillatorNode from farms_network.models.li_danner import PyLIDannerNode from farms_network.models.li_nap_danner import PyLINaPDannerNode from farms_network.models.linear import PyLinearNode @@ -44,7 +44,7 @@ class NodeFactory: nodes = { # 'if': IntegrateAndFire, 'oscillator': PyOscillatorNode, - # 'hopf_oscillator': HopfOscillator, + 'hopf_oscillator': PyHopfOscillatorNode, # 'morphed_oscillator': MorphedOscillator, # 'leaky': LeakyIntegrator, 'external_relay': PyExternalRelayNode, diff --git a/farms_network/models/hopf_oscillator.pxd b/farms_network/models/hopf_oscillator.pxd index e69de29..2b4047c 100644 --- a/farms_network/models/hopf_oscillator.pxd +++ b/farms_network/models/hopf_oscillator.pxd @@ -0,0 +1,72 @@ +""" +----------------------------------------------------------------------- +Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty +Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +----------------------------------------------------------------------- + +Hopf-Oscillator model +""" + + +from ..core.node cimport Node, PyNode +from ..core.edge cimport Edge, PyEdge + + +cdef enum: + + #STATES + NSTATES = 2 + STATE_X = 0 + STATE_Y= 1 + + +cdef packed struct HopfOscillatorNodeParameters: + + double mu + double omega + double alpha + double beta + + +cdef: + void ode( + double time, + double* states, + double* derivatives, + double external_input, + double* network_outputs, + unsigned int* inputs, + double* weights, + double noise, + Node* node, + Edge** edges, + ) noexcept + double output( + double time, + double* states, + double external_input, + double* network_outputs, + unsigned int* inputs, + double* weights, + Node* node, + Edge** edges, + ) noexcept + + +cdef class PyHopfOscillatorNode(PyNode): + """ Python interface to HopfOscillator Node C-Structure """ + + cdef: + HopfOscillatorNodeParameters parameters diff --git a/farms_network/models/hopf_oscillator.pyx b/farms_network/models/hopf_oscillator.pyx index e69de29..3dcce4a 100644 --- a/farms_network/models/hopf_oscillator.pyx +++ b/farms_network/models/hopf_oscillator.pyx @@ -0,0 +1,132 @@ +""" +----------------------------------------------------------------------- +Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty +Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +----------------------------------------------------------------------- + +Hopf Oscillator + +[1]L. Righetti and A. J. Ijspeert, “Pattern generators with sensory +feedback for the control of quadruped locomotion,” in 2008 IEEE +International Conference on Robotics and Automation, May 2008, +pp. 819–824. doi: 10.1109/ROBOT.2008.4543306. + +""" + +from libc.math cimport M_PI +from libc.math cimport sin as csin +from libc.stdio cimport printf +from libc.stdlib cimport free, malloc +from libc.string cimport strdup + + +cpdef enum STATE: + + #STATES + nstates = NSTATES + x = STATE_X + y = STATE_Y + + +cdef void ode( + double time, + double* states, + double* derivatives, + double external_input, + double* network_outputs, + unsigned int* inputs, + double* weights, + double noise, + Node* node, + Edge** edges, +) noexcept: + """ ODE """ + # Parameters + cdef HopfOscillatorNodeParameters params = ( node[0].parameters)[0] + + # States + cdef double state_x = states[STATE.x] + cdef double state_y = states[STATE.y] + + cdef: + double _sum = 0.0 + unsigned int j + double _input, _weight + + cdef unsigned int ninputs = node.ninputs + for j in range(ninputs): + _input = network_outputs[inputs[j]] + _weight = weights[j] + _sum += (_weight*_input) + + # xdot : x_dot + derivatives[STATE.x] = ( + params.alpha*( + params.mu - (state_x**2 + state_y**2) + )*state_x - params.omega*state_y + ) + # ydot : y_dot + derivatives[STATE.y] = ( + params.beta*( + params.mu - (state_x**2 + state_y**2) + )*state_y + params.omega*state_x + (_sum) + ) + + +cdef double output( + double time, + double* states, + double external_input, + double* network_outputs, + unsigned int* inputs, + double* weights, + Node* node, + Edge** edges, +) noexcept: + """ Node output. """ + return states[STATE.y] + + +cdef class PyHopfOscillatorNode(PyNode): + """ Python interface to HopfOscillator Node C-Structure """ + + def __cinit__(self): + self.node.model_type = strdup("OSCILLATOR".encode('UTF-8')) + # override default ode and out methods + self.node.is_statefull = True + self.node.ode = ode + self.node.output = output + # parameters + self.node.parameters = malloc(sizeof(HopfOscillatorNodeParameters)) + if self.node.parameters is NULL: + raise MemoryError("Failed to allocate memory for node parameters") + + def __init__(self, name: str, **kwargs): + super().__init__(name) + + # Set node parameters + cdef HopfOscillatorNodeParameters* params = (self.node.parameters) + params.mu = kwargs.pop("mu") + params.omega = kwargs.pop("omega") + params.alpha = kwargs.pop("alpha") + params.beta = kwargs.pop("beta") + if kwargs: + raise Exception(f'Unknown kwargs: {kwargs}') + + @property + def parameters(self): + """ Parameters in the network """ + cdef HopfOscillatorNodeParameters params = ( self.node.parameters)[0] + return params From 73700d8ba9c6afcf373dbb04d4351059aaa4533b Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Mon, 13 Jan 2025 11:13:29 -0500 Subject: [PATCH 199/316] [MODELS] Removed unused imports in hopf oscillator pyx --- farms_network/models/hopf_oscillator.pyx | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/farms_network/models/hopf_oscillator.pyx b/farms_network/models/hopf_oscillator.pyx index 3dcce4a..185cbec 100644 --- a/farms_network/models/hopf_oscillator.pyx +++ b/farms_network/models/hopf_oscillator.pyx @@ -25,10 +25,8 @@ pp. 819–824. doi: 10.1109/ROBOT.2008.4543306. """ -from libc.math cimport M_PI -from libc.math cimport sin as csin from libc.stdio cimport printf -from libc.stdlib cimport free, malloc +from libc.stdlib cimport malloc from libc.string cimport strdup @@ -71,17 +69,14 @@ cdef void ode( _weight = weights[j] _sum += (_weight*_input) + r_square = (state_x**2 + state_y**2) # xdot : x_dot derivatives[STATE.x] = ( - params.alpha*( - params.mu - (state_x**2 + state_y**2) - )*state_x - params.omega*state_y + params.alpha*(params.mu - r_square)*state_x - params.omega*state_y ) # ydot : y_dot derivatives[STATE.y] = ( - params.beta*( - params.mu - (state_x**2 + state_y**2) - )*state_y + params.omega*state_x + (_sum) + params.beta*(params.mu - r_square)*state_y + params.omega*state_x + (_sum) ) @@ -103,7 +98,7 @@ cdef class PyHopfOscillatorNode(PyNode): """ Python interface to HopfOscillator Node C-Structure """ def __cinit__(self): - self.node.model_type = strdup("OSCILLATOR".encode('UTF-8')) + self.node.model_type = strdup("HOPF_OSCILLATOR".encode('UTF-8')) # override default ode and out methods self.node.is_statefull = True self.node.ode = ode From 918a08b46b27ab37b411c7e98b5230875da22412 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Mon, 13 Jan 2025 11:14:03 -0500 Subject: [PATCH 200/316] [MODELS] Added Izhikevich preliminary model files --- farms_network/models/izhikevich.pxd | 12 ++++++++--- farms_network/models/izhikevich.pyx | 33 ++++++++++++++++++++++++++++- 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/farms_network/models/izhikevich.pxd b/farms_network/models/izhikevich.pxd index add7c2d..5b675f2 100644 --- a/farms_network/models/izhikevich.pxd +++ b/farms_network/models/izhikevich.pxd @@ -25,11 +25,17 @@ from ..core.edge cimport Edge, PyEdge cdef enum: #STATES - NSTATES = 0 + NSTATES = 2 + STATE_V = 0 + STATE_U = 1 + cdef packed struct IzhikevichNodeParameters: - double param + double a # recovery time scale + double b # recovery sensitivity + double c # after-spike reset + double d # after-spike recovery reset cdef: @@ -58,7 +64,7 @@ cdef: cdef class PyIzhikevichNode(PyNode): - """ Python interface to Leaky Integrator Node C-Structure """ + """ Python interface to Izhikevich Node C-Structure """ cdef: IzhikevichNodeParameters parameters diff --git a/farms_network/models/izhikevich.pyx b/farms_network/models/izhikevich.pyx index 0e1028e..f71cabe 100644 --- a/farms_network/models/izhikevich.pyx +++ b/farms_network/models/izhikevich.pyx @@ -28,6 +28,8 @@ cpdef enum STATE: #STATES nstates = NSTATES + v = STATE_V + u = STATE_U cdef void ode( @@ -43,7 +45,36 @@ cdef void ode( Edge** edges, ) noexcept: """ Node ODE """ - ... + # Parameters + cdef IzhikevichNodeParameters params = ( + node[0].parameters + )[0] + + # States + cdef double state_v = states[STATE.v] + cdef double state_u = states[STATE.u] + + # Node inputs + # cdef: + # double _sum = 0.0 + # unsigned int j + # double _node_out, res, _input, _weight + + # cdef unsigned int ninputs = node.ninputs + # for j in range(ninputs): + # _input = network_outputs[inputs[j]] + # _weight = weights[j] + # if _weight >= 0.0: + # # Excitatory Synapse + # _sum += params.g_syn_e*cfabs(_weight)*_input*(state_v - params.e_syn_e) + # elif _weight < 0.0: + # # Inhibitory Synapse + # _sum += params.g_syn_i*cfabs(_weight)*_input*(state_v - params.e_syn_i) + + # # dV + # derivatives[STATE.v] = 0.04*state_v**2 + 5.0*state_v + 140.0 - state_u + _sum + # # dU + # derivatives[STATE.u] = params.a*(params.b*state_v - state_u) cdef double output( From 69acd7f29cc4b493097b0b503322358f43afa825 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Mon, 13 Jan 2025 11:17:30 -0500 Subject: [PATCH 201/316] [MODELS] Added preliminary files for morphed oscillator --- farms_network/models/morphed_oscillator.pxd | 73 ++++++++++++ farms_network/models/morphed_oscillator.pyx | 124 ++++++++++++++++++++ 2 files changed, 197 insertions(+) create mode 100644 farms_network/models/morphed_oscillator.pxd create mode 100644 farms_network/models/morphed_oscillator.pyx diff --git a/farms_network/models/morphed_oscillator.pxd b/farms_network/models/morphed_oscillator.pxd new file mode 100644 index 0000000..5375e1d --- /dev/null +++ b/farms_network/models/morphed_oscillator.pxd @@ -0,0 +1,73 @@ +""" +----------------------------------------------------------------------- +Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty +Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +----------------------------------------------------------------------- + +Morphed Oscillator model +""" + + +# from ..core.node cimport Node, PyNode + + +# cdef enum: + +# #STATES +# NSTATES = 3 +# STATE_THETA = 0 +# STATE_R= 1 +# # Morphing function state +# STATE_F = 2 + + +# cdef packed struct MorphedOscillatorNodeParameters: + +# double f +# double gamma +# double mu +# double zeta + + +# cdef: +# void ode( +# double time, +# double* states, +# double* derivatives, +# double external_input, +# double* network_outputs, +# unsigned int* inputs, +# double* weights, +# double noise, +# Node* node, +# Edge** edges, +# ) noexcept +# double output( +# double time, +# double* states, +# double external_input, +# double* network_outputs, +# unsigned int* inputs, +# double* weights, +# Node* node, +# Edge** edges, +# ) noexcept + + +# cdef class PyMorphedOscillatorNode(PyNode): +# """ Python interface to MorphedOscillator Node C-Structure """ + +# cdef: +# MorphedOscillatorNodeParameters parameters diff --git a/farms_network/models/morphed_oscillator.pyx b/farms_network/models/morphed_oscillator.pyx new file mode 100644 index 0000000..7bab29a --- /dev/null +++ b/farms_network/models/morphed_oscillator.pyx @@ -0,0 +1,124 @@ +""" +----------------------------------------------------------------------- +Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty +Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +----------------------------------------------------------------------- + +Morphed Oscillator model +""" + + +# from libc.stdio cimport printf +# from libc.stdlib cimport malloc +# from libc.string cimport strdup + + +# cpdef enum STATE: + +# #STATES +# nstates = NSTATES +# theta = STATE_THETA +# r = STATE_R +# # Morphing function state +# f = STATE_F + + +# cdef void ode( +# double time, +# double* states, +# double* derivatives, +# double external_input, +# double* network_outputs, +# unsigned int* inputs, +# double* weights, +# double noise, +# Node* node, +# Edge** edges, +# ) noexcept: +# """ ODE """ +# # Parameters +# cdef MorphedOscillatorNodeParameters params = ( node[0].parameters)[0] + +# # States +# cdef double state_x = states[STATE.x] +# cdef double state_y = states[STATE.y] + +# cdef: +# double _sum = 0.0 +# unsigned int j +# double _input, _weight + +# cdef unsigned int ninputs = node.ninputs +# for j in range(ninputs): +# _input = network_outputs[inputs[j]] +# _weight = weights[j] +# _sum += (_weight*_input) + +# r_square = (state_x**2 + state_y**2) +# # xdot : x_dot +# derivatives[STATE.x] = ( +# params.alpha*(params.mu - r_square)*state_x - params.omega*state_y +# ) +# # ydot : y_dot +# derivatives[STATE.y] = ( +# params.beta*(params.mu - r_square)*state_y + params.omega*state_x + (_sum) +# ) + + +# cdef double output( +# double time, +# double* states, +# double external_input, +# double* network_outputs, +# unsigned int* inputs, +# double* weights, +# Node* node, +# Edge** edges, +# ) noexcept: +# """ Node output. """ +# return states[STATE.y] + + +# cdef class PyMorphedOscillatorNode(PyNode): +# """ Python interface to MorphedOscillator Node C-Structure """ + +# def __cinit__(self): +# self.node.model_type = strdup("MORPHED_OSCILLATOR".encode('UTF-8')) +# # override default ode and out methods +# self.node.is_statefull = True +# self.node.ode = ode +# self.node.output = output +# # parameters +# self.node.parameters = malloc(sizeof(MorphedOscillatorNodeParameters)) +# if self.node.parameters is NULL: +# raise MemoryError("Failed to allocate memory for node parameters") + +# def __init__(self, name: str, **kwargs): +# super().__init__(name) + +# # Set node parameters +# cdef MorphedOscillatorNodeParameters* params = (self.node.parameters) +# params.f = kwargs.pop("f") +# params.gamme = kwargs.pop("gamme") +# params.mu = kwargs.pop("mu") +# params.zeta = kwargs.pop("zeta") +# if kwargs: +# raise Exception(f'Unknown kwargs: {kwargs}') + +# @property +# def parameters(self): +# """ Parameters in the network """ +# cdef MorphedOscillatorNodeParameters params = ( self.node.parameters)[0] +# return params From e57f89614b835018fad67c1b0af054ed59ddb8c5 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Mon, 13 Jan 2025 13:08:12 -0500 Subject: [PATCH 202/316] [MODELS] Added preliminary missing model files from old framework --- farms_network/models/fitzhugh_nagumo.pxd | 21 ++++++++++++++++++++ farms_network/models/fitzhugh_nagumo.pyx | 21 ++++++++++++++++++++ farms_network/models/hh_daun_motoneuron.pxd | 0 farms_network/models/hh_daun_motoneuron.pyx | 0 farms_network/models/li_daun_interneuron.pxd | 0 farms_network/models/li_daun_interneuron.pyx | 0 farms_network/models/matsuoka.pxd | 19 ++++++++++++++++++ farms_network/models/matsuoka.pyx | 19 ++++++++++++++++++ farms_network/models/morris_lecar.pxd | 20 +++++++++++++++++++ farms_network/models/morris_lecar.pyx | 20 +++++++++++++++++++ 10 files changed, 120 insertions(+) create mode 100644 farms_network/models/hh_daun_motoneuron.pxd create mode 100644 farms_network/models/hh_daun_motoneuron.pyx create mode 100644 farms_network/models/li_daun_interneuron.pxd create mode 100644 farms_network/models/li_daun_interneuron.pyx create mode 100644 farms_network/models/matsuoka.pxd create mode 100644 farms_network/models/matsuoka.pyx create mode 100644 farms_network/models/morris_lecar.pxd create mode 100644 farms_network/models/morris_lecar.pyx diff --git a/farms_network/models/fitzhugh_nagumo.pxd b/farms_network/models/fitzhugh_nagumo.pxd index e69de29..bb32ad7 100644 --- a/farms_network/models/fitzhugh_nagumo.pxd +++ b/farms_network/models/fitzhugh_nagumo.pxd @@ -0,0 +1,21 @@ +""" +---------------------------------------------------------------------- +Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty +Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +----------------------------------------------------------------------- + +Fitzhugh Nagumo model. + +""" diff --git a/farms_network/models/fitzhugh_nagumo.pyx b/farms_network/models/fitzhugh_nagumo.pyx index e69de29..bb32ad7 100644 --- a/farms_network/models/fitzhugh_nagumo.pyx +++ b/farms_network/models/fitzhugh_nagumo.pyx @@ -0,0 +1,21 @@ +""" +---------------------------------------------------------------------- +Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty +Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +----------------------------------------------------------------------- + +Fitzhugh Nagumo model. + +""" diff --git a/farms_network/models/hh_daun_motoneuron.pxd b/farms_network/models/hh_daun_motoneuron.pxd new file mode 100644 index 0000000..e69de29 diff --git a/farms_network/models/hh_daun_motoneuron.pyx b/farms_network/models/hh_daun_motoneuron.pyx new file mode 100644 index 0000000..e69de29 diff --git a/farms_network/models/li_daun_interneuron.pxd b/farms_network/models/li_daun_interneuron.pxd new file mode 100644 index 0000000..e69de29 diff --git a/farms_network/models/li_daun_interneuron.pyx b/farms_network/models/li_daun_interneuron.pyx new file mode 100644 index 0000000..e69de29 diff --git a/farms_network/models/matsuoka.pxd b/farms_network/models/matsuoka.pxd new file mode 100644 index 0000000..2f21108 --- /dev/null +++ b/farms_network/models/matsuoka.pxd @@ -0,0 +1,19 @@ +""" +----------------------------------------------------------------------- +Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty +Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +----------------------------------------------------------------------- +Matsuoka Neuron model +""" diff --git a/farms_network/models/matsuoka.pyx b/farms_network/models/matsuoka.pyx new file mode 100644 index 0000000..2f21108 --- /dev/null +++ b/farms_network/models/matsuoka.pyx @@ -0,0 +1,19 @@ +""" +----------------------------------------------------------------------- +Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty +Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +----------------------------------------------------------------------- +Matsuoka Neuron model +""" diff --git a/farms_network/models/morris_lecar.pxd b/farms_network/models/morris_lecar.pxd new file mode 100644 index 0000000..7fe32ef --- /dev/null +++ b/farms_network/models/morris_lecar.pxd @@ -0,0 +1,20 @@ +""" +----------------------------------------------------------------------- +Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty +Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +----------------------------------------------------------------------- + +Morris Lecar Neuron model. +""" diff --git a/farms_network/models/morris_lecar.pyx b/farms_network/models/morris_lecar.pyx new file mode 100644 index 0000000..7fe32ef --- /dev/null +++ b/farms_network/models/morris_lecar.pyx @@ -0,0 +1,20 @@ +""" +----------------------------------------------------------------------- +Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty +Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +----------------------------------------------------------------------- + +Morris Lecar Neuron model. +""" From 029279eb6adb1a66ec7074a948da55c3671e7e7d Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Mon, 13 Jan 2025 16:00:50 -0500 Subject: [PATCH 203/316] [EX] Added hopf oscillator righetti 08 scripts --- examples/righetti08/run.py | 255 +++++++++++++++++++++++++++++++++++++ 1 file changed, 255 insertions(+) create mode 100644 examples/righetti08/run.py diff --git a/examples/righetti08/run.py b/examples/righetti08/run.py new file mode 100644 index 0000000..9d2de3c --- /dev/null +++ b/examples/righetti08/run.py @@ -0,0 +1,255 @@ +""" +Hopf Oscillator + +[1]L. Righetti and A. J. Ijspeert, “Pattern generators with sensory +feedback for the control of quadruped locomotion,” in 2008 IEEE +International Conference on Robotics and Automation, May 2008, +pp. 819–824. doi: 10.1109/ROBOT.2008.4543306. +""" + + +import farms_pylog as pylog +import matplotlib.pyplot as plt +import networkx as nx +import numpy as np +import seaborn as sns +from farms_core.utils import profile +from farms_network.core import options +from farms_network.core.data import NetworkData +from farms_network.core.network import PyNetwork +from tqdm import tqdm + +plt.rcParams['text.usetex'] = False + + +def join_strings(strings): + return "_".join(strings) + + +class RhythmDrive: + """ Generate Drive Network """ + + def __init__(self, name="", anchor_x=0.0, anchor_y=0.0): + """Initialization.""" + super().__init__() + self.name = name + + def nodes(self): + """Add nodes.""" + nodes = {} + name = join_strings((self.name, "RG", "F", "DR")) + nodes[name] = options.LinearNodeOptions( + name=name, + parameters=options.LinearParameterOptions.defaults(slope=0.1, bias=0.0), + visual=options.NodeVisualOptions(), + ) + name = join_strings((self.name, "RG", "E", "DR")) + nodes[name] = options.LinearNodeOptions( + name=name, + parameters=options.LinearParameterOptions.defaults(slope=0.0, bias=0.1), + visual=options.NodeVisualOptions(), + ) + + return nodes + + +def generate_network(iterations=20000): + """ Generate network """ + + # Main network + network_options = options.NetworkOptions( + directed=True, + multigraph=False, + graph={"name": "rhigetti08"}, + integration=options.IntegrationOptions.defaults( + n_iterations=iterations, + timestep=float(1e-3), + ), + logs=options.NetworkLogOptions( + n_iterations=iterations, + buffer_size=iterations, + ) + ) + + # Generate rhythm centers + n_oscillators = 4 + + # Neuron + # Create an oscillator for each joint + num_oscillators = 4 + oscillator_names = [f'n{num}' for num in range(num_oscillators)] + for j, neuron_name in enumerate(oscillator_names): + network_options.add_node( + options.HopfOscillatorNodeOptions( + name=neuron_name, + parameters=options.HopfOscillatorNodeParameterOptions.defaults( + mu=1.0, + omega=5.0, + alpha=5.0, + beta=5.0, + ), + visual=options.NodeVisualOptions( + label=f"{j}", color=[1.0, 0.0, 0.0] + ), + state=options.HopfOscillatorStateOptions.from_kwargs( + x=np.random.uniform(0.0, 1.0), + y=np.random.uniform(0.0, 1.0), + ), + noise=None, + ) + ) + + # Connect edges + connection_matrix_walk = np.asarray( + [ + [0, -1, 1, -1], + [-1, 0, -1, 1], + [-1, 1, 0, -1], + [1, -1, -1, 0] + ] + ).T + + connection_matrix_trot = np.asarray( + [ + [0, -1, -1, 1], + [-1, 0, 1, -1], + [-1, 1, 0, -1], + [1, -1, -1, 0] + ] + ).T + + for i, j in zip(*np.nonzero(connection_matrix_trot)): + network_options.add_edge( + options.EdgeOptions( + source=oscillator_names[i], + target=oscillator_names[j], + weight=connection_matrix_trot[i, j]*1, + type="excitatory", + visual=options.EdgeVisualOptions(), + ) + ) + + data = NetworkData.from_options(network_options) + + network = PyNetwork.from_options(network_options) + network.setup_integrator(network_options) + + # nnodes = len(network_options.nodes) + # integrator.set_initial_value(np.zeros(len(data.states.array),), 0.0) + + # print("Data ------------", np.array(network.data.states.array)) + + # data.to_file("/tmp/sim.hdf5") + + # integrator.integrate(integrator.t + 1e-3) + + # # Integrate + states = np.ones((iterations+1, len(data.states.array)))*0.0 + outputs = np.ones((iterations, len(data.outputs.array)))*1.0 + # states[0, 2] = -1.0 + + # for index, node in enumerate(network_options.nodes): + # print(index, node.name) + # network.data.external_inputs.array[:] = np.ones((1,))*(iteration/iterations)*1.0 + for iteration in tqdm(range(0, iterations), colour='green', ascii=' >='): + # network.step(network.ode, iteration*1e-3, network.data.states.array) + # network.step() + # states[iteration+1, :] = network.data.states.array + network.step() + network.data.times.array[iteration] = iteration*1e-3 + + # network.data.to_file("/tmp/network.h5") + plt.figure() + for j in range(n_oscillators): + plt.fill_between( + np.array(network.data.times.array), + 2*j + (1 + np.sin(network.data.nodes[j].output.array)), + 2*j, + alpha=0.2, + lw=1.0, + ) + plt.plot( + np.array(network.data.times.array), + 2*j + (1 + np.sin(network.data.nodes[j].output.array)), + label=f"{j}" + ) + plt.legend() + + graph = nx.node_link_graph( + network_options, + directed=True, + multigraph=False, + link="edges", + name="name", + source="source", + target="target" + ) + plt.figure() + node_positions = nx.circular_layout(graph) + node_positions = nx.spring_layout(graph) + for index, node in enumerate(network_options.nodes): + node.visual.position[:2] = node_positions[node.name] + + network_options.save("/tmp/network_options.yaml") + + _ = nx.draw_networkx_nodes( + graph, + pos=node_positions, + node_color=[data["visual"]["color"] for node, data in graph.nodes.items()], + alpha=0.25, + edgecolors='k', + linewidths=2.0, + ) + nx.draw_networkx_labels( + graph, + pos=node_positions, + labels={node: data["visual"]["label"] for node, data in graph.nodes.items()}, + font_size=11.0, + font_weight='bold', + font_family='sans-serif', + alpha=1.0, + ) + nx.draw_networkx_edges( + graph, + pos=node_positions, + edge_color=[ + [0.0, 1.0, 0.0] if data["type"] == "excitatory" else [1.0, 0.0, 0.0] + for edge, data in graph.edges.items() + ], + width=1., + arrowsize=10, + style='dashed', + arrows=True, + min_source_margin=5, + min_target_margin=5, + connectionstyle="arc3,rad=-0.2", + ) + plt.figure() + sparse_array = nx.to_scipy_sparse_array(graph) + sns.heatmap( + sparse_array.todense(), cbar=False, square=True, + linewidths=0.5, + annot=True + ) + plt.show() + + # generate_tikz_figure( + # graph, + # paths.get_project_data_path().joinpath("templates", "network",), + # "tikz-full-network.tex", + # paths.get_project_images_path().joinpath("quadruped_network.tex") + # ) + + +def main(): + """Main.""" + + # Generate the network + profile.profile(generate_network) + + # Run the network + # run_network() + + +if __name__ == "__main__": + main() From 8e7bfa1ab9677ab321058606ad6b832e820dc20a Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Mon, 13 Jan 2025 16:03:28 -0500 Subject: [PATCH 204/316] [EX] Removed graphviz dependency from ijspeert07 run script --- examples/ijspeert07/run.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/examples/ijspeert07/run.py b/examples/ijspeert07/run.py index 62a4377..2146811 100644 --- a/examples/ijspeert07/run.py +++ b/examples/ijspeert07/run.py @@ -232,17 +232,16 @@ def generate_network(iterations=2000): target="target" ) plt.figure() - pos_circular = nx.circular_layout(graph) - pos_spring = nx.spring_layout(graph) - pos_graphviz = nx.nx_agraph.pygraphviz_layout(graph) + node_positions = nx.circular_layout(graph) + node_positions = nx.spring_layout(graph) for index, node in enumerate(network_options.nodes): - node.visual.position[:2] = pos_graphviz[node.name] + node.visual.position[:2] = node_positions[node.name] network_options.save("/tmp/network_options.yaml") _ = nx.draw_networkx_nodes( graph, - pos=pos_graphviz, + pos=node_positions, node_color=[data["visual"]["color"] for node, data in graph.nodes.items()], alpha=0.25, edgecolors='k', @@ -250,7 +249,7 @@ def generate_network(iterations=2000): ) nx.draw_networkx_labels( graph, - pos=pos_graphviz, + pos=node_positions, labels={node: data["visual"]["label"] for node, data in graph.nodes.items()}, font_size=11.0, font_weight='bold', @@ -259,7 +258,7 @@ def generate_network(iterations=2000): ) nx.draw_networkx_edges( graph, - pos=pos_graphviz, + pos=node_positions, edge_color=[ [0.0, 1.0, 0.0] if data["type"] == "excitatory" else [1.0, 0.0, 0.0] for edge, data in graph.edges.items() From 43ffe8fd239c6ee3dd15cd51820dd7ab4e21a947 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Mon, 13 Jan 2025 16:35:40 -0500 Subject: [PATCH 205/316] [GUI][SCRATCH] Improved row-col ratios for better gait diagrams --- scratch/test_gui.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/scratch/test_gui.py b/scratch/test_gui.py index 382bf5b..285ba8b 100644 --- a/scratch/test_gui.py +++ b/scratch/test_gui.py @@ -42,7 +42,7 @@ def connect_positions(source, destination, dir_shift, perp_shift): def compute_phases(times, data): phases = (np.array(data) > 0.1).astype(np.int16) - phases = np.logical_not(phases).astype(np.int16) + # phases = np.logical_not(phases).astype(np.int16) phases_xs = [] phases_ys = [] for j in range(len(data)): @@ -55,6 +55,7 @@ def compute_phases(times, data): # if np.all(len(phases_start) > 3): phases_ys[j][:, 1] += 1 phases_ys[j][:, 2] += 1 + return phases_xs, phases_ys @@ -132,7 +133,7 @@ def add_plot(iteration, data): # if len(phases_start) > 3: # phases_ys[:, 1] += 1.0 # phases_ys[:, 2] += 1.0 - row_col_ratios = implot.SubplotsRowColRatios(row_ratios=[0.1, 0.6, 0.3], col_ratios=[1]) + colors = { "RF": imgui.IM_COL32(28, 107, 180, 255), "LF": imgui.IM_COL32(23, 163, 74, 255), @@ -149,7 +150,7 @@ def add_plot(iteration, data): 3, 1, imgui.ImVec2(-1, -1), - row_col_ratios=implot.SubplotsRowColRatios(row_ratios=[0.1, 0.6, 0.3], col_ratios=[1]) + row_col_ratios=implot.SubplotsRowColRatios(row_ratios=[0.1, 0.8, 0.1], col_ratios=[1]) ): if implot.begin_plot(""): flags = ( From b1fe2986ea0b2d4b726340181c21a0ddefa78c29 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Tue, 14 Jan 2025 09:22:19 -0500 Subject: [PATCH 206/316] [GUI][SCRATCH] Fixed implot setup_axis_links limits type issue Need to use BoxedValue instead of floats! --- scratch/test_gui.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scratch/test_gui.py b/scratch/test_gui.py index 285ba8b..7888ac2 100644 --- a/scratch/test_gui.py +++ b/scratch/test_gui.py @@ -158,7 +158,7 @@ def add_plot(iteration, data): ) implot.setup_axis(implot.ImAxis_.y1, "Drive") implot.setup_axis(implot.ImAxis_.x1, flags=flags) - implot.setup_axis_links(implot.ImAxis_.x1, -1.0, 0.0) + implot.setup_axis_links(implot.ImAxis_.x1, implot.BoxedValue(-1.0), implot.BoxedValue(0.0)) implot.setup_axis_limits(implot.ImAxis_.x1, -1.0, 0.0) implot.setup_axis_limits(implot.ImAxis_.y1, 0.0, 1.5) implot.setup_axis_limits_constraints(implot.ImAxis_.x1, -1.0, 0.0) @@ -174,7 +174,7 @@ def add_plot(iteration, data): implot.AxisFlags_.no_tick_marks ) ) - implot.setup_axis_links(implot.ImAxis_.x1, -1.0, 0.0) + implot.setup_axis_links(implot.ImAxis_.x1, implot.BoxedValue(-1.0), implot.BoxedValue(0.0)) implot.setup_axis_limits(implot.ImAxis_.y1, -1*len(plot_names), 1.0) implot.setup_axis_limits_constraints(implot.ImAxis_.x1, -1.0, 0.0) implot.setup_axis_limits_constraints(implot.ImAxis_.y1, -8.2, 1.0) @@ -195,7 +195,7 @@ def add_plot(iteration, data): implot.plot_line(plot_names[j], times, plot_data[j, :] - j) implot.end_plot() if len(plot_nodes) > 7: - if implot.begin_plot(""): + if implot.begin_plot("", flags=implot.Flags_.no_legend): implot.setup_axis_limits(implot.ImAxis_.x1, -1.0, 0.0) implot.setup_axis_limits(implot.ImAxis_.y1, 0.0, 4.0) implot.setup_axis_limits_constraints(implot.ImAxis_.x1, -1.0, 0.0) @@ -245,7 +245,7 @@ def draw_muscle_activity(iteration, data, plot_nodes, plot_names, title): if implot.begin_plot("Muscle Activity", imgui.ImVec2(-1, -1)): implot.setup_axis(implot.ImAxis_.x1, "Time") implot.setup_axis(implot.ImAxis_.y1, "Activity") - implot.setup_axis_links(implot.ImAxis_.x1, -1.0, 0.0) + implot.setup_axis_links(implot.ImAxis_.x1, implot.BoxedValue(-1.0), implot.BoxedValue(0.0)) implot.setup_axis_limits(implot.ImAxis_.x1, -1.0, 0.0) implot.setup_axis_limits(implot.ImAxis_.y1, -1*(len(plot_nodes)-1), 1.0) implot.setup_axis_limits_constraints(implot.ImAxis_.x1, -1.0, 0.0) From b7202319587e17425a30b0e0672c77e065ebcd65 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Tue, 14 Jan 2025 09:23:00 -0500 Subject: [PATCH 207/316] [GUI][SCRATCH] Added brainstem drive to the controls --- scratch/test_gui.py | 59 ++++++++++++++++++++++++++++----------------- 1 file changed, 37 insertions(+), 22 deletions(-) diff --git a/scratch/test_gui.py b/scratch/test_gui.py index 7888ac2..9d4279d 100644 --- a/scratch/test_gui.py +++ b/scratch/test_gui.py @@ -264,7 +264,7 @@ def draw_muscle_activity(iteration, data, plot_nodes, plot_names, title): def plot_hind_motor_activity(iteration, data, side="right"): - side = "right" + side = "left" limb = "hind" muscle_names = [ @@ -585,38 +585,44 @@ def draw_slider( ): with imgui_ctx.begin(name): clicked, values[0] = imgui.slider_float( - label="drive", + label="alpha", v=values[0], v_min=min_value, v_max=max_value, ) clicked, values[1] = imgui.slider_float( - label="Ia", + label="drive", v=values[1], v_min=min_value, v_max=max_value, ) clicked, values[2] = imgui.slider_float( - label="II", + label="Ia", v=values[2], v_min=min_value, v_max=max_value, ) clicked, values[3] = imgui.slider_float( - label="Ib", + label="II", v=values[3], v_min=min_value, v_max=max_value, ) clicked, values[4] = imgui.slider_float( - label="Vn", + label="Ib", v=values[4], - v_min=-1.0, + v_min=min_value, v_max=max_value, ) clicked, values[5] = imgui.slider_float( - label="Cut", + label="Vn", v=values[5], + v_min=-1.0, + v_max=max_value, + ) + clicked, values[6] = imgui.slider_float( + label="Cut", + v=values[6], v_min=min_value, v_max=max_value, ) @@ -711,15 +717,22 @@ def main(): _time_draw_last = _time_draw _realtime = 0.1 - imgui.get_io().config_flags |= imgui.ConfigFlags_.docking_enable + io = imgui.get_io() + io.config_flags |= imgui.ConfigFlags_.docking_enable imgui.get_style().anti_aliased_lines = True imgui.get_style().anti_aliased_lines_use_tex = True imgui.get_style().anti_aliased_fill = True + + alpha_input_indices = [ + index + for index, node in enumerate(network_options.nodes) + if "input" in node.name and node.model == "external_relay" + ] drive_input_indices = [ index for index, node in enumerate(network_options.nodes) - if "DR" in node.name + if "DR" in node.name and node.model == "linear" ] Ia_input_indices = [ index @@ -746,20 +759,24 @@ def main(): for index, node in enumerate(network_options.nodes) if "cut" == node.name[-3:] and node.model == "external_relay" ] - slider_values = np.zeros((6,)) - slider_values[0] = 0.25 + slider_values = np.zeros((7,)) + slider_values[0] = 1.0 input_array = np.zeros(np.shape(inputs_view)) - input_array[drive_input_indices] = 0.25 + input_array[alpha_input_indices] = 1.0 button_state = False # input_array[drive_input_indices[0]] *= 1.05 + for index, node in enumerate(network_options.nodes): + if "BS_DR" in node.name and node.model == "linear": + bs_dr = index for iteration in tqdm(range(0, N_ITERATIONS), colour="green", ascii=" >="): - input_array[drive_input_indices] = slider_values[0] - input_array[Ia_input_indices] = slider_values[1] - input_array[II_input_indices] = slider_values[2] - input_array[Ib_input_indices] = slider_values[3] - input_array[Vn_input_indices] = slider_values[4] - input_array[Cut_input_indices] = slider_values[5] + input_array[alpha_input_indices] = slider_values[0] + input_array[drive_input_indices] = slider_values[1] + input_array[Ia_input_indices] = slider_values[2] + input_array[II_input_indices] = slider_values[3] + input_array[Ib_input_indices] = slider_values[4] + input_array[Vn_input_indices] = slider_values[5] + input_array[Cut_input_indices] = slider_values[6] inputs_view[:] = input_array network.step() @@ -768,10 +785,8 @@ def main(): _time_draw_last = _time_draw _time_draw = time.time() fps = _realtime*1/(_time_draw-_time_draw_last)+(1-_realtime)*fps - # print(imgui.get_io().delta_time, imgui.get_io().framerate) implot.push_style_var(implot.StyleVar_.line_weight, 2.0) - if not (iteration % 1): - time.sleep(1e-4) + if not (iteration % 2): gui.new_frame() slider_values = draw_slider(label="d", name="Drive", values=slider_values) add_plot(buffer_iteration, network.data) From a3848b665d608e148466c102ed6c8fd943dbdc77 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Tue, 14 Jan 2025 09:24:20 -0500 Subject: [PATCH 208/316] [MAIN] Removed pylog dependency from toml as it is part of farms_core --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index f1b630f..d375037 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,7 +13,6 @@ version = "0.1" description = "Module to generate, develop and visualize neural networks" license = {file = "LICENSE"} dependencies = [ - "farms_pylog @ git+https://gitlab.com/FARMSIM/farms_pylog.git", "tqdm", ] # authors = [ From 3fd437cf5566382c6ceafed6ce815c03d990a00c Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Tue, 14 Jan 2025 13:37:41 -0500 Subject: [PATCH 209/316] [EX][MOUSE] Fixed brainstem drive --- examples/mouse/components.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/examples/mouse/components.py b/examples/mouse/components.py index 79edd95..b6746f9 100644 --- a/examples/mouse/components.py +++ b/examples/mouse/components.py @@ -271,7 +271,7 @@ def nodes(self): """Add nodes.""" node_specs = [ ( - join_str(("BS", "DR")), + join_str(("BS", "input")), "ExternalRelay", np.array((3.0, 0.0)), "A", @@ -279,6 +279,15 @@ def nodes(self): {}, {}, ), + ( + join_str(("BS", "DR")), + "Linear", + np.array((3.0, -1.0)), + "A", + [0.0, 0.0, 0.0], + None, + {"slope": 1.0, "bias": 0.0}, + ), ] # Loop through the node specs to create each node using the create_node function @@ -289,7 +298,9 @@ def edges(self): """Add edges.""" # Define edge details in a list for easier iteration - edge_specs = [] + edge_specs = [ + (("BS", "input"), ("BS", "DR"), 1.0, "excitatory"), + ] # Loop through the edge specs to create each edge edges = create_edges(edge_specs, self.name) @@ -1818,7 +1829,7 @@ def brain_stem_circuit( edge_specs = [] for node in network_options.nodes: - if ("DR" in node.name) and node.model == "linear": + if ("DR" in node.name) and ("BS_DR" not in node.name) and node.model == "linear": edge_specs.append( ( ("BS", "DR"), From 96e65972aa5504dc274c444d4c5c3b05880fca09 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Tue, 14 Jan 2025 13:37:59 -0500 Subject: [PATCH 210/316] [EX][MOUSE] Removed pattern-commissural edges in components --- examples/mouse/components.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/mouse/components.py b/examples/mouse/components.py index b6746f9..f388819 100644 --- a/examples/mouse/components.py +++ b/examples/mouse/components.py @@ -1921,7 +1921,7 @@ def quadruped_circuit( # Connect pattern to commissural # ################################## pattern_commissural_edges = connect_pattern_commissural() - network_options.add_edges(pattern_commissural_edges.values()) + # network_options.add_edges(pattern_commissural_edges.values()) ############################## # Connect fore and hind lpsn # From 9fb808f806a64619cd0d9d817bf2f6caff3abeb0 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Tue, 14 Jan 2025 13:38:28 -0500 Subject: [PATCH 211/316] [EX][MOUSE] Updated main script --- examples/mouse/run.py | 90 ++++++++++++++++++------------------------- 1 file changed, 38 insertions(+), 52 deletions(-) diff --git a/examples/mouse/run.py b/examples/mouse/run.py index 07a3566..ffdfcee 100644 --- a/examples/mouse/run.py +++ b/examples/mouse/run.py @@ -549,59 +549,45 @@ def main(): # network_options = generate_network(int(1e4)) # network_options = generate_limb_circuit(int(5e4)) network_options = generate_quadruped_circuit((5e4)) - network_options.save("/tmp/network_options.yaml") - - graph = nx.node_link_graph( - network_options, - directed=True, - multigraph=False, - link="edges", - name="name", - source="source", - target="target", - ) - - - # Run the network - # network = profile.profile(run_network, network_options) - # network = run_network(network_options) - - # nodes_names = [ - # node.name - # for node in network.data.nodes - # ] - # print(nodes_names) - - # plot_nodes = [ - # nodes_names.index(name) - # for name in ["left_hind_PF_FA",] - # if name in nodes_names - # ] - - # plt.plot( - # # np.array(network.data.times.array), - # np.array(network.data.nodes[0].output.array) - # ) - # plt.figure() - # plt.plot( - # np.array(network.data.times.array), - # np.array(network.data.nodes[plot_nodes[0]].states.array), - # ) - # plt.show() - # print(network.data.nodes[5].name) - # plt.plot( - # np.array(network.data.times.array), - # np.array(network.data.nodes[5].output.array) - # ) - # plt.plot( - # np.array(network.data.times.array), - # np.array(network.data.nodes[6].output.array) - # ) - # plot_network(network_options) - - # Results - # run_network() + plot_network(network_options) + network = run_network(network_options) + plot_data(network, network_options) + + + # from abstract_control.control.generate import quadruped_siggraph_network + # from copy import deepcopy + # og_graph = quadruped_siggraph_network() + + # def update_names(old_names): + # replace_names = { + # "IIIn": "II_In", + # "IbIn": "Ib_In", + # "IaIn": "Ia_In", + # "_motor": "", + # } + # new_names = {} + # for name in old_names: + # new_name = deepcopy(name) + # for old, new in replace_names.items(): + # new_name = new_name.replace(old, new) + # new_names[name] = new_name + # return new_names + + # new_names = update_names(og_graph.nodes) + # og_graph = nx.relabel_nodes(og_graph, mapping=new_names) + + # print(f" OG edges {len(og_graph.edges)}") + # print(f" new edges {len(graph.edges)}") + + # check_edges = 0 + # for edge in graph.edges(): + # if edge in og_graph.edges: + # pass + # else: + # check_edges += 1 + # print(f"{edge} not found...") + # print(f"Check edges {check_edges}") if __name__ == "__main__": From 01ed6e68c44f047653c5f7dd20a52d8b94097c0b Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Wed, 15 Jan 2025 14:03:13 -0500 Subject: [PATCH 212/316] [MODELS] Added standard leaky integrator model --- farms_network/core/options.py | 90 ++++++++++++++++ farms_network/models/factory.py | 6 +- farms_network/models/leaky_integrator.pxd | 69 ++++++++++++ farms_network/models/leaky_integrator.pyx | 126 ++++++++++++++++++++++ 4 files changed, 288 insertions(+), 3 deletions(-) create mode 100644 farms_network/models/leaky_integrator.pxd create mode 100644 farms_network/models/leaky_integrator.pyx diff --git a/farms_network/core/options.py b/farms_network/core/options.py index 13e19f4..287cce9 100644 --- a/farms_network/core/options.py +++ b/farms_network/core/options.py @@ -58,6 +58,7 @@ def from_options(cls, kwargs): "oscillator": OscillatorNodeOptions, "li_danner": LIDannerNodeOptions, "li_nap_danner": LINaPDannerNodeOptions, + "leaky_integrator": LeakyIntegratorNodeOptions, } options["nodes"] = [ node_types[node["model"]].from_options(node) @@ -844,6 +845,95 @@ def __init__(self, **kwargs): assert len(self.initial) == 2, f"Number of initial states {len(self.initial)} should be 2" +################################## +# Leaky Integrator Model Options # +################################## +class LeakyIntegratorNodeOptions(NodeOptions): + """ Class to define the properties for standard leaky integrator model """ + + def __init__(self, **kwargs): + """ Initialize """ + model = "leaky_integrator" + super().__init__( + name=kwargs.pop("name"), + model=model, + parameters=kwargs.pop("parameters"), + visual=kwargs.pop("visual"), + state=kwargs.pop("state"), + noise=kwargs.pop("noise"), + ) + self._nstates = 1 + self._nparameters = 3 + + if kwargs: + raise Exception(f'Unknown kwargs: {kwargs}') + + @classmethod + def from_options(cls, kwargs: Dict): + """ Load from options """ + options = {} + options["name"] = kwargs.pop("name") + options["parameters"] = LeakyIntegratorParameterOptions.from_options( + kwargs["parameters"] + ) + options["visual"] = NodeVisualOptions.from_options( + kwargs["visual"] + ) + options["state"] = LeakyIntegratorStateOptions.from_options( + kwargs["state"] + ) + options["noise"] = None + if kwargs["noise"] is not None: + options["noise"] = NoiseOptions.from_options( + kwargs["noise"] + ) + return cls(**options) + + +class LeakyIntegratorParameterOptions(NodeParameterOptions): + """ + Class to define the parameters of Leaky Integrator model. + + Attributes: + tau (float): Time constant. + bias (float) + D (float) + """ + + def __init__(self, **kwargs): + super().__init__() + self.tau = kwargs.pop("tau") + self.bias = kwargs.pop("bias") + self.D = kwargs.pop("D") + if kwargs: + raise Exception(f'Unknown kwargs: {kwargs}') + + @classmethod + def defaults(cls, **kwargs): + """ Get the default parameters for LI Danner Node model """ + + options = {} + + options["tau"] = kwargs.pop("tau", 0.1) + options["bias"] = kwargs.pop("bias", -2.75) + options["D"] = kwargs.pop("D", 1.0) + + return cls(**options) + + +class LeakyIntegratorStateOptions(NodeStateOptions): + """ LeakyIntegrator node state options """ + + STATE_NAMES = ["m",] + + def __init__(self, **kwargs): + super().__init__( + initial=kwargs.pop("initial"), + names=LeakyIntegratorStateOptions.STATE_NAMES + ) + assert len(self.initial) == 1, f"Number of initial states {len(self.initial)} should be 1" + + ######################################### # Leaky Integrator Danner Model Options # ######################################### diff --git a/farms_network/models/factory.py b/farms_network/models/factory.py index 7ea1ab3..d468e9d 100644 --- a/farms_network/models/factory.py +++ b/farms_network/models/factory.py @@ -20,7 +20,7 @@ """ from farms_network.core.edge import PyEdge -# from farms_network.models.leaky_integrator import LeakyIntegrator +from farms_network.models.leaky_integrator import PyLeakyIntegratorNode from farms_network.models.external_relay import PyExternalRelayNode # from farms_network.models.fitzhugh_nagumo import FitzhughNagumo # from farms_network.models.hh_daun_motoneuron import HHDaunMotoneuron @@ -42,11 +42,11 @@ class NodeFactory: """Implementation of Factory Node class. """ nodes = { - # 'if': IntegrateAndFire, + # 'if': PyIntegrateAndFire, 'oscillator': PyOscillatorNode, 'hopf_oscillator': PyHopfOscillatorNode, # 'morphed_oscillator': MorphedOscillator, - # 'leaky': LeakyIntegrator, + 'leaky_integrator': PyLeakyIntegratorNode, 'external_relay': PyExternalRelayNode, 'linear': PyLinearNode, 'li_nap_danner': PyLINaPDannerNode, diff --git a/farms_network/models/leaky_integrator.pxd b/farms_network/models/leaky_integrator.pxd new file mode 100644 index 0000000..80d553a --- /dev/null +++ b/farms_network/models/leaky_integrator.pxd @@ -0,0 +1,69 @@ +""" +----------------------------------------------------------------------- +Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty +Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +----------------------------------------------------------------------- + +Leaky Integrator Neuron. +""" + +from ..core.node cimport Node, PyNode +from ..core.edge cimport Edge + + +cdef enum: + + #STATES + NSTATES = 1 + STATE_M = 0 + + +cdef packed struct LeakyIntegratorNodeParameters: + + double tau + double bias + double D + + +cdef: + void ode( + double time, + double* states, + double* derivatives, + double external_input, + double* network_outputs, + unsigned int* inputs, + double* weights, + double noise, + Node* node, + Edge** edges, + ) noexcept + double output( + double time, + double* states, + double external_input, + double* network_outputs, + unsigned int* inputs, + double* weights, + Node* node, + Edge** edges, + ) noexcept + + +cdef class PyLeakyIntegratorNode(PyNode): + """ Python interface to Leaky Integrator Node C-Structure """ + + cdef: + LeakyIntegratorNodeParameters parameters diff --git a/farms_network/models/leaky_integrator.pyx b/farms_network/models/leaky_integrator.pyx new file mode 100644 index 0000000..2586be0 --- /dev/null +++ b/farms_network/models/leaky_integrator.pyx @@ -0,0 +1,126 @@ +""" +----------------------------------------------------------------------- +Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty +Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +----------------------------------------------------------------------- + +Leaky Integrator Neuron. +""" + + +from libc.math cimport exp as cexp +from libc.stdio cimport printf +from libc.stdlib cimport malloc +from libc.string cimport strdup + +from ..core.options import LeakyIntegratorParameterOptions + + +cpdef enum STATE: + + #STATES + nstates = NSTATES + m = STATE_M + + +cdef void ode( + double time, + double* states, + double* derivatives, + double external_input, + double* network_outputs, + unsigned int* inputs, + double* weights, + double noise, + Node* node, + Edge** edges, +) noexcept: + """ ODE """ + # Parameters + cdef LeakyIntegratorNodeParameters params = ( + node[0].parameters + )[0] + + # States + cdef double state_m = states[STATE.m] + + # Node inputs + cdef: + double _sum = 0.0 + unsigned int j + double _node_out, res, _input, _weight + + cdef unsigned int ninputs = node.ninputs + for j in range(ninputs): + _input = network_outputs[inputs[j]] + _weight = weights[j] + _sum += _input*_weight + + # noise current + cdef double i_noise = noise + + # dV + derivatives[STATE.m] = (-state_m + _sum + i_noise)/params.tau + + +cdef double output( + double time, + double* states, + double external_input, + double* network_outputs, + unsigned int* inputs, + double* weights, + Node* node, + Edge** edges, +) noexcept: + """ Node output. """ + + cdef LeakyIntegratorNodeParameters params = ( node.parameters)[0] + + cdef double state_m = states[STATE.m] + cdef double _n_out = 1.0 / (1.0 + cexp(-params.D * (state_m + params.bias))) + return _n_out + + +cdef class PyLeakyIntegratorNode(PyNode): + """ Python interface to Leaky Integrator Node C-Structure """ + + def __cinit__(self): + self.node.model_type = strdup("LEAKY_INTEGRATOR".encode('UTF-8')) + # override default ode and out methods + self.node.is_statefull = True + self.node.ode = ode + self.node.output = output + # parameters + self.node.parameters = malloc(sizeof(LeakyIntegratorNodeParameters)) + if self.node.parameters is NULL: + raise MemoryError("Failed to allocate memory for node parameters") + + def __init__(self, name: str, **kwargs): + super().__init__(name) + + # Set node parameters + cdef LeakyIntegratorNodeParameters* param = (self.node.parameters) + param.tau = kwargs.pop("tau") + param.bias = kwargs.pop("bias") + param.D = kwargs.pop("D") + if kwargs: + raise Exception(f'Unknown kwargs: {kwargs}') + + @property + def parameters(self): + """ Parameters in the network """ + cdef LeakyIntegratorNodeParameters params = ( self.node.parameters)[0] + return params From ef0109db1a9c0e3cdc67b627abc14f96b7acfd1a Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Wed, 15 Jan 2025 14:03:30 -0500 Subject: [PATCH 213/316] [EX] Added example to simulate beer95 leaky integrator model --- examples/beer95/run.py | 212 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 212 insertions(+) create mode 100644 examples/beer95/run.py diff --git a/examples/beer95/run.py b/examples/beer95/run.py new file mode 100644 index 0000000..6569eab --- /dev/null +++ b/examples/beer95/run.py @@ -0,0 +1,212 @@ +"""Leaky integrator + +[1] Beer RD. 1995. On the Dynamics of Small Continuous-Time Recurrent Neural Networks. +Adaptive Behavior 3:469–509. doi:10.1177/105971239500300405 + +""" + + +import matplotlib.pyplot as plt +import networkx as nx +import numpy as np +import seaborn as sns +from farms_core import pylog +from farms_core.utils import profile +from farms_network.core import options +from farms_network.core.data import NetworkData +from farms_network.core.network import PyNetwork +from tqdm import tqdm + +plt.rcParams['text.usetex'] = False + + +def join_strings(strings): + return "_".join(strings) + + +def generate_network(iterations=20000): + """ Generate network """ + + # Main network + network_options = options.NetworkOptions( + directed=True, + multigraph=False, + graph={"name": "beer95"}, + integration=options.IntegrationOptions.defaults( + n_iterations=iterations, + timestep=float(1e-3), + ), + logs=options.NetworkLogOptions( + n_iterations=iterations, + buffer_size=iterations, + ) + ) + + # Generate rhythm centers + n_neurons = 2 + + # Neuron + # Create an neuron for each joint + num_neurons = 2 + neuron_names = [f'n{num}' for num in range(num_neurons)] + biases = [-2.75, -1.75] + positions = [(0.0, -5.0), (0.0, 5.0)] + for j, neuron_name in enumerate(neuron_names): + network_options.add_node( + options.LeakyIntegratorNodeOptions( + name=neuron_name, + parameters=options.LeakyIntegratorParameterOptions.defaults( + tau=0.1, + bias=biases[j], + D=1.0, + ), + visual=options.NodeVisualOptions( + label=f"{j}", color=[1.0, 0.0, 0.0] + ), + state=options.LeakyIntegratorStateOptions.from_kwargs( + m=np.random.uniform(0.0, 1.0), + ), + noise=None, + ) + ) + + # Connect edges + connection_matrix = np.asarray( + [ + [4.5, 1,], + [-1, 4.5,], + ] + ).T + + for i, j in zip(*np.nonzero(connection_matrix)): + weight = connection_matrix[i, j] + print(f"{neuron_names[i]}-->{neuron_names[j]}={weight}") + network_options.add_edge( + options.EdgeOptions( + source=neuron_names[i], + target=neuron_names[j], + weight=weight, + type="excitatory" if weight > 0.0 else "inhibitory", + visual=options.EdgeVisualOptions(), + ) + ) + + network_options.save("/tmp/beer95.yaml") + + data = NetworkData.from_options(network_options) + + network = PyNetwork.from_options(network_options) + network.setup_integrator(network_options) + + # nnodes = len(network_options.nodes) + # integrator.set_initial_value(np.zeros(len(data.states.array),), 0.0) + + # print("Data ------------", np.array(network.data.states.array)) + + # data.to_file("/tmp/sim.hdf5") + + # integrator.integrate(integrator.t + 1e-3) + + # # Integrate + states = np.ones((iterations+1, len(data.states.array)))*0.0 + outputs = np.ones((iterations, len(data.outputs.array)))*1.0 + # states[0, 2] = -1.0 + + # for index, node in enumerate(network_options.nodes): + # print(index, node.name) + # network.data.external_inputs.array[:] = np.ones((1,))*(iteration/iterations)*1.0 + for iteration in tqdm(range(0, iterations), colour='green', ascii=' >='): + # network.step(network.ode, iteration*1e-3, network.data.states.array) + # network.step() + # states[iteration+1, :] = network.data.states.array + network.step() + network.data.times.array[iteration] = iteration*1e-3 + + # network.data.to_file("/tmp/network.h5") + plt.figure() + for j in range(n_neurons): + plt.plot( + np.array(network.data.times.array), + np.asarray(network.data.nodes[j].output.array), + label=f"{j}" + ) + plt.legend() + + graph = nx.node_link_graph( + network_options, + directed=True, + multigraph=False, + link="edges", + name="name", + source="source", + target="target" + ) + plt.figure() + node_positions = nx.circular_layout(graph) + node_positions = nx.spring_layout(graph) + for index, node in enumerate(network_options.nodes): + node.visual.position[:2] = node_positions[node.name] + + network_options.save("/tmp/network_options.yaml") + + _ = nx.draw_networkx_nodes( + graph, + pos=node_positions, + node_color=[data["visual"]["color"] for node, data in graph.nodes.items()], + alpha=0.25, + edgecolors='k', + linewidths=2.0, + ) + nx.draw_networkx_labels( + graph, + pos=node_positions, + labels={node: data["visual"]["label"] for node, data in graph.nodes.items()}, + font_size=11.0, + font_weight='bold', + font_family='sans-serif', + alpha=1.0, + ) + nx.draw_networkx_edges( + graph, + pos=node_positions, + edge_color=[ + [0.0, 1.0, 0.0] if data["type"] == "excitatory" else [1.0, 0.0, 0.0] + for edge, data in graph.edges.items() + ], + width=1., + arrowsize=10, + style='dashed', + arrows=True, + min_source_margin=5, + min_target_margin=5, + connectionstyle="arc3,rad=-0.2", + ) + plt.figure() + sparse_array = nx.to_scipy_sparse_array(graph) + sns.heatmap( + sparse_array.todense(), cbar=False, square=True, + linewidths=0.5, + annot=True + ) + plt.show() + + # generate_tikz_figure( + # graph, + # paths.get_project_data_path().joinpath("templates", "network",), + # "tikz-full-network.tex", + # paths.get_project_images_path().joinpath("quadruped_network.tex") + # ) + + +def main(): + """Main.""" + + # Generate the network + profile.profile(generate_network) + + # Run the network + # run_network() + + +if __name__ == "__main__": + main() From c3116f131c0d7052151485694b7d9718014871b1 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Wed, 15 Jan 2025 14:55:01 -0500 Subject: [PATCH 214/316] [OPTIONS] Made noise optional for li_danner models --- farms_network/core/options.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/farms_network/core/options.py b/farms_network/core/options.py index 287cce9..d82ae11 100644 --- a/farms_network/core/options.py +++ b/farms_network/core/options.py @@ -971,7 +971,11 @@ def from_options(cls, kwargs: Dict): options["state"] = LIDannerStateOptions.from_options( kwargs["state"] ) - options["noise"] = NoiseOptions.from_options(kwargs["noise"]) + options["noise"] = None + if kwargs["noise"] is not None: + options["noise"] = NoiseOptions.from_options( + kwargs["noise"] + ) return cls(**options) @@ -1074,9 +1078,11 @@ def from_options(cls, kwargs: Dict): options["state"] = LINaPDannerStateOptions.from_options( kwargs["state"] ) - options["noise"] = NoiseOptions.from_options( - kwargs["noise"] - ) + options["noise"] = None + if kwargs["noise"] is not None: + options["noise"] = NoiseOptions.from_options( + kwargs["noise"] + ) return cls(**options) From 2fe754b428e358911f1268278cc87b5385326a3e Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Wed, 15 Jan 2025 15:04:43 -0500 Subject: [PATCH 215/316] [UTILS] Fixed setup_integrator method in run script --- farms_network/utils/run.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/farms_network/utils/run.py b/farms_network/utils/run.py index 15c7d66..d9f3c03 100644 --- a/farms_network/utils/run.py +++ b/farms_network/utils/run.py @@ -11,7 +11,7 @@ def run_network(network_options): network = PyNetwork.from_options(network_options) - network.setup_integrator(network_options.integration) + network.setup_integrator(network_options) # data.to_file("/tmp/sim.hdf5") From ceb519ef03496ff9b3356fa4fa6ea6814e28ddfe Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Wed, 15 Jan 2025 18:33:22 -0500 Subject: [PATCH 216/316] [CORE] Added analysis module and plot --- farms_network/analysis/__init__.py | 0 farms_network/analysis/plot.py | 77 ++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+) create mode 100644 farms_network/analysis/__init__.py create mode 100644 farms_network/analysis/plot.py diff --git a/farms_network/analysis/__init__.py b/farms_network/analysis/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/farms_network/analysis/plot.py b/farms_network/analysis/plot.py new file mode 100644 index 0000000..ef4e2df --- /dev/null +++ b/farms_network/analysis/plot.py @@ -0,0 +1,77 @@ +import networkx as nx +from farms_core.analysis import plot + + +def visualize(network_options): + """ Visualize network """ + + graph = nx.node_link_graph( + network_options, + directed=True, + multigraph=False, + link="edges", + name="name", + source="source", + target="target", + ) + + plt.figure() + pos_circular = nx.circular_layout(graph) + pos_spring = nx.spring_layout(graph) + pos_graphviz = nx.nx_agraph.pygraphviz_layout(graph) + + _ = nx.draw_networkx_nodes( + graph, + pos={ + node: data["visual"]["position"][:2] + for node, data in graph.nodes.items() + }, + node_color=[ + data["visual"]["color"] + for node, data in graph.nodes.items() + ], + alpha=0.25, + edgecolors="k", + linewidths=2.0, + node_size=[ + 300*data["visual"]["radius"] + for node, data in graph.nodes.items() + ], + ) + nx.draw_networkx_labels( + graph, + pos={ + node: data["visual"]["position"][:2] + for node, data in graph.nodes.items() + }, + labels={ + node: data["visual"]["label"] + for node, data in graph.nodes.items() + }, + font_size=11.0, + font_weight="bold", + font_family="sans-serif", + alpha=1.0, + ) + nx.draw_networkx_edges( + graph, + pos={ + node: data["visual"]["position"][:2] + for node, data in graph.nodes.items() + }, + edge_color=[ + [0.3, 1.0, 0.3] if data["type"] == "excitatory" else [0.7, 0.3, 0.3] + for edge, data in graph.edges.items() + ], + width=1.0, + arrowsize=10, + style="-", + arrows=True, + min_source_margin=5, + min_target_margin=5, + connectionstyle=[ + data["visual"]["connectionstyle"] + for edge, data in graph.edges.items() + ], + ) + plt.show() From 5bd655a7e0fd6f979d978f4b9bf8dd0c55535884 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Wed, 15 Jan 2025 18:35:16 -0500 Subject: [PATCH 217/316] [EX] Minor refactor in Beer95 --- examples/beer95/run.py | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/examples/beer95/run.py b/examples/beer95/run.py index 6569eab..183bcc5 100644 --- a/examples/beer95/run.py +++ b/examples/beer95/run.py @@ -93,24 +93,13 @@ def generate_network(iterations=20000): network_options.save("/tmp/beer95.yaml") - data = NetworkData.from_options(network_options) - network = PyNetwork.from_options(network_options) network.setup_integrator(network_options) - - # nnodes = len(network_options.nodes) - # integrator.set_initial_value(np.zeros(len(data.states.array),), 0.0) - - # print("Data ------------", np.array(network.data.states.array)) - - # data.to_file("/tmp/sim.hdf5") - - # integrator.integrate(integrator.t + 1e-3) + data = network.data # # Integrate states = np.ones((iterations+1, len(data.states.array)))*0.0 outputs = np.ones((iterations, len(data.outputs.array)))*1.0 - # states[0, 2] = -1.0 # for index, node in enumerate(network_options.nodes): # print(index, node.name) From 29a29eda8f433a4a990ab4a50ca25bc8bc56a7f4 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Wed, 15 Jan 2025 18:49:30 -0500 Subject: [PATCH 218/316] [MAIN] Changed naming convention for cython and python objs and attrs --- examples/beer95/run.py | 4 +- examples/ijspeert07/run.py | 4 +- examples/righetti08/run.py | 4 +- farms_network/core/edge.pxd | 6 +- farms_network/core/edge.pyx | 56 +++--- farms_network/core/network.pxd | 18 +- farms_network/core/network.pyx | 107 ++++++----- farms_network/core/node.pxd | 24 +-- farms_network/core/node.pyx | 90 +++++----- farms_network/models/external_relay.pxd | 10 +- farms_network/models/external_relay.pyx | 14 +- farms_network/models/factory.py | 205 ++++++++++++---------- farms_network/models/hopf_oscillator.pxd | 14 +- farms_network/models/hopf_oscillator.pyx | 30 ++-- farms_network/models/izhikevich.pxd | 14 +- farms_network/models/izhikevich.pyx | 28 +-- farms_network/models/leaky_integrator.pxd | 14 +- farms_network/models/leaky_integrator.pyx | 34 ++-- farms_network/models/li_danner.pxd | 14 +- farms_network/models/li_danner.pyx | 32 ++-- farms_network/models/li_nap_danner.pxd | 14 +- farms_network/models/li_nap_danner.pyx | 32 ++-- farms_network/models/linear.pxd | 10 +- farms_network/models/linear.pyx | 24 +-- farms_network/models/oscillator.pxd | 16 +- farms_network/models/oscillator.pyx | 42 ++--- farms_network/models/relu.pxd | 10 +- farms_network/models/relu.pyx | 24 +-- scratch/test_gui.py | 4 +- 29 files changed, 460 insertions(+), 438 deletions(-) diff --git a/examples/beer95/run.py b/examples/beer95/run.py index 183bcc5..7ecad12 100644 --- a/examples/beer95/run.py +++ b/examples/beer95/run.py @@ -14,7 +14,7 @@ from farms_core.utils import profile from farms_network.core import options from farms_network.core.data import NetworkData -from farms_network.core.network import PyNetwork +from farms_network.core.network import Network from tqdm import tqdm plt.rcParams['text.usetex'] = False @@ -93,7 +93,7 @@ def generate_network(iterations=20000): network_options.save("/tmp/beer95.yaml") - network = PyNetwork.from_options(network_options) + network = Network.from_options(network_options) network.setup_integrator(network_options) data = network.data diff --git a/examples/ijspeert07/run.py b/examples/ijspeert07/run.py index 2146811..6b0a87d 100644 --- a/examples/ijspeert07/run.py +++ b/examples/ijspeert07/run.py @@ -10,7 +10,7 @@ from farms_core.utils import profile from farms_network.core import options from farms_network.core.data import NetworkData -from farms_network.core.network import PyNetwork +from farms_network.core.network import Network from tqdm import tqdm plt.rcParams['text.usetex'] = False @@ -178,7 +178,7 @@ def generate_network(iterations=2000): data = NetworkData.from_options(network_options) - network = PyNetwork.from_options(network_options) + network = Network.from_options(network_options) network.setup_integrator(network_options) # nnodes = len(network_options.nodes) diff --git a/examples/righetti08/run.py b/examples/righetti08/run.py index 9d2de3c..40552fa 100644 --- a/examples/righetti08/run.py +++ b/examples/righetti08/run.py @@ -16,7 +16,7 @@ from farms_core.utils import profile from farms_network.core import options from farms_network.core.data import NetworkData -from farms_network.core.network import PyNetwork +from farms_network.core.network import Network from tqdm import tqdm plt.rcParams['text.usetex'] = False @@ -131,7 +131,7 @@ def generate_network(iterations=20000): data = NetworkData.from_options(network_options) - network = PyNetwork.from_options(network_options) + network = Network.from_options(network_options) network.setup_integrator(network_options) # nnodes = len(network_options.nodes) diff --git a/farms_network/core/edge.pxd b/farms_network/core/edge.pxd index a4d576e..dae9e71 100644 --- a/farms_network/core/edge.pxd +++ b/farms_network/core/edge.pxd @@ -22,7 +22,7 @@ Header for Edge Base Struture. """ -cdef struct Edge: +cdef struct EdgeCy: # char* source # Source node @@ -35,8 +35,8 @@ cdef struct Edge: -cdef class PyEdge: +cdef class Edge: """ Python interface to Edge C-Structure""" cdef: - Edge* edge + EdgeCy* c_edge diff --git a/farms_network/core/edge.pyx b/farms_network/core/edge.pyx index 303651f..dd10687 100644 --- a/farms_network/core/edge.pyx +++ b/farms_network/core/edge.pyx @@ -24,31 +24,31 @@ from libc.string cimport strdup from .options import EdgeOptions -cdef class PyEdge: +cdef class Edge: """ Python interface to Edge C-Structure""" def __cinit__(self): - self.edge = malloc(sizeof(Edge)) - if self.edge is NULL: - raise MemoryError("Failed to allocate memory for Edge") - self.edge.nparameters = 0 + self.c_edge = malloc(sizeof(EdgeCy)) + if self.c_edge is NULL: + raise MemoryError("Failed to allocate memory for EdgeCy") + self.c_edge.nparameters = 0 def __dealloc__(self): - if self.edge is not NULL: - if self.edge.source is not NULL: - free(self.edge.source) - if self.edge.target is not NULL: - free(self.edge.target) - if self.edge.type is not NULL: - free(self.edge.type) - if self.edge.parameters is not NULL: - free(self.edge.parameters) - free(self.edge) + if self.c_edge is not NULL: + if self.c_edge.source is not NULL: + free(self.c_edge.source) + if self.c_edge.target is not NULL: + free(self.c_edge.target) + if self.c_edge.type is not NULL: + free(self.c_edge.type) + if self.c_edge.parameters is not NULL: + free(self.c_edge.parameters) + free(self.c_edge) def __init__(self, source: str, target: str, edge_type: str, **kwargs): - self.edge.source = strdup(source.encode('UTF-8')) - self.edge.target = strdup(source.encode('UTF-8')) - self.edge.type = strdup(edge_type.encode('UTF-8')) + self.c_edge.source = strdup(source.encode('UTF-8')) + self.c_edge.target = strdup(source.encode('UTF-8')) + self.c_edge.type = strdup(edge_type.encode('UTF-8')) @classmethod def from_options(cls, edge_options: EdgeOptions): @@ -62,33 +62,33 @@ cdef class PyEdge: # Property methods for source @property def source(self): - if self.edge.source is NULL: + if self.c_edge.source is NULL: return None - return self.edge.source.decode('UTF-8') + return self.c_edge.source.decode('UTF-8') @property def target(self): - if self.edge.target is NULL: + if self.c_edge.target is NULL: return None - return self.edge.target.decode('UTF-8') + return self.c_edge.target.decode('UTF-8') @property def type(self): - if self.edge.type is NULL: + if self.c_edge.type is NULL: return None - return self.edge.type.decode('UTF-8') + return self.c_edge.type.decode('UTF-8') # Property methods for nparameters @property def nparameters(self): - return self.edge.nparameters + return self.c_edge.nparameters @property def parameters(self): """Generic accessor for parameters.""" - if not self.edge.parameters: - raise ValueError("Edge parameters are NULL") - if self.edge.nparameters == 0: + if not self.c_edge.parameters: + raise ValueError("EdgeCy parameters are NULL") + if self.c_edge.nparameters == 0: raise ValueError("No parameters available") # The derived class should override this method to provide specific behavior diff --git a/farms_network/core/network.pxd b/farms_network/core/network.pxd index 949faeb..a8049f4 100644 --- a/farms_network/core/network.pxd +++ b/farms_network/core/network.pxd @@ -3,11 +3,11 @@ cimport numpy as cnp from ..numeric.integrators_cy cimport EulerMaruyamaSolver, RK4Solver from ..numeric.system cimport ODESystem, SDESystem from .data_cy cimport NetworkDataCy, NodeDataCy -from .edge cimport Edge -from .node cimport Node +from .edge cimport Edge, EdgeCy +from .node cimport Node, NodeCy -cdef struct Network: +cdef struct NetworkCy: # info unsigned long int nnodes @@ -15,19 +15,19 @@ cdef struct Network: unsigned long int nstates # nodes list - Node** nodes + NodeCy** c_nodes # edges list - Edge** edges + EdgeCy** c_edges -cdef class PyNetwork(ODESystem): +cdef class Network(ODESystem): """ Python interface to Network ODE """ cdef: - Network *network - public list pynodes - public list pyedges + NetworkCy *c_network + public list nodes + public list edges public NetworkDataCy data double[:] __tmp_node_outputs diff --git a/farms_network/core/network.pyx b/farms_network/core/network.pyx index 6ba4ff2..2f4899b 100644 --- a/farms_network/core/network.pyx +++ b/farms_network/core/network.pyx @@ -31,8 +31,6 @@ from .data import NetworkData, NetworkStates from .data_cy cimport (NetworkConnectivityCy, NetworkDataCy, NetworkNoiseCy, NetworkStatesCy) -from .edge cimport Edge, PyEdge -from .node cimport Node, PyNode from .options import (EdgeOptions, IntegrationOptions, NetworkOptions, NodeOptions) @@ -42,16 +40,16 @@ cdef inline void ode( double time, double[:] states_arr, NetworkDataCy data, - Network* network, + NetworkCy* c_network, double[:] node_outputs_tmp, ) noexcept: """ C Implementation to compute full network state """ cdef unsigned int j, nnodes - cdef Node __node - cdef Node** nodes = network.nodes - cdef Edge** edges = network.edges - nnodes = network.nnodes + cdef NodeCy __node + cdef NodeCy** c_nodes = c_network.c_nodes + cdef EdgeCy** c_edges = c_network.c_edges + nnodes = c_network.nnodes # It is important to use the states passed to the function and not from the data.states cdef double* states = &states_arr[0] @@ -72,7 +70,7 @@ cdef inline void ode( cdef double* node_outputs_tmp_ptr = &node_outputs_tmp[0] for j in range(nnodes): - __node = nodes[j][0] + __node = c_nodes[j][0] if __node.is_statefull: __node.ode( time, @@ -83,8 +81,8 @@ cdef inline void ode( input_neurons + input_neurons_indices[j], weights + input_neurons_indices[j], noise[j], - nodes[j], - edges + input_neurons_indices[j], + c_nodes[j], + c_edges + input_neurons_indices[j], ) node_outputs_tmp_ptr[j] = __node.output( time, @@ -93,17 +91,17 @@ cdef inline void ode( outputs, input_neurons + input_neurons_indices[j], weights + input_neurons_indices[j], - nodes[j], - edges + input_neurons_indices[j], + c_nodes[j], + c_edges + input_neurons_indices[j], ) cdef inline void logger( int iteration, NetworkDataCy data, - Network* network + NetworkCy* c_network ) noexcept: - cdef unsigned int nnodes = network.nnodes + cdef unsigned int nnodes = c_network.nnodes cdef unsigned int j cdef double* states_ptr = &data.states.array[0] cdef unsigned int[:] state_indices = data.states.indices @@ -139,23 +137,23 @@ cdef inline void _noise_states_to_output( outputs[indices[index]] = states[index] -cdef class PyNetwork(ODESystem): +cdef class Network(ODESystem): """ Python interface to Network ODE """ def __cinit__(self, network_options: NetworkOptions): """ C initialization for manual memory allocation """ - self.network = malloc(sizeof(Network)) - if self.network is NULL: + self.c_network = malloc(sizeof(NetworkCy)) + if self.c_network is NULL: raise MemoryError("Failed to allocate memory for Network") - self.network.nnodes = len(network_options.nodes) - self.network.nedges = len(network_options.edges) + self.c_network.nnodes = len(network_options.nodes) + self.c_network.nedges = len(network_options.edges) # Allocate memory for c-node structs - self.network.nodes = malloc(self.nnodes * sizeof(Node *)) - if self.network.nodes is NULL: + self.c_network.c_nodes = malloc(self.nnodes * sizeof(NodeCy *)) + if self.c_network.c_nodes is NULL: raise MemoryError("Failed to allocate memory for Network nodes") # Allocation memory for c-edge structs - self.network.edges = malloc(self.nedges * sizeof(Edge *)) - if self.network.edges is NULL: + self.c_network.c_edges = malloc(self.nedges * sizeof(EdgeCy *)) + if self.c_network.c_edges is NULL: raise MemoryError("Failed to allocate memory for Network edges") def __init__(self, network_options: NetworkOptions): @@ -164,10 +162,10 @@ cdef class PyNetwork(ODESystem): super().__init__() self.data = NetworkData.from_options(network_options) - self.pynodes = [] - self.pyedges = [] + self.nodes = [] + self.edges = [] self.nodes_output_data = [] - self.__tmp_node_outputs = np.zeros((self.network.nnodes,)) + self.__tmp_node_outputs = np.zeros((self.c_network.nnodes,)) self.setup_network(network_options, self.data) # Integration options @@ -182,10 +180,10 @@ cdef class PyNetwork(ODESystem): def __dealloc__(self): """ Deallocate any manual memory as part of clean up """ - if self.network.nodes is not NULL: - free(self.network.nodes) - if self.network is not NULL: - free(self.network) + if self.c_network.c_nodes is not NULL: + free(self.c_network.c_nodes) + if self.c_network is not NULL: + free(self.c_network) @classmethod def from_options(cls, options: NetworkOptions): @@ -201,7 +199,7 @@ cdef class PyNetwork(ODESystem): # Setup ODE numerical integrator integration_options = network_options.integration cdef double timestep = integration_options.timestep - self.ode_integrator = RK4Solver(self.network.nstates, timestep) + self.ode_integrator = RK4Solver(self.c_network.nstates, timestep) # Setup SDE numerical integrator for noise models if any noise_options = [] for node in network_options.nodes: @@ -215,46 +213,45 @@ cdef class PyNetwork(ODESystem): def setup_network(self, options: NetworkOptions, data: NetworkData): """ Setup network """ - cdef Node* c_node - connectivity = data.connectivity cdef unsigned int __nstates = 0 cdef unsigned int index - cdef PyNode pyn - cdef PyEdge pye + cdef NodeCy* c_node + cdef Node node for index, node_options in enumerate(options.nodes): - self.pynodes.append(self.generate_node(node_options)) - pyn = self.pynodes[index] - c_node = (pyn.node) + self.nodes.append(self.generate_node(node_options)) + node = self.nodes[index] + c_node = (node.c_node) c_node.ninputs = len( connectivity.sources[ connectivity.indices[index]:connectivity.indices[index+1] ] ) if connectivity.indices else 0 - self.network.nodes[index] = c_node + self.c_network.c_nodes[index] = c_node __nstates += node_options._nstates - self.network.nstates = __nstates + self.c_network.nstates = __nstates - cdef Edge* c_edge + cdef EdgeCy* c_edge + cdef Edge edge for index, edge_options in enumerate(options.edges): - self.pyedges.append(self.generate_edge(edge_options, options.nodes)) - pye = self.pyedges[index] - c_edge = (pye.edge) - self.network.edges[index] = c_edge + self.edges.append(self.generate_edge(edge_options, options.nodes)) + edge = self.edges[index] + c_edge = (edge.c_edge) + self.c_network.c_edges[index] = c_edge # Initial states data # Initialize states - for j, node in enumerate(options.nodes): - if node.state: + for j, node_opts in enumerate(options.nodes): + if node_opts.state: for state_index, index in enumerate( range(data.states.indices[j], data.states.indices[j+1]) ): - data.states.array[index] = node.state.initial[state_index] + data.states.array[index] = node_opts.state.initial[state_index] @staticmethod def generate_node(node_options: NodeOptions): """ Generate a node from options """ - Node = NodeFactory.generate_node(node_options.model) + Node = NodeFactory.create(node_options.model) node = Node.from_options(node_options) return node @@ -262,24 +259,24 @@ cdef class PyNetwork(ODESystem): def generate_edge(edge_options: EdgeOptions, nodes_options): """ Generate a edge from options """ target = nodes_options[nodes_options.index(edge_options.target)] - Edge = EdgeFactory.generate_edge(target.model) + Edge = EdgeFactory.create(target.model) edge = Edge.from_options(edge_options) return edge @property def nnodes(self): """ Number of nodes in the network """ - return self.network.nnodes + return self.c_network.nnodes @property def nedges(self): """ Number of edges in the network """ - return self.network.nedges + return self.c_network.nedges @property def nstates(self): """ Number of states in the network """ - return self.network.nstates + return self.c_network.nstates cpdef void step(self): """ Step the network state """ @@ -304,7 +301,7 @@ cdef class PyNetwork(ODESystem): ) # Logging # TODO: Use network options to check global logging flag - logger((self.iteration%self.buffer_size), self.data, self.network) + logger((self.iteration%self.buffer_size), self.data, self.c_network) self.iteration += 1 cdef void evaluate(self, double time, double[:] states, double[:] derivatives) noexcept: @@ -312,6 +309,6 @@ cdef class PyNetwork(ODESystem): # Update noise model cdef NetworkDataCy data = self.data - ode(time, states, data, self.network, self.__tmp_node_outputs) + ode(time, states, data, self.c_network, self.__tmp_node_outputs) data.outputs.array[:] = self.__tmp_node_outputs derivatives[:] = data.derivatives.array diff --git a/farms_network/core/node.pxd b/farms_network/core/node.pxd index fec2554..8447f50 100644 --- a/farms_network/core/node.pxd +++ b/farms_network/core/node.pxd @@ -21,10 +21,10 @@ Header for Node Base Struture. """ -from .edge cimport Edge +from .edge cimport EdgeCy -cdef struct Node: +cdef struct NodeCy: # Generic parameters unsigned int nstates # Number of state variables in the node. unsigned int nparameters # Number of parameters for the node. @@ -48,8 +48,8 @@ cdef struct Node: unsigned int* inputs, double* weights, double noise, - Node* node, - Edge** edges, + NodeCy* c_node, + EdgeCy** c_edges, ) noexcept double output( @@ -59,8 +59,8 @@ cdef struct Node: double* network_outputs, unsigned int* inputs, double* weights, - Node* node, - Edge** edges, + NodeCy* c_node, + EdgeCy** c_edges, ) noexcept @@ -74,8 +74,8 @@ cdef: unsigned int* inputs, double* weights, double noise, - Node* node, - Edge** edges, + NodeCy* c_node, + EdgeCy** c_edges, ) noexcept double output( double time, @@ -84,13 +84,13 @@ cdef: double* network_outputs, unsigned int* inputs, double* weights, - Node* node, - Edge** edges, + NodeCy* c_node, + EdgeCy** c_edges, ) noexcept -cdef class PyNode: +cdef class Node: """ Python interface to Node C-Structure""" cdef: - Node* node + NodeCy* c_node diff --git a/farms_network/core/node.pyx b/farms_network/core/node.pyx index 1730025..a08d407 100644 --- a/farms_network/core/node.pyx +++ b/farms_network/core/node.pyx @@ -33,10 +33,10 @@ cdef void ode( unsigned int* inputs, double* weights, double noise, - Node* node, - Edge** edges, + NodeCy* node, + EdgeCy** c_edges, ) noexcept: - """ Node ODE """ + """ NodeCy ODE """ printf("Base implementation of ODE C function \n") @@ -47,37 +47,37 @@ cdef double output( double* network_outputs, unsigned int* inputs, double* weights, - Node* node, - Edge** edges, + NodeCy* node, + EdgeCy** c_edges, ) noexcept: - """ Node output """ + """ NodeCy output """ printf("Base implementation of output C function \n") return 0.0 -cdef class PyNode: +cdef class Node: """ Python interface to Node C-Structure""" def __cinit__(self): - self.node = malloc(sizeof(Node)) - if self.node is NULL: - raise MemoryError("Failed to allocate memory for Node") - self.node.name = NULL - self.node.model_type = strdup("base".encode('UTF-8')) - self.node.ode = ode - self.node.output = output - self.node.nparameters = 0 - self.node.ninputs = 0 + self.c_node = malloc(sizeof(NodeCy)) + if self.c_node is NULL: + raise MemoryError("Failed to allocate memory for NodeCy") + self.c_node.name = NULL + self.c_node.model_type = strdup("base".encode('UTF-8')) + self.c_node.ode = ode + self.c_node.output = output + self.c_node.nparameters = 0 + self.c_node.ninputs = 0 def __dealloc__(self): - if self.node is not NULL: - if self.node.name is not NULL: - free(self.node.name) - if self.node.model_type is not NULL: - free(self.node.model_type) - if self.node.parameters is not NULL: - free(self.node.parameters) - free(self.node) + if self.c_node is not NULL: + if self.c_node.name is not NULL: + free(self.c_node.name) + if self.c_node.model_type is not NULL: + free(self.c_node.model_type) + if self.c_node.parameters is not NULL: + free(self.c_node.parameters) + free(self.c_node) def __init__(self, name: str, **kwargs): self.name = name @@ -91,44 +91,44 @@ cdef class PyNode: # Property methods for name @property def name(self): - if self.node.name is NULL: + if self.c_node.name is NULL: return None - return self.node.name.decode('UTF-8') + return self.c_node.name.decode('UTF-8') @name.setter def name(self, value): - if self.node.name is not NULL: - free(self.node.name) - self.node.name = strdup(value.encode('UTF-8')) + if self.c_node.name is not NULL: + free(self.c_node.name) + self.c_node.name = strdup(value.encode('UTF-8')) # Property methods for model_type @property def model_type(self): - if self.node.model_type is NULL: + if self.c_node.model_type is NULL: return None - return self.node.model_type.decode('UTF-8') + return self.c_node.model_type.decode('UTF-8') # Property methods for nstates @property def nstates(self): - return self.node.nstates + return self.c_node.nstates # Property methods for ninputs @property def ninputs(self): - return self.node.ninputs + return self.c_node.ninputs # Property methods for nparameters @property def nparameters(self): - return self.node.nparameters + return self.c_node.nparameters @property def parameters(self): """Generic accessor for parameters.""" - if not self.node.parameters: - raise ValueError("Node parameters are NULL") - if self.node.nparameters == 0: + if not self.c_node.parameters: + raise ValueError("NodeCy parameters are NULL") + if self.c_node.nparameters == 0: raise ValueError("No parameters available") # The derived class should override this method to provide specific behavior @@ -152,10 +152,10 @@ cdef class PyNode: cdef unsigned int* inputs_ptr = &inputs[0] cdef double* weights_ptr = &weights[0] - cdef Edge** edges = NULL + cdef EdgeCy** c_edges = NULL # Call the C function directly - self.node.ode( + self.c_node.ode( time, states_ptr, derivatives_ptr, @@ -164,8 +164,8 @@ cdef class PyNode: inputs_ptr, weights_ptr, noise, - self.node, - edges + self.c_node, + c_edges ) def output( @@ -182,14 +182,14 @@ cdef class PyNode: cdef double* network_outputs_ptr = &network_outputs[0] cdef unsigned int* inputs_ptr = &inputs[0] cdef double* weights_ptr = &weights[0] - cdef Edge** edges = NULL - return self.node.output( + cdef EdgeCy** c_edges = NULL + return self.c_node.output( time, states_ptr, external_input, network_outputs_ptr, inputs_ptr, weights_ptr, - self.node, - edges + self.c_node, + c_edges ) diff --git a/farms_network/models/external_relay.pxd b/farms_network/models/external_relay.pxd index 34f39b5..c8f15ec 100644 --- a/farms_network/models/external_relay.pxd +++ b/farms_network/models/external_relay.pxd @@ -20,8 +20,8 @@ Oscillator model """ -from ..core.node cimport Node, PyNode -from ..core.edge cimport Edge +from ..core.node cimport NodeCy, Node +from ..core.edge cimport EdgeCy cdef: @@ -32,10 +32,10 @@ cdef: double* network_outputs, unsigned int* inputs, double* weights, - Node* node, - Edge** edges, + NodeCy* c_node, + EdgeCy** c_edges, ) noexcept -cdef class PyExternalRelayNode(PyNode): +cdef class ExternalRelayNode(Node): """ Python interface to External Relay Node C-Structure """ diff --git a/farms_network/models/external_relay.pyx b/farms_network/models/external_relay.pyx index e5a8d2b..381d046 100644 --- a/farms_network/models/external_relay.pyx +++ b/farms_network/models/external_relay.pyx @@ -20,7 +20,7 @@ External model """ from libc.stdio cimport printf -from libc.stdlib cimport free, malloc +from libc.stdlib cimport malloc from libc.string cimport strdup @@ -31,21 +31,21 @@ cdef double output( double* network_outputs, unsigned int* inputs, double* weights, - Node* node, - Edge** edges, + NodeCy* c_node, + EdgeCy** c_edges, ) noexcept: """ Node output. """ return external_input -cdef class PyExternalRelayNode(PyNode): +cdef class ExternalRelayNode(Node): """ Python interface to External Relay Node C-Structure """ def __cinit__(self): - self.node.model_type = strdup("EXTERNAL_RELAY".encode('UTF-8')) + self.c_node.model_type = strdup("EXTERNAL_RELAY".encode('UTF-8')) # override default ode and out methods - self.node.is_statefull = False - self.node.output = output + self.c_node.is_statefull = False + self.c_node.output = output def __init__(self, name: str, **kwargs): super().__init__(name) diff --git a/farms_network/models/factory.py b/farms_network/models/factory.py index d468e9d..33dedaf 100644 --- a/farms_network/models/factory.py +++ b/farms_network/models/factory.py @@ -19,128 +19,151 @@ Factory class for generating the node model. """ -from farms_network.core.edge import PyEdge -from farms_network.models.leaky_integrator import PyLeakyIntegratorNode -from farms_network.models.external_relay import PyExternalRelayNode +from typing import Dict, Optional, Type + +from farms_network.core.edge import Edge +from farms_network.core.node import Node +from farms_network.models.external_relay import ExternalRelayNode # from farms_network.models.fitzhugh_nagumo import FitzhughNagumo # from farms_network.models.hh_daun_motoneuron import HHDaunMotoneuron -from farms_network.models.hopf_oscillator import PyHopfOscillatorNode -from farms_network.models.li_danner import PyLIDannerNode -from farms_network.models.li_nap_danner import PyLINaPDannerNode -from farms_network.models.linear import PyLinearNode +from farms_network.models.hopf_oscillator import HopfOscillatorNode +from farms_network.models.leaky_integrator import LeakyIntegratorNode +from farms_network.models.li_danner import LIDannerNode +from farms_network.models.li_nap_danner import LINaPDannerNode +from farms_network.models.linear import LinearNode # from farms_network.models.lif_daun_interneuron import LIFDaunInterneuron # from farms_network.models.matsuoka_node import MatsuokaNode # from farms_network.models.morphed_oscillator import MorphedOscillator # from farms_network.models.morris_lecar import MorrisLecarNode -from farms_network.models.oscillator import PyOscillatorEdge, PyOscillatorNode -from farms_network.models.relu import PyReLUNode - -# from farms_network.models.sensory_node import SensoryNode +from farms_network.models.oscillator import OscillatorEdge, OscillatorNode +from farms_network.models.relu import ReLUNode class NodeFactory: """Implementation of Factory Node class. """ - nodes = { + _nodes: Dict[str, Type[Edge]] = { # 'if': PyIntegrateAndFire, - 'oscillator': PyOscillatorNode, - 'hopf_oscillator': PyHopfOscillatorNode, + 'oscillator': OscillatorNode, + 'hopf_oscillator': HopfOscillatorNode, # 'morphed_oscillator': MorphedOscillator, - 'leaky_integrator': PyLeakyIntegratorNode, - 'external_relay': PyExternalRelayNode, - 'linear': PyLinearNode, - 'li_nap_danner': PyLINaPDannerNode, - 'li_danner': PyLIDannerNode, + 'leaky_integrator': LeakyIntegratorNode, + 'external_relay': ExternalRelayNode, + 'linear': LinearNode, + 'li_nap_danner': LINaPDannerNode, + 'li_danner': LIDannerNode, # 'lif_daun_interneuron': LIFDaunInterneuron, # 'hh_daun_motoneuron': HHDaunMotoneuron, # 'fitzhugh_nagumo': FitzhughNagumo, # 'matsuoka_node': MatsuokaNode, # 'morris_lecar': MorrisLecarNode, - 'relu': PyReLUNode, + 'relu': ReLUNode, } - def __init__(self): - """Factory initialization.""" - super(NodeFactory, self).__init__() + @classmethod + def available_types(cls) -> list[str]: + """Get list of registered node types. - @staticmethod - def register_node(node_type, node_instance): + Returns: + Sorted list of registered node type identifiers """ - Register a new type of node that is a child class of Node. - Parameters - ---------- - self: type - description - node_type: - String to identifier for the node. - node_instance: - Class of the node to register. + return sorted(cls._nodes.keys()) + + @classmethod + def create(cls, node_type: str) -> Node: + """Create a node instance of the specified type. + + Args: + node_type: Type identifier of node to create + + Returns: + Instance of requested node class + + Raises: + KeyError: If node_type is not registered """ - NodeFactory.nodes[node_type] = node_instance - - @staticmethod - def generate_node(node_type): - """Generate the necessary type of node. - Parameters - ---------- - self: type - description - node_type: - One of the following list of available nodes. - 1. if - Integrate and Fire - 2. lif_danner_nap - LIF Danner Nap - 3. lif_danner - LIF Danner - 4. lif_daun_internode - LIF Daun Internode - 5. hh_daun_motornode - HH_Daun_Motornode - Returns - ------- - node: - Appropriate node class. + try: + node_class = cls._nodes[node_type] + return node_class + except KeyError: + available = ', '.join(sorted(cls._nodes.keys())) + raise KeyError( + f"Unknown node type: {node_type}. " + f"Available types: {available}" + ) + + @classmethod + def register(cls, node_type: str, node_class: Type[Node]) -> None: + """Register a new node type. + + Args: + node_type: Unique identifier for the node + node_class: Node class to register, must inherit from Node + + Raises: + TypeError: If node_class doesn't inherit from Node + ValueError: If node_type is already registered """ - node = NodeFactory.nodes.get(node_type) - if not node: - raise ValueError(node_type) - return node + if not issubclass(node_class, Node): + raise TypeError(f"Node class must inherit from Node: {node_class}") + + if node_type in cls._nodes: + raise ValueError(f"Node type already registered: {node_type}") + + cls._nodes[node_type] = node_class class EdgeFactory: """Implementation of Factory Edge class. """ - edges = { - 'oscillator': PyOscillatorEdge, + _edges: Dict[str, Type[Edge]] = { + 'oscillator': OscillatorEdge, } - def __init__(self): - """Factory initialization.""" - super().__init__() + @classmethod + def available_types(cls) -> list[str]: + """Get list of registered edge types.""" + return sorted(cls._edges.keys()) - @staticmethod - def register_edge(edge_type, edge_instance): - """ - Register a new type of edge that is a child class of Edge. - Parameters - ---------- - self: type - description - edge_type: - String to identifier for the edge. - edge_instance: - Class of the edge to register. + @classmethod + def create(cls, edge_type: str) -> Edge: + """Create an edge instance of the specified type. + + Args: + edge_type: Type identifier of edge to create + + Returns: + Instance of requested edge class + + Raises: + KeyError: If edge_type is not registered """ - EdgeFactory.edges[edge_type] = edge_instance - - @staticmethod - def generate_edge(edge_type): - """Generate the necessary type of edge. - Parameters - ---------- - edge_type: - Returns - ------- - edge: - Appropriate edge class. + try: + edge_class = cls._edges.get(edge_type, Edge) + return edge_class + except KeyError: + available = ', '.join(sorted(cls._edges.keys())) + raise KeyError( + f"Unknown edge type: {edge_type}. " + f"Available types: {available}" + ) + + @classmethod + def register(cls, edge_type: str, edge_class: Type[Edge]) -> None: + """Register a new edge type. + + Args: + edge_type: Unique identifier for the edge + edge_class: Edge class to register, must inherit from Edge + + Raises: + TypeError: If edge_class doesn't inherit from Edge + ValueError: If edge_type is already registered """ - edge = EdgeFactory.edges.get(edge_type, PyEdge) - if not edge: - raise ValueError(edge_type) - return edge + if not issubclass(edge_class, Edge): + raise TypeError(f"Edge class must inherit from Edge: {edge_class}") + + if edge_type in cls._edges: + raise ValueError(f"Edge type already registered: {edge_type}") + + cls._edges[edge_type] = edge_class diff --git a/farms_network/models/hopf_oscillator.pxd b/farms_network/models/hopf_oscillator.pxd index 2b4047c..1a7fc32 100644 --- a/farms_network/models/hopf_oscillator.pxd +++ b/farms_network/models/hopf_oscillator.pxd @@ -20,8 +20,8 @@ Hopf-Oscillator model """ -from ..core.node cimport Node, PyNode -from ..core.edge cimport Edge, PyEdge +from ..core.node cimport NodeCy, Node +from ..core.edge cimport EdgeCy, Edge cdef enum: @@ -50,8 +50,8 @@ cdef: unsigned int* inputs, double* weights, double noise, - Node* node, - Edge** edges, + NodeCy* c_node, + EdgeCy** c_edges, ) noexcept double output( double time, @@ -60,12 +60,12 @@ cdef: double* network_outputs, unsigned int* inputs, double* weights, - Node* node, - Edge** edges, + NodeCy* c_node, + EdgeCy** c_edges, ) noexcept -cdef class PyHopfOscillatorNode(PyNode): +cdef class HopfOscillatorNode(Node): """ Python interface to HopfOscillator Node C-Structure """ cdef: diff --git a/farms_network/models/hopf_oscillator.pyx b/farms_network/models/hopf_oscillator.pyx index 185cbec..c3ba230 100644 --- a/farms_network/models/hopf_oscillator.pyx +++ b/farms_network/models/hopf_oscillator.pyx @@ -47,12 +47,12 @@ cdef void ode( unsigned int* inputs, double* weights, double noise, - Node* node, - Edge** edges, + NodeCy* c_node, + EdgeCy** c_edges, ) noexcept: """ ODE """ # Parameters - cdef HopfOscillatorNodeParameters params = ( node[0].parameters)[0] + cdef HopfOscillatorNodeParameters params = ( c_node[0].parameters)[0] # States cdef double state_x = states[STATE.x] @@ -63,7 +63,7 @@ cdef void ode( unsigned int j double _input, _weight - cdef unsigned int ninputs = node.ninputs + cdef unsigned int ninputs = c_node.ninputs for j in range(ninputs): _input = network_outputs[inputs[j]] _weight = weights[j] @@ -87,32 +87,32 @@ cdef double output( double* network_outputs, unsigned int* inputs, double* weights, - Node* node, - Edge** edges, + NodeCy* c_node, + EdgeCy** c_edges, ) noexcept: """ Node output. """ return states[STATE.y] -cdef class PyHopfOscillatorNode(PyNode): +cdef class HopfOscillatorNode(Node): """ Python interface to HopfOscillator Node C-Structure """ def __cinit__(self): - self.node.model_type = strdup("HOPF_OSCILLATOR".encode('UTF-8')) + self.c_node.model_type = strdup("HOPF_OSCILLATOR".encode('UTF-8')) # override default ode and out methods - self.node.is_statefull = True - self.node.ode = ode - self.node.output = output + self.c_node.is_statefull = True + self.c_node.ode = ode + self.c_node.output = output # parameters - self.node.parameters = malloc(sizeof(HopfOscillatorNodeParameters)) - if self.node.parameters is NULL: + self.c_node.parameters = malloc(sizeof(HopfOscillatorNodeParameters)) + if self.c_node.parameters is NULL: raise MemoryError("Failed to allocate memory for node parameters") def __init__(self, name: str, **kwargs): super().__init__(name) # Set node parameters - cdef HopfOscillatorNodeParameters* params = (self.node.parameters) + cdef HopfOscillatorNodeParameters* params = (self.c_node.parameters) params.mu = kwargs.pop("mu") params.omega = kwargs.pop("omega") params.alpha = kwargs.pop("alpha") @@ -123,5 +123,5 @@ cdef class PyHopfOscillatorNode(PyNode): @property def parameters(self): """ Parameters in the network """ - cdef HopfOscillatorNodeParameters params = ( self.node.parameters)[0] + cdef HopfOscillatorNodeParameters params = ( self.c_node.parameters)[0] return params diff --git a/farms_network/models/izhikevich.pxd b/farms_network/models/izhikevich.pxd index 5b675f2..fb94545 100644 --- a/farms_network/models/izhikevich.pxd +++ b/farms_network/models/izhikevich.pxd @@ -19,8 +19,8 @@ limitations under the License. Izhikevich neuron model based on Izhikevich et.al. 2003 """ -from ..core.node cimport Node, PyNode -from ..core.edge cimport Edge, PyEdge +from ..core.node cimport NodeCy, Node +from ..core.edge cimport EdgeCy, Edge cdef enum: @@ -48,8 +48,8 @@ cdef: unsigned int* inputs, double* weights, double noise, - Node* node, - Edge** edges, + NodeCy* c_node, + EdgeCy** c_edges, ) noexcept double output( double time, @@ -58,12 +58,12 @@ cdef: double* network_outputs, unsigned int* inputs, double* weights, - Node* node, - Edge** edges, + NodeCy* c_node, + EdgeCy** c_edges, ) noexcept -cdef class PyIzhikevichNode(PyNode): +cdef class IzhikevichNode(Node): """ Python interface to Izhikevich Node C-Structure """ cdef: diff --git a/farms_network/models/izhikevich.pyx b/farms_network/models/izhikevich.pyx index f71cabe..09f7735 100644 --- a/farms_network/models/izhikevich.pyx +++ b/farms_network/models/izhikevich.pyx @@ -41,13 +41,13 @@ cdef void ode( unsigned int* inputs, double* weights, double noise, - Node* node, - Edge** edges, + NodeCy* c_node, + EdgeCy** c_edges, ) noexcept: """ Node ODE """ # Parameters cdef IzhikevichNodeParameters params = ( - node[0].parameters + c_node[0].parameters )[0] # States @@ -60,7 +60,7 @@ cdef void ode( # unsigned int j # double _node_out, res, _input, _weight - # cdef unsigned int ninputs = node.ninputs + # cdef unsigned int ninputs = c_node.ninputs # for j in range(ninputs): # _input = network_outputs[inputs[j]] # _weight = weights[j] @@ -84,36 +84,36 @@ cdef double output( double* network_outputs, unsigned int* inputs, double* weights, - Node* node, - Edge** edges, + NodeCy* c_node, + EdgeCy** c_edges, ) noexcept: """ Node output. """ ... -cdef class PyIzhikevichNode(PyNode): +cdef class IzhikevichNode(Node): """ Python interface to Izhikevich Node C-Structure """ def __cinit__(self): - self.node.model_type = strdup("IZHIKEVICH".encode('UTF-8')) + self.c_node.model_type = strdup("IZHIKEVICH".encode('UTF-8')) # override default ode and out methods - self.node.is_statefull = True - self.node.output = output + self.c_node.is_statefull = True + self.c_node.output = output # parameters - self.node.parameters = malloc(sizeof(IzhikevichNodeParameters)) - if self.node.parameters is NULL: + self.c_node.parameters = malloc(sizeof(IzhikevichNodeParameters)) + if self.c_node.parameters is NULL: raise MemoryError("Failed to allocate memory for node parameters") def __init__(self, name: str, **kwargs): super().__init__(name) # Set node parameters - cdef IzhikevichNodeParameters* param = (self.node.parameters) + cdef IzhikevichNodeParameters* param = (self.c_node.parameters) if kwargs: raise Exception(f'Unknown kwargs: {kwargs}') @property def parameters(self): """ Parameters in the network """ - cdef IzhikevichNodeParameters params = ( self.node.parameters)[0] + cdef IzhikevichNodeParameters params = ( self.c_node.parameters)[0] return params diff --git a/farms_network/models/leaky_integrator.pxd b/farms_network/models/leaky_integrator.pxd index 80d553a..a1c9153 100644 --- a/farms_network/models/leaky_integrator.pxd +++ b/farms_network/models/leaky_integrator.pxd @@ -19,8 +19,8 @@ limitations under the License. Leaky Integrator Neuron. """ -from ..core.node cimport Node, PyNode -from ..core.edge cimport Edge +from ..core.node cimport NodeCy, Node +from ..core.edge cimport EdgeCy cdef enum: @@ -47,8 +47,8 @@ cdef: unsigned int* inputs, double* weights, double noise, - Node* node, - Edge** edges, + NodeCy* c_node, + EdgeCy** c_edges, ) noexcept double output( double time, @@ -57,12 +57,12 @@ cdef: double* network_outputs, unsigned int* inputs, double* weights, - Node* node, - Edge** edges, + NodeCy* c_node, + EdgeCy** c_edges, ) noexcept -cdef class PyLeakyIntegratorNode(PyNode): +cdef class LeakyIntegratorNode(Node): """ Python interface to Leaky Integrator Node C-Structure """ cdef: diff --git a/farms_network/models/leaky_integrator.pyx b/farms_network/models/leaky_integrator.pyx index 2586be0..bbea734 100644 --- a/farms_network/models/leaky_integrator.pyx +++ b/farms_network/models/leaky_integrator.pyx @@ -44,13 +44,13 @@ cdef void ode( unsigned int* inputs, double* weights, double noise, - Node* node, - Edge** edges, + NodeCy* c_node, + EdgeCy** c_edges, ) noexcept: """ ODE """ # Parameters cdef LeakyIntegratorNodeParameters params = ( - node[0].parameters + c_node[0].parameters )[0] # States @@ -62,7 +62,7 @@ cdef void ode( unsigned int j double _node_out, res, _input, _weight - cdef unsigned int ninputs = node.ninputs + cdef unsigned int ninputs = c_node.ninputs for j in range(ninputs): _input = network_outputs[inputs[j]] _weight = weights[j] @@ -82,37 +82,39 @@ cdef double output( double* network_outputs, unsigned int* inputs, double* weights, - Node* node, - Edge** edges, + NodeCy* c_node, + EdgeCy** c_edges, ) noexcept: """ Node output. """ - cdef LeakyIntegratorNodeParameters params = ( node.parameters)[0] + cdef LeakyIntegratorNodeParameters params = ( + c_node[0].parameters + )[0] cdef double state_m = states[STATE.m] cdef double _n_out = 1.0 / (1.0 + cexp(-params.D * (state_m + params.bias))) return _n_out -cdef class PyLeakyIntegratorNode(PyNode): +cdef class LeakyIntegratorNode(Node): """ Python interface to Leaky Integrator Node C-Structure """ def __cinit__(self): - self.node.model_type = strdup("LEAKY_INTEGRATOR".encode('UTF-8')) + self.c_node.model_type = strdup("LEAKY_INTEGRATOR".encode('UTF-8')) # override default ode and out methods - self.node.is_statefull = True - self.node.ode = ode - self.node.output = output + self.c_node.is_statefull = True + self.c_node.ode = ode + self.c_node.output = output # parameters - self.node.parameters = malloc(sizeof(LeakyIntegratorNodeParameters)) - if self.node.parameters is NULL: + self.c_node.parameters = malloc(sizeof(LeakyIntegratorNodeParameters)) + if self.c_node.parameters is NULL: raise MemoryError("Failed to allocate memory for node parameters") def __init__(self, name: str, **kwargs): super().__init__(name) # Set node parameters - cdef LeakyIntegratorNodeParameters* param = (self.node.parameters) + cdef LeakyIntegratorNodeParameters* param = (self.c_node.parameters) param.tau = kwargs.pop("tau") param.bias = kwargs.pop("bias") param.D = kwargs.pop("D") @@ -122,5 +124,5 @@ cdef class PyLeakyIntegratorNode(PyNode): @property def parameters(self): """ Parameters in the network """ - cdef LeakyIntegratorNodeParameters params = ( self.node.parameters)[0] + cdef LeakyIntegratorNodeParameters params = ( self.c_node.parameters)[0] return params diff --git a/farms_network/models/li_danner.pxd b/farms_network/models/li_danner.pxd index cd2b584..577678f 100644 --- a/farms_network/models/li_danner.pxd +++ b/farms_network/models/li_danner.pxd @@ -19,8 +19,8 @@ limitations under the License. Leaky Integrator Node Based on Danner et.al. """ -from ..core.node cimport Node, PyNode -from ..core.edge cimport Edge +from ..core.node cimport NodeCy, Node +from ..core.edge cimport EdgeCy cdef enum: @@ -53,8 +53,8 @@ cdef: unsigned int* inputs, double* weights, double noise, - Node* node, - Edge** edges, + NodeCy* c_node, + EdgeCy** c_edges, ) noexcept double output( double time, @@ -63,12 +63,12 @@ cdef: double* network_outputs, unsigned int* inputs, double* weights, - Node* node, - Edge** edges, + NodeCy* c_node, + EdgeCy** c_edges, ) noexcept -cdef class PyLIDannerNode(PyNode): +cdef class LIDannerNode(Node): """ Python interface to Leaky Integrator Node C-Structure """ cdef: diff --git a/farms_network/models/li_danner.pyx b/farms_network/models/li_danner.pyx index bafbd9c..e91d886 100644 --- a/farms_network/models/li_danner.pyx +++ b/farms_network/models/li_danner.pyx @@ -43,13 +43,13 @@ cdef void ode( unsigned int* inputs, double* weights, double noise, - Node* node, - Edge** edges, + NodeCy* c_node, + EdgeCy** c_edges, ) noexcept: """ ODE """ # Parameters cdef LIDannerNodeParameters params = ( - node[0].parameters + c_node[0].parameters )[0] # States @@ -64,7 +64,7 @@ cdef void ode( unsigned int j double _node_out, res, _input, _weight - cdef unsigned int ninputs = node.ninputs + cdef unsigned int ninputs = c_node.ninputs for j in range(ninputs): _input = network_outputs[inputs[j]] _weight = weights[j] @@ -89,12 +89,12 @@ cdef double output( double* network_outputs, unsigned int* inputs, double* weights, - Node* node, - Edge** edges, + NodeCy* c_node, + EdgeCy** c_edges, ) noexcept: """ Node output. """ - cdef LIDannerNodeParameters params = ( node.parameters)[0] + cdef LIDannerNodeParameters params = ( c_node.parameters)[0] cdef double _n_out = 0.0 cdef double state_v = states[STATE.v] @@ -107,25 +107,25 @@ cdef double output( return _n_out -cdef class PyLIDannerNode(PyNode): +cdef class LIDannerNode(Node): """ Python interface to Leaky Integrator Node C-Structure """ def __cinit__(self): - self.node.model_type = strdup("LI_DANNER".encode('UTF-8')) + self.c_node.model_type = strdup("LI_DANNER".encode('UTF-8')) # override default ode and out methods - self.node.is_statefull = True - self.node.ode = ode - self.node.output = output + self.c_node.is_statefull = True + self.c_node.ode = ode + self.c_node.output = output # parameters - self.node.parameters = malloc(sizeof(LIDannerNodeParameters)) - if self.node.parameters is NULL: + self.c_node.parameters = malloc(sizeof(LIDannerNodeParameters)) + if self.c_node.parameters is NULL: raise MemoryError("Failed to allocate memory for node parameters") def __init__(self, name: str, **kwargs): super().__init__(name) # Set node parameters - cdef LIDannerNodeParameters* param = (self.node.parameters) + cdef LIDannerNodeParameters* param = (self.c_node.parameters) param.c_m = kwargs.pop("c_m") param.g_leak = kwargs.pop("g_leak") param.e_leak = kwargs.pop("e_leak") @@ -141,5 +141,5 @@ cdef class PyLIDannerNode(PyNode): @property def parameters(self): """ Parameters in the network """ - cdef LIDannerNodeParameters params = ( self.node.parameters)[0] + cdef LIDannerNodeParameters params = ( self.c_node.parameters)[0] return params diff --git a/farms_network/models/li_nap_danner.pxd b/farms_network/models/li_nap_danner.pxd index e86fed5..60a3044 100644 --- a/farms_network/models/li_nap_danner.pxd +++ b/farms_network/models/li_nap_danner.pxd @@ -19,8 +19,8 @@ limitations under the License. Leaky Integrator Node Based on Danner et.al. with Na and K channels """ -from ..core.node cimport Node, PyNode -from ..core.edge cimport Edge +from ..core.node cimport NodeCy, Node +from ..core.edge cimport EdgeCy cdef enum: @@ -64,8 +64,8 @@ cdef: unsigned int* inputs, double* weights, double noise, - Node* node, - Edge** edges, + NodeCy* c_node, + EdgeCy** c_edges, ) noexcept double output( double time, @@ -74,12 +74,12 @@ cdef: double* network_outputs, unsigned int* inputs, double* weights, - Node* node, - Edge** edges, + NodeCy* c_node, + EdgeCy** c_edges, ) noexcept -cdef class PyLINaPDannerNode(PyNode): +cdef class LINaPDannerNode(Node): """ Python interface to Leaky Integrator Node C-Structure """ cdef: diff --git a/farms_network/models/li_nap_danner.pyx b/farms_network/models/li_nap_danner.pyx index 220c1a8..05df91a 100644 --- a/farms_network/models/li_nap_danner.pyx +++ b/farms_network/models/li_nap_danner.pyx @@ -46,11 +46,11 @@ cdef void ode( unsigned int* inputs, double* weights, double noise, - Node* node, - Edge** edges, + NodeCy* c_node, + EdgeCy** c_edges, ) noexcept: """ ODE """ - cdef LINaPDannerNodeParameters params = ( node[0].parameters)[0] + cdef LINaPDannerNodeParameters params = ( c_node[0].parameters)[0] # States cdef double state_v = states[STATE.v] @@ -79,7 +79,7 @@ cdef void ode( unsigned int j double _input, _weight - cdef unsigned int ninputs = node.ninputs + cdef unsigned int ninputs = c_node.ninputs for j in range(ninputs): _input = network_outputs[inputs[j]] _weight = weights[j] @@ -107,12 +107,12 @@ cdef double output( double* network_outputs, unsigned int* inputs, double* weights, - Node* node, - Edge** edges, + NodeCy* c_node, + EdgeCy** c_edges, ) noexcept: """ Node output. """ - cdef LINaPDannerNodeParameters params = ( node.parameters)[0] + cdef LINaPDannerNodeParameters params = ( c_node.parameters)[0] cdef double _n_out = 0.0 cdef double state_v = states[STATE.v] if state_v >= params.v_max: @@ -124,19 +124,19 @@ cdef double output( return _n_out -cdef class PyLINaPDannerNode(PyNode): +cdef class LINaPDannerNode(Node): """ Python interface to Leaky Integrator Node with persistence sodium C-Structure """ def __cinit__(self): # override defaults - self.node.model_type = strdup("LI_NAP_DANNER".encode('UTF-8')) + self.c_node.model_type = strdup("LI_NAP_DANNER".encode('UTF-8')) # override default ode and out methods - self.node.is_statefull = True - self.node.ode = ode - self.node.output = output + self.c_node.is_statefull = True + self.c_node.ode = ode + self.c_node.output = output # parameters - self.node.parameters = malloc(sizeof(LINaPDannerNodeParameters)) - if self.node.parameters is NULL: + self.c_node.parameters = malloc(sizeof(LINaPDannerNodeParameters)) + if self.c_node.parameters is NULL: raise MemoryError("Failed to allocate memory for node parameters") def __init__(self, name: str, **kwargs): @@ -144,7 +144,7 @@ cdef class PyLINaPDannerNode(PyNode): # Set node parameters cdef LINaPDannerNodeParameters* param = ( - self.node.parameters + self.c_node.parameters ) param.c_m = kwargs.pop("c_m") param.g_nap = kwargs.pop("g_nap") @@ -172,6 +172,6 @@ cdef class PyLINaPDannerNode(PyNode): def parameters(self): """ Parameters in the network """ cdef LINaPDannerNodeParameters params = ( - self.node.parameters + self.c_node.parameters )[0] return params diff --git a/farms_network/models/linear.pxd b/farms_network/models/linear.pxd index 5371a7b..d473c7f 100644 --- a/farms_network/models/linear.pxd +++ b/farms_network/models/linear.pxd @@ -20,8 +20,8 @@ Linear model """ -from ..core.node cimport Node, PyNode -from ..core.edge cimport Edge, PyEdge +from ..core.node cimport NodeCy, Node +from ..core.edge cimport EdgeCy, Edge cdef enum: @@ -42,12 +42,12 @@ cdef: double* network_outputs, unsigned int* inputs, double* weights, - Node* node, - Edge** edges, + NodeCy* c_node, + EdgeCy** c_edges, ) noexcept -cdef class PyLinearNode(PyNode): +cdef class LinearNode(Node): """ Python interface to Linear Node C-Structure """ cdef: diff --git a/farms_network/models/linear.pyx b/farms_network/models/linear.pyx index df92085..ac655d0 100644 --- a/farms_network/models/linear.pyx +++ b/farms_network/models/linear.pyx @@ -37,17 +37,17 @@ cdef double output( double* network_outputs, unsigned int* inputs, double* weights, - Node* node, - Edge** edges, + NodeCy* c_node, + EdgeCy** c_edges, ) noexcept: """ Node output. """ - cdef LinearNodeParameters params = ( node[0].parameters)[0] + cdef LinearNodeParameters params = ( c_node[0].parameters)[0] cdef: double _sum = 0.0 unsigned int j double _input, _weight - cdef unsigned int ninputs = node.ninputs + cdef unsigned int ninputs = c_node.ninputs _sum += external_input for j in range(ninputs): _input = network_outputs[inputs[j]] @@ -57,24 +57,24 @@ cdef double output( return (params.slope*_sum + params.bias) -cdef class PyLinearNode(PyNode): +cdef class LinearNode(Node): """ Python interface to Linear Node C-Structure """ def __cinit__(self): - self.node.model_type = strdup("LINEAR".encode('UTF-8')) + self.c_node.model_type = strdup("LINEAR".encode('UTF-8')) # override default ode and out methods - self.node.is_statefull = False - self.node.output = output + self.c_node.is_statefull = False + self.c_node.output = output # parameters - self.node.parameters = malloc(sizeof(LinearNodeParameters)) - if self.node.parameters is NULL: + self.c_node.parameters = malloc(sizeof(LinearNodeParameters)) + if self.c_node.parameters is NULL: raise MemoryError("Failed to allocate memory for node parameters") def __init__(self, name: str, **kwargs): super().__init__(name) # Set node parameters - cdef LinearNodeParameters* param = (self.node.parameters) + cdef LinearNodeParameters* param = (self.c_node.parameters) param.slope = kwargs.pop("slope") param.bias = kwargs.pop("bias") if kwargs: @@ -83,5 +83,5 @@ cdef class PyLinearNode(PyNode): @property def parameters(self): """ Parameters in the network """ - cdef LinearNodeParameters params = ( self.node.parameters)[0] + cdef LinearNodeParameters params = ( self.c_node.parameters)[0] return params diff --git a/farms_network/models/oscillator.pxd b/farms_network/models/oscillator.pxd index bad1953..d60a477 100644 --- a/farms_network/models/oscillator.pxd +++ b/farms_network/models/oscillator.pxd @@ -20,8 +20,8 @@ Oscillator model """ -from ..core.node cimport Node, PyNode -from ..core.edge cimport Edge, PyEdge +from ..core.node cimport NodeCy, Node +from ..core.edge cimport EdgeCy, Edge cdef enum: @@ -55,8 +55,8 @@ cdef: unsigned int* inputs, double* weights, double noise, - Node* node, - Edge** edges, + NodeCy* c_node, + EdgeCy** c_edges, ) noexcept double output( double time, @@ -65,19 +65,19 @@ cdef: double* network_outputs, unsigned int* inputs, double* weights, - Node* node, - Edge** edges, + NodeCy* c_node, + EdgeCy** c_edges, ) noexcept -cdef class PyOscillatorNode(PyNode): +cdef class OscillatorNode(Node): """ Python interface to Oscillator Node C-Structure """ cdef: OscillatorNodeParameters parameters -cdef class PyOscillatorEdge(PyEdge): +cdef class OscillatorEdge(Edge): """ Python interface to Oscillator Edge C-Structure """ cdef: diff --git a/farms_network/models/oscillator.pyx b/farms_network/models/oscillator.pyx index f98a884..27f4f37 100644 --- a/farms_network/models/oscillator.pyx +++ b/farms_network/models/oscillator.pyx @@ -45,12 +45,12 @@ cdef void ode( unsigned int* inputs, double* weights, double noise, - Node* node, - Edge** edges, + NodeCy* c_node, + EdgeCy** c_edges, ) noexcept: """ ODE """ # Parameters - cdef OscillatorNodeParameters params = ( node[0].parameters)[0] + cdef OscillatorNodeParameters params = ( c_node[0].parameters)[0] cdef OscillatorEdgeParameters edge_params # States @@ -63,11 +63,11 @@ cdef void ode( unsigned int j double _input, _weight - cdef unsigned int ninputs = node.ninputs + cdef unsigned int ninputs = c_node.ninputs for j in range(ninputs): _input = network_outputs[inputs[j]] _weight = weights[j] - edge_params = ( edges[j].parameters)[0] + edge_params = ( c_edges[j].parameters)[0] _sum += _weight*state_amplitude*csin( _input - state_phase - edge_params.phase_difference ) @@ -88,32 +88,32 @@ cdef double output( double* network_outputs, unsigned int* inputs, double* weights, - Node* node, - Edge** edges, + NodeCy* c_node, + EdgeCy** c_edges, ) noexcept: """ Node output. """ return states[STATE.phase] -cdef class PyOscillatorNode(PyNode): +cdef class OscillatorNode(Node): """ Python interface to Oscillator Node C-Structure """ def __cinit__(self): - self.node.model_type = strdup("OSCILLATOR".encode('UTF-8')) + self.c_node.model_type = strdup("OSCILLATOR".encode('UTF-8')) # override default ode and out methods - self.node.is_statefull = True - self.node.ode = ode - self.node.output = output + self.c_node.is_statefull = True + self.c_node.ode = ode + self.c_node.output = output # parameters - self.node.parameters = malloc(sizeof(OscillatorNodeParameters)) - if self.node.parameters is NULL: + self.c_node.parameters = malloc(sizeof(OscillatorNodeParameters)) + if self.c_node.parameters is NULL: raise MemoryError("Failed to allocate memory for node parameters") def __init__(self, name: str, **kwargs): super().__init__(name) # Set node parameters - cdef OscillatorNodeParameters* param = (self.node.parameters) + cdef OscillatorNodeParameters* param = (self.c_node.parameters) param.intrinsic_frequency = kwargs.pop("intrinsic_frequency") param.nominal_amplitude = kwargs.pop("nominal_amplitude") param.amplitude_rate = kwargs.pop("amplitude_rate") @@ -123,25 +123,25 @@ cdef class PyOscillatorNode(PyNode): @property def parameters(self): """ Parameters in the network """ - cdef OscillatorNodeParameters params = ( self.node.parameters)[0] + cdef OscillatorNodeParameters params = ( self.c_node.parameters)[0] return params -cdef class PyOscillatorEdge(PyEdge): +cdef class OscillatorEdge(Edge): """ Python interface to Oscillator Edge C-Structure """ def __cinit__(self): # parameters - self.edge.parameters = malloc(sizeof(OscillatorEdgeParameters)) - if self.edge.parameters is NULL: + self.c_edge.parameters = malloc(sizeof(OscillatorEdgeParameters)) + if self.c_edge.parameters is NULL: raise MemoryError("Failed to allocate memory for edge parameters") def __init__(self, source: str, target: str, edge_type: str, **kwargs): super().__init__(source, target, edge_type) # Set edge parameters - cdef OscillatorEdgeParameters* param = (self.edge.parameters) + cdef OscillatorEdgeParameters* param = (self.c_edge.parameters) param.phase_difference = kwargs.pop("phase_difference") if kwargs: @@ -150,5 +150,5 @@ cdef class PyOscillatorEdge(PyEdge): @property def parameters(self): """ Parameters in the network """ - cdef OscillatorEdgeParameters params = ( self.edge.parameters)[0] + cdef OscillatorEdgeParameters params = ( self.c_edge.parameters)[0] return params diff --git a/farms_network/models/relu.pxd b/farms_network/models/relu.pxd index e0eb110..3fb8e71 100644 --- a/farms_network/models/relu.pxd +++ b/farms_network/models/relu.pxd @@ -20,8 +20,8 @@ Rectified Linear Unit """ -from ..core.node cimport Node, PyNode -from ..core.edge cimport Edge, PyEdge +from ..core.node cimport NodeCy, Node +from ..core.edge cimport EdgeCy, Edge cdef enum: @@ -43,12 +43,12 @@ cdef: double* network_outputs, unsigned int* inputs, double* weights, - Node* node, - Edge** edges, + NodeCy* c_node, + EdgeCy** c_edges, ) noexcept -cdef class PyReLUNode(PyNode): +cdef class ReLUNode(Node): """ Python interface to ReLU Node C-Structure """ cdef: diff --git a/farms_network/models/relu.pyx b/farms_network/models/relu.pyx index f145fa8..eb00a7f 100644 --- a/farms_network/models/relu.pyx +++ b/farms_network/models/relu.pyx @@ -38,17 +38,17 @@ cdef double output( double* network_outputs, unsigned int* inputs, double* weights, - Node* node, - Edge** edges, + NodeCy* c_node, + EdgeCy** c_edges, ) noexcept: """ Node output. """ - cdef ReLUNodeParameters params = ( node[0].parameters)[0] + cdef ReLUNodeParameters params = ( c_node[0].parameters)[0] cdef: double _sum = 0.0 unsigned int j double _input, _weight - cdef unsigned int ninputs = node.ninputs + cdef unsigned int ninputs = c_node.ninputs _sum += external_input for j in range(ninputs): _input = network_outputs[inputs[j]] @@ -59,24 +59,24 @@ cdef double output( return res -cdef class PyReLUNode(PyNode): +cdef class ReLUNode(Node): """ Python interface to ReLU Node C-Structure """ def __cinit__(self): - self.node.model_type = strdup("RELU".encode('UTF-8')) + self.c_node.model_type = strdup("RELU".encode('UTF-8')) # override default ode and out methods - self.node.is_statefull = False - self.node.output = output + self.c_node.is_statefull = False + self.c_node.output = output # parameters - self.node.parameters = malloc(sizeof(ReLUNodeParameters)) - if self.node.parameters is NULL: + self.c_node.parameters = malloc(sizeof(ReLUNodeParameters)) + if self.c_node.parameters is NULL: raise MemoryError("Failed to allocate memory for node parameters") def __init__(self, name: str, **kwargs): super().__init__(name) # Set node parameters - cdef ReLUNodeParameters* param = (self.node.parameters) + cdef ReLUNodeParameters* param = (self.c_node.parameters) param.gain = kwargs.pop("gain") param.sign = kwargs.pop("sign") param.offset = kwargs.pop("offset") @@ -86,5 +86,5 @@ cdef class PyReLUNode(PyNode): @property def parameters(self): """ Parameters in the network """ - cdef ReLUNodeParameters params = ( self.node.parameters)[0] + cdef ReLUNodeParameters params = ( self.c_node.parameters)[0] return params diff --git a/scratch/test_gui.py b/scratch/test_gui.py index 9d4279d..6b812b4 100644 --- a/scratch/test_gui.py +++ b/scratch/test_gui.py @@ -3,7 +3,7 @@ import numpy as np from farms_core.io.yaml import read_yaml -from farms_network.core.network import PyNetwork +from farms_network.core.network import Network from farms_network.core.options import NetworkOptions from farms_network.gui.gui import NetworkGUI from imgui_bundle import imgui, imgui_ctx, implot @@ -679,7 +679,7 @@ def main(): # run network network_options = NetworkOptions.from_options(read_yaml(clargs.config_path)) - network = PyNetwork.from_options(network_options) + network = Network.from_options(network_options) network.setup_integrator(network_options) # Integrate From 235e0b434f76f8cb16b90a08636e3dab68466374 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Fri, 17 Jan 2025 22:07:49 -0500 Subject: [PATCH 219/316] [MAIN] Removed -ffast-math from extra compile args This option was causing runtime issues on different platforms when importing libc math --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 236957b..53074cb 100644 --- a/setup.py +++ b/setup.py @@ -32,7 +32,7 @@ [f"farms_network/{subpackage}/*.pyx"], include_dirs=[numpy.get_include(),], # libraries=["c", "stdc++"], - extra_compile_args=['-ffast-math', '-O3'], + extra_compile_args=['-O3'], extra_link_args=['-O3'], ) for subpackage in ('core', 'models', 'numeric', 'noise') From 8ace59177213cbe8c316706a090a933e6c28aa28 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Fri, 17 Jan 2025 22:09:35 -0500 Subject: [PATCH 220/316] [CORE] Fixed segfault due to mem free in edge parameters ptr --- farms_network/core/edge.pyx | 4 ++++ farms_network/core/network.pyx | 2 ++ farms_network/core/node.pyx | 1 + 3 files changed, 7 insertions(+) diff --git a/farms_network/core/edge.pyx b/farms_network/core/edge.pyx index dd10687..5b24bed 100644 --- a/farms_network/core/edge.pyx +++ b/farms_network/core/edge.pyx @@ -31,6 +31,10 @@ cdef class Edge: self.c_edge = malloc(sizeof(EdgeCy)) if self.c_edge is NULL: raise MemoryError("Failed to allocate memory for EdgeCy") + self.c_edge.source = NULL + self.c_edge.target = NULL + self.c_edge.type = NULL + self.c_edge.parameters = NULL self.c_edge.nparameters = 0 def __dealloc__(self): diff --git a/farms_network/core/network.pyx b/farms_network/core/network.pyx index 2f4899b..0ab0226 100644 --- a/farms_network/core/network.pyx +++ b/farms_network/core/network.pyx @@ -182,6 +182,8 @@ cdef class Network(ODESystem): """ Deallocate any manual memory as part of clean up """ if self.c_network.c_nodes is not NULL: free(self.c_network.c_nodes) + if self.c_network.c_edges is not NULL: + free(self.c_network.c_edges) if self.c_network is not NULL: free(self.c_network) diff --git a/farms_network/core/node.pyx b/farms_network/core/node.pyx index a08d407..cbffd96 100644 --- a/farms_network/core/node.pyx +++ b/farms_network/core/node.pyx @@ -66,6 +66,7 @@ cdef class Node: self.c_node.model_type = strdup("base".encode('UTF-8')) self.c_node.ode = ode self.c_node.output = output + self.c_node.parameters = NULL self.c_node.nparameters = 0 self.c_node.ninputs = 0 From dfbf7e85d08be9afc2bc091ab21307f436ef2229 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Fri, 17 Jan 2025 22:10:49 -0500 Subject: [PATCH 221/316] [MAIN] Added farms-core to runtime dependencies in pyproject toml --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index d375037..86f63f5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,6 +14,7 @@ description = "Module to generate, develop and visualize neural networks" license = {file = "LICENSE"} dependencies = [ "tqdm", + "farms_core @ git+https://github.com/ShravanTata/farms_core.git@pyproject-toml-setup", ] # authors = [ # {name = "Jonathan Arreguit", email = ""}, From b95511b2cb66cba015c52eca7d57e4565942aa89 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Fri, 17 Jan 2025 23:12:22 -0500 Subject: [PATCH 222/316] [MAIN] Added seasborn to gui dependency in pyproject --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 86f63f5..41997f5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,6 +46,7 @@ Changelog = "https://github.com/me/spam/blob/master/CHANGELOG.md" gui = [ "scipy", "matplotlib", + "seaborn", "PyQt5", "networkx", "pydot", From 2f8002335081bb21c8025b9cb0c8a7af12d71eee Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Fri, 17 Jan 2025 23:25:53 -0500 Subject: [PATCH 223/316] [EX][IJSPEERT07] Removed deprecrated farms-pylog import --- examples/ijspeert07/run.py | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/ijspeert07/run.py b/examples/ijspeert07/run.py index 6b0a87d..c0430e2 100644 --- a/examples/ijspeert07/run.py +++ b/examples/ijspeert07/run.py @@ -2,7 +2,6 @@ DOI: 10.1126/science.1138353 """ -import farms_pylog as pylog import matplotlib.pyplot as plt import networkx as nx import numpy as np From 4759f4199f3189d3f40fbfc604adf8063c929372 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Fri, 17 Jan 2025 23:26:26 -0500 Subject: [PATCH 224/316] [CORE] Removed unused main function from options --- farms_network/core/options.py | 67 ----------------------------------- 1 file changed, 67 deletions(-) diff --git a/farms_network/core/options.py b/farms_network/core/options.py index d82ae11..1bd0d7a 100644 --- a/farms_network/core/options.py +++ b/farms_network/core/options.py @@ -1216,70 +1216,3 @@ def __init__(self, **kwargs): names=IzhikevichStateOptions.STATE_NAMES ) assert len(self.initial) == 2, f"Number of initial states {len(self.initial)} should be 2" - - -######## -# Main # -######## -def main(): - """ Main """ - - import typing - - network_opts = NetworkOptions(name="new-network") - - li_opts = LIDannerNodeOptions( - name="li", - parameters=IzhikevichParameterOptions.defaults(), - visual=NodeVisualOptions(), - state=IzhikevichStateOptions.from_kwargs( - v0=-60.0, h0=0.1 - ), - ) - - print(f"Is hashable {isinstance(li_opts, typing.Hashable)}") - - network_opts.add_node(li_opts) - li_opts = IzhikevichNodeOptions( - name="li-2", - parameters=IzhikevichParameterOptions.defaults(), - visual=NodeVisualOptions(), - state=IzhikevichStateOptions.from_kwargs( - v0=-60.0, h0=0.1 - ), - ) - network_opts.add_node(li_opts) - - network_opts.add_edge( - EdgeOptions( - source="li", - target="li-2", - weight=0.0, - type="excitatory", - visual=EdgeVisualOptions() - ) - ) - - network_opts.save("/tmp/network_opts.yaml") - - network = NetworkOptions.load("/tmp/rhythm_opts.yaml") - - graph = nx.node_link_graph( - network, - directed=True, - multigraph=False, - link="edges", - name="name", - source="source", - target="target" - ) - nx.draw( - graph, pos=nx.nx_agraph.graphviz_layout(graph), - node_shape="s", - connectionstyle="arc3,rad=-0.2" - ) - plt.show() - - -if __name__ == '__main__': - main() From 8d648e9340828086de9780f740a1365e36558c9a Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Tue, 21 Jan 2025 10:03:50 -0500 Subject: [PATCH 225/316] [CORE] Separated network into python and cython files --- farms_network/core/network.py | 73 +++++++++++++++++++ .../core/{network.pxd => network_cy.pxd} | 6 +- .../core/{network.pyx => network_cy.pyx} | 65 +---------------- 3 files changed, 80 insertions(+), 64 deletions(-) create mode 100644 farms_network/core/network.py rename farms_network/core/{network.pxd => network_cy.pxd} (91%) rename farms_network/core/{network.pyx => network_cy.pyx} (79%) diff --git a/farms_network/core/network.py b/farms_network/core/network.py new file mode 100644 index 0000000..ff65ded --- /dev/null +++ b/farms_network/core/network.py @@ -0,0 +1,73 @@ +""" Network """ + +import numpy as np + +from .data import NetworkData +from .network_cy import NetworkCy +from .options import NetworkOptions + + +class Network(NetworkCy): + """ Network class """ + + def __init__(self, network_options: NetworkOptions): + """ Initialize """ + + super().__init__(network_options) + self.data = NetworkData.from_options(network_options) + + self.nodes: list = [] + self.edges = [] + self.nodes_output_data = [] + self.__tmp_node_outputs = np.zeros((self.c_network.nnodes,)) + self.setup_network(network_options, self.data) + + # Integration options + self.n_iterations: int = network_options.integration.n_iterations + self.timestep: int = network_options.integration.timestep + self.iteration: int = 0 + self.buffer_size: int = network_options.logs.buffer_size + + # Set the seed for random number generation + random_seed = network_options.random_seed + # np.random.seed(random_seed) + + @classmethod + def from_options(cls, options: NetworkOptions): + """ Initialize network from NetworkOptions """ + return cls(options) + + def to_options(self): + """ Return NetworkOptions from network """ + return self.options + + def setup_integrator(self, network_options: NetworkOptions): + """ Setup integrator for neural network """ + # Setup ODE numerical integrator + integration_options = network_options.integration + timestep = integration_options.timestep + self.ode_integrator = RK4Solver(self.c_network.nstates, timestep) + # Setup SDE numerical integrator for noise models if any + noise_options = [] + for node in network_options.nodes: + if node.noise is not None: + if node.noise.is_stochastic: + noise_options.append(node.noise) + + self.sde_system = OrnsteinUhlenbeck(noise_options) + self.sde_integrator = EulerMaruyamaSolver(len(noise_options), timestep) + + @staticmethod + def generate_node(node_options: NodeOptions): + """ Generate a node from options """ + Node = NodeFactory.create(node_options.model) + node = Node.from_options(node_options) + return node + + @staticmethod + def generate_edge(edge_options: EdgeOptions, nodes_options): + """ Generate a edge from options """ + target = nodes_options[nodes_options.index(edge_options.target)] + Edge = EdgeFactory.create(target.model) + edge = Edge.from_options(edge_options) + return edge diff --git a/farms_network/core/network.pxd b/farms_network/core/network_cy.pxd similarity index 91% rename from farms_network/core/network.pxd rename to farms_network/core/network_cy.pxd index a8049f4..81407c7 100644 --- a/farms_network/core/network.pxd +++ b/farms_network/core/network_cy.pxd @@ -7,7 +7,7 @@ from .edge cimport Edge, EdgeCy from .node cimport Node, NodeCy -cdef struct NetworkCy: +cdef struct NetworkStruct: # info unsigned long int nnodes @@ -21,11 +21,11 @@ cdef struct NetworkCy: EdgeCy** c_edges -cdef class Network(ODESystem): +cdef class NetworkCy(ODESystem): """ Python interface to Network ODE """ cdef: - NetworkCy *c_network + NetworkStruct *c_network public list nodes public list edges public NetworkDataCy data diff --git a/farms_network/core/network.pyx b/farms_network/core/network_cy.pyx similarity index 79% rename from farms_network/core/network.pyx rename to farms_network/core/network_cy.pyx index 0ab0226..d027560 100644 --- a/farms_network/core/network.pyx +++ b/farms_network/core/network_cy.pyx @@ -40,7 +40,7 @@ cdef inline void ode( double time, double[:] states_arr, NetworkDataCy data, - NetworkCy* c_network, + NetworkStruct* c_network, double[:] node_outputs_tmp, ) noexcept: """ C Implementation to compute full network state """ @@ -99,7 +99,7 @@ cdef inline void ode( cdef inline void logger( int iteration, NetworkDataCy data, - NetworkCy* c_network + NetworkStruct* c_network ) noexcept: cdef unsigned int nnodes = c_network.nnodes cdef unsigned int j @@ -137,12 +137,12 @@ cdef inline void _noise_states_to_output( outputs[indices[index]] = states[index] -cdef class Network(ODESystem): +cdef class NetworkCy(ODESystem): """ Python interface to Network ODE """ def __cinit__(self, network_options: NetworkOptions): """ C initialization for manual memory allocation """ - self.c_network = malloc(sizeof(NetworkCy)) + self.c_network = malloc(sizeof(NetworkStruct)) if self.c_network is NULL: raise MemoryError("Failed to allocate memory for Network") self.c_network.nnodes = len(network_options.nodes) @@ -160,23 +160,6 @@ cdef class Network(ODESystem): """ Initialize """ super().__init__() - self.data = NetworkData.from_options(network_options) - - self.nodes = [] - self.edges = [] - self.nodes_output_data = [] - self.__tmp_node_outputs = np.zeros((self.c_network.nnodes,)) - self.setup_network(network_options, self.data) - - # Integration options - self.n_iterations: int = network_options.integration.n_iterations - self.timestep: int = network_options.integration.timestep - self.iteration: int = 0 - self.buffer_size: int = network_options.logs.buffer_size - - # Set the seed for random number generation - random_seed = network_options.random_seed - # np.random.seed(random_seed) def __dealloc__(self): """ Deallocate any manual memory as part of clean up """ @@ -187,31 +170,6 @@ cdef class Network(ODESystem): if self.c_network is not NULL: free(self.c_network) - @classmethod - def from_options(cls, options: NetworkOptions): - """ Initialize network from NetworkOptions """ - return cls(options) - - def to_options(self): - """ Return NetworkOptions from network """ - return self.options - - def setup_integrator(self, network_options: NetworkOptions): - """ Setup integrator for neural network """ - # Setup ODE numerical integrator - integration_options = network_options.integration - cdef double timestep = integration_options.timestep - self.ode_integrator = RK4Solver(self.c_network.nstates, timestep) - # Setup SDE numerical integrator for noise models if any - noise_options = [] - for node in network_options.nodes: - if node.noise is not None: - if node.noise.is_stochastic: - noise_options.append(node.noise) - - self.sde_system = OrnsteinUhlenbeck(noise_options) - self.sde_integrator = EulerMaruyamaSolver(len(noise_options), timestep) - def setup_network(self, options: NetworkOptions, data: NetworkData): """ Setup network """ @@ -250,21 +208,6 @@ cdef class Network(ODESystem): ): data.states.array[index] = node_opts.state.initial[state_index] - @staticmethod - def generate_node(node_options: NodeOptions): - """ Generate a node from options """ - Node = NodeFactory.create(node_options.model) - node = Node.from_options(node_options) - return node - - @staticmethod - def generate_edge(edge_options: EdgeOptions, nodes_options): - """ Generate a edge from options """ - target = nodes_options[nodes_options.index(edge_options.target)] - Edge = EdgeFactory.create(target.model) - edge = Edge.from_options(edge_options) - return edge - @property def nnodes(self): """ Number of nodes in the network """ From 3ece0986528b67f28e5f22c68c30e82800aa7775 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Tue, 28 Jan 2025 21:58:30 -0500 Subject: [PATCH 226/316] [CORE] Added edge options for each model --- farms_network/core/options.py | 659 ++++++++++++++++++++++------------ 1 file changed, 421 insertions(+), 238 deletions(-) diff --git a/farms_network/core/options.py b/farms_network/core/options.py index 1bd0d7a..1bd07d8 100644 --- a/farms_network/core/options.py +++ b/farms_network/core/options.py @@ -1,195 +1,12 @@ """ Options to configure the neural and network models """ + import time -from enum import IntEnum -from typing import Dict, Iterable, List, Self, Union +from typing import Dict, Iterable, List, Self, Type, Union -import matplotlib.pyplot as plt -import networkx as nx from farms_core import pylog from farms_core.options import Options - - -############################## -# Network Base Class Options # -############################## -class NetworkOptions(Options): - """ Base class for neural network options """ - - def __init__(self, **kwargs): - super().__init__() - - # Default properties to make it compatible with networkx - # seed - self.directed: bool = kwargs.pop("directed", True) - self.multigraph: bool = kwargs.pop("multigraph", False) - self.graph: dict = kwargs.pop("graph", {"name": ""}) - self.units = kwargs.pop("units", None) - self.logs: NetworkLogOptions = kwargs.pop("logs") - self.random_seed: int = kwargs.pop("random_seed", time.time_ns()) - - self.integration = kwargs.pop( - "integration", IntegrationOptions.defaults() - ) - - self.nodes: List[NodeOptions] = kwargs.pop("nodes", []) - self.edges: List[EdgeOptions] = kwargs.pop("edges", []) - - if kwargs: - raise Exception(f'Unknown kwargs: {kwargs}') - - @classmethod - def from_options(cls, kwargs): - """ From options """ - options = {} - options["directed"] = kwargs["directed"] - options["multigraph"] = kwargs["multigraph"] - options["graph"] = kwargs["graph"] - options["units"] = kwargs["units"] - # Log options - options["logs"] = NetworkLogOptions.from_options(kwargs["logs"]) - # Integration options - options["integration"] = IntegrationOptions.from_options(kwargs["integration"]) - # Nodes - node_types = { - "linear": LinearNodeOptions, - "external_relay": ExternalRelayNodeOptions, - "relu": ReLUNodeOptions, - "oscillator": OscillatorNodeOptions, - "li_danner": LIDannerNodeOptions, - "li_nap_danner": LINaPDannerNodeOptions, - "leaky_integrator": LeakyIntegratorNodeOptions, - } - options["nodes"] = [ - node_types[node["model"]].from_options(node) - for node in kwargs["nodes"] - ] - # Edges - edge_types = { - "standard": EdgeOptions, - "oscillator": OscillatorEdgeOptions, - } - options["edges"] = [ - edge_types[edge["model"]].from_options(edge) - for edge in kwargs["edges"] - ] - return cls(**options) - - def add_node(self, options: "NodeOptions"): - """ Add a node if it does not already exist in the list """ - assert isinstance(options, NodeOptions), f"{type(options)} not an instance of NodeOptions" - if options not in self.nodes: - self.nodes.append(options) - else: - print(f"Node {options.name} already exists and will not be added again.") - - def add_nodes(self, options: Iterable["NodeOptions"]): - """ Add a collection of nodes """ - for node in options: - self.add_node(node) - - def add_edge(self, options: "EdgeOptions"): - """ Add a node if it does not already exist in the list """ - if (options.source in self.nodes) and (options.target in self.nodes): - self.edges.append(options) - else: - missing_nodes = [ - "" if (options.source in self.nodes) else options.source, - "" if (options.target in self.nodes) else options.target, - ] - pylog.debug(f"Missing node {*missing_nodes,} in Edge {options}") - - def add_edges(self, options: Iterable["EdgeOptions"]): - """ Add a collection of edges """ - for edge in options: - self.add_edge(edge) - - def __add__(self, other: Self): - """ Combine two network options """ - assert isinstance(other, NetworkOptions) - for node in other.nodes: - self.add_node(node) - for edge in other.edges: - self.add_edge(edge) - return self - - -################################# -# Numerical Integration Options # -################################# -class IntegrationOptions(Options): - """ Class to set the options for numerical integration """ - - def __init__(self, **kwargs): - super().__init__() - - self.timestep: float = kwargs.pop("timestep") - self.n_iterations: int = int(kwargs.pop("n_iterations")) - self.integrator: str = kwargs.pop("integrator") - self.method: str = kwargs.pop("method") - self.atol: float = kwargs.pop("atol") - self.rtol: float = kwargs.pop("rtol") - self.max_step: float = kwargs.pop("max_step") - self.checks: bool = kwargs.pop("checks") - - if kwargs: - raise Exception(f'Unknown kwargs: {kwargs}') - - @classmethod - def defaults(cls, **kwargs): - - options = {} - - options["timestep"] = kwargs.pop("timestep", 1e-3) - options["n_iterations"] = int(kwargs.pop("n_iterations", 1e3)) - options["integrator"] = kwargs.pop("integrator", "rk4") - options["method"] = kwargs.pop("method", "adams") - options["atol"] = kwargs.pop("atol", 1e-12) - options["rtol"] = kwargs.pop("rtol", 1e-6) - options["max_step"] = kwargs.pop("max_step", 0.0) - options["checks"] = kwargs.pop("checks", True) - return cls(**options) - - @classmethod - def from_options(cls, kwargs: Dict): - """ From options """ - return cls(**kwargs) - - - -################### -# Logging Options # -################### -class NetworkLogOptions(Options): - """ Log options for the network level - - Configure logging for network events and iterations. - - Attributes: - n_iterations (int): Number of iterations to log. - buffer_size (int): Size of the log buffer. Defaults to n_iterations if 0. - nodes_all (bool): Whether to log all nodes or only selected ones. Defaults to False. - """ - - def __init__(self, n_iterations: int, **kwargs): - super().__init__(**kwargs) - - self.n_iterations: int = n_iterations - assert isinstance(self.n_iterations, int), "iterations shoulde be an integer" - self.buffer_size: int = kwargs.pop('buffer_size', self.n_iterations) - if self.buffer_size == 0: - self.buffer_size = self.n_iterations - assert isinstance(self.buffer_size, int), "buffer_size shoulde be an integer" - self.nodes_all: bool = kwargs.pop("nodes_all", False) - - if kwargs: - raise Exception(f'Unknown kwargs: {kwargs}') - - @classmethod - def from_options(cls, kwargs: Dict): - """ From options """ - n_iterations = kwargs.pop("n_iterations") - return cls(n_iterations, **kwargs) +from farms_network.models import EdgeTypes, Models ########################### @@ -203,7 +20,7 @@ def __init__(self, **kwargs): super().__init__() self.name: str = kwargs.pop("name") - self.model: str = kwargs.pop("model", None) + self.model: str = kwargs.pop("model") self.parameters: NodeParameterOptions = kwargs.pop("parameters") self.visual: NodeVisualOptions = kwargs.pop("visual") self.state: NodeStateOptions = kwargs.pop("state") @@ -239,9 +56,6 @@ def from_options(cls, kwargs: Dict): class NodeParameterOptions(Options): """ Base class for node specific parameters """ - def __init__(self): - super().__init__() - @classmethod def from_options(cls, kwargs: Dict): """ From options """ @@ -251,12 +65,14 @@ def from_options(cls, kwargs: Dict): class NodeStateOptions(Options): """ Base class for node specific state options """ + STATE_NAMES: List[str] = [] # Override in subclasses + def __init__(self, **kwargs): super().__init__() self.initial: List[float] = kwargs.pop("initial") self.names: List[str] = kwargs.pop("names") if kwargs: - raise Exception(f'Unknown kwargs: {kwargs}') + raise ValueError(f'Unknown kwargs: {kwargs}') @classmethod def from_kwargs(cls, **kwargs): @@ -266,7 +82,7 @@ def from_kwargs(cls, **kwargs): for name in cls.STATE_NAMES ] if kwargs: - raise Exception(f'Unknown kwargs: {kwargs}') + raise ValueError(f'Unknown kwargs: {kwargs}') return cls(initial=initial) @classmethod @@ -324,7 +140,7 @@ def __init__(self, **kwargs): self.source: str = kwargs.pop("source") self.target: str = kwargs.pop("target") self.weight: float = kwargs.pop("weight") - self.type: str = kwargs.pop("type") + self.type: Union[EdgeTypes, int] = kwargs.pop("type") self.parameters: EdgeParameterOptions = kwargs.pop( "parameters", EdgeParameterOptions() ) @@ -456,25 +272,26 @@ def defaults(cls, **kwargs: Dict): return cls(**options) -################################ -# External Relay Model Options # -################################ -class ExternalRelayNodeOptions(NodeOptions): - """ Class to define the properties of ExternalRelay node model - - # TODO: Remove parameters from options - - """ +####################### +# Relay Model Options # +####################### +class RelayNodeOptions(NodeOptions): + """ Class to define the properties of Relay node model """ + MODEL = Models.RELAY def __init__(self, **kwargs): """ Initialize """ - model = "external_relay" + state = kwargs.pop("state", None) + parameters = kwargs.pop("parameters", None) + + assert state is None + assert parameters is None super().__init__( name=kwargs.pop("name"), - model=model, - parameters=kwargs.pop("parameters"), + model=RelayNodeOptions.MODEL, + parameters=parameters, visual=kwargs.pop("visual"), - state=kwargs.pop("state", None), + state=state, noise=kwargs.pop("noise"), ) self._nstates = 0 @@ -488,27 +305,62 @@ def from_options(cls, kwargs: Dict): """ Load from options """ options = {} options["name"] = kwargs.pop("name") - options["parameters"] = NodeParameterOptions() + options["parameters"] = None options["visual"] = NodeVisualOptions.from_options(kwargs["visual"]) options["noise"] = kwargs.pop("noise", None) return cls(**options) +class RelayEdgeOptions(EdgeOptions): + """ Relay edge options """ + MODEL = Models.RELAY + + def __init__(self, **kwargs): + """ Initialize """ + + super().__init__( + model=RelayEdgeOptions.MODEL, + source=kwargs.pop("source"), + target=kwargs.pop("target"), + weight=kwargs.pop("weight"), + type=kwargs.pop("type"), + parameters=None, + visual=kwargs.pop("visual"), + ) + if kwargs: + raise Exception(f'Unknown kwargs: {kwargs}') + + @classmethod + def from_options(cls, kwargs: Dict): + """ From options """ + + options = {} + options["source"] = kwargs["source"] + options["target"] = kwargs["target"] + options["weight"] = kwargs["weight"] + options["type"] = kwargs["type"] + options["visual"] = EdgeVisualOptions.from_options(kwargs["visual"]) + return cls(**options) + + ######################## # Linear Model Options # ######################## class LinearNodeOptions(NodeOptions): """ Class to define the properties of Linear node model """ + MODEL = Models.LINEAR def __init__(self, **kwargs): """ Initialize """ - model = "linear" + + state = kwargs.pop("state", None) + assert state is None super().__init__( name=kwargs.pop("name"), - model=model, + model=LinearNodeOptions.MODEL, parameters=kwargs.pop("parameters"), visual=kwargs.pop("visual"), - state=kwargs.pop("state", None), + state=state, noise=kwargs.pop("noise"), ) self._nstates = 0 @@ -528,7 +380,6 @@ def from_options(cls, kwargs: Dict): options["visual"] = NodeVisualOptions.from_options( kwargs["visual"] ) - options["state"] = None options["noise"] = kwargs.pop("noise", None) return cls(**options) @@ -548,10 +399,39 @@ def defaults(cls, **kwargs): """ Get the default parameters for Linear Node model """ options = {} - options["slope"] = kwargs.pop("slope", 1.0) options["bias"] = kwargs.pop("bias", 0.0) + return cls(**options) + + +class LinearEdgeOptions(EdgeOptions): + """ Linear edge options """ + + def __init__(self, **kwargs): + """ Initialize """ + + super().__init__( + model=Models.LINEAR, + source=kwargs.pop("source"), + target=kwargs.pop("target"), + weight=kwargs.pop("weight"), + type=kwargs.pop("type"), + visual=kwargs.pop("visual"), + ) + if kwargs: + raise Exception(f'Unknown kwargs: {kwargs}') + + @classmethod + def from_options(cls, kwargs: Dict): + """ From options """ + options = {} + options["source"] = kwargs["source"] + options["target"] = kwargs["target"] + options["weight"] = kwargs["weight"] + options["type"] = kwargs["type"] + options["parameters"] = None + options["visual"] = EdgeVisualOptions.from_options(kwargs["visual"]) return cls(**options) @@ -561,15 +441,19 @@ def defaults(cls, **kwargs): class ReLUNodeOptions(NodeOptions): """ Class to define the properties of ReLU node model """ + MODEL = Models.RELU + def __init__(self, **kwargs): """ Initialize """ - model = "relu" + + state = kwargs.pop("state", None) + assert state is None super().__init__( name=kwargs.pop("name"), - model=model, + model=ReLUNodeOptions.MODEL, parameters=kwargs.pop("parameters"), visual=kwargs.pop("visual"), - state=kwargs.pop("state", None), + state=state, noise=kwargs.pop("noise"), ) self._nstates = 0 @@ -608,9 +492,7 @@ def __init__(self, **kwargs): @classmethod def defaults(cls, **kwargs): """ Get the default parameters for ReLU Node model """ - options = {} - options["gain"] = kwargs.pop("gain", 1.0) options["sign"] = kwargs.pop("sign", 1) options["offset"] = kwargs.pop("offset", 0.0) @@ -618,6 +500,37 @@ def defaults(cls, **kwargs): return cls(**options) +class ReLUEdgeOptions(EdgeOptions): + """ ReLU edge options """ + + def __init__(self, **kwargs): + """ Initialize """ + + super().__init__( + model=Models.RELU, + source=kwargs.pop("source"), + target=kwargs.pop("target"), + weight=kwargs.pop("weight"), + type=kwargs.pop("type"), + visual=kwargs.pop("visual"), + ) + if kwargs: + raise Exception(f'Unknown kwargs: {kwargs}') + + @classmethod + def from_options(cls, kwargs: Dict): + """ From options """ + + options = {} + options["source"] = kwargs["source"] + options["target"] = kwargs["target"] + options["weight"] = kwargs["weight"] + options["type"] = kwargs["type"] + options["parameters"] = None + options["visual"] = EdgeVisualOptions.from_options(kwargs["visual"]) + return cls(**options) + + ############################################ # Phase-Amplitude Oscillator Model Options # ############################################ @@ -940,19 +853,20 @@ def __init__(self, **kwargs): class LIDannerNodeOptions(NodeOptions): """ Class to define the properties of Leaky integrator danner node model """ + MODEL = Models.LI_DANNER + def __init__(self, **kwargs): """ Initialize """ - model = "li_danner" super().__init__( name=kwargs.pop("name"), - model=model, + model=LIDannerNodeOptions.MODEL, parameters=kwargs.pop("parameters"), visual=kwargs.pop("visual"), state=kwargs.pop("state"), noise=kwargs.pop("noise"), ) - self._nstates = 1 - self._nparameters = 13 + self._nstates = 2 + self._nparameters = 10 if kwargs: raise Exception(f'Unknown kwargs: {kwargs}') @@ -962,7 +876,7 @@ def from_options(cls, kwargs: Dict): """ Load from options """ options = {} options["name"] = kwargs.pop("name") - options["parameters"] = LIDannerParameterOptions.from_options( + options["parameters"] = LIDannerNodeParameterOptions.from_options( kwargs["parameters"] ) options["visual"] = NodeVisualOptions.from_options( @@ -979,7 +893,7 @@ def from_options(cls, kwargs: Dict): return cls(**options) -class LIDannerParameterOptions(NodeParameterOptions): +class LIDannerNodeParameterOptions(NodeParameterOptions): """ Class to define the parameters of Leaky Integrator Danner node model. @@ -993,6 +907,7 @@ class LIDannerParameterOptions(NodeParameterOptions): g_syn_i (float): Inhibitory synaptic conductance (in nS). e_syn_e (float): Excitatory synaptic reversal potential (in mV). e_syn_i (float): Inhibitory synaptic reversal potential (in mV). + tau_ch (float): Cholinergic time constant (in mS) """ def __init__(self, **kwargs): @@ -1006,6 +921,7 @@ def __init__(self, **kwargs): self.g_syn_i = kwargs.pop("g_syn_i") # nS self.e_syn_e = kwargs.pop("e_syn_e") # mV self.e_syn_i = kwargs.pop("e_syn_i") # mV + self.tau_ch = kwargs.pop("tau_ch", 5.0) # tau-cholinergic if kwargs: raise Exception(f'Unknown kwargs: {kwargs}') @@ -1014,7 +930,6 @@ def defaults(cls, **kwargs): """ Get the default parameters for LI Danner Node model """ options = {} - options["c_m"] = kwargs.pop("c_m", 10.0) options["g_leak"] = kwargs.pop("g_leak", 2.8) options["e_leak"] = kwargs.pop("e_leak", -60.0) @@ -1024,21 +939,54 @@ def defaults(cls, **kwargs): options["g_syn_i"] = kwargs.pop("g_syn_i", 10.0) options["e_syn_e"] = kwargs.pop("e_syn_e", -10.0) options["e_syn_i"] = kwargs.pop("e_syn_i", -75.0) - + options["tau_ch"] = kwargs.pop("tau_ch", 5.0) return cls(**options) class LIDannerStateOptions(NodeStateOptions): """ LI Danner node state options """ - STATE_NAMES = ["v",] + STATE_NAMES = ["v", "a"] def __init__(self, **kwargs): super().__init__( - initial=kwargs.pop("initial"), + initial=kwargs.pop("initial", [-60.0, 0.0]), names=LIDannerStateOptions.STATE_NAMES ) - assert len(self.initial) == 1, f"Number of initial states {len(self.initial)} should be 1" + # assert len(self.initial) == 2, f"Number of initial states {len(self.initial)} should be 2" + + +class LIDannerEdgeOptions(EdgeOptions): + """ LIDanner edge options """ + + MODEL = Models.LI_DANNER + + def __init__(self, **kwargs): + """ Initialize """ + super().__init__( + model=LIDannerEdgeOptions.MODEL, + source=kwargs.pop("source"), + target=kwargs.pop("target"), + weight=kwargs.pop("weight"), + type=kwargs.pop("type"), + parameters=kwargs.pop("parameters"), + visual=kwargs.pop("visual"), + ) + if kwargs: + raise Exception(f'Unknown kwargs: {kwargs}') + + @classmethod + def from_options(cls, kwargs: Dict): + """ From options """ + + options = {} + options["source"] = kwargs["source"] + options["target"] = kwargs["target"] + options["weight"] = kwargs["weight"] + options["type"] = kwargs["type"] + options["parameters"] = None + options["visual"] = EdgeVisualOptions.from_options(kwargs["visual"]) + return cls(**options) ################################################## @@ -1069,7 +1017,7 @@ def from_options(cls, kwargs: Dict): """ Load from options """ options = {} options["name"] = kwargs.pop("name") - options["parameters"] = LINaPDannerParameterOptions.from_options( + options["parameters"] = LINaPDannerNodeParameterOptions.from_options( kwargs["parameters"] ) options["visual"] = NodeVisualOptions.from_options( @@ -1086,7 +1034,7 @@ def from_options(cls, kwargs: Dict): return cls(**options) -class LINaPDannerParameterOptions(NodeParameterOptions): +class LINaPDannerNodeParameterOptions(NodeParameterOptions): """ Class to define the parameters of Leaky integrator danner node model """ def __init__(self, **kwargs): @@ -1133,10 +1081,10 @@ def defaults(cls, **kwargs): options["tau_max"] = kwargs.pop("tau_max", 160.0) # mS options["v_max"] = kwargs.pop("v_max", 0.0) # mV options["v_thr"] = kwargs.pop("v_thr", -50.0) # mV - options["g_syn_e"] = kwargs.pop("g_syn_e", 10.0) # nS - options["g_syn_i"] = kwargs.pop("g_syn_i", 10.0) # nS - options["e_syn_e"] = kwargs.pop("e_syn_e", -10.0) # mV - options["e_syn_i"] = kwargs.pop("e_syn_i", -75.0) # mV + options["g_syn_e"] = kwargs.pop("g_syn_e", 10.0) + options["g_syn_i"] = kwargs.pop("g_syn_i", 10.0) + options["e_syn_e"] = kwargs.pop("e_syn_e", -10.0) + options["e_syn_i"] = kwargs.pop("e_syn_i", -75.0) return cls(**options) @@ -1154,6 +1102,38 @@ def __init__(self, **kwargs): assert len(self.initial) == 2, f"Number of initial states {len(self.initial)} should be 2" +class LINaPDannerEdgeOptions(EdgeOptions): + """ LINaPDanner edge options """ + + def __init__(self, **kwargs): + """ Initialize """ + model = "li_danner" + super().__init__( + model=model, + source=kwargs.pop("source"), + target=kwargs.pop("target"), + weight=kwargs.pop("weight"), + type=kwargs.pop("type"), + parameters=kwargs.pop("parameters"), + visual=kwargs.pop("visual"), + ) + if kwargs: + raise Exception(f'Unknown kwargs: {kwargs}') + + @classmethod + def from_options(cls, kwargs: Dict): + """ From options """ + + options = {} + options["source"] = kwargs["source"] + options["target"] = kwargs["target"] + options["weight"] = kwargs["weight"] + options["type"] = kwargs["type"] + options["parameters"] = None + options["visual"] = EdgeVisualOptions.from_options(kwargs["visual"]) + return cls(**options) + + #################### # Izhikevich Model # #################### @@ -1196,11 +1176,11 @@ def defaults(cls, **kwargs): options = {} - options["recovery_time"] = kwargs.pop("recovery_time", 0.02) # pF + options["recovery_time"] = kwargs.pop("recovery_time", 0.02) # pF options["recovery_sensitivity"] = kwargs.pop("recovery_sensitivity", 0.2) # nS - options["membrane_reset"] = kwargs.pop("membrane_reset", -65.0) # mV - options["recovery_reset"] = kwargs.pop("recovery_reset", 2) # mV - options["membrane_threshold"] = kwargs.pop("membrane_threshold", 30.0) # mV + options["membrane_reset"] = kwargs.pop("membrane_reset", -65.0) # mV + options["recovery_reset"] = kwargs.pop("recovery_reset", 2) # mV + options["membrane_threshold"] = kwargs.pop("membrane_threshold", 30.0) # mV return cls(**options) @@ -1216,3 +1196,206 @@ def __init__(self, **kwargs): names=IzhikevichStateOptions.STATE_NAMES ) assert len(self.initial) == 2, f"Number of initial states {len(self.initial)} should be 2" + + +############################## +# Network Base Class Options # +############################## +class NetworkOptions(Options): + """ Base class for neural network options """ + + NODE_TYPES: Dict[Models, Type] = { + Models.RELAY: RelayNodeOptions, + Models.LINEAR: LinearNodeOptions, + Models.RELU: ReLUNodeOptions, + Models.OSCILLATOR: OscillatorNodeOptions, + # Models.HOPF_OSCILLATOR: HopfOscillatorNodeOptions, + # Models.MORPHED_OSCILLATOR: MorphedOscillatorNodeOptions, + # Models.MATSUOKA: MatsuokaNodeOptions, + # Models.FITZHUGH_NAGUMO: FitzhughNagumoNodeOptions, + # Models.MORRIS_LECAR: MorrisLecarNodeOptions, + # Models.LEAKY_INTEGRATOR: LeakyIntegratorNodeOptions, + Models.LI_DANNER: LIDannerNodeOptions, + Models.LI_NAP_DANNER: LINaPDannerNodeOptions, + # Models.LI_DAUN: LIDaunNodeOptions, + # Models.HH_DAUN: HHDaunNodeOptions, + } + + EDGE_TYPES: Dict[Models, Type] = { + Models.RELAY: RelayEdgeOptions, + Models.LINEAR: LinearEdgeOptions, + Models.RELU: ReLUEdgeOptions, + Models.OSCILLATOR: OscillatorEdgeOptions, + # Models.HOPF_OSCILLATOR: HopfOscillatorEdgeOptions, + # Models.MORPHED_OSCILLATOR: MorphedOscillatorEdgeOptions, + # Models.MATSUOKA: MatsuokaEdgeOptions, + # Models.FITZHUGH_NAGUMO: FitzhughNagumoEdgeOptions, + # Models.MORRIS_LECAR: MorrisLecarEdgeOptions, + # Models.LEAKY_INTEGRATOR: LeakyIntegratorEdgeOptions, + Models.LI_DANNER: LIDannerEdgeOptions, + Models.LI_NAP_DANNER: LINaPDannerEdgeOptions, + # Models.LI_DAUN: LIDaunEdgeOptions, + # Models.HH_DAUN: HHDaunEdgeOptions, + } + + def __init__(self, **kwargs): + super().__init__() + + # Default properties to make it compatible with networkx + # seed + self.directed: bool = kwargs.pop("directed", True) + self.multigraph: bool = kwargs.pop("multigraph", False) + self.graph: dict = kwargs.pop("graph", {"name": ""}) + self.units = kwargs.pop("units", None) + self.logs: NetworkLogOptions = kwargs.pop("logs") + self.random_seed: int = kwargs.pop("random_seed", time.time_ns()) + + self.integration = kwargs.pop( + "integration", IntegrationOptions.defaults() + ) + + self.nodes: List[NodeOptions] = kwargs.pop("nodes", []) + self.edges: List[EdgeOptions] = kwargs.pop("edges", []) + + if kwargs: + raise Exception(f'Unknown kwargs: {kwargs}') + + @classmethod + def from_options(cls, kwargs): + """ From options """ + options = {} + options["directed"] = kwargs["directed"] + options["multigraph"] = kwargs["multigraph"] + options["graph"] = kwargs["graph"] + options["units"] = kwargs["units"] + # Log options + options["logs"] = NetworkLogOptions.from_options(kwargs["logs"]) + # Integration options + options["integration"] = IntegrationOptions.from_options(kwargs["integration"]) + # Nodes + options["nodes"] = [ + cls.NODE_TYPES[node["model"]].from_options(node) + for node in kwargs["nodes"] + ] + # Edges + options["edges"] = [ + # cls.EDGE_TYPES[edge["model"]].from_options(edge) + EdgeOptions.from_options(edge) + for edge in kwargs["edges"] + ] + return cls(**options) + + def add_node(self, options: NodeOptions): + """ Add a node if it does not already exist in the list """ + assert isinstance(options, NodeOptions), f"{type(options)} not an instance of NodeOptions" + if options not in self.nodes: + self.nodes.append(options) + else: + print(f"Node {options.name} already exists and will not be added again.") + + def add_nodes(self, options: Iterable[NodeOptions]): + """ Add a collection of nodes """ + for node in options: + self.add_node(node) + + def add_edge(self, options: EdgeOptions): + """ Add a node if it does not already exist in the list """ + if (options.source in self.nodes) and (options.target in self.nodes): + self.edges.append(options) + else: + missing_nodes = [ + "" if (options.source in self.nodes) else options.source, + "" if (options.target in self.nodes) else options.target, + ] + pylog.debug(f"Missing node {*missing_nodes,} in Edge {options}") + + def add_edges(self, options: Iterable[EdgeOptions]): + """ Add a collection of edges """ + for edge in options: + self.add_edge(edge) + + def __add__(self, other: Self): + """ Combine two network options """ + assert isinstance(other, NetworkOptions) + for node in other.nodes: + self.add_node(node) + for edge in other.edges: + self.add_edge(edge) + return self + + +################################# +# Numerical Integration Options # +################################# +class IntegrationOptions(Options): + """ Class to set the options for numerical integration """ + + def __init__(self, **kwargs): + super().__init__() + + self.timestep: float = kwargs.pop("timestep") + self.n_iterations: int = int(kwargs.pop("n_iterations")) + self.integrator: str = kwargs.pop("integrator") + self.method: str = kwargs.pop("method") + self.atol: float = kwargs.pop("atol") + self.rtol: float = kwargs.pop("rtol") + self.max_step: float = kwargs.pop("max_step") + self.checks: bool = kwargs.pop("checks") + + if kwargs: + raise Exception(f'Unknown kwargs: {kwargs}') + + @classmethod + def defaults(cls, **kwargs): + + options = {} + + options["timestep"] = kwargs.pop("timestep", 1e-3) + options["n_iterations"] = int(kwargs.pop("n_iterations", 1e3)) + options["integrator"] = kwargs.pop("integrator", "rk4") + options["method"] = kwargs.pop("method", "adams") + options["atol"] = kwargs.pop("atol", 1e-12) + options["rtol"] = kwargs.pop("rtol", 1e-6) + options["max_step"] = kwargs.pop("max_step", 0.0) + options["checks"] = kwargs.pop("checks", True) + return cls(**options) + + @classmethod + def from_options(cls, kwargs: Dict): + """ From options """ + return cls(**kwargs) + + +################### +# Logging Options # +################### +class NetworkLogOptions(Options): + """ Log options for the network level + + Configure logging for network events and iterations. + + Attributes: + n_iterations (int): Number of iterations to log. + buffer_size (int): Size of the log buffer. Defaults to n_iterations if 0. + nodes_all (bool): Whether to log all nodes or only selected ones. Defaults to False. + """ + + def __init__(self, n_iterations: int, **kwargs): + super().__init__(**kwargs) + + self.n_iterations: int = n_iterations + assert isinstance(self.n_iterations, int), "iterations shoulde be an integer" + self.buffer_size: int = kwargs.pop('buffer_size', self.n_iterations) + if self.buffer_size == 0: + self.buffer_size = self.n_iterations + assert isinstance(self.buffer_size, int), "buffer_size shoulde be an integer" + self.nodes_all: bool = kwargs.pop("nodes_all", False) + + if kwargs: + raise Exception(f'Unknown kwargs: {kwargs}') + + @classmethod + def from_options(cls, kwargs: Dict): + """ From options """ + n_iterations = kwargs.pop("n_iterations") + return cls(n_iterations, **kwargs) From ec505b75305c2eabef6052315f35a8a7b440fb23 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Wed, 29 Jan 2025 13:16:39 -0500 Subject: [PATCH 227/316] [CORE] Separated node cython implementation --- farms_network/core/node.py | 18 +++ farms_network/core/{node.pxd => node_cy.pxd} | 43 ++----- farms_network/core/{node.pyx => node_cy.pyx} | 118 +++++++------------ 3 files changed, 72 insertions(+), 107 deletions(-) create mode 100644 farms_network/core/node.py rename farms_network/core/{node.pxd => node_cy.pxd} (56%) rename farms_network/core/{node.pyx => node_cy.pyx} (51%) diff --git a/farms_network/core/node.py b/farms_network/core/node.py new file mode 100644 index 0000000..7a6e967 --- /dev/null +++ b/farms_network/core/node.py @@ -0,0 +1,18 @@ +""" Node """ + + +from farms_network.core.node_cy import NodeCy +from farms_network.core.options import NodeOptions, NodeParameterOptions + + +class Node(NodeCy): + + def __init__(self, name: str): + super().__init__(name) + + @classmethod + def from_options(cls, node_options: NodeOptions): + """ From node options """ + name: str = node_options.name + parameters: NodeParameterOptions = node_options.parameters if node_options.parameters else {} + return cls(name, **parameters) diff --git a/farms_network/core/node.pxd b/farms_network/core/node_cy.pxd similarity index 56% rename from farms_network/core/node.pxd rename to farms_network/core/node_cy.pxd index 8447f50..5923919 100644 --- a/farms_network/core/node.pxd +++ b/farms_network/core/node_cy.pxd @@ -1,36 +1,15 @@ -""" +""" Node Base Struture. """ ------------------------------------------------------------------------ -Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty -Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne +from farms_network.core.edge cimport EdgeCy -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. ------------------------------------------------------------------------ - -Header for Node Base Struture. - -""" - -from .edge cimport EdgeCy - - -cdef struct NodeCy: +cdef struct node_t: # Generic parameters unsigned int nstates # Number of state variables in the node. unsigned int nparameters # Number of parameters for the node. unsigned int ninputs # Number of inputs - char* model_type # Type of the model (e.g., "empty"). + char* model # Type of the model (e.g., "empty"). char* name # Unique name of the node. bint is_statefull # Flag indicating whether the node is stateful. (ODE) @@ -48,7 +27,7 @@ cdef struct NodeCy: unsigned int* inputs, double* weights, double noise, - NodeCy* c_node, + node_t* c_node, EdgeCy** c_edges, ) noexcept @@ -59,7 +38,7 @@ cdef struct NodeCy: double* network_outputs, unsigned int* inputs, double* weights, - NodeCy* c_node, + node_t* c_node, EdgeCy** c_edges, ) noexcept @@ -74,7 +53,7 @@ cdef: unsigned int* inputs, double* weights, double noise, - NodeCy* c_node, + node_t* c_node, EdgeCy** c_edges, ) noexcept double output( @@ -84,13 +63,13 @@ cdef: double* network_outputs, unsigned int* inputs, double* weights, - NodeCy* c_node, + node_t* c_node, EdgeCy** c_edges, ) noexcept -cdef class Node: - """ Python interface to Node C-Structure""" +cdef class NodeCy: + """ Interface to Node C-Structure """ cdef: - NodeCy* c_node + node_t* _node diff --git a/farms_network/core/node.pyx b/farms_network/core/node_cy.pyx similarity index 51% rename from farms_network/core/node.pyx rename to farms_network/core/node_cy.pyx index cbffd96..4e927fd 100644 --- a/farms_network/core/node.pyx +++ b/farms_network/core/node_cy.pyx @@ -1,27 +1,10 @@ -""" ------------------------------------------------------------------------ -Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty -Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. ------------------------------------------------------------------------ -""" +""" Node """ from libc.stdio cimport printf from libc.stdlib cimport free, malloc from libc.string cimport strdup -from .options import NodeOptions +from farms_network.core.options import NodeOptions cdef void ode( @@ -33,10 +16,10 @@ cdef void ode( unsigned int* inputs, double* weights, double noise, - NodeCy* node, + node_t* node, EdgeCy** c_edges, ) noexcept: - """ NodeCy ODE """ + """ node_t ODE """ printf("Base implementation of ODE C function \n") @@ -47,89 +30,74 @@ cdef double output( double* network_outputs, unsigned int* inputs, double* weights, - NodeCy* node, + node_t* node, EdgeCy** c_edges, ) noexcept: - """ NodeCy output """ + """ node_t output """ printf("Base implementation of output C function \n") return 0.0 -cdef class Node: - """ Python interface to Node C-Structure""" +cdef class NodeCy: + """ Interface to Node C-Structure """ - def __cinit__(self): - self.c_node = malloc(sizeof(NodeCy)) - if self.c_node is NULL: - raise MemoryError("Failed to allocate memory for NodeCy") - self.c_node.name = NULL - self.c_node.model_type = strdup("base".encode('UTF-8')) - self.c_node.ode = ode - self.c_node.output = output - self.c_node.parameters = NULL - self.c_node.nparameters = 0 - self.c_node.ninputs = 0 + def __cinit__(self, name: str, model: str): + self._node = malloc(sizeof(node_t)) + if self._node is NULL: + raise MemoryError("Failed to allocate memory for node_t") + self._node.name = strdup(name.encode('UTF-8')) + self._node.model = strdup(model.encode('UTF-8')) + self._node.ode = ode + self._node.output = output + self._node.parameters = NULL + self._node.nparameters = 0 + self._node.ninputs = 0 def __dealloc__(self): - if self.c_node is not NULL: - if self.c_node.name is not NULL: - free(self.c_node.name) - if self.c_node.model_type is not NULL: - free(self.c_node.model_type) - if self.c_node.parameters is not NULL: - free(self.c_node.parameters) - free(self.c_node) - - def __init__(self, name: str, **kwargs): - self.name = name - - @classmethod - def from_options(cls, node_options: NodeOptions): - """ From node options """ - name: str = node_options.name - return cls(name, **node_options.parameters) + if self._node is not NULL: + if self._node.name is not NULL: + free(self._node.name) + if self._node.model is not NULL: + free(self._node.model) + if self._node.parameters is not NULL: + free(self._node.parameters) + free(self._node) # Property methods for name @property def name(self): - if self.c_node.name is NULL: + if self._node.name is NULL: return None - return self.c_node.name.decode('UTF-8') - - @name.setter - def name(self, value): - if self.c_node.name is not NULL: - free(self.c_node.name) - self.c_node.name = strdup(value.encode('UTF-8')) + return self._node.name.decode('UTF-8') - # Property methods for model_type + # Property methods for model @property - def model_type(self): - if self.c_node.model_type is NULL: + def model(self): + if self._node.model is NULL: return None - return self.c_node.model_type.decode('UTF-8') + return self._node.model.decode('UTF-8') # Property methods for nstates @property def nstates(self): - return self.c_node.nstates + return self._node.nstates # Property methods for ninputs @property def ninputs(self): - return self.c_node.ninputs + return self._node.ninputs # Property methods for nparameters @property def nparameters(self): - return self.c_node.nparameters + return self._node.nparameters @property def parameters(self): """Generic accessor for parameters.""" - if not self.c_node.parameters: - raise ValueError("NodeCy parameters are NULL") - if self.c_node.nparameters == 0: + if not self._node.parameters: + raise ValueError("node_t parameters are NULL") + if self._node.nparameters == 0: raise ValueError("No parameters available") # The derived class should override this method to provide specific behavior @@ -156,7 +124,7 @@ cdef class Node: cdef EdgeCy** c_edges = NULL # Call the C function directly - self.c_node.ode( + self._node.ode( time, states_ptr, derivatives_ptr, @@ -165,7 +133,7 @@ cdef class Node: inputs_ptr, weights_ptr, noise, - self.c_node, + self._node, c_edges ) @@ -184,13 +152,13 @@ cdef class Node: cdef unsigned int* inputs_ptr = &inputs[0] cdef double* weights_ptr = &weights[0] cdef EdgeCy** c_edges = NULL - return self.c_node.output( + return self._node.output( time, states_ptr, external_input, network_outputs_ptr, inputs_ptr, weights_ptr, - self.c_node, + self._node, c_edges ) From 5e11e7e3a47bcf507f51d7a125c196926beb20df Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Thu, 30 Jan 2025 14:06:25 -0500 Subject: [PATCH 228/316] [CORE] Separated edge cython implementation --- farms_network/core/edge.pxd | 42 --------------- farms_network/core/edge.py | 20 +++++++ farms_network/core/edge.pyx | 99 ---------------------------------- farms_network/core/edge_cy.pxd | 31 +++++++++++ farms_network/core/edge_cy.pyx | 77 ++++++++++++++++++++++++++ 5 files changed, 128 insertions(+), 141 deletions(-) delete mode 100644 farms_network/core/edge.pxd create mode 100644 farms_network/core/edge.py delete mode 100644 farms_network/core/edge.pyx create mode 100644 farms_network/core/edge_cy.pxd create mode 100644 farms_network/core/edge_cy.pyx diff --git a/farms_network/core/edge.pxd b/farms_network/core/edge.pxd deleted file mode 100644 index dae9e71..0000000 --- a/farms_network/core/edge.pxd +++ /dev/null @@ -1,42 +0,0 @@ -""" - ------------------------------------------------------------------------ -Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty -Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. ------------------------------------------------------------------------ - -Header for Edge Base Struture. - -""" - - -cdef struct EdgeCy: - - # - char* source # Source node - char* target # Target node - char* type # Type of connection - - # Edge parameters - unsigned int nparameters - void* parameters - - - -cdef class Edge: - """ Python interface to Edge C-Structure""" - - cdef: - EdgeCy* c_edge diff --git a/farms_network/core/edge.py b/farms_network/core/edge.py new file mode 100644 index 0000000..9490851 --- /dev/null +++ b/farms_network/core/edge.py @@ -0,0 +1,20 @@ +""" Edge """ + +from farms_network.core.edge_cy import EdgeCy +from farms_network.core.options import EdgeOptions + + +class Edge(EdgeCy): + """ Interface to edge class """ + + def __init__(self, source: str, target: str, edge_type: str, **kwargs): + super().__init__(source, target, edge_type, **kwargs) + + @classmethod + def from_options(cls, edge_options: EdgeOptions): + """ From edge options """ + source: str = edge_options.source + target: str = edge_options.target + edge_type: str = edge_options.type + parameter_options = {} if edge_options.parameters is None else edge_options.parameters + return cls(source, target, edge_type, **parameter_options) diff --git a/farms_network/core/edge.pyx b/farms_network/core/edge.pyx deleted file mode 100644 index 5b24bed..0000000 --- a/farms_network/core/edge.pyx +++ /dev/null @@ -1,99 +0,0 @@ -""" ------------------------------------------------------------------------ -Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty -Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. ------------------------------------------------------------------------ -""" - -from libc.stdio cimport printf -from libc.stdlib cimport free, malloc -from libc.string cimport strdup - -from .options import EdgeOptions - - -cdef class Edge: - """ Python interface to Edge C-Structure""" - - def __cinit__(self): - self.c_edge = malloc(sizeof(EdgeCy)) - if self.c_edge is NULL: - raise MemoryError("Failed to allocate memory for EdgeCy") - self.c_edge.source = NULL - self.c_edge.target = NULL - self.c_edge.type = NULL - self.c_edge.parameters = NULL - self.c_edge.nparameters = 0 - - def __dealloc__(self): - if self.c_edge is not NULL: - if self.c_edge.source is not NULL: - free(self.c_edge.source) - if self.c_edge.target is not NULL: - free(self.c_edge.target) - if self.c_edge.type is not NULL: - free(self.c_edge.type) - if self.c_edge.parameters is not NULL: - free(self.c_edge.parameters) - free(self.c_edge) - - def __init__(self, source: str, target: str, edge_type: str, **kwargs): - self.c_edge.source = strdup(source.encode('UTF-8')) - self.c_edge.target = strdup(source.encode('UTF-8')) - self.c_edge.type = strdup(edge_type.encode('UTF-8')) - - @classmethod - def from_options(cls, edge_options: EdgeOptions): - """ From edge options """ - source: str = edge_options.source - target: str = edge_options.target - edge_type: str = edge_options.type - - return cls(source, target, edge_type, **edge_options.parameters) - - # Property methods for source - @property - def source(self): - if self.c_edge.source is NULL: - return None - return self.c_edge.source.decode('UTF-8') - - @property - def target(self): - if self.c_edge.target is NULL: - return None - return self.c_edge.target.decode('UTF-8') - - @property - def type(self): - if self.c_edge.type is NULL: - return None - return self.c_edge.type.decode('UTF-8') - - # Property methods for nparameters - @property - def nparameters(self): - return self.c_edge.nparameters - - @property - def parameters(self): - """Generic accessor for parameters.""" - if not self.c_edge.parameters: - raise ValueError("EdgeCy parameters are NULL") - if self.c_edge.nparameters == 0: - raise ValueError("No parameters available") - - # The derived class should override this method to provide specific behavior - raise NotImplementedError("Base class does not define parameter handling") diff --git a/farms_network/core/edge_cy.pxd b/farms_network/core/edge_cy.pxd new file mode 100644 index 0000000..23347b3 --- /dev/null +++ b/farms_network/core/edge_cy.pxd @@ -0,0 +1,31 @@ +""" Edge Base Struture """ + + +cdef enum: + + #EDGE TYPES + OPEN = 0 + EXCITATORY = 1 + INHIBITORY = 2 + CHOLINERGIC = 3 + + +cdef struct edge_t: + + # + char* source # Source node + char* target # Target node + unsigned int type # Type of connection + char* model # Type of the model (e.g., "base") + + # Edge parameters + unsigned int nparameters + void* parameters + + + +cdef class EdgeCy: + """ Python interface to Edge C-Structure""" + + cdef: + edge_t* _edge diff --git a/farms_network/core/edge_cy.pyx b/farms_network/core/edge_cy.pyx new file mode 100644 index 0000000..294635d --- /dev/null +++ b/farms_network/core/edge_cy.pyx @@ -0,0 +1,77 @@ +""" Edge """ + +from libc.stdio cimport printf +from libc.stdlib cimport free, malloc +from libc.string cimport strdup + +from farms_network.core.options import EdgeOptions + + +cpdef enum types: + + #EDGE TYPES + open = OPEN + excitatory = EXCITATORY + inhibitory = INHIBITORY + cholinergic = CHOLINERGIC + + +cdef class EdgeCy: + """ Python interface to Edge C-Structure""" + + def __cinit__(self, source: str, target: str, edge_type: str, model: str): + self._edge = malloc(sizeof(edge_t)) + if self._edge is NULL: + raise MemoryError("Failed to allocate memory for edge_t") + self._edge.source = strdup(source.encode('UTF-8')) + self._edge.target = strdup(target.encode('UTF-8')) + self._edge.type = types[edge_type] + if model is None: + model = "" + self._edge.model = strdup(model.encode('UTF-8')) + self._edge.parameters = NULL + self._edge.nparameters = 0 + + def __dealloc__(self): + if self._edge is not NULL: + if self._edge.source is not NULL: + free(self._edge.source) + if self._edge.target is not NULL: + free(self._edge.target) + if self._edge.parameters is not NULL: + free(self._edge.parameters) + free(self._edge) + + def __init__(self, source: str, target: str, edge_type: str, model: str): + ... + + @property + def source(self): + if self._edge.source is NULL: + return None + return self._edge.source.decode('UTF-8') + + @property + def target(self): + if self._edge.target is NULL: + return None + return self._edge.target.decode('UTF-8') + + @property + def type(self): + return self._edge.type + + @property + def nparameters(self): + return self._edge.nparameters + + @property + def parameters(self): + """Generic accessor for parameters.""" + if not self._edge.parameters: + raise ValueError("edge_t parameters are NULL") + if self._edge.nparameters == 0: + raise ValueError("No parameters available") + + # The derived class should override this method to provide specific behavior + raise NotImplementedError("Base class does not define parameter handling") From 48efbc647c5fa591ffcffefb7b48d176bbef56ac Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Thu, 20 Feb 2025 15:14:38 -0500 Subject: [PATCH 229/316] [CORE][OPTIONS] Refactored node and edge base class options --- farms_network/core/options.py | 68 ++++++++++++++++++++++------------- 1 file changed, 43 insertions(+), 25 deletions(-) diff --git a/farms_network/core/options.py b/farms_network/core/options.py index 1bd07d8..9e5fc77 100644 --- a/farms_network/core/options.py +++ b/farms_network/core/options.py @@ -2,7 +2,7 @@ import time -from typing import Dict, Iterable, List, Self, Type, Union +from typing import Any, Dict, Iterable, List, Self, Type from farms_core import pylog from farms_core.options import Options @@ -14,22 +14,37 @@ ########################### class NodeOptions(Options): """ Base class for defining node options """ + MODEL = Models.BASE - def __init__(self, **kwargs): + def __init__(self, **kwargs: Any): """ Initialize """ super().__init__() self.name: str = kwargs.pop("name") - - self.model: str = kwargs.pop("model") - self.parameters: NodeParameterOptions = kwargs.pop("parameters") - self.visual: NodeVisualOptions = kwargs.pop("visual") - self.state: NodeStateOptions = kwargs.pop("state") + model = kwargs.pop("model", NodeOptions.MODEL) + if isinstance(model, Models): + model = Models.to_str(model) + elif not isinstance(model, str): + raise TypeError( + f"{model} is of {type(model)}. Needs to {type(Models)} or {type(str)}" + ) + self.model: str = model + self.parameters: NodeParameterOptions = kwargs.pop("parameters", None) + self.visual: NodeVisualOptions = NodeVisualOptions.from_options( + kwargs.pop("visual") + ) + self.state: NodeStateOptions = kwargs.pop("state", None) self.noise: NoiseOptions = kwargs.pop("noise", None) - self._nstates: int = 0 - self._nparameters: int = 0 - if kwargs: - raise Exception(f'Unknown kwargs: {kwargs}') + @classmethod + def from_options(cls, kwargs: Dict): + """ Load from options """ + options = {} + options["name"] = kwargs.pop("name") + options["parameters"] = kwargs.pop("parameters") + options["visual"] = kwargs.pop("visual") + options["state"] = kwargs.pop("state") + options["noise"] = kwargs.pop("noise") + return cls(**options) def __eq__(self, other): if isinstance(other, NodeOptions): @@ -41,16 +56,12 @@ def __eq__(self, other): def __hash__(self): return hash(self.name) # Hash based on the node name (or any unique property) - @classmethod - def from_options(cls, kwargs: Dict): - """ Load from options """ - options = {} - options["name"] = kwargs.pop("name") - options["parameters"] = kwargs.pop("parameters") - options["visual"] = kwargs.pop("visual") - options["state"] = kwargs.pop("state") - options["noise"] = kwargs.pop("noise") - return cls(**options) + def __str__(self) -> str: + attrs_str = ',\n'.join(f'{attr}={getattr(self, attr)}' for attr in self.keys()) + return f"{self.__class__.__name__}(\n{attrs_str}\n)" + + def __repr__(self) -> str: + return self.__str__() class NodeParameterOptions(Options): @@ -71,6 +82,7 @@ def __init__(self, **kwargs): super().__init__() self.initial: List[float] = kwargs.pop("initial") self.names: List[str] = kwargs.pop("names") + if kwargs: raise ValueError(f'Unknown kwargs: {kwargs}') @@ -135,12 +147,10 @@ class EdgeOptions(Options): def __init__(self, **kwargs): """ Initialize """ super().__init__() - model = "standard" - self.model: str = kwargs.pop("model", model) self.source: str = kwargs.pop("source") self.target: str = kwargs.pop("target") self.weight: float = kwargs.pop("weight") - self.type: Union[EdgeTypes, int] = kwargs.pop("type") + self.type = EdgeTypes.to_str(kwargs.pop("type")) self.parameters: EdgeParameterOptions = kwargs.pop( "parameters", EdgeParameterOptions() ) @@ -174,12 +184,20 @@ def from_options(cls, kwargs: Dict): ) return cls(**options) + def __str__(self) -> str: + attrs_str = ',\n'.join(f'{attr}={getattr(self, attr)}' for attr in self.keys()) + return f"{self.__class__.__name__}(\n{attrs_str}\n)" + + def __repr__(self) -> str: + return self.__str__() + class EdgeParameterOptions(Options): """ Base class for edge specific parameters """ - def __init__(self): + def __init__(self, **kwargs): super().__init__() + self.model: str = kwargs.pop("model", None) @classmethod def from_options(cls, kwargs: Dict): From 69ad966c3ab6f202574a7d1501abd56272e5172e Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Thu, 20 Feb 2025 15:25:38 -0500 Subject: [PATCH 230/316] [CORE][OPTIONS] Removed model specific edge options --- farms_network/core/options.py | 190 +--------------------------------- 1 file changed, 3 insertions(+), 187 deletions(-) diff --git a/farms_network/core/options.py b/farms_network/core/options.py index 9e5fc77..1250adc 100644 --- a/farms_network/core/options.py +++ b/farms_network/core/options.py @@ -329,38 +329,6 @@ def from_options(cls, kwargs: Dict): return cls(**options) -class RelayEdgeOptions(EdgeOptions): - """ Relay edge options """ - MODEL = Models.RELAY - - def __init__(self, **kwargs): - """ Initialize """ - - super().__init__( - model=RelayEdgeOptions.MODEL, - source=kwargs.pop("source"), - target=kwargs.pop("target"), - weight=kwargs.pop("weight"), - type=kwargs.pop("type"), - parameters=None, - visual=kwargs.pop("visual"), - ) - if kwargs: - raise Exception(f'Unknown kwargs: {kwargs}') - - @classmethod - def from_options(cls, kwargs: Dict): - """ From options """ - - options = {} - options["source"] = kwargs["source"] - options["target"] = kwargs["target"] - options["weight"] = kwargs["weight"] - options["type"] = kwargs["type"] - options["visual"] = EdgeVisualOptions.from_options(kwargs["visual"]) - return cls(**options) - - ######################## # Linear Model Options # ######################## @@ -422,37 +390,6 @@ def defaults(cls, **kwargs): return cls(**options) -class LinearEdgeOptions(EdgeOptions): - """ Linear edge options """ - - def __init__(self, **kwargs): - """ Initialize """ - - super().__init__( - model=Models.LINEAR, - source=kwargs.pop("source"), - target=kwargs.pop("target"), - weight=kwargs.pop("weight"), - type=kwargs.pop("type"), - visual=kwargs.pop("visual"), - ) - if kwargs: - raise Exception(f'Unknown kwargs: {kwargs}') - - @classmethod - def from_options(cls, kwargs: Dict): - """ From options """ - - options = {} - options["source"] = kwargs["source"] - options["target"] = kwargs["target"] - options["weight"] = kwargs["weight"] - options["type"] = kwargs["type"] - options["parameters"] = None - options["visual"] = EdgeVisualOptions.from_options(kwargs["visual"]) - return cls(**options) - - ###################### # ReLU Model Options # ###################### @@ -518,37 +455,6 @@ def defaults(cls, **kwargs): return cls(**options) -class ReLUEdgeOptions(EdgeOptions): - """ ReLU edge options """ - - def __init__(self, **kwargs): - """ Initialize """ - - super().__init__( - model=Models.RELU, - source=kwargs.pop("source"), - target=kwargs.pop("target"), - weight=kwargs.pop("weight"), - type=kwargs.pop("type"), - visual=kwargs.pop("visual"), - ) - if kwargs: - raise Exception(f'Unknown kwargs: {kwargs}') - - @classmethod - def from_options(cls, kwargs: Dict): - """ From options """ - - options = {} - options["source"] = kwargs["source"] - options["target"] = kwargs["target"] - options["weight"] = kwargs["weight"] - options["type"] = kwargs["type"] - options["parameters"] = None - options["visual"] = EdgeVisualOptions.from_options(kwargs["visual"]) - return cls(**options) - - ############################################ # Phase-Amplitude Oscillator Model Options # ############################################ @@ -631,53 +537,13 @@ def __init__(self, **kwargs): assert len(self.initial) == 3, f"Number of initial states {len(self.initial)} should be 3" -class OscillatorEdgeOptions(EdgeOptions): - """ Oscillator edge options """ - - def __init__(self, **kwargs): - """ Initialize """ - model = "oscillator" - super().__init__( - model=model, - source=kwargs.pop("source"), - target=kwargs.pop("target"), - weight=kwargs.pop("weight"), - type=kwargs.pop("type"), - parameters=kwargs.pop("parameters"), - visual=kwargs.pop("visual"), - ) - if kwargs: - raise Exception(f'Unknown kwargs: {kwargs}') - - def __eq__(self, other): - if isinstance(other, EdgeOptions): - return ( - (self.source == other.source) and - (self.target == other.target) - ) - return False - - @classmethod - def from_options(cls, kwargs: Dict): - """ From options """ - - options = {} - options["source"] = kwargs["source"] - options["target"] = kwargs["target"] - options["weight"] = kwargs["weight"] - options["type"] = kwargs["type"] - options["parameters"] = OscillatorEdgeParameterOptions.from_options( - kwargs["parameters"] - ) - options["visual"] = EdgeVisualOptions.from_options(kwargs["visual"]) - return cls(**options) - - class OscillatorEdgeParameterOptions(EdgeParameterOptions): """ Oscillator edge parameter options """ + MODEL = Models.OSCILLATOR + def __init__(self, **kwargs): - super().__init__() + super().__init__(model=OscillatorEdgeParameterOptions.MODEL) self.phase_difference = kwargs.pop("phase_difference") # radians if kwargs: @@ -1120,38 +986,6 @@ def __init__(self, **kwargs): assert len(self.initial) == 2, f"Number of initial states {len(self.initial)} should be 2" -class LINaPDannerEdgeOptions(EdgeOptions): - """ LINaPDanner edge options """ - - def __init__(self, **kwargs): - """ Initialize """ - model = "li_danner" - super().__init__( - model=model, - source=kwargs.pop("source"), - target=kwargs.pop("target"), - weight=kwargs.pop("weight"), - type=kwargs.pop("type"), - parameters=kwargs.pop("parameters"), - visual=kwargs.pop("visual"), - ) - if kwargs: - raise Exception(f'Unknown kwargs: {kwargs}') - - @classmethod - def from_options(cls, kwargs: Dict): - """ From options """ - - options = {} - options["source"] = kwargs["source"] - options["target"] = kwargs["target"] - options["weight"] = kwargs["weight"] - options["type"] = kwargs["type"] - options["parameters"] = None - options["visual"] = EdgeVisualOptions.from_options(kwargs["visual"]) - return cls(**options) - - #################### # Izhikevich Model # #################### @@ -1239,23 +1073,6 @@ class NetworkOptions(Options): # Models.HH_DAUN: HHDaunNodeOptions, } - EDGE_TYPES: Dict[Models, Type] = { - Models.RELAY: RelayEdgeOptions, - Models.LINEAR: LinearEdgeOptions, - Models.RELU: ReLUEdgeOptions, - Models.OSCILLATOR: OscillatorEdgeOptions, - # Models.HOPF_OSCILLATOR: HopfOscillatorEdgeOptions, - # Models.MORPHED_OSCILLATOR: MorphedOscillatorEdgeOptions, - # Models.MATSUOKA: MatsuokaEdgeOptions, - # Models.FITZHUGH_NAGUMO: FitzhughNagumoEdgeOptions, - # Models.MORRIS_LECAR: MorrisLecarEdgeOptions, - # Models.LEAKY_INTEGRATOR: LeakyIntegratorEdgeOptions, - Models.LI_DANNER: LIDannerEdgeOptions, - Models.LI_NAP_DANNER: LINaPDannerEdgeOptions, - # Models.LI_DAUN: LIDaunEdgeOptions, - # Models.HH_DAUN: HHDaunEdgeOptions, - } - def __init__(self, **kwargs): super().__init__() @@ -1297,7 +1114,6 @@ def from_options(cls, kwargs): ] # Edges options["edges"] = [ - # cls.EDGE_TYPES[edge["model"]].from_options(edge) EdgeOptions.from_options(edge) for edge in kwargs["edges"] ] From 1a457086aa4283b596e3fa23ebfc5d6fe4a8a4d4 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Thu, 20 Feb 2025 15:58:15 -0500 Subject: [PATCH 231/316] [CORE][OPTIONS] Replaced print statements with pylog --- farms_network/core/options.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/farms_network/core/options.py b/farms_network/core/options.py index 1250adc..5e9c88d 100644 --- a/farms_network/core/options.py +++ b/farms_network/core/options.py @@ -1125,7 +1125,7 @@ def add_node(self, options: NodeOptions): if options not in self.nodes: self.nodes.append(options) else: - print(f"Node {options.name} already exists and will not be added again.") + pylog.warning(f"Node {options.name} already exists and will not be added again.") def add_nodes(self, options: Iterable[NodeOptions]): """ Add a collection of nodes """ From 3d583bae37b0a1253697eed56da7b710311a8d0b Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Sat, 24 May 2025 21:09:43 -0400 Subject: [PATCH 232/316] [CORE] Refactored node class --- farms_network/core/node.py | 32 +++++++++-- farms_network/core/node_cy.pxd | 44 ++++----------- farms_network/core/node_cy.pyx | 97 ++++++++-------------------------- 3 files changed, 58 insertions(+), 115 deletions(-) diff --git a/farms_network/core/node.py b/farms_network/core/node.py index 7a6e967..fad9909 100644 --- a/farms_network/core/node.py +++ b/farms_network/core/node.py @@ -1,18 +1,40 @@ """ Node """ +from abc import ABC, abstractmethod +from typing import Any, Dict, Type + from farms_network.core.node_cy import NodeCy -from farms_network.core.options import NodeOptions, NodeParameterOptions +from farms_network.core.options import NodeOptions + + +class Node(ABC): + + CY_NODE_CLASS: Type[NodeCy] = None + def __init__(self, name: str, model: str, **parameters): + self.name: str = name # Unique name of the node + self.model: str = model # Type of the model (e.g., "empty") + self._node_cy = self._create_cy_node(**parameters) -class Node(NodeCy): + def _create_cy_node(self, **kwargs) -> NodeCy: + if self.CY_NODE_CLASS is None: + raise NotImplementedError("Must define CY_NODE_CLASS") + return self.CY_NODE_CLASS(**kwargs) - def __init__(self, name: str): - super().__init__(name) + def print_parameters(self): + return self._node_cy.parameters @classmethod def from_options(cls, node_options: NodeOptions): """ From node options """ name: str = node_options.name - parameters: NodeParameterOptions = node_options.parameters if node_options.parameters else {} + parameters = node_options.parameters + return cls(name, **parameters) + + def to_options(self): + """ To node options """ + print(self) + name: str = node_options.name + parameters = node_options.parameters return cls(name, **parameters) diff --git a/farms_network/core/node_cy.pxd b/farms_network/core/node_cy.pxd index 5923919..c391c7f 100644 --- a/farms_network/core/node_cy.pxd +++ b/farms_network/core/node_cy.pxd @@ -1,21 +1,21 @@ """ Node Base Struture. """ -from farms_network.core.edge cimport EdgeCy +from farms_network.core.edge_cy cimport edge_t cdef struct node_t: # Generic parameters unsigned int nstates # Number of state variables in the node. - unsigned int nparameters # Number of parameters for the node. - unsigned int ninputs # Number of inputs + unsigned int ninputs # Number of inputs to the node within the network + unsigned int nparams # Number of parameters in the node char* model # Type of the model (e.g., "empty"). char* name # Unique name of the node. - bint is_statefull # Flag indicating whether the node is stateful. (ODE) + bint is_statefull # Flag indicating whether the node is stateful. (ODE) # Parameters - void* parameters # Pointer to the parameters of the node. + void* params # Pointer to the parameters of the node. # Functions void ode( @@ -27,8 +27,8 @@ cdef struct node_t: unsigned int* inputs, double* weights, double noise, - node_t* c_node, - EdgeCy** c_edges, + node_t* node, + edge_t** edges, ) noexcept double output( @@ -38,38 +38,12 @@ cdef struct node_t: double* network_outputs, unsigned int* inputs, double* weights, - node_t* c_node, - EdgeCy** c_edges, - ) noexcept - - -cdef: - void ode( - double time, - double* states, - double* derivatives, - double external_input, - double* network_outputs, - unsigned int* inputs, - double* weights, - double noise, - node_t* c_node, - EdgeCy** c_edges, - ) noexcept - double output( - double time, - double* states, - double external_input, - double* network_outputs, - unsigned int* inputs, - double* weights, - node_t* c_node, - EdgeCy** c_edges, + node_t* node, + edge_t** edges, ) noexcept cdef class NodeCy: """ Interface to Node C-Structure """ - cdef: node_t* _node diff --git a/farms_network/core/node_cy.pyx b/farms_network/core/node_cy.pyx index 4e927fd..094089c 100644 --- a/farms_network/core/node_cy.pyx +++ b/farms_network/core/node_cy.pyx @@ -1,107 +1,54 @@ """ Node """ +from typing import Optional + from libc.stdio cimport printf from libc.stdlib cimport free, malloc from libc.string cimport strdup from farms_network.core.options import NodeOptions - - -cdef void ode( - double time, - double* states, - double* derivatives, - double external_input, - double* network_outputs, - unsigned int* inputs, - double* weights, - double noise, - node_t* node, - EdgeCy** c_edges, -) noexcept: - """ node_t ODE """ - printf("Base implementation of ODE C function \n") - - -cdef double output( - double time, - double* states, - double external_input, - double* network_outputs, - unsigned int* inputs, - double* weights, - node_t* node, - EdgeCy** c_edges, -) noexcept: - """ node_t output """ - printf("Base implementation of output C function \n") - return 0.0 +from farms_network.models import Models cdef class NodeCy: """ Interface to Node C-Structure """ - def __cinit__(self, name: str, model: str): + MODEL = Models.BASE.value + + def __cinit__(self, **kwargs): self._node = malloc(sizeof(node_t)) if self._node is NULL: raise MemoryError("Failed to allocate memory for node_t") - self._node.name = strdup(name.encode('UTF-8')) - self._node.model = strdup(model.encode('UTF-8')) - self._node.ode = ode - self._node.output = output - self._node.parameters = NULL - self._node.nparameters = 0 - self._node.ninputs = 0 + self._node.nstates = 0 + self._node.ode = NULL + self._node.output = NULL + + # Setup parameters + self._node.params = NULL + + def __init__(self, **kwargs): + ... def __dealloc__(self): if self._node is not NULL: - if self._node.name is not NULL: - free(self._node.name) - if self._node.model is not NULL: - free(self._node.model) - if self._node.parameters is not NULL: - free(self._node.parameters) free(self._node) - # Property methods for name - @property - def name(self): - if self._node.name is NULL: - return None - return self._node.name.decode('UTF-8') - - # Property methods for model - @property - def model(self): - if self._node.model is NULL: - return None - return self._node.model.decode('UTF-8') - - # Property methods for nstates + # Property methods @property def nstates(self): return self._node.nstates - # Property methods for ninputs @property def ninputs(self): return self._node.ninputs - # Property methods for nparameters @property - def nparameters(self): - return self._node.nparameters + def nparams(self): + return self._node.nparams @property - def parameters(self): - """Generic accessor for parameters.""" - if not self._node.parameters: - raise ValueError("node_t parameters are NULL") - if self._node.nparameters == 0: - raise ValueError("No parameters available") - - # The derived class should override this method to provide specific behavior - raise NotImplementedError("Base class does not define parameter handling") + def is_statefull(self): + return self._node.is_statefull # Methods to wrap the ODE and output functions def ode( @@ -121,7 +68,7 @@ cdef class NodeCy: cdef unsigned int* inputs_ptr = &inputs[0] cdef double* weights_ptr = &weights[0] - cdef EdgeCy** c_edges = NULL + cdef edge_t** c_edges = NULL # Call the C function directly self._node.ode( @@ -151,7 +98,7 @@ cdef class NodeCy: cdef double* network_outputs_ptr = &network_outputs[0] cdef unsigned int* inputs_ptr = &inputs[0] cdef double* weights_ptr = &weights[0] - cdef EdgeCy** c_edges = NULL + cdef edge_t** c_edges = NULL return self._node.output( time, states_ptr, From 2d70a4a61845ae7da8b5b461aa47ec962c254724 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Sat, 24 May 2025 21:20:12 -0400 Subject: [PATCH 233/316] [CORE] Refactored edge class --- farms_network/core/edge.py | 21 +++++++++++++--- farms_network/core/edge_cy.pxd | 13 +++------- farms_network/core/edge_cy.pyx | 46 +++++----------------------------- 3 files changed, 26 insertions(+), 54 deletions(-) diff --git a/farms_network/core/edge.py b/farms_network/core/edge.py index 9490851..b6a535c 100644 --- a/farms_network/core/edge.py +++ b/farms_network/core/edge.py @@ -2,19 +2,32 @@ from farms_network.core.edge_cy import EdgeCy from farms_network.core.options import EdgeOptions +from farms_network.models import EdgeTypes +from typing import Dict class Edge(EdgeCy): """ Interface to edge class """ - def __init__(self, source: str, target: str, edge_type: str, **kwargs): - super().__init__(source, target, edge_type, **kwargs) + def __init__(self, source: str, target: str, edge_type: EdgeTypes, **kwargs): + self.source: str = source + self.target: str = target + self._edge_cy = EdgeCy(source, target, edge_type) + + @property + def edge_type(self): + return self._edge_cy.type + + @edge_type.setter + def edge_type(self, edge_type: EdgeTypes): + self._edge_cy.type = edge_type @classmethod def from_options(cls, edge_options: EdgeOptions): """ From edge options """ source: str = edge_options.source target: str = edge_options.target - edge_type: str = edge_options.type - parameter_options = {} if edge_options.parameters is None else edge_options.parameters + edge_type: EdgeTypes = edge_options.type + # Need to generate parameters based on the model specified + parameter_options: Dict = {} # if edge_options.parameters is None else edge_options.parameters return cls(source, target, edge_type, **parameter_options) diff --git a/farms_network/core/edge_cy.pxd b/farms_network/core/edge_cy.pxd index 23347b3..a29b6f5 100644 --- a/farms_network/core/edge_cy.pxd +++ b/farms_network/core/edge_cy.pxd @@ -11,21 +11,14 @@ cdef enum: cdef struct edge_t: - - # - char* source # Source node - char* target # Target node unsigned int type # Type of connection - char* model # Type of the model (e.g., "base") - # Edge parameters - unsigned int nparameters - void* parameters - + unsigned int nparams + void* params cdef class EdgeCy: - """ Python interface to Edge C-Structure""" + """ Python interface to Edge C-Structure """ cdef: edge_t* _edge diff --git a/farms_network/core/edge_cy.pyx b/farms_network/core/edge_cy.pyx index 294635d..7fd7994 100644 --- a/farms_network/core/edge_cy.pyx +++ b/farms_network/core/edge_cy.pyx @@ -19,59 +19,25 @@ cpdef enum types: cdef class EdgeCy: """ Python interface to Edge C-Structure""" - def __cinit__(self, source: str, target: str, edge_type: str, model: str): + def __cinit__(self, source: str, target: str, edge_type: str): self._edge = malloc(sizeof(edge_t)) if self._edge is NULL: raise MemoryError("Failed to allocate memory for edge_t") - self._edge.source = strdup(source.encode('UTF-8')) - self._edge.target = strdup(target.encode('UTF-8')) self._edge.type = types[edge_type] - if model is None: - model = "" - self._edge.model = strdup(model.encode('UTF-8')) - self._edge.parameters = NULL - self._edge.nparameters = 0 + self._edge.params = NULL + self._edge.nparams = 0 def __dealloc__(self): if self._edge is not NULL: - if self._edge.source is not NULL: - free(self._edge.source) - if self._edge.target is not NULL: - free(self._edge.target) - if self._edge.parameters is not NULL: - free(self._edge.parameters) free(self._edge) - def __init__(self, source: str, target: str, edge_type: str, model: str): + def __init__(self, source: str, target: str, edge_type: str): ... - @property - def source(self): - if self._edge.source is NULL: - return None - return self._edge.source.decode('UTF-8') - - @property - def target(self): - if self._edge.target is NULL: - return None - return self._edge.target.decode('UTF-8') - @property def type(self): return self._edge.type @property - def nparameters(self): - return self._edge.nparameters - - @property - def parameters(self): - """Generic accessor for parameters.""" - if not self._edge.parameters: - raise ValueError("edge_t parameters are NULL") - if self._edge.nparameters == 0: - raise ValueError("No parameters available") - - # The derived class should override this method to provide specific behavior - raise NotImplementedError("Base class does not define parameter handling") + def nparams(self): + return self._edge.nparams From 11597d3dc83701f504dc49bb3f48c5861413a6bb Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Sat, 24 May 2025 21:25:05 -0400 Subject: [PATCH 234/316] [MODELS] Refactored relu and relay nodes --- farms_network/models/external_relay.pyx | 54 ----------- farms_network/models/relay.py | 18 ++++ .../{external_relay.pxd => relay_cy.pxd} | 12 +-- farms_network/models/relay_cy.pyx | 33 +++++++ farms_network/models/relu.pxd | 55 ------------ farms_network/models/relu.py | 37 ++++++++ farms_network/models/relu.pyx | 90 ------------------- farms_network/models/relu_cy.pxd | 36 ++++++++ farms_network/models/relu_cy.pyx | 82 +++++++++++++++++ 9 files changed, 212 insertions(+), 205 deletions(-) delete mode 100644 farms_network/models/external_relay.pyx create mode 100644 farms_network/models/relay.py rename farms_network/models/{external_relay.pxd => relay_cy.pxd} (86%) create mode 100644 farms_network/models/relay_cy.pyx delete mode 100644 farms_network/models/relu.pxd create mode 100644 farms_network/models/relu.py delete mode 100644 farms_network/models/relu.pyx create mode 100644 farms_network/models/relu_cy.pxd create mode 100644 farms_network/models/relu_cy.pyx diff --git a/farms_network/models/external_relay.pyx b/farms_network/models/external_relay.pyx deleted file mode 100644 index 381d046..0000000 --- a/farms_network/models/external_relay.pyx +++ /dev/null @@ -1,54 +0,0 @@ -""" ------------------------------------------------------------------------ -Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty -Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. ------------------------------------------------------------------------ - -External model -""" - -from libc.stdio cimport printf -from libc.stdlib cimport malloc -from libc.string cimport strdup - - -cdef double output( - double time, - double* states, - double external_input, - double* network_outputs, - unsigned int* inputs, - double* weights, - NodeCy* c_node, - EdgeCy** c_edges, -) noexcept: - """ Node output. """ - return external_input - - -cdef class ExternalRelayNode(Node): - """ Python interface to External Relay Node C-Structure """ - - def __cinit__(self): - self.c_node.model_type = strdup("EXTERNAL_RELAY".encode('UTF-8')) - # override default ode and out methods - self.c_node.is_statefull = False - self.c_node.output = output - - def __init__(self, name: str, **kwargs): - super().__init__(name) - - if kwargs: - raise Exception(f'Unknown kwargs: {kwargs}') diff --git a/farms_network/models/relay.py b/farms_network/models/relay.py new file mode 100644 index 0000000..4feabf5 --- /dev/null +++ b/farms_network/models/relay.py @@ -0,0 +1,18 @@ +""" Relay """ + + +from farms_network.core.options import RelayNodeOptions +from farms_network.models.relay_cy import RelayNodeCy + + +class RelayNode: + """ Relay node Cy """ + + def __init__(self, name: str, **kwargs): + self._node_cy: RelayNodeCy = RelayNodeCy(name, **kwargs) + + @classmethod + def from_options(cls, node_options: RelayNodeOptions): + """ Instantiate relay node from options """ + name: str = node_options.name + return cls(name) diff --git a/farms_network/models/external_relay.pxd b/farms_network/models/relay_cy.pxd similarity index 86% rename from farms_network/models/external_relay.pxd rename to farms_network/models/relay_cy.pxd index c8f15ec..d299300 100644 --- a/farms_network/models/external_relay.pxd +++ b/farms_network/models/relay_cy.pxd @@ -16,12 +16,12 @@ See the License for the specific language governing permissions and limitations under the License. ----------------------------------------------------------------------- -Oscillator model +Relay model """ -from ..core.node cimport NodeCy, Node -from ..core.edge cimport EdgeCy +from ..core.node_cy cimport NodeCy, node_t +from ..core.edge_cy cimport edge_t cdef: @@ -32,10 +32,10 @@ cdef: double* network_outputs, unsigned int* inputs, double* weights, - NodeCy* c_node, - EdgeCy** c_edges, + node_t* c_node, + edge_t** c_edges, ) noexcept -cdef class ExternalRelayNode(Node): +cdef class RelayNodeCy(NodeCy): """ Python interface to External Relay Node C-Structure """ diff --git a/farms_network/models/relay_cy.pyx b/farms_network/models/relay_cy.pyx new file mode 100644 index 0000000..d61b740 --- /dev/null +++ b/farms_network/models/relay_cy.pyx @@ -0,0 +1,33 @@ +""" Relay model """ + +from libc.stdio cimport printf +from libc.stdlib cimport malloc + + +cdef double output( + double time, + double* states, + double external_input, + double* network_outputs, + unsigned int* inputs, + double* weights, + node_t* c_node, + edge_t** c_edges, +) noexcept: + """ Node output. """ + return external_input + + +cdef class RelayNodeCy(NodeCy): + """ Python interface to Relay Node C-Structure """ + + def __cinit__(self): + # override default ode and out methods + self._node.is_statefull = False + self._node.output = output + + def __init__(self, **kwargs): + super().__init__() + + if kwargs: + raise Exception(f'Unknown kwargs: {kwargs}') diff --git a/farms_network/models/relu.pxd b/farms_network/models/relu.pxd deleted file mode 100644 index 3fb8e71..0000000 --- a/farms_network/models/relu.pxd +++ /dev/null @@ -1,55 +0,0 @@ -""" ------------------------------------------------------------------------ -Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty -Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. ------------------------------------------------------------------------ - -Rectified Linear Unit -""" - - -from ..core.node cimport NodeCy, Node -from ..core.edge cimport EdgeCy, Edge - - -cdef enum: - #STATES - NSTATES = 0 - - -cdef packed struct ReLUNodeParameters: - double gain - double sign - double offset - - -cdef: - double output( - double time, - double* states, - double external_input, - double* network_outputs, - unsigned int* inputs, - double* weights, - NodeCy* c_node, - EdgeCy** c_edges, - ) noexcept - - -cdef class ReLUNode(Node): - """ Python interface to ReLU Node C-Structure """ - - cdef: - ReLUNodeParameters parameters diff --git a/farms_network/models/relu.py b/farms_network/models/relu.py new file mode 100644 index 0000000..2296753 --- /dev/null +++ b/farms_network/models/relu.py @@ -0,0 +1,37 @@ +from farms_network.core.node import Node +from farms_network.models import Models +from farms_network.core.options import ReLUNodeOptions +from farms_network.models.relu_cy import ReLUNodeCy + + +class ReLUNode(Node): + + CY_NODE_CLASS = ReLUNodeCy + + def __init__(self, name: str, **kwargs): + super().__init__(name=name, model=Models.RELU, **kwargs) + + # ReLU-specific properties + @property + def gain(self): + return self._node_cy.gain + + @gain.setter + def gain(self, value): + self._node_cy.gain = value + + @property + def sign(self): + return self._node_cy.sign + + @sign.setter + def sign(self, value): + self._node_cy.sign = value + + @property + def offset(self): + return self._node_cy.offset + + @offset.setter + def offset(self, value): + self._node_cy.offset = value diff --git a/farms_network/models/relu.pyx b/farms_network/models/relu.pyx deleted file mode 100644 index eb00a7f..0000000 --- a/farms_network/models/relu.pyx +++ /dev/null @@ -1,90 +0,0 @@ -""" ------------------------------------------------------------------------ -Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty -Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. ------------------------------------------------------------------------ - -Rectified Linear Unit -""" - - -from libc.stdio cimport printf -from libc.stdlib cimport free, malloc -from libc.string cimport strdup - - -cpdef enum STATE: - - #STATES - nstates = NSTATES - - -cdef double output( - double time, - double* states, - double external_input, - double* network_outputs, - unsigned int* inputs, - double* weights, - NodeCy* c_node, - EdgeCy** c_edges, -) noexcept: - """ Node output. """ - cdef ReLUNodeParameters params = ( c_node[0].parameters)[0] - - cdef: - double _sum = 0.0 - unsigned int j - double _input, _weight - cdef unsigned int ninputs = c_node.ninputs - _sum += external_input - for j in range(ninputs): - _input = network_outputs[inputs[j]] - _weight = weights[j] - _sum += _weight*_input - - cdef double res = max(0.0, params.gain*(params.sign*_sum + params.offset)) - return res - - -cdef class ReLUNode(Node): - """ Python interface to ReLU Node C-Structure """ - - def __cinit__(self): - self.c_node.model_type = strdup("RELU".encode('UTF-8')) - # override default ode and out methods - self.c_node.is_statefull = False - self.c_node.output = output - # parameters - self.c_node.parameters = malloc(sizeof(ReLUNodeParameters)) - if self.c_node.parameters is NULL: - raise MemoryError("Failed to allocate memory for node parameters") - - def __init__(self, name: str, **kwargs): - super().__init__(name) - - # Set node parameters - cdef ReLUNodeParameters* param = (self.c_node.parameters) - param.gain = kwargs.pop("gain") - param.sign = kwargs.pop("sign") - param.offset = kwargs.pop("offset") - if kwargs: - raise Exception(f'Unknown kwargs: {kwargs}') - - @property - def parameters(self): - """ Parameters in the network """ - cdef ReLUNodeParameters params = ( self.c_node.parameters)[0] - return params diff --git a/farms_network/models/relu_cy.pxd b/farms_network/models/relu_cy.pxd new file mode 100644 index 0000000..edd83cf --- /dev/null +++ b/farms_network/models/relu_cy.pxd @@ -0,0 +1,36 @@ +""" Rectified Linear Unit """ + + +from ..core.node_cy cimport node_t, NodeCy +from ..core.edge_cy cimport edge_t + + +cdef enum: + #STATES + NSTATES = 0 + + +cdef packed struct relu_params_t: + double gain + double sign + double offset + + +cdef: + double output( + double time, + double* states, + double external_input, + double* network_outputs, + unsigned int* inputs, + double* weights, + node_t* c_node, + edge_t** c_edges, + ) noexcept + + +cdef class ReLUNodeCy(NodeCy): + """ Python interface to ReLU Node C-Structure """ + + cdef: + relu_params_t params diff --git a/farms_network/models/relu_cy.pyx b/farms_network/models/relu_cy.pyx new file mode 100644 index 0000000..ce04d5e --- /dev/null +++ b/farms_network/models/relu_cy.pyx @@ -0,0 +1,82 @@ +""" Rectified Linear Unit """ + + +from libc.stdio cimport printf +from libc.stdlib cimport free, malloc + + +cpdef enum STATE: + + #STATES + nstates = NSTATES + + +cdef double output( + double time, + double* states, + double external_input, + double* network_outputs, + unsigned int* inputs, + double* weights, + node_t* c_node, + edge_t** c_edges, +) noexcept: + """ Node output. """ + cdef relu_params_t params = ( c_node[0].params)[0] + + cdef: + double _sum = 0.0 + unsigned int j + double _input, _weight + cdef unsigned int ninputs = c_node.ninputs + _sum += external_input + for j in range(ninputs): + _input = network_outputs[inputs[j]] + _weight = weights[j] + _sum += _weight*_input + + cdef double res = max(0.0, params.gain*(params.sign*_sum + params.offset)) + return res + + +cdef class ReLUNodeCy(NodeCy): + """ Python interface to ReLU Node C-Structure """ + + def __cinit__(self): + # override default ode and out methods + self._node.nstates = 0 + self._node.nparams = 3 + + self._node.is_statefull = False + # self._node.output = output + # parameters + self.params = relu_params_t() + self._node.params = &self.params + if self._node.params is NULL: + raise MemoryError("Failed to allocate memory for node parameters") + + def __init__(self, **kwargs): + super().__init__() + + # Set node parameters + self.params.gain = kwargs.pop("gain") + self.params.sign = kwargs.pop("sign") + self.params.offset = kwargs.pop("offset") + if kwargs: + raise Exception(f'Unknown kwargs: {kwargs}') + + @property + def gain(self): + """ Gain property """ + return ( self._node.params)[0].gain + + @gain.setter + def gain(self, value): + """ Set gain """ + ( self._node.params)[0].gain = value + + @property + def parameters(self): + """ Parameters in the network """ + cdef relu_params_t params = ( self._node.params)[0] + return params From 63a0346eb9ba6a810649b1758ed016e10938c9cd Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Wed, 28 May 2025 12:00:14 -0400 Subject: [PATCH 235/316] [MODELS] Fixed relay base node missing inheritence --- farms_network/models/relay.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/farms_network/models/relay.py b/farms_network/models/relay.py index 4feabf5..da60e7b 100644 --- a/farms_network/models/relay.py +++ b/farms_network/models/relay.py @@ -3,13 +3,17 @@ from farms_network.core.options import RelayNodeOptions from farms_network.models.relay_cy import RelayNodeCy +from farms_network.core.node import Node +from farms_network.models import Models -class RelayNode: +class RelayNode(Node): """ Relay node Cy """ + CY_NODE_CLASS = RelayNodeCy + def __init__(self, name: str, **kwargs): - self._node_cy: RelayNodeCy = RelayNodeCy(name, **kwargs) + super().__init__(name=name, model=Models.RELAY, **kwargs) @classmethod def from_options(cls, node_options: RelayNodeOptions): From e8072c11009ab13838b9b7c6afdb9c6f1c687078 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Wed, 28 May 2025 12:01:25 -0400 Subject: [PATCH 236/316] [CORE] Exposed default node properties --- farms_network/core/node.py | 14 +++++++++++++- farms_network/core/node_cy.pyx | 4 ++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/farms_network/core/node.py b/farms_network/core/node.py index fad9909..abad542 100644 --- a/farms_network/core/node.py +++ b/farms_network/core/node.py @@ -22,6 +22,19 @@ def _create_cy_node(self, **kwargs) -> NodeCy: raise NotImplementedError("Must define CY_NODE_CLASS") return self.CY_NODE_CLASS(**kwargs) + # General node properties + @property + def nstates(self): + return self._node_cy.nstates + + @property + def nparams(self): + return self._node_cy.nparams + + @property + def ninputs(self): + return self._node_cy.ninputs + def print_parameters(self): return self._node_cy.parameters @@ -34,7 +47,6 @@ def from_options(cls, node_options: NodeOptions): def to_options(self): """ To node options """ - print(self) name: str = node_options.name parameters = node_options.parameters return cls(name, **parameters) diff --git a/farms_network/core/node_cy.pyx b/farms_network/core/node_cy.pyx index 094089c..cd4d8a4 100644 --- a/farms_network/core/node_cy.pyx +++ b/farms_network/core/node_cy.pyx @@ -42,6 +42,10 @@ cdef class NodeCy: def ninputs(self): return self._node.ninputs + @ninputs.setter + def ninputs(self, value: int): + self._node.ninputs = value + @property def nparams(self): return self._node.nparams From 9d66b398975b6182da6dc2bef460134fff244ce0 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Wed, 28 May 2025 12:02:01 -0400 Subject: [PATCH 237/316] [MODELS] Added sub-module level enum types for model instances --- farms_network/models/__init__.py | 43 ++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/farms_network/models/__init__.py b/farms_network/models/__init__.py index e69de29..ff00c36 100644 --- a/farms_network/models/__init__.py +++ b/farms_network/models/__init__.py @@ -0,0 +1,43 @@ +from abc import ABC +from enum import Enum, unique +from typing import Type, Union + + +class BaseTypes(Enum): + """ Base class for enum types""" + + @classmethod + def to_str(cls, value: Union[str, Type]) -> Type: + if isinstance(value, cls): + return value.value + if value in cls._value2member_map_: + return value + valid_types = ", ".join(type.value for type in cls) + raise ValueError(f"Invalid type '{value}'. Must be one of: {valid_types}") + + +@unique +class Models(str, BaseTypes): + BASE = "base" + RELAY = "relay" + LINEAR = "linear" + RELU = "relu" + OSCILLATOR = "oscillator" + HOPF_OSCILLATOR = "hopf_oscillator" + MORPHED_OSCILLATOR = "morphed_oscillator" + MATSUOKA = "matsuoka" + FITZHUGH_NAGUMO = "fitzhugh_nagumo" + MORRIS_LECAR = "morris_lecar" + LEAKY_INTEGRATOR = "leaky_integrator" + LI_DANNER = "li_danner" + LI_NAP_DANNER = "li_nap_danner" + LI_DAUN = "li_daun" + HH_DAUN = "hh_daun" + + +@unique +class EdgeTypes(str, BaseTypes): + OPEN = "open" + EXCITATORY = "excitatory" + INHIBITORY = "inhibitory" + CHOLINERGIC = "cholinergic" From dec678ff8d81cf3efb7d3946779cd242fa6c0961 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Wed, 16 Jul 2025 22:20:24 -0400 Subject: [PATCH 238/316] [GITHUB] Removed branch from python installer workflow --- .github/workflows/python-cross-platform-installer.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-cross-platform-installer.yml b/.github/workflows/python-cross-platform-installer.yml index ef4bb6a..b6b2593 100644 --- a/.github/workflows/python-cross-platform-installer.yml +++ b/.github/workflows/python-cross-platform-installer.yml @@ -5,7 +5,7 @@ name: Python application on: push: - branches: [ "main", "farms-core-integration" ] + branches: [ "main",] pull_request: branches: [ "main" ] From 306216acafa3fe47c1754da8ea46b6fb600faf8b Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Sun, 20 Jul 2025 17:54:12 -0500 Subject: [PATCH 239/316] [NODE] Full refactor --- farms_network/core/node.py | 36 ++++++++ farms_network/core/node_cy.pxd | 73 +++++++++++---- farms_network/core/node_cy.pyx | 158 +++++++++++++++++++++------------ 3 files changed, 191 insertions(+), 76 deletions(-) diff --git a/farms_network/core/node.py b/farms_network/core/node.py index abad542..e1d564a 100644 --- a/farms_network/core/node.py +++ b/farms_network/core/node.py @@ -35,9 +35,28 @@ def nparams(self): def ninputs(self): return self._node_cy.ninputs + @property + def is_statefull(self): + return self._node_cy.is_statefull + def print_parameters(self): return self._node_cy.parameters + def input_tf(self): + """ Input transfer function """ + pass + + def ode(self, time, states, derivatives, external_input, network_outputs, inputs, weights, noise): + """ ODE computation """ + return self._node_cy.ode( + time, states, derivatives, external_input, + network_outputs, inputs, weights, noise + ) + + def output_tf(self, time, states, input_val, noise): + """ ODE computation """ + return self._node_cy.output_tf(time, states, input_val, noise) + @classmethod def from_options(cls, node_options: NodeOptions): """ From node options """ @@ -50,3 +69,20 @@ def to_options(self): name: str = node_options.name parameters = node_options.parameters return cls(name, **parameters) + + def debug_info(self): + """ Get debug information about the node """ + return { + 'class': self.__class__.__name__, + 'model': self.model, + 'name': self.name, + 'nstates': self.nstates, + 'ninputs': self.ninputs, + 'nparams': self.nparams, + 'is_statefull': self.is_statefull, + 'initialized': self._initialized, + 'has_ode_func': self._node.ode_func is not NULL, + 'has_output_func': self._node.output_func is not NULL, + 'has_params': self._node.params is not NULL, + 'parameters': self.parameters + } diff --git a/farms_network/core/node_cy.pxd b/farms_network/core/node_cy.pxd index c391c7f..5fb82c7 100644 --- a/farms_network/core/node_cy.pxd +++ b/farms_network/core/node_cy.pxd @@ -3,6 +3,45 @@ from farms_network.core.edge_cy cimport edge_t +cdef struct node_inputs_t: + double* network_outputs + double* weights # Network connection weights + unsigned int* source_indices # Which nodes provide input + int ninputs # Number of inputs + unsigned int node_index # This node's index (for self-reference) + + +# Input transfer function +# Receives n-inputs and produces one output to be fed into ode/output_tf +cdef double base_input_tf( + double time, + const node_inputs_t inputs, + const node_t* node, + const edge_t** edges, +) + + +# ODE to compute the neural dynamics based on current state and inputs +cdef void base_ode( + double time, + const double* states, + double* derivatives, + double input_val, + double noise, + const node_t* node, +) + + +# Output transfer function based on current state +cdef double base_output_tf( + double time, + const double* states, + double input_val, + double noise, + const node_t* node, +) + + cdef struct node_t: # Generic parameters unsigned int nstates # Number of state variables in the node. @@ -18,29 +57,27 @@ cdef struct node_t: void* params # Pointer to the parameters of the node. # Functions + double input_tf( + double time, + const node_inputs_t inputs, + const node_t* node, + const edge_t** edges, + ) void ode( double time, - double* states, + const double* states, double* derivatives, - double external_input, - double* network_outputs, - unsigned int* inputs, - double* weights, + double input, double noise, - node_t* node, - edge_t** edges, - ) noexcept - - double output( + const node_t* node, + ) + double output_tf( double time, - double* states, - double external_input, - double* network_outputs, - unsigned int* inputs, - double* weights, - node_t* node, - edge_t** edges, - ) noexcept + const double* states, + double input, + double noise, + const node_t* node, + ) cdef class NodeCy: diff --git a/farms_network/core/node_cy.pyx b/farms_network/core/node_cy.pyx index cd4d8a4..34b1a38 100644 --- a/farms_network/core/node_cy.pyx +++ b/farms_network/core/node_cy.pyx @@ -10,6 +10,39 @@ from farms_network.core.options import NodeOptions from farms_network.models import Models +# Input transfer function +# Receives n-inputs and produces one output to be fed into ode/output_tf +cdef double base_input_tf( + double time, + const node_inputs_t inputs, + const node_t* node, + const edge_t** edges, +) noexcept: + raise NotImplementedError("input_tf must be implemented by node type") + +# ODE to compute the neural dynamics based on current state and inputs +cdef void base_ode( + double time, + const double* states, + double* derivatives, + double input, + double noise, + const node_t* node, +) noexcept: + raise NotImplementedError("ode must be implemented by node type") + + +# Output transfer function based on current state +cdef double base_output_tf( + double time, + const double* states, + double input, + double noise, + const node_t* node, +) noexcept: + raise NotImplementedError("output_tf must be implemented by node type") + + cdef class NodeCy: """ Interface to Node C-Structure """ @@ -20,8 +53,9 @@ cdef class NodeCy: if self._node is NULL: raise MemoryError("Failed to allocate memory for node_t") self._node.nstates = 0 - self._node.ode = NULL - self._node.output = NULL + self._node.input_tf = base_input_tf + self._node.ode = base_ode + self._node.output_tf = base_output_tf # Setup parameters self._node.params = NULL @@ -54,62 +88,70 @@ cdef class NodeCy: def is_statefull(self): return self._node.is_statefull - # Methods to wrap the ODE and output functions - def ode( - self, - double time, - double[:] states, - double[:] derivatives, - double external_input, - double[:] network_outputs, - unsigned int[:] inputs, - double[:] weights, - double noise, - ): + def ode(self, time, double[:] states, double[:] derivatives, input_val, noise): cdef double* states_ptr = &states[0] cdef double* derivatives_ptr = &derivatives[0] - cdef double* network_outputs_ptr = &network_outputs[0] - cdef unsigned int* inputs_ptr = &inputs[0] - cdef double* weights_ptr = &weights[0] - - cdef edge_t** c_edges = NULL - - # Call the C function directly - self._node.ode( - time, - states_ptr, - derivatives_ptr, - external_input, - network_outputs_ptr, - inputs_ptr, - weights_ptr, - noise, - self._node, - c_edges - ) - - def output( - self, - double time, - double[:] states, - double external_input, - double[:] network_outputs, - unsigned int[:] inputs, - double[:] weights, - ): - # Call the C function and return its result + self._node.ode(time, states_ptr, derivatives_ptr, input_val, noise, self._node) + + def output_tf(self, time, double[:] states, input_val, noise): + """ Call C node output """ cdef double* states_ptr = &states[0] - cdef double* network_outputs_ptr = &network_outputs[0] - cdef unsigned int* inputs_ptr = &inputs[0] - cdef double* weights_ptr = &weights[0] - cdef edge_t** c_edges = NULL - return self._node.output( - time, - states_ptr, - external_input, - network_outputs_ptr, - inputs_ptr, - weights_ptr, - self._node, - c_edges - ) + return self._node.output_tf(time, states_ptr, input_val, noise, self._node) + + # Methods to wrap the ODE and output functions + # def ode( + # self, + # double time, + # double[:] states, + # double[:] derivatives, + # unsigned int[:] inputs, + # double noise, + # ): + # cdef double* states_ptr = &states[0] + # cdef double* derivatives_ptr = &derivatives[0] + # cdef double* network_outputs_ptr = &network_outputs[0] + # cdef unsigned int* inputs_ptr = &inputs[0] + # cdef double* weights_ptr = &weights[0] + + # cdef edge_t** c_edges = NULL + + # # Call the C function directly + # if self._node.ode is not NULL: + # self._node.ode( + # time, + # states_ptr, + # derivatives_ptr, + # external_input, + # network_outputs_ptr, + # inputs_ptr, + # weights_ptr, + # noise, + # self._node, + # c_edges + # ) + + # def output( + # self, + # double time, + # double[:] states, + # double external_input, + # double[:] network_outputs, + # unsigned int[:] inputs, + # double[:] weights, + # ): + # # Call the C function and return its result + # cdef double* states_ptr = &states[0] + # cdef double* network_outputs_ptr = &network_outputs[0] + # cdef unsigned int* inputs_ptr = &inputs[0] + # cdef double* weights_ptr = &weights[0] + # cdef edge_t** c_edges = NULL + # return self._node.output( + # time, + # states_ptr, + # external_input, + # network_outputs_ptr, + # inputs_ptr, + # weights_ptr, + # self._node, + # c_edges + # ) From 284c4ea1ab166fd09237801ba0b2317723995c25 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Sun, 20 Jul 2025 17:54:39 -0500 Subject: [PATCH 240/316] [MODELS] Refactored ReLU to changes in node refactor --- farms_network/models/relu_cy.pxd | 38 +++++++++++++++-------- farms_network/models/relu_cy.pyx | 52 +++++++++++++++++++++----------- 2 files changed, 61 insertions(+), 29 deletions(-) diff --git a/farms_network/models/relu_cy.pxd b/farms_network/models/relu_cy.pxd index edd83cf..cd1b50f 100644 --- a/farms_network/models/relu_cy.pxd +++ b/farms_network/models/relu_cy.pxd @@ -1,7 +1,7 @@ """ Rectified Linear Unit """ -from ..core.node_cy cimport node_t, NodeCy +from ..core.node_cy cimport node_t, node_inputs_t, NodeCy from ..core.edge_cy cimport edge_t @@ -16,17 +16,31 @@ cdef packed struct relu_params_t: double offset -cdef: - double output( - double time, - double* states, - double external_input, - double* network_outputs, - unsigned int* inputs, - double* weights, - node_t* c_node, - edge_t** c_edges, - ) noexcept +cdef double relu_input_tf( + double time, + const node_inputs_t inputs, + const node_t* node, + const edge_t** edges, +) noexcept + + +cdef void relu_ode( + double time, + const double* states, + double* derivatives, + double input_val, + double noise, + const node_t* node, +) noexcept + + +cdef double relu_output_tf( + double time, + const double* states, + double input_val, + double noise, + const node_t* node, +) noexcept cdef class ReLUNodeCy(NodeCy): diff --git a/farms_network/models/relu_cy.pyx b/farms_network/models/relu_cy.pyx index ce04d5e..4e45ec3 100644 --- a/farms_network/models/relu_cy.pyx +++ b/farms_network/models/relu_cy.pyx @@ -11,31 +11,46 @@ cpdef enum STATE: nstates = NSTATES -cdef double output( +cdef double relu_input_tf( double time, - double* states, - double external_input, - double* network_outputs, - unsigned int* inputs, - double* weights, - node_t* c_node, - edge_t** c_edges, + const node_inputs_t inputs, + const node_t* node, + const edge_t** edges, ) noexcept: - """ Node output. """ - cdef relu_params_t params = ( c_node[0].params)[0] + cdef relu_params_t params = ( node[0].params)[0] cdef: double _sum = 0.0 unsigned int j double _input, _weight - cdef unsigned int ninputs = c_node.ninputs - _sum += external_input - for j in range(ninputs): - _input = network_outputs[inputs[j]] - _weight = weights[j] + + for j in range(inputs.ninputs): + _input = inputs.network_outputs[inputs.source_indices[j]] + _weight = inputs.weights[j] _sum += _weight*_input + return _sum + + +cdef void relu_ode( + double time, + const double* states, + double* derivatives, + double input_val, + double noise, + const node_t* node, +) noexcept: + raise NotImplementedError("ode must be implemented by node type") - cdef double res = max(0.0, params.gain*(params.sign*_sum + params.offset)) + +cdef double relu_output_tf( + double time, + const double* states, + double input_val, + double noise, + const node_t* node, +) noexcept: + cdef relu_params_t params = ( node[0].params)[0] + cdef double res = max(0.0, params.gain*(params.sign*input_val + params.offset)) return res @@ -48,7 +63,8 @@ cdef class ReLUNodeCy(NodeCy): self._node.nparams = 3 self._node.is_statefull = False - # self._node.output = output + self._node.input_tf = relu_input_tf + self._node.output_tf = relu_output_tf # parameters self.params = relu_params_t() self._node.params = &self.params @@ -58,6 +74,8 @@ cdef class ReLUNodeCy(NodeCy): def __init__(self, **kwargs): super().__init__() + printf("Node %p\n", self._node) + # Set node parameters self.params.gain = kwargs.pop("gain") self.params.sign = kwargs.pop("sign") From dac68d5e4892a7ba9f805f75e47bbcf41d1e0299 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Sun, 20 Jul 2025 20:12:15 -0500 Subject: [PATCH 241/316] [NODE] Added state access to input_tf signature --- farms_network/core/node_cy.pxd | 12 +++++++----- farms_network/core/node_cy.pyx | 1 + 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/farms_network/core/node_cy.pxd b/farms_network/core/node_cy.pxd index 5fb82c7..167916b 100644 --- a/farms_network/core/node_cy.pxd +++ b/farms_network/core/node_cy.pxd @@ -4,17 +4,18 @@ from farms_network.core.edge_cy cimport edge_t cdef struct node_inputs_t: - double* network_outputs - double* weights # Network connection weights - unsigned int* source_indices # Which nodes provide input - int ninputs # Number of inputs - unsigned int node_index # This node's index (for self-reference) + double* network_outputs # Network level outputs + double* weights # Network connection weights + unsigned int* source_indices # Which nodes provide input + int ninputs # Number of inputs + unsigned int node_index # This node's index (for self-reference) # Input transfer function # Receives n-inputs and produces one output to be fed into ode/output_tf cdef double base_input_tf( double time, + const double* states, const node_inputs_t inputs, const node_t* node, const edge_t** edges, @@ -59,6 +60,7 @@ cdef struct node_t: # Functions double input_tf( double time, + const double* states, const node_inputs_t inputs, const node_t* node, const edge_t** edges, diff --git a/farms_network/core/node_cy.pyx b/farms_network/core/node_cy.pyx index 34b1a38..b88f591 100644 --- a/farms_network/core/node_cy.pyx +++ b/farms_network/core/node_cy.pyx @@ -14,6 +14,7 @@ from farms_network.models import Models # Receives n-inputs and produces one output to be fed into ode/output_tf cdef double base_input_tf( double time, + const double* states, const node_inputs_t inputs, const node_t* node, const edge_t** edges, From 4fba7cc558f0afbbae548ae061e088d1ecba5b52 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Sun, 20 Jul 2025 20:16:30 -0500 Subject: [PATCH 242/316] [MODELS] Adapted relu to input_tf state access --- farms_network/models/relu_cy.pxd | 1 + farms_network/models/relu_cy.pyx | 1 + 2 files changed, 2 insertions(+) diff --git a/farms_network/models/relu_cy.pxd b/farms_network/models/relu_cy.pxd index cd1b50f..c57ef86 100644 --- a/farms_network/models/relu_cy.pxd +++ b/farms_network/models/relu_cy.pxd @@ -18,6 +18,7 @@ cdef packed struct relu_params_t: cdef double relu_input_tf( double time, + const double* states, const node_inputs_t inputs, const node_t* node, const edge_t** edges, diff --git a/farms_network/models/relu_cy.pyx b/farms_network/models/relu_cy.pyx index 4e45ec3..a25ebbb 100644 --- a/farms_network/models/relu_cy.pyx +++ b/farms_network/models/relu_cy.pyx @@ -13,6 +13,7 @@ cpdef enum STATE: cdef double relu_input_tf( double time, + const double* states, const node_inputs_t inputs, const node_t* node, const edge_t** edges, From 6fb6656e188367317ad999912e6ba637d370ea1a Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Sun, 20 Jul 2025 20:16:57 -0500 Subject: [PATCH 243/316] [EDGE] Added phase coupling to edge types --- farms_network/core/edge_cy.pxd | 1 + farms_network/core/edge_cy.pyx | 1 + 2 files changed, 2 insertions(+) diff --git a/farms_network/core/edge_cy.pxd b/farms_network/core/edge_cy.pxd index a29b6f5..4bdb2ad 100644 --- a/farms_network/core/edge_cy.pxd +++ b/farms_network/core/edge_cy.pxd @@ -8,6 +8,7 @@ cdef enum: EXCITATORY = 1 INHIBITORY = 2 CHOLINERGIC = 3 + PHASE_COUPLING = 4 cdef struct edge_t: diff --git a/farms_network/core/edge_cy.pyx b/farms_network/core/edge_cy.pyx index 7fd7994..6cebc3c 100644 --- a/farms_network/core/edge_cy.pyx +++ b/farms_network/core/edge_cy.pyx @@ -14,6 +14,7 @@ cpdef enum types: excitatory = EXCITATORY inhibitory = INHIBITORY cholinergic = CHOLINERGIC + phase_coupling = PHASE_COUPLING cdef class EdgeCy: From e9f6d11bb890d1665a8f3d51d1417c417e2ee7e5 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Wed, 23 Jul 2025 16:18:21 -0400 Subject: [PATCH 244/316] [NETWORK] Added minimal working ODE --- farms_network/core/network.py | 159 ++++++++---- farms_network/core/network_cy.pxd | 33 ++- farms_network/core/network_cy.pyx | 411 +++++++++++++++++------------- 3 files changed, 367 insertions(+), 236 deletions(-) diff --git a/farms_network/core/network.py b/farms_network/core/network.py index ff65ded..befc11a 100644 --- a/farms_network/core/network.py +++ b/farms_network/core/network.py @@ -4,70 +4,135 @@ from .data import NetworkData from .network_cy import NetworkCy -from .options import NetworkOptions +from .options import (EdgeOptions, IntegrationOptions, NetworkOptions, + NodeOptions) +from ..models.factory import NodeFactory +from .edge import Edge +from .node import Node +from typing import Optional -class Network(NetworkCy): - """ Network class """ +class Network: + """ Network class using composition with NetworkCy """ def __init__(self, network_options: NetworkOptions): - """ Initialize """ + """ Initialize network with composition approach """ + self.options = network_options - super().__init__(network_options) + # Core network data and Cython implementation self.data = NetworkData.from_options(network_options) - self.nodes: list = [] - self.edges = [] - self.nodes_output_data = [] - self.__tmp_node_outputs = np.zeros((self.c_network.nnodes,)) - self.setup_network(network_options, self.data) + self._network_cy = NetworkCy( + nnodes=len(network_options.nodes), + nedges=len(network_options.edges), + data=self.data + ) - # Integration options + + # Python-level collections + self.nodes: List[Node] = [] + self.edges: List[Edge] = [] + + # Setup the network + self._setup_network() + # self._setup_integrator() + + # Simulation parameters self.n_iterations: int = network_options.integration.n_iterations - self.timestep: int = network_options.integration.timestep + self.timestep: float = network_options.integration.timestep self.iteration: int = 0 self.buffer_size: int = network_options.logs.buffer_size - # Set the seed for random number generation - random_seed = network_options.random_seed - # np.random.seed(random_seed) + def _setup_network(self): + """ Setup network nodes and edges """ + # Create Python nodes + nstates = 0 + for index, node_options in enumerate(self.options.nodes): + python_node = self._generate_node(node_options) + python_node._node_cy.ninputs = len( + self.data.connectivity.sources[ + self.data.connectivity.indices[index]:self.data.connectivity.indices[index+1] + ] + ) if self.data.connectivity.indices else 0 + nstates += python_node.nstates + self.nodes.append(python_node) - @classmethod - def from_options(cls, options: NetworkOptions): - """ Initialize network from NetworkOptions """ - return cls(options) + # Create Python edges + for edge_options in self.options.edges: + python_edge = self._generate_edge(edge_options) + self.edges.append(python_edge) - def to_options(self): - """ Return NetworkOptions from network """ - return self.options + self._network_cy.nstates = nstates + + # Pass Python nodes/edges to Cython layer for C struct setup + self._network_cy.setup_network(self.options, self.data, self.nodes, self.edges) + + # Initialize states + self._initialize_states() - def setup_integrator(self, network_options: NetworkOptions): - """ Setup integrator for neural network """ - # Setup ODE numerical integrator - integration_options = network_options.integration - timestep = integration_options.timestep - self.ode_integrator = RK4Solver(self.c_network.nstates, timestep) - # Setup SDE numerical integrator for noise models if any - noise_options = [] - for node in network_options.nodes: - if node.noise is not None: - if node.noise.is_stochastic: - noise_options.append(node.noise) - - self.sde_system = OrnsteinUhlenbeck(noise_options) - self.sde_integrator = EulerMaruyamaSolver(len(noise_options), timestep) + def _setup_integrator(self): + """ Setup numerical integrators """ + self._network_cy.setup_integrator(self.options) + + def _initialize_states(self): + """ Initialize node states from options """ + for j, node_opts in enumerate(self.options.nodes): + if node_opts.state: + for state_index, index in enumerate( + range(self.data.states.indices[j], self.data.states.indices[j+1]) + ): + self.data.states.array[index] = node_opts.state.initial[state_index] @staticmethod - def generate_node(node_options: NodeOptions): + def _generate_node(node_options: NodeOptions) -> Node: """ Generate a node from options """ - Node = NodeFactory.create(node_options.model) - node = Node.from_options(node_options) - return node + NodeClass = NodeFactory.create(node_options.model) + return NodeClass.from_options(node_options) @staticmethod - def generate_edge(edge_options: EdgeOptions, nodes_options): - """ Generate a edge from options """ - target = nodes_options[nodes_options.index(edge_options.target)] - Edge = EdgeFactory.create(target.model) - edge = Edge.from_options(edge_options) - return edge + def _generate_edge(edge_options: EdgeOptions) -> Edge: + """ Generate an edge from options """ + return Edge.from_options(edge_options) + + # Delegate properties to Cython implementation + @property + def nnodes(self) -> int: + return self._network_cy.nnodes + + @property + def nedges(self) -> int: + return self._network_cy.nedges + + @property + def nstates(self) -> int: + return self._network_cy.nstates + + # Delegate methods to Cython implementation + def evaluate(self, time, states): + """ Evaluate the ODE """ + derivatives = np.zeros(np.size(states)) + self._network_cy.evaluate(time, states, derivatives) + return derivatives + + def step(self): + """ Step the network simulation """ + self._network_cy.step() + self.iteration += 1 + + def run(self, n_iterations: Optional[int] = None): + """ Run the network for n_iterations """ + if n_iterations is None: + n_iterations = self.n_iterations + + for _ in range(n_iterations): + self.step() + + # Factory methods + @classmethod + def from_options(cls, options: NetworkOptions): + """ Initialize network from NetworkOptions """ + return cls(options) + + def to_options(self) -> NetworkOptions: + """ Return NetworkOptions from network """ + return self.options diff --git a/farms_network/core/network_cy.pxd b/farms_network/core/network_cy.pxd index 81407c7..459a325 100644 --- a/farms_network/core/network_cy.pxd +++ b/farms_network/core/network_cy.pxd @@ -3,29 +3,44 @@ cimport numpy as cnp from ..numeric.integrators_cy cimport EulerMaruyamaSolver, RK4Solver from ..numeric.system cimport ODESystem, SDESystem from .data_cy cimport NetworkDataCy, NodeDataCy -from .edge cimport Edge, EdgeCy -from .node cimport Node, NodeCy +from .edge_cy cimport EdgeCy, edge_t +from .node_cy cimport NodeCy, node_t, node_inputs_t -cdef struct NetworkStruct: - +cdef struct network_t: # info unsigned long int nnodes unsigned long int nedges unsigned long int nstates # nodes list - NodeCy** c_nodes - + node_t** nodes # edges list - EdgeCy** c_edges + edge_t** edges + + # ODE + double* states + unsigned int* states_indices + + double* derivatives + unsigned int* derivatives_indices + + double* outputs + + double* external_inputs + + double* noise + + unsigned int* input_neurons + double* weights + unsigned int* input_neurons_indices cdef class NetworkCy(ODESystem): """ Python interface to Network ODE """ cdef: - NetworkStruct *c_network + network_t *_network public list nodes public list edges public NetworkDataCy data @@ -44,5 +59,5 @@ cdef class NetworkCy(ODESystem): list nodes_output_data # cpdef void step(self) - cdef void evaluate(self, double time, double[:] states, double[:] derivatives) noexcept + cpdef void evaluate(self, double time, double[:] states, double[:] derivatives) noexcept cpdef void step(self) diff --git a/farms_network/core/network_cy.pyx b/farms_network/core/network_cy.pyx index d027560..8517580 100644 --- a/farms_network/core/network_cy.pyx +++ b/farms_network/core/network_cy.pyx @@ -1,21 +1,4 @@ -""" ------------------------------------------------------------------------ -Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty -Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. ------------------------------------------------------------------------ -""" +""" Network """ include "types.pxd" @@ -25,13 +8,15 @@ from libc.stdio cimport printf from libc.stdlib cimport free, malloc from libc.string cimport strdup -from ..models.factory import EdgeFactory, NodeFactory +from ..models.factory import NodeFactory from ..noise.ornstein_uhlenbeck import OrnsteinUhlenbeck from .data import NetworkData, NetworkStates from .data_cy cimport (NetworkConnectivityCy, NetworkDataCy, NetworkNoiseCy, NetworkStatesCy) +from typing import List + from .options import (EdgeOptions, IntegrationOptions, NetworkOptions, NodeOptions) @@ -40,220 +25,286 @@ cdef inline void ode( double time, double[:] states_arr, NetworkDataCy data, - NetworkStruct* c_network, + network_t* c_network, double[:] node_outputs_tmp, ) noexcept: """ C Implementation to compute full network state """ cdef unsigned int j, nnodes - cdef NodeCy __node - cdef NodeCy** c_nodes = c_network.c_nodes - cdef EdgeCy** c_edges = c_network.c_edges + cdef node_t __node + cdef node_t** c_nodes = c_network.nodes + cdef edge_t** c_edges = c_network.edges nnodes = c_network.nnodes # It is important to use the states passed to the function and not from the data.states - cdef double* states = &states_arr[0] - cdef unsigned int* states_indices = &data.states.indices[0] - - cdef double* derivatives = &data.derivatives.array[0] - cdef unsigned int* derivatives_indices = &data.derivatives.indices[0] - - cdef double* external_input = &data.external_inputs.array[0] - cdef double* outputs = &data.outputs.array[0] - - cdef double* noise = &data.noise.outputs[0] + cdef double* node_outputs_tmp_ptr = &node_outputs_tmp[0] - cdef unsigned int* input_neurons = &data.connectivity.sources[0] - cdef double* weights = &data.connectivity.weights[0] - cdef unsigned int* input_neurons_indices = &data.connectivity.indices[0] + cdef node_inputs_t node_inputs + node_inputs.network_outputs = c_network.outputs + cdef total_input_val = 0.0 - cdef double* node_outputs_tmp_ptr = &node_outputs_tmp[0] for j in range(nnodes): + total_input_val = 0.0 __node = c_nodes[j][0] + # Prepare node context + node_inputs.source_indices = c_network.input_neurons + c_network.input_neurons_indices[j] + node_inputs.weights = c_network.weights + c_network.input_neurons_indices[j] + node_inputs.external_input = c_network.external_inputs[j] + + node_inputs.ninputs = __node.ninputs + node_inputs.node_index = j if __node.is_statefull: + # Compute the inputs from all nodes + total_input_val = __node.input_tf( + time, + c_network.states + c_network.states_indices[j], + node_inputs, + c_nodes[j], + c_edges + c_network.input_neurons_indices[j], + ) + # Compute the ode __node.ode( time, - states + states_indices[j], - derivatives + derivatives_indices[j], - external_input[j], - outputs, - input_neurons + input_neurons_indices[j], - weights + input_neurons_indices[j], - noise[j], + c_network.states + c_network.states_indices[j], + c_network.derivatives + c_network.derivatives_indices[j], + total_input_val, + 0.0, + c_nodes[j] + ) + # Compute all the node outputs based on the current state + node_outputs_tmp_ptr[j] = __node.output_tf( + time, + c_network.states + c_network.states_indices[j], + total_input_val, + 0.0, c_nodes[j], - c_edges + input_neurons_indices[j], ) - node_outputs_tmp_ptr[j] = __node.output( - time, - states + states_indices[j], - external_input[j], - outputs, - input_neurons + input_neurons_indices[j], - weights + input_neurons_indices[j], - c_nodes[j], - c_edges + input_neurons_indices[j], - ) - - -cdef inline void logger( - int iteration, - NetworkDataCy data, - NetworkStruct* c_network -) noexcept: - cdef unsigned int nnodes = c_network.nnodes - cdef unsigned int j - cdef double* states_ptr = &data.states.array[0] - cdef unsigned int[:] state_indices = data.states.indices - cdef double[:] outputs = data.outputs.array - cdef double* outputs_ptr = &data.outputs.array[0] - cdef double[:] external_inputs = data.external_inputs.array - cdef NodeDataCy node_data - cdef double[:] node_states - cdef int state_idx, start_idx, end_idx, state_iteration - cdef NodeDataCy[:] nodes_data = data.nodes - for j in range(nnodes): - # Log states - start_idx = state_indices[j] - end_idx = state_indices[j+1] - state_iteration = 0 - node_states = nodes_data[j].states.array[iteration] - for state_idx in range(start_idx, end_idx): - node_states[state_iteration] = states_ptr[state_idx] - state_iteration += 1 - nodes_data[j].output.array[iteration] = outputs_ptr[j] - nodes_data[j].external_input.array[iteration] = external_inputs[j] - - -cdef inline void _noise_states_to_output( - double[:] states, - unsigned int[:] indices, - double[:] outputs, -) noexcept: - """ Copy noise states data to noise outputs """ - cdef int n_indices = indices.shape[0] - cdef int index - for index in range(n_indices): - outputs[indices[index]] = states[index] + else: + total_input_val = __node.input_tf( + time, + NULL, + node_inputs, + c_nodes[j], + c_edges + c_network.input_neurons_indices[j], + ) + # Compute all the node outputs based on the current state + node_outputs_tmp_ptr[j] = __node.output_tf( + time, + NULL, + total_input_val, + 0.0, + c_nodes[j], + ) + + +# cdef inline void logger( +# int iteration, +# NetworkDataCy data, +# network_t* c_network +# ) noexcept: +# cdef unsigned int nnodes = c_network.nnodes +# cdef unsigned int j +# cdef double* states_ptr = &data.states.array[0] +# cdef unsigned int[:] state_indices = data.states.indices +# cdef double[:] outputs = data.outputs.array +# cdef double* outputs_ptr = &data.outputs.array[0] +# cdef double[:] external_inputs = data.external_inputs.array +# cdef NodeDataCy node_data +# cdef double[:] node_states +# cdef int state_idx, start_idx, end_idx, state_iteration +# cdef NodeDataCy[:] nodes_data = data.nodes +# for j in range(nnodes): +# # Log states +# start_idx = state_indices[j] +# end_idx = state_indices[j+1] +# state_iteration = 0 +# node_states = nodes_data[j].states.array[iteration] +# for state_idx in range(start_idx, end_idx): +# node_states[state_iteration] = states_ptr[state_idx] +# state_iteration += 1 +# nodes_data[j].output.array[iteration] = outputs_ptr[j] +# nodes_data[j].external_input.array[iteration] = external_inputs[j] + + +# cdef inline void _noise_states_to_output( +# double[:] states, +# unsigned int[:] indices, +# double[:] outputs, +# ) noexcept: +# """ Copy noise states data to noise outputs """ +# cdef int n_indices = indices.shape[0] +# cdef int index +# for index in range(n_indices): +# outputs[indices[index]] = states[index] cdef class NetworkCy(ODESystem): """ Python interface to Network ODE """ - def __cinit__(self, network_options: NetworkOptions): - """ C initialization for manual memory allocation """ - self.c_network = malloc(sizeof(NetworkStruct)) - if self.c_network is NULL: + def __cinit__(self, nnodes: int, nedges: int, data: NetworkDataCy): + # Memory allocation only + self._network = malloc(sizeof(network_t)) + if self._network is NULL: raise MemoryError("Failed to allocate memory for Network") - self.c_network.nnodes = len(network_options.nodes) - self.c_network.nedges = len(network_options.edges) - # Allocate memory for c-node structs - self.c_network.c_nodes = malloc(self.nnodes * sizeof(NodeCy *)) - if self.c_network.c_nodes is NULL: + + self._network.nnodes = nnodes + self._network.nedges = nedges + self.__tmp_node_outputs = np.zeros((nnodes,)) + + # Allocate C arrays + self._network.nodes = malloc(self.nnodes * sizeof(node_t*)) + if self._network.nodes is NULL: raise MemoryError("Failed to allocate memory for Network nodes") - # Allocation memory for c-edge structs - self.c_network.c_edges = malloc(self.nedges * sizeof(EdgeCy *)) - if self.c_network.c_edges is NULL: + self._network.edges = malloc(self.nedges * sizeof(edge_t*)) + if self._network.edges is NULL: raise MemoryError("Failed to allocate memory for Network edges") - def __init__(self, network_options: NetworkOptions): + # Initialize network context + self.data = data + if self.data.states.array.size > 0: + self._network.states = &self.data.states.array[0] + else: + self._network.states = NULL # No stateful + + if self.data.states.indices.size > 0: + self._network.states_indices = &self.data.states.indices[0] + else: + self._network.states_indices = NULL + + if self.data.derivatives.array.size > 0: + self._network.derivatives = &self.data.derivatives.array[0] + else: + self._network.derivatives = NULL + + if self.data.derivatives.indices.size > 0: + self._network.derivatives_indices = &self.data.derivatives.indices[0] + else: + self._network.derivatives_indices = NULL + + if self.data.external_inputs.array.size > 0: + self._network.external_inputs = &self.data.external_inputs.array[0] + else: + self._network.external_inputs = NULL + + if self.data.outputs.array.size > 0: + self._network.outputs = &self.data.outputs.array[0] + else: + self._network.outputs = NULL + + if self.data.noise.outputs.size > 0: + self._network.noise = &self.data.noise.outputs[0] + else: + self._network.noise = NULL + + if self.data.connectivity.sources.size > 0: + self._network.input_neurons = &self.data.connectivity.sources[0] + else: + self._network.input_neurons = NULL + + if self.data.connectivity.weights.size > 0: + self._network.weights = &self.data.connectivity.weights[0] + else: + self._network.weights = NULL + + if self.data.connectivity.indices.size > 0: + self._network.input_neurons_indices = &self.data.connectivity.indices[0] + print("indices size", self.data.connectivity.indices.size) + else: + self._network.input_neurons_indices = NULL + + # cdef double* node_outputs_tmp_ptr = &node_outputs_tmp[0] + + def __init__(self, nnodes, nedges, data: NetworkDataCy): """ Initialize """ - super().__init__() + def __dealloc__(self): """ Deallocate any manual memory as part of clean up """ - if self.c_network.c_nodes is not NULL: - free(self.c_network.c_nodes) - if self.c_network.c_edges is not NULL: - free(self.c_network.c_edges) - if self.c_network is not NULL: - free(self.c_network) - - def setup_network(self, options: NetworkOptions, data: NetworkData): + if self._network.nodes is not NULL: + free(self._network.nodes) + if self._network.edges is not NULL: + free(self._network.edges) + if self._network is not NULL: + free(self._network) + + def setup_network(self, options: NetworkOptions, data: NetworkData, nodes: List[NodeCy], edges: List[EdgeCy]): """ Setup network """ - connectivity = data.connectivity - cdef unsigned int __nstates = 0 - cdef unsigned int index - cdef NodeCy* c_node - cdef Node node - for index, node_options in enumerate(options.nodes): - self.nodes.append(self.generate_node(node_options)) - node = self.nodes[index] - c_node = (node.c_node) - c_node.ninputs = len( - connectivity.sources[ - connectivity.indices[index]:connectivity.indices[index+1] - ] - ) if connectivity.indices else 0 - self.c_network.c_nodes[index] = c_node - __nstates += node_options._nstates - self.c_network.nstates = __nstates - - cdef EdgeCy* c_edge - cdef Edge edge - for index, edge_options in enumerate(options.edges): - self.edges.append(self.generate_edge(edge_options, options.nodes)) - edge = self.edges[index] - c_edge = (edge.c_edge) - self.c_network.c_edges[index] = c_edge - - # Initial states data - # Initialize states - for j, node_opts in enumerate(options.nodes): - if node_opts.state: - for state_index, index in enumerate( - range(data.states.indices[j], data.states.indices[j+1]) - ): - data.states.array[index] = node_opts.state.initial[state_index] + for index, node in enumerate(nodes): + self._network.nodes[index] = ((node._node_cy)._node) + + for index, edge in enumerate(edges): + self._network.edges[index] = ((edge._edge_cy)._edge) @property def nnodes(self): """ Number of nodes in the network """ - return self.c_network.nnodes + return self._network.nnodes @property def nedges(self): """ Number of edges in the network """ - return self.c_network.nedges + return self._network.nedges @property def nstates(self): """ Number of states in the network """ - return self.c_network.nstates + return self._network.nstates + + @nstates.setter + def nstates(self, value: int): + """ Number of network states """ + self._network.nstates = value + + cpdef void evaluate(self, double time, double[:] states, double[:] derivatives) noexcept: + """ Evaluate the ODE """ + # Update noise model + cdef NetworkDataCy data = self.data + + ode(time, states, data, self._network, self.__tmp_node_outputs) + # data.outputs.array[:] = self.__tmp_node_outputs + # derivatives[:] = data.derivatives.array cpdef void step(self): """ Step the network state """ - # cdef NetworkDataCy data = self.data + cdef NetworkDataCy data = self.data cdef SDESystem sde_system = self.sde_system cdef EulerMaruyamaSolver sde_integrator = self.sde_integrator - sde_integrator.step( - sde_system, - (self.iteration%self.buffer_size)*self.timestep, - self.data.noise.states - ) - _noise_states_to_output( - self.data.noise.states, - self.data.noise.indices, - self.data.noise.outputs - ) - self.ode_integrator.step( - self, - (self.iteration%self.buffer_size)*self.timestep, - self.data.states.array - ) + # sde_integrator.step( + # sde_system, + # (self.iteration%self.buffer_size)*self.timestep, + # self.data.noise.states + # ) + # _noise_states_to_output( + # self.data.noise.states, + # self.data.noise.indices, + # self.data.noise.outputs + # ) + # self.ode_integrator.step( + # self, + # (self.iteration%self.buffer_size)*self.timestep, + # self.data.states.array + # ) # Logging # TODO: Use network options to check global logging flag - logger((self.iteration%self.buffer_size), self.data, self.c_network) + # logger((self.iteration%self.buffer_size), self.data, self._network) self.iteration += 1 - cdef void evaluate(self, double time, double[:] states, double[:] derivatives) noexcept: - """ Evaluate the ODE """ - # Update noise model - cdef NetworkDataCy data = self.data - - ode(time, states, data, self.c_network, self.__tmp_node_outputs) - data.outputs.array[:] = self.__tmp_node_outputs - derivatives[:] = data.derivatives.array + def setup_integrator(self, network_options: NetworkOptions): + """ Setup integrator for neural network """ + # Setup ODE numerical integrator + integration_options = network_options.integration + timestep = integration_options.timestep + self.ode_integrator = RK4Solver(self._network.nstates, timestep) + # Setup SDE numerical integrator for noise models if any + noise_options = [] + for node in network_options.nodes: + if node.noise is not None: + if node.noise.is_stochastic: + noise_options.append(node.noise) + + self.sde_system = OrnsteinUhlenbeck(noise_options) + self.sde_integrator = EulerMaruyamaSolver(len(noise_options), timestep) From 912f652b343639a841e1e2224aaf1a01b1c495cd Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Thu, 24 Jul 2025 15:54:09 -0400 Subject: [PATCH 245/316] [MODELS] Added refactored version of oscillator --- farms_network/models/oscillator.pxd | 84 -------------- farms_network/models/oscillator.py | 26 +++++ farms_network/models/oscillator.pyx | 154 ------------------------- farms_network/models/oscillator_cy.pxd | 67 +++++++++++ farms_network/models/oscillator_cy.pyx | 148 ++++++++++++++++++++++++ 5 files changed, 241 insertions(+), 238 deletions(-) delete mode 100644 farms_network/models/oscillator.pxd create mode 100644 farms_network/models/oscillator.py delete mode 100644 farms_network/models/oscillator.pyx create mode 100644 farms_network/models/oscillator_cy.pxd create mode 100644 farms_network/models/oscillator_cy.pyx diff --git a/farms_network/models/oscillator.pxd b/farms_network/models/oscillator.pxd deleted file mode 100644 index d60a477..0000000 --- a/farms_network/models/oscillator.pxd +++ /dev/null @@ -1,84 +0,0 @@ -""" ------------------------------------------------------------------------ -Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty -Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. ------------------------------------------------------------------------ - -Oscillator model -""" - - -from ..core.node cimport NodeCy, Node -from ..core.edge cimport EdgeCy, Edge - - -cdef enum: - - #STATES - NSTATES = 3 - STATE_PHASE = 0 - STATE_AMPLITUDE= 1 - STATE_AMPLITUDE_0 = 2 - - -cdef packed struct OscillatorNodeParameters: - - double intrinsic_frequency # Hz - double nominal_amplitude # - double amplitude_rate # - - -cdef packed struct OscillatorEdgeParameters: - - double phase_difference # radians - - -cdef: - void ode( - double time, - double* states, - double* derivatives, - double external_input, - double* network_outputs, - unsigned int* inputs, - double* weights, - double noise, - NodeCy* c_node, - EdgeCy** c_edges, - ) noexcept - double output( - double time, - double* states, - double external_input, - double* network_outputs, - unsigned int* inputs, - double* weights, - NodeCy* c_node, - EdgeCy** c_edges, - ) noexcept - - -cdef class OscillatorNode(Node): - """ Python interface to Oscillator Node C-Structure """ - - cdef: - OscillatorNodeParameters parameters - - -cdef class OscillatorEdge(Edge): - """ Python interface to Oscillator Edge C-Structure """ - - cdef: - OscillatorEdgeParameters parameters diff --git a/farms_network/models/oscillator.py b/farms_network/models/oscillator.py new file mode 100644 index 0000000..45a351f --- /dev/null +++ b/farms_network/models/oscillator.py @@ -0,0 +1,26 @@ +from farms_network.core.node import Node +from farms_network.core.edge import Edge +from farms_network.models import Models +from farms_network.core.options import OscillatorNodeOptions +from farms_network.models.oscillator_cy import OscillatorNodeCy +from farms_network.models.oscillator_cy import OscillatorEdgeCy + + +class OscillatorNode(Node): + + CY_NODE_CLASS = OscillatorNodeCy + + def __init__(self, name: str, **kwargs): + super().__init__(name=name, model=Models.OSCILLATOR, **kwargs) + + # Oscillator-specific properties + + +class OscillatorEdge(Edge): + + CY_EDGE_CLASS = OscillatorEdgeCy + + def __init__(self, source, target, edge_type, model=Models.OSCILLATOR, **kwargs): + super().__init__( + source=source, target=target, edge_type=edge_type, model=Models.OSCILLATOR, **kwargs + ) diff --git a/farms_network/models/oscillator.pyx b/farms_network/models/oscillator.pyx deleted file mode 100644 index 27f4f37..0000000 --- a/farms_network/models/oscillator.pyx +++ /dev/null @@ -1,154 +0,0 @@ -""" ------------------------------------------------------------------------ -Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty -Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. ------------------------------------------------------------------------ - -Oscillator model -""" - - -from libc.math cimport M_PI -from libc.math cimport sin as csin -from libc.stdio cimport printf -from libc.stdlib cimport free, malloc -from libc.string cimport strdup - - -cpdef enum STATE: - - #STATES - nstates = NSTATES - phase = STATE_PHASE - amplitude = STATE_AMPLITUDE - amplitude_0 = STATE_AMPLITUDE_0 - - -cdef void ode( - double time, - double* states, - double* derivatives, - double external_input, - double* network_outputs, - unsigned int* inputs, - double* weights, - double noise, - NodeCy* c_node, - EdgeCy** c_edges, -) noexcept: - """ ODE """ - # Parameters - cdef OscillatorNodeParameters params = ( c_node[0].parameters)[0] - cdef OscillatorEdgeParameters edge_params - - # States - cdef double state_phase = states[STATE.phase] - cdef double state_amplitude = states[STATE.amplitude] - cdef double state_amplitude_0 = states[STATE.amplitude_0] - - cdef: - double _sum = 0.0 - unsigned int j - double _input, _weight - - cdef unsigned int ninputs = c_node.ninputs - for j in range(ninputs): - _input = network_outputs[inputs[j]] - _weight = weights[j] - edge_params = ( c_edges[j].parameters)[0] - _sum += _weight*state_amplitude*csin( - _input - state_phase - edge_params.phase_difference - ) - - # phidot : phase_dot - derivatives[STATE.phase] = 2*M_PI*params.intrinsic_frequency + _sum - # ampdot - derivatives[STATE.amplitude] = state_amplitude_0 - derivatives[STATE.amplitude_0] = params.amplitude_rate*( - (params.amplitude_rate/4.0)*(params.nominal_amplitude - state_amplitude) - state_amplitude_0 - ) - - -cdef double output( - double time, - double* states, - double external_input, - double* network_outputs, - unsigned int* inputs, - double* weights, - NodeCy* c_node, - EdgeCy** c_edges, -) noexcept: - """ Node output. """ - return states[STATE.phase] - - -cdef class OscillatorNode(Node): - """ Python interface to Oscillator Node C-Structure """ - - def __cinit__(self): - self.c_node.model_type = strdup("OSCILLATOR".encode('UTF-8')) - # override default ode and out methods - self.c_node.is_statefull = True - self.c_node.ode = ode - self.c_node.output = output - # parameters - self.c_node.parameters = malloc(sizeof(OscillatorNodeParameters)) - if self.c_node.parameters is NULL: - raise MemoryError("Failed to allocate memory for node parameters") - - def __init__(self, name: str, **kwargs): - super().__init__(name) - - # Set node parameters - cdef OscillatorNodeParameters* param = (self.c_node.parameters) - param.intrinsic_frequency = kwargs.pop("intrinsic_frequency") - param.nominal_amplitude = kwargs.pop("nominal_amplitude") - param.amplitude_rate = kwargs.pop("amplitude_rate") - if kwargs: - raise Exception(f'Unknown kwargs: {kwargs}') - - @property - def parameters(self): - """ Parameters in the network """ - cdef OscillatorNodeParameters params = ( self.c_node.parameters)[0] - return params - - -cdef class OscillatorEdge(Edge): - """ Python interface to Oscillator Edge C-Structure """ - - def __cinit__(self): - - # parameters - self.c_edge.parameters = malloc(sizeof(OscillatorEdgeParameters)) - if self.c_edge.parameters is NULL: - raise MemoryError("Failed to allocate memory for edge parameters") - - def __init__(self, source: str, target: str, edge_type: str, **kwargs): - super().__init__(source, target, edge_type) - - # Set edge parameters - cdef OscillatorEdgeParameters* param = (self.c_edge.parameters) - param.phase_difference = kwargs.pop("phase_difference") - - if kwargs: - raise Exception(f'Unknown kwargs: {kwargs}') - - @property - def parameters(self): - """ Parameters in the network """ - cdef OscillatorEdgeParameters params = ( self.c_edge.parameters)[0] - return params diff --git a/farms_network/models/oscillator_cy.pxd b/farms_network/models/oscillator_cy.pxd new file mode 100644 index 0000000..cea4490 --- /dev/null +++ b/farms_network/models/oscillator_cy.pxd @@ -0,0 +1,67 @@ +""" Oscillator model """ + + +from ..core.node_cy cimport node_t, node_inputs_t, processed_inputs_t, NodeCy +from ..core.edge_cy cimport edge_t, EdgeCy + + +cdef enum: + #STATES + NSTATES = 3 + STATE_PHASE = 0 + STATE_AMPLITUDE= 1 + STATE_AMPLITUDE_0 = 2 + + +cdef packed struct oscillator_params_t: + + double intrinsic_frequency # Hz + double nominal_amplitude # + double amplitude_rate # + + +cdef packed struct oscillator_edge_params_t: + + double phase_difference # radians + + +cdef processed_inputs_t oscillator_input_tf( + double time, + const double* states, + const node_inputs_t inputs, + const node_t* node, + const edge_t** edges, +) noexcept + + +cdef void oscillator_ode( + double time, + const double* states, + double* derivatives, + processed_inputs_t input_vals, + double noise, + const node_t* node, +) noexcept + + +cdef double oscillator_output_tf( + double time, + const double* states, + processed_inputs_t input_vals, + double noise, + const node_t* node, +) noexcept + + +cdef class OscillatorNodeCy(NodeCy): + """ Python interface to Oscillator Node C-Structure """ + + cdef: + oscillator_params_t params + + +cdef class OscillatorEdgeCy(EdgeCy): + """ Python interface to Oscillator Edge C-Structure """ + + cdef: + oscillator_edge_params_t params diff --git a/farms_network/models/oscillator_cy.pyx b/farms_network/models/oscillator_cy.pyx new file mode 100644 index 0000000..bd8c437 --- /dev/null +++ b/farms_network/models/oscillator_cy.pyx @@ -0,0 +1,148 @@ +""" Oscillator model """ + + +from libc.math cimport M_PI +from libc.math cimport sin as csin +from libc.stdio cimport printf +from libc.stdlib cimport free, malloc +from libc.string cimport strdup + + +cpdef enum STATE: + + #STATES + nstates = NSTATES + phase = STATE_PHASE + amplitude = STATE_AMPLITUDE + amplitude_0 = STATE_AMPLITUDE_0 + + +cdef processed_inputs_t oscillator_input_tf( + double time, + const double* states, + const node_inputs_t inputs, + const node_t* node, + const edge_t** edges, +) noexcept: + + # Parameters + cdef oscillator_params_t params = ( node[0].params)[0] + cdef oscillator_edge_params_t edge_params + + # States + cdef double state_phase = states[STATE.phase] + cdef double state_amplitude = states[STATE.amplitude] + + cdef processed_inputs_t processed_inputs = { + 'generic': 0.0, + 'excitatory': 0.0, + 'inhibitory': 0.0, + 'cholinergic': 0.0, + 'phase_coupling': 0.0 + } + + cdef: + double _sum = 0.0 + unsigned int j + double _input, _weight + + for j in range(inputs.ninputs): + _input = inputs.network_outputs[inputs.source_indices[j]] + _weight = inputs.weights[j] + edge_params = ( edges[j].params)[0] + processed_inputs.generic += _weight*state_amplitude*csin(_input - state_phase - edge_params.phase_difference) + + return processed_inputs + + +cdef void oscillator_ode( + double time, + const double* states, + double* derivatives, + processed_inputs_t input_vals, + double noise, + const node_t* node, +) noexcept: + # Parameters + cdef oscillator_params_t params = ( node[0].params)[0] + cdef oscillator_edge_params_t edge_params + + # States + cdef double state_phase = states[STATE.phase] + cdef double state_amplitude = states[STATE.amplitude] + cdef double state_amplitude_0 = states[STATE.amplitude_0] + + cdef double input_val = input_vals.generic + + # phidot : phase_dot + derivatives[STATE.phase] = 2*M_PI*params.intrinsic_frequency + input_val + # ampdot + derivatives[STATE.amplitude] = state_amplitude_0 + derivatives[STATE.amplitude_0] = params.amplitude_rate*( + (params.amplitude_rate/4.0)*(params.nominal_amplitude - state_amplitude) - state_amplitude_0 + ) + + +cdef double oscillator_output_tf( + double time, + const double* states, + processed_inputs_t input_val, + double noise, + const node_t* node, +) noexcept: + return states[STATE.phase] + + +cdef class OscillatorNodeCy(NodeCy): + """ Python interface to Oscillator Node C-Structure """ + + def __cinit__(self): + # override default ode and out methods + self._node.nstates = 3 + self._node.nparams = 3 + + self._node.is_statefull = True + self._node.input_tf = oscillator_input_tf + self._node.ode = oscillator_ode + self._node.output_tf = oscillator_output_tf + # parameters + self.params = oscillator_params_t() + self._node.params = &self.params + if self._node.params is NULL: + raise MemoryError("Failed to allocate memory for node parameters") + + def __init__(self, **kwargs): + super().__init__() + + # Set node parameters + self.params.intrinsic_frequency = kwargs.pop("intrinsic_frequency") + self.params.nominal_amplitude = kwargs.pop("nominal_amplitude") + self.params.amplitude_rate = kwargs.pop("amplitude_rate") + if kwargs: + raise Exception(f'Unknown kwargs: {kwargs}') + + +cdef class OscillatorEdgeCy(EdgeCy): + """ Python interface to Oscillator Edge C-Structure """ + + def __cinit__(self, edge_type: str, **kwargs): + # parameters + self.params = oscillator_edge_params_t() + self._edge.params = &self.params + self._edge.nparams = 1 + if self._edge.params is NULL: + raise MemoryError("Failed to allocate memory for edge parameters") + + def __init__(self, edge_type: str, **kwargs): + super().__init__(edge_type) + + # Set edge parameters + self.params.phase_difference = kwargs.pop("phase_difference") + if kwargs: + raise Exception(f'Unknown kwargs: {kwargs}') + + @property + def parameters(self): + """ Parameters in the network """ + cdef oscillator_edge_params_t params = ( self._edge.params)[0] + return params From c36a1c937d039571bdef485841afe972f8581dd4 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Thu, 24 Jul 2025 15:54:50 -0400 Subject: [PATCH 246/316] [MODELS] Added refactored version of li_danner --- farms_network/models/li_danner.pxd | 75 ------------ farms_network/models/li_danner.py | 12 ++ farms_network/models/li_danner.pyx | 145 ------------------------ farms_network/models/li_danner_cy.pxd | 61 ++++++++++ farms_network/models/li_danner_cy.pyx | 157 ++++++++++++++++++++++++++ 5 files changed, 230 insertions(+), 220 deletions(-) delete mode 100644 farms_network/models/li_danner.pxd create mode 100644 farms_network/models/li_danner.py delete mode 100644 farms_network/models/li_danner.pyx create mode 100644 farms_network/models/li_danner_cy.pxd create mode 100644 farms_network/models/li_danner_cy.pyx diff --git a/farms_network/models/li_danner.pxd b/farms_network/models/li_danner.pxd deleted file mode 100644 index 577678f..0000000 --- a/farms_network/models/li_danner.pxd +++ /dev/null @@ -1,75 +0,0 @@ -""" ------------------------------------------------------------------------ -Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty -Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. ------------------------------------------------------------------------ - -Leaky Integrator Node Based on Danner et.al. -""" - -from ..core.node cimport NodeCy, Node -from ..core.edge cimport EdgeCy - - -cdef enum: - - #STATES - NSTATES = 1 - STATE_V = 0 - - -cdef packed struct LIDannerNodeParameters: - - double c_m # pF - double g_leak # nS - double e_leak # mV - double v_max # mV - double v_thr # mV - double g_syn_e # nS - double g_syn_i # nS - double e_syn_e # mV - double e_syn_i # mV - - -cdef: - void ode( - double time, - double* states, - double* derivatives, - double external_input, - double* network_outputs, - unsigned int* inputs, - double* weights, - double noise, - NodeCy* c_node, - EdgeCy** c_edges, - ) noexcept - double output( - double time, - double* states, - double external_input, - double* network_outputs, - unsigned int* inputs, - double* weights, - NodeCy* c_node, - EdgeCy** c_edges, - ) noexcept - - -cdef class LIDannerNode(Node): - """ Python interface to Leaky Integrator Node C-Structure """ - - cdef: - LIDannerNodeParameters parameters diff --git a/farms_network/models/li_danner.py b/farms_network/models/li_danner.py new file mode 100644 index 0000000..0235cfc --- /dev/null +++ b/farms_network/models/li_danner.py @@ -0,0 +1,12 @@ +from farms_network.core.node import Node +from farms_network.models import Models +from farms_network.core.options import LIDannerNodeOptions +from farms_network.models.li_danner_cy import LIDannerNodeCy + + +class LIDannerNode(Node): + + CY_NODE_CLASS = LIDannerNodeCy + + def __init__(self, name: str, **kwargs): + super().__init__(name=name, model=Models.LI_DANNER, **kwargs) diff --git a/farms_network/models/li_danner.pyx b/farms_network/models/li_danner.pyx deleted file mode 100644 index e91d886..0000000 --- a/farms_network/models/li_danner.pyx +++ /dev/null @@ -1,145 +0,0 @@ -""" ------------------------------------------------------------------------ -Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty -Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. ------------------------------------------------------------------------ - -Leaky Integrator Node based on Danner et.al. -""" - -from libc.math cimport fabs as cfabs -from libc.stdio cimport printf -from libc.stdlib cimport free, malloc -from libc.string cimport strdup - -from ..core.options import LIDannerParameterOptions - - -cpdef enum STATE: - - #STATES - nstates = NSTATES - v = STATE_V - - -cdef void ode( - double time, - double* states, - double* derivatives, - double external_input, - double* network_outputs, - unsigned int* inputs, - double* weights, - double noise, - NodeCy* c_node, - EdgeCy** c_edges, -) noexcept: - """ ODE """ - # Parameters - cdef LIDannerNodeParameters params = ( - c_node[0].parameters - )[0] - - # States - cdef double state_v = states[STATE.v] - - # Ileak - cdef double i_leak = params.g_leak * (state_v - params.e_leak) - - # Node inputs - cdef: - double _sum = 0.0 - unsigned int j - double _node_out, res, _input, _weight - - cdef unsigned int ninputs = c_node.ninputs - for j in range(ninputs): - _input = network_outputs[inputs[j]] - _weight = weights[j] - if _weight >= 0.0: - # Excitatory Synapse - _sum += params.g_syn_e*cfabs(_weight)*_input*(state_v - params.e_syn_e) - elif _weight < 0.0: - # Inhibitory Synapse - _sum += params.g_syn_i*cfabs(_weight)*_input*(state_v - params.e_syn_i) - - # noise current - cdef double i_noise = noise - - # dV - derivatives[STATE.v] = -(i_leak + i_noise + _sum)/params.c_m - - -cdef double output( - double time, - double* states, - double external_input, - double* network_outputs, - unsigned int* inputs, - double* weights, - NodeCy* c_node, - EdgeCy** c_edges, -) noexcept: - """ Node output. """ - - cdef LIDannerNodeParameters params = ( c_node.parameters)[0] - - cdef double _n_out = 0.0 - cdef double state_v = states[STATE.v] - if state_v >= params.v_max: - _n_out = 1.0 - elif (params.v_thr <= state_v) and (state_v < params.v_max): - _n_out = (state_v - params.v_thr) / (params.v_max - params.v_thr) - elif state_v < params.v_thr: - _n_out = 0.0 - return _n_out - - -cdef class LIDannerNode(Node): - """ Python interface to Leaky Integrator Node C-Structure """ - - def __cinit__(self): - self.c_node.model_type = strdup("LI_DANNER".encode('UTF-8')) - # override default ode and out methods - self.c_node.is_statefull = True - self.c_node.ode = ode - self.c_node.output = output - # parameters - self.c_node.parameters = malloc(sizeof(LIDannerNodeParameters)) - if self.c_node.parameters is NULL: - raise MemoryError("Failed to allocate memory for node parameters") - - def __init__(self, name: str, **kwargs): - super().__init__(name) - - # Set node parameters - cdef LIDannerNodeParameters* param = (self.c_node.parameters) - param.c_m = kwargs.pop("c_m") - param.g_leak = kwargs.pop("g_leak") - param.e_leak = kwargs.pop("e_leak") - param.v_max = kwargs.pop("v_max") - param.v_thr = kwargs.pop("v_thr") - param.g_syn_e = kwargs.pop("g_syn_e") - param.g_syn_i = kwargs.pop("g_syn_i") - param.e_syn_e = kwargs.pop("e_syn_e") - param.e_syn_i = kwargs.pop("e_syn_i") - if kwargs: - raise Exception(f'Unknown kwargs: {kwargs}') - - @property - def parameters(self): - """ Parameters in the network """ - cdef LIDannerNodeParameters params = ( self.c_node.parameters)[0] - return params diff --git a/farms_network/models/li_danner_cy.pxd b/farms_network/models/li_danner_cy.pxd new file mode 100644 index 0000000..9851d73 --- /dev/null +++ b/farms_network/models/li_danner_cy.pxd @@ -0,0 +1,61 @@ +""" Leaky Integrator Node Based on Danner et.al. 2016 """ + + +from ..core.node_cy cimport node_t, node_inputs_t, processed_inputs_t, NodeCy +from ..core.edge_cy cimport edge_t, EXCITATORY, INHIBITORY, CHOLINERGIC + + +cdef enum: + #STATES + NSTATES = 2 + STATE_V = 0 + STATE_A = 1 + + +cdef packed struct li_danner_params_t: + + double c_m # pF + double g_leak # nS + double e_leak # mV + double v_max # mV + double v_thr # mV + double g_syn_e # nS + double g_syn_i # nS + double e_syn_e # mV + double e_syn_i # mV + double tau_ch # ms + + +cdef processed_inputs_t li_danner_input_tf( + double time, + const double* states, + const node_inputs_t inputs, + const node_t* node, + const edge_t** edges, +) noexcept + + +cdef void li_danner_ode( + double time, + const double* states, + double* derivatives, + processed_inputs_t input_vals, + double noise, + const node_t* node, +) noexcept + + +cdef double li_danner_output_tf( + double time, + const double* states, + processed_inputs_t input_vals, + double noise, + const node_t* node, +) noexcept + + +cdef class LIDannerNodeCy(NodeCy): + """ Python interface to LI Danner Node C-Structure """ + + cdef: + li_danner_params_t params diff --git a/farms_network/models/li_danner_cy.pyx b/farms_network/models/li_danner_cy.pyx new file mode 100644 index 0000000..e843764 --- /dev/null +++ b/farms_network/models/li_danner_cy.pyx @@ -0,0 +1,157 @@ +""" Leaky Integrator Node based on Danner et.al. """ + +from libc.math cimport fabs as cfabs +from libc.stdio cimport printf +from libc.string cimport strdup + +from farms_network.models import Models + + +cpdef enum STATE: + + #STATES + nstates = NSTATES + v = STATE_V + a = STATE_A + + +cdef processed_inputs_t li_danner_input_tf( + double time, + const double* states, + const node_inputs_t inputs, + const node_t* node, + const edge_t** edges, +) noexcept: + # Parameters + cdef li_danner_params_t params = ( node[0].params)[0] + + # States + cdef double state_v = states[STATE.v] + cdef double state_a = states[STATE.a] + + cdef processed_inputs_t processed_inputs = { + 'generic': 0.0, + 'excitatory': 0.0, + 'inhibitory': 0.0, + 'cholinergic': 0.0, + 'phase_coupling': 0.0 + } + + # Node inputs + cdef: + double _sum = 0.0 + double _cholinergic_sum = 0.0 + unsigned int j + double _node_out, res, _input, _weight + + cdef unsigned int ninputs = inputs.ninputs + for j in range(ninputs): + _input = inputs.network_outputs[inputs.source_indices[j]] + _weight = inputs.weights[j] + if edges[j].type == EXCITATORY: + # Excitatory Synapse + processed_inputs.excitatory += params.g_syn_e*cfabs(_weight)*_input*(state_v - params.e_syn_e) + elif edges[j].type == INHIBITORY: + # Inhibitory Synapse + processed_inputs.inhibitory += params.g_syn_i*cfabs(_weight)*_input*(state_v - params.e_syn_i) + elif edges[j].type == CHOLINERGIC: + processed_inputs.cholinergic += cfabs(_weight)*_input + return processed_inputs + + +cdef void li_danner_ode( + double time, + const double* states, + double* derivatives, + processed_inputs_t input_vals, + double noise, + const node_t* node, +) noexcept: + cdef li_danner_params_t params = ( node[0].params)[0] + + # States + cdef double state_v = states[STATE.v] + cdef double state_a = states[STATE.a] + + # Ileak + cdef double i_leak = params.g_leak * (state_v - params.e_leak) + + # noise current + cdef double i_noise = noise + + # da + derivatives[STATE.a] = (-state_a + input_vals.cholinergic)/params.tau_ch + + # dV + derivatives[STATE.v] = -( + i_leak + i_noise + input_vals.excitatory + input_vals.inhibitory + )/params.c_m + + +cdef double li_danner_output_tf( + double time, + const double* states, + processed_inputs_t input_vals, + double noise, + const node_t* node, +) noexcept: + cdef li_danner_params_t params = ( node.params)[0] + + cdef double _n_out = 0.0 + cdef double cholinergic_gain = 1.0 + cdef double state_v = states[STATE.v] + cdef double state_a = states[STATE.a] + + if state_v >= params.v_max: + _n_out = 1.0 + elif (params.v_thr <= state_v) and (state_v < params.v_max): + _n_out = (state_v - params.v_thr) / (params.v_max - params.v_thr) + elif state_v < params.v_thr: + _n_out = 0.0 + if state_a > 0.0: + cholinergic_gain = (1.0 + state_a) + _n_out = min(cholinergic_gain*_n_out, 1.0) + return _n_out + + +cdef class LIDannerNodeCy(NodeCy): + """ Python interface to Leaky Integrator Node C-Structure """ + + def __cinit__(self): + self._node.nstates = 2 + self._node.nparams = 10 + + self._node.is_statefull = True + + self._node.input_tf = li_danner_input_tf + self._node.ode = li_danner_ode + self._node.output_tf = li_danner_output_tf + # parameters + self.params = li_danner_params_t() + self._node.params = &self.params + if self._node.params is NULL: + raise MemoryError("Failed to allocate memory for node parameters") + + def __init__(self, **kwargs): + super().__init__() + + # Set node parameters + self.params.c_m = kwargs.pop("c_m") + self.params.g_leak = kwargs.pop("g_leak") + self.params.e_leak = kwargs.pop("e_leak") + self.params.v_max = kwargs.pop("v_max") + self.params.v_thr = kwargs.pop("v_thr") + self.params.g_syn_e = kwargs.pop("g_syn_e") + self.params.g_syn_i = kwargs.pop("g_syn_i") + self.params.e_syn_e = kwargs.pop("e_syn_e") + self.params.e_syn_i = kwargs.pop("e_syn_i") + self.params.tau_ch = kwargs.pop("tau_ch") + + if kwargs: + raise Exception(f'Unknown kwargs: {kwargs}') + + @property + def parameters(self): + """ Parameters in the network """ + cdef li_danner_params_t params = ( self._node.params)[0] + return params From 7f5ff6a28aba1b2940b3e4db9d28914ed84ba7e6 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Thu, 24 Jul 2025 15:55:32 -0400 Subject: [PATCH 247/316] [MODELS] Added refactored version of li_nap_danner --- farms_network/models/li_nap_danner.pxd | 86 ----------- farms_network/models/li_nap_danner.py | 12 ++ farms_network/models/li_nap_danner.pyx | 177 ---------------------- farms_network/models/li_nap_danner_cy.pxd | 72 +++++++++ farms_network/models/li_nap_danner_cy.pyx | 171 +++++++++++++++++++++ 5 files changed, 255 insertions(+), 263 deletions(-) delete mode 100644 farms_network/models/li_nap_danner.pxd create mode 100644 farms_network/models/li_nap_danner.py delete mode 100644 farms_network/models/li_nap_danner.pyx create mode 100644 farms_network/models/li_nap_danner_cy.pxd create mode 100644 farms_network/models/li_nap_danner_cy.pyx diff --git a/farms_network/models/li_nap_danner.pxd b/farms_network/models/li_nap_danner.pxd deleted file mode 100644 index 60a3044..0000000 --- a/farms_network/models/li_nap_danner.pxd +++ /dev/null @@ -1,86 +0,0 @@ -""" ------------------------------------------------------------------------ -Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty -Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. ------------------------------------------------------------------------ - -Leaky Integrator Node Based on Danner et.al. with Na and K channels -""" - -from ..core.node cimport NodeCy, Node -from ..core.edge cimport EdgeCy - - -cdef enum: - - #STATES - NSTATES = 2 - STATE_V = 0 - STATE_H = 1 - - -cdef packed struct LINaPDannerNodeParameters: - - double c_m # pF - double g_leak # nS - double e_leak # mV - double g_nap # nS - double e_na # mV - double g_syn_e # nS - double g_syn_i # nS - double e_syn_e # mV - double e_syn_i # mV - double v1_2_m # mV - double k_m # - double v1_2_h # mV - double k_h # - double v1_2_t # mV - double k_t # - double tau_0 # mS - double tau_max # mS - double v_max # mV - double v_thr # mV - - -cdef: - void ode( - double time, - double* states, - double* derivatives, - double external_input, - double* network_outputs, - unsigned int* inputs, - double* weights, - double noise, - NodeCy* c_node, - EdgeCy** c_edges, - ) noexcept - double output( - double time, - double* states, - double external_input, - double* network_outputs, - unsigned int* inputs, - double* weights, - NodeCy* c_node, - EdgeCy** c_edges, - ) noexcept - - -cdef class LINaPDannerNode(Node): - """ Python interface to Leaky Integrator Node C-Structure """ - - cdef: - LINaPDannerNodeParameters parameters diff --git a/farms_network/models/li_nap_danner.py b/farms_network/models/li_nap_danner.py new file mode 100644 index 0000000..1aa7547 --- /dev/null +++ b/farms_network/models/li_nap_danner.py @@ -0,0 +1,12 @@ +from farms_network.core.node import Node +from farms_network.models import Models +from farms_network.core.options import LINaPDannerNodeOptions +from farms_network.models.li_nap_danner_cy import LINaPDannerNodeCy + + +class LINaPDannerNode(Node): + + CY_NODE_CLASS = LINaPDannerNodeCy + + def __init__(self, name: str, **kwargs): + super().__init__(name=name, model=Models.LI_NAP_DANNER, **kwargs) diff --git a/farms_network/models/li_nap_danner.pyx b/farms_network/models/li_nap_danner.pyx deleted file mode 100644 index 05df91a..0000000 --- a/farms_network/models/li_nap_danner.pyx +++ /dev/null @@ -1,177 +0,0 @@ -""" ------------------------------------------------------------------------ -Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty -Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. ------------------------------------------------------------------------ - -Leaky Integrator Node based on Danner et.al. -""" - -from libc.math cimport cosh as ccosh -from libc.math cimport exp as cexp -from libc.math cimport fabs as cfabs -from libc.stdio cimport printf -from libc.stdlib cimport free, malloc -from libc.string cimport strdup - -from ..core.options import LINaPDannerParameterOptions - - -cpdef enum STATE: - - #STATES - nstates = NSTATES - v = STATE_V - h = STATE_H - - -cdef void ode( - double time, - double* states, - double* derivatives, - double external_input, - double* network_outputs, - unsigned int* inputs, - double* weights, - double noise, - NodeCy* c_node, - EdgeCy** c_edges, -) noexcept: - """ ODE """ - cdef LINaPDannerNodeParameters params = ( c_node[0].parameters)[0] - - # States - cdef double state_v = states[STATE.v] - cdef double state_h = states[STATE.h] - - # tau_h(V) - cdef double tau_h = params.tau_0 + (params.tau_max - params.tau_0) / \ - ccosh((state_v - params.v1_2_t) / params.k_t) - - # h_inf(V) - cdef double h_inf = 1./(1.0 + cexp((state_v - params.v1_2_h) / params.k_h)) - - # m(V) - cdef double m = 1./(1.0 + cexp((state_v - params.v1_2_m) / params.k_m)) - - # Inap - # pylint: disable=no-member - cdef double i_nap = params.g_nap * m * state_h * (state_v - params.e_na) - - # Ileak - cdef double i_leak = params.g_leak * (state_v - params.e_leak) - - # Neuron inputs - cdef: - double _sum = 0.0 - unsigned int j - double _input, _weight - - cdef unsigned int ninputs = c_node.ninputs - for j in range(ninputs): - _input = network_outputs[inputs[j]] - _weight = weights[j] - if _weight >= 0.0: - # Excitatory Synapse - _sum += params.g_syn_e*cfabs(_weight)*_input*(state_v - params.e_syn_e) - elif _weight < 0.0: - # Inhibitory Synapse - _sum += params.g_syn_i*cfabs(_weight)*_input*(state_v - params.e_syn_i) - - # noise current - cdef double i_noise = noise - - # Slow inactivation - derivatives[STATE.h] = (h_inf - state_h) / tau_h - - # dV - derivatives[STATE.v] = -(i_nap + i_leak + i_noise + _sum)/params.c_m - - -cdef double output( - double time, - double* states, - double external_input, - double* network_outputs, - unsigned int* inputs, - double* weights, - NodeCy* c_node, - EdgeCy** c_edges, -) noexcept: - """ Node output. """ - - cdef LINaPDannerNodeParameters params = ( c_node.parameters)[0] - cdef double _n_out = 0.0 - cdef double state_v = states[STATE.v] - if state_v >= params.v_max: - _n_out = 1.0 - elif (params.v_thr <= state_v) and (state_v < params.v_max): - _n_out = (state_v - params.v_thr) / (params.v_max - params.v_thr) - elif state_v < params.v_thr: - _n_out = 0.0 - return _n_out - - -cdef class LINaPDannerNode(Node): - """ Python interface to Leaky Integrator Node with persistence sodium C-Structure """ - - def __cinit__(self): - # override defaults - self.c_node.model_type = strdup("LI_NAP_DANNER".encode('UTF-8')) - # override default ode and out methods - self.c_node.is_statefull = True - self.c_node.ode = ode - self.c_node.output = output - # parameters - self.c_node.parameters = malloc(sizeof(LINaPDannerNodeParameters)) - if self.c_node.parameters is NULL: - raise MemoryError("Failed to allocate memory for node parameters") - - def __init__(self, name: str, **kwargs): - super().__init__(name) - - # Set node parameters - cdef LINaPDannerNodeParameters* param = ( - self.c_node.parameters - ) - param.c_m = kwargs.pop("c_m") - param.g_nap = kwargs.pop("g_nap") - param.e_na = kwargs.pop("e_na") - param.v1_2_m = kwargs.pop("v1_2_m") - param.k_m = kwargs.pop("k_m") - param.v1_2_h = kwargs.pop("v1_2_h") - param.k_h = kwargs.pop("k_h") - param.v1_2_t = kwargs.pop("v1_2_t") - param.k_t = kwargs.pop("k_t") - param.g_leak = kwargs.pop("g_leak") - param.e_leak = kwargs.pop("e_leak") - param.tau_0 = kwargs.pop("tau_0") - param.tau_max = kwargs.pop("tau_max") - param.v_max = kwargs.pop("v_max") - param.v_thr = kwargs.pop("v_thr") - param.g_syn_e = kwargs.pop("g_syn_e") - param.g_syn_i = kwargs.pop("g_syn_i") - param.e_syn_e = kwargs.pop("e_syn_e") - param.e_syn_i = kwargs.pop("e_syn_i") - if kwargs: - raise Exception(f'Unknown kwargs: {kwargs}') - - @property - def parameters(self): - """ Parameters in the network """ - cdef LINaPDannerNodeParameters params = ( - self.c_node.parameters - )[0] - return params diff --git a/farms_network/models/li_nap_danner_cy.pxd b/farms_network/models/li_nap_danner_cy.pxd new file mode 100644 index 0000000..1498667 --- /dev/null +++ b/farms_network/models/li_nap_danner_cy.pxd @@ -0,0 +1,72 @@ +""" +Leaky Integrator Node Based on Danner et.al. with Na and K channels +""" + +from ..core.node_cy cimport node_t, node_inputs_t, processed_inputs_t, NodeCy +from ..core.edge_cy cimport edge_t, EXCITATORY, INHIBITORY, CHOLINERGIC + + +cdef enum: + + #STATES + NSTATES = 2 + STATE_V = 0 + STATE_H = 1 + + +cdef packed struct li_nap_danner_params_t: + + double c_m # pF + double g_leak # nS + double e_leak # mV + double g_nap # nS + double e_na # mV + double v1_2_m # mV + double k_m # + double v1_2_h # mV + double k_h # + double v1_2_t # mV + double k_t # + double tau_0 # mS + double tau_max # mS + double v_max # mV + double v_thr # mV + double g_syn_e # nS + double g_syn_i # nS + double e_syn_e # mV + double e_syn_i # mV + + +cdef processed_inputs_t li_nap_danner_input_tf( + double time, + const double* states, + const node_inputs_t inputs, + const node_t* node, + const edge_t** edges, +) noexcept + + +cdef void li_nap_danner_ode( + double time, + const double* states, + double* derivatives, + processed_inputs_t input_vals, + double noise, + const node_t* node, +) noexcept + + +cdef double li_nap_danner_output_tf( + double time, + const double* states, + processed_inputs_t input_vals, + double noise, + const node_t* node, +) noexcept + + +cdef class LINaPDannerNodeCy(NodeCy): + """ Python interface to LI Danner NaP Node C-Structure """ + + cdef: + li_nap_danner_params_t params diff --git a/farms_network/models/li_nap_danner_cy.pyx b/farms_network/models/li_nap_danner_cy.pyx new file mode 100644 index 0000000..79142c6 --- /dev/null +++ b/farms_network/models/li_nap_danner_cy.pyx @@ -0,0 +1,171 @@ +from libc.math cimport cosh as ccosh +from libc.math cimport exp as cexp +from libc.math cimport fabs as cfabs +from libc.stdio cimport printf +from libc.string cimport strdup + +from farms_network.models import Models + + +cpdef enum STATE: + + #STATES + nstates = NSTATES + v = STATE_V + h = STATE_H + + +cdef processed_inputs_t li_nap_danner_input_tf( + double time, + const double* states, + const node_inputs_t inputs, + const node_t* node, + const edge_t** edges, +) noexcept: + + cdef li_nap_danner_params_t params = ( node[0].params)[0] + + # States + cdef double state_v = states[STATE.v] + cdef double state_h = states[STATE.h] + + cdef processed_inputs_t processed_inputs = { + 'generic': 0.0, + 'excitatory': 0.0, + 'inhibitory': 0.0, + 'cholinergic': 0.0, + 'phase_coupling': 0.0 + } + + # Neuron inputs + cdef: + double _sum = 0.0 + unsigned int j + double _input, _weight + + cdef unsigned int ninputs = inputs.ninputs + for j in range(ninputs): + _input = inputs.network_outputs[inputs.source_indices[j]] + _weight = inputs.weights[j] + if edges[j].type == EXCITATORY: + # Excitatory Synapse + processed_inputs.excitatory += params.g_syn_e*cfabs(_weight)*_input*(state_v - params.e_syn_e) + elif edges[j].type == INHIBITORY: + # Inhibitory Synapse + processed_inputs.inhibitory += params.g_syn_i*cfabs(_weight)*_input*(state_v - params.e_syn_i) + return processed_inputs + + +cdef void li_nap_danner_ode( + double time, + const double* states, + double* derivatives, + processed_inputs_t input_vals, + double noise, + const node_t* node, +) noexcept: + cdef li_nap_danner_params_t params = ( node[0].params)[0] + + # States + cdef double state_v = states[STATE.v] + cdef double state_h = states[STATE.h] + + # tau_h(V) + cdef double tau_h = params.tau_0 + (params.tau_max - params.tau_0) / \ + ccosh((state_v - params.v1_2_t) / params.k_t) + + # h_inf(V) + cdef double h_inf = 1./(1.0 + cexp((state_v - params.v1_2_h) / params.k_h)) + + # m(V) + cdef double m = 1./(1.0 + cexp((state_v - params.v1_2_m) / params.k_m)) + + # Inap + # pylint: disable=no-member + cdef double i_nap = params.g_nap * m * state_h * (state_v - params.e_na) + + # Ileak + cdef double i_leak = params.g_leak * (state_v - params.e_leak) + + # noise current + cdef double i_noise = noise + + # Slow inactivation + derivatives[STATE.h] = (h_inf - state_h) / tau_h + + # dV + derivatives[STATE.v] = -( + i_nap + i_leak + i_noise + input_vals.excitatory + input_vals.inhibitory + )/params.c_m + + +cdef double li_nap_danner_output_tf( + double time, + const double* states, + processed_inputs_t input_vals, + double noise, + const node_t* node, +) noexcept: + cdef li_nap_danner_params_t params = ( node[0].params)[0] + + cdef double _n_out = 0.0 + cdef double state_v = states[STATE.v] + if state_v >= params.v_max: + _n_out = 1.0 + elif (params.v_thr <= state_v) and (state_v < params.v_max): + _n_out = (state_v - params.v_thr) / (params.v_max - params.v_thr) + elif state_v < params.v_thr: + _n_out = 0.0 + return _n_out + + +cdef class LINaPDannerNodeCy(NodeCy): + """ Python interface to LI Danner NaP Node C-Structure """ + + def __cinit__(self): + self._node.nstates = 2 + self._node.nparams = 19 + + self._node.is_statefull = True + + self._node.input_tf = li_nap_danner_input_tf + self._node.ode = li_nap_danner_ode + self._node.output_tf = li_nap_danner_output_tf + # parameters + self.params = li_nap_danner_params_t() + self._node.params = &self.params + if self._node.params is NULL: + raise MemoryError("Failed to allocate memory for node parameters") + + def __init__(self, name: str, **kwargs): + super().__init__(name) + + # Set node parameters + self.params.c_m = kwargs.pop("c_m") + self.params.g_nap = kwargs.pop("g_nap") + self.params.e_na = kwargs.pop("e_na") + self.params.v1_2_m = kwargs.pop("v1_2_m") + self.params.k_m = kwargs.pop("k_m") + self.params.v1_2_h = kwargs.pop("v1_2_h") + self.params.k_h = kwargs.pop("k_h") + self.params.v1_2_t = kwargs.pop("v1_2_t") + self.params.k_t = kwargs.pop("k_t") + self.params.g_leak = kwargs.pop("g_leak") + self.params.e_leak = kwargs.pop("e_leak") + self.params.tau_0 = kwargs.pop("tau_0") + self.params.tau_max = kwargs.pop("tau_max") + self.params.v_max = kwargs.pop("v_max") + self.params.v_thr = kwargs.pop("v_thr") + self.params.g_syn_e = kwargs.pop("g_syn_e") + self.params.g_syn_i = kwargs.pop("g_syn_i") + self.params.e_syn_e = kwargs.pop("e_syn_e") + self.params.e_syn_i = kwargs.pop("e_syn_i") + + if kwargs: + raise Exception(f'Unknown kwargs: {kwargs}') + + @property + def parameters(self): + """ Parameters in the network """ + cdef li_nap_danner_params_t params = ( self._node.params)[0] + return params From b0239a5bebb88613e0ae3f9611e158fcd3bb8d06 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Thu, 24 Jul 2025 15:56:36 -0400 Subject: [PATCH 248/316] [CORE] Refactored node and edge classes to support more models --- farms_network/core/edge.py | 21 +++++++++++++++------ farms_network/core/edge_cy.pxd | 2 +- farms_network/core/edge_cy.pyx | 7 ++++--- farms_network/core/node_cy.pxd | 27 ++++++++++++++++++--------- farms_network/core/node_cy.pyx | 6 +++--- 5 files changed, 41 insertions(+), 22 deletions(-) diff --git a/farms_network/core/edge.py b/farms_network/core/edge.py index b6a535c..d5a3a38 100644 --- a/farms_network/core/edge.py +++ b/farms_network/core/edge.py @@ -3,16 +3,24 @@ from farms_network.core.edge_cy import EdgeCy from farms_network.core.options import EdgeOptions from farms_network.models import EdgeTypes -from typing import Dict +from typing import Dict, Type -class Edge(EdgeCy): +class Edge: """ Interface to edge class """ - def __init__(self, source: str, target: str, edge_type: EdgeTypes, **kwargs): + CY_EDGE_CLASS: Type[EdgeCy] = None + + def __init__(self, source: str, target: str, edge_type: EdgeTypes, model, **kwargs): + self.model = model self.source: str = source self.target: str = target - self._edge_cy = EdgeCy(source, target, edge_type) + self._edge_cy = self._create_cy_edge(edge_type, **kwargs) + + def _create_cy_edge(self, edge_type, **kwargs) -> EdgeCy: + if self.CY_EDGE_CLASS is None: + return EdgeCy(edge_type, **kwargs) + return self.CY_EDGE_CLASS(edge_type, **kwargs) @property def edge_type(self): @@ -25,9 +33,10 @@ def edge_type(self, edge_type: EdgeTypes): @classmethod def from_options(cls, edge_options: EdgeOptions): """ From edge options """ + model = edge_options.model source: str = edge_options.source target: str = edge_options.target edge_type: EdgeTypes = edge_options.type # Need to generate parameters based on the model specified - parameter_options: Dict = {} # if edge_options.parameters is None else edge_options.parameters - return cls(source, target, edge_type, **parameter_options) + parameter_options: Dict = {} if edge_options.parameters is None else edge_options.parameters + return cls(source, target, edge_type, model, **parameter_options) diff --git a/farms_network/core/edge_cy.pxd b/farms_network/core/edge_cy.pxd index 4bdb2ad..70e1a74 100644 --- a/farms_network/core/edge_cy.pxd +++ b/farms_network/core/edge_cy.pxd @@ -4,7 +4,7 @@ cdef enum: #EDGE TYPES - OPEN = 0 + GENERIC = 0 EXCITATORY = 1 INHIBITORY = 2 CHOLINERGIC = 3 diff --git a/farms_network/core/edge_cy.pyx b/farms_network/core/edge_cy.pyx index 6cebc3c..5ea74a0 100644 --- a/farms_network/core/edge_cy.pyx +++ b/farms_network/core/edge_cy.pyx @@ -5,12 +5,13 @@ from libc.stdlib cimport free, malloc from libc.string cimport strdup from farms_network.core.options import EdgeOptions +from farms_network.models import EdgeTypes cpdef enum types: #EDGE TYPES - open = OPEN + generic = GENERIC excitatory = EXCITATORY inhibitory = INHIBITORY cholinergic = CHOLINERGIC @@ -20,7 +21,7 @@ cpdef enum types: cdef class EdgeCy: """ Python interface to Edge C-Structure""" - def __cinit__(self, source: str, target: str, edge_type: str): + def __cinit__(self, edge_type: str, **kwargs): self._edge = malloc(sizeof(edge_t)) if self._edge is NULL: raise MemoryError("Failed to allocate memory for edge_t") @@ -32,7 +33,7 @@ cdef class EdgeCy: if self._edge is not NULL: free(self._edge) - def __init__(self, source: str, target: str, edge_type: str): + def __init__(self, edge_type: str, **kwargs): ... @property diff --git a/farms_network/core/node_cy.pxd b/farms_network/core/node_cy.pxd index 167916b..0166b72 100644 --- a/farms_network/core/node_cy.pxd +++ b/farms_network/core/node_cy.pxd @@ -7,13 +7,22 @@ cdef struct node_inputs_t: double* network_outputs # Network level outputs double* weights # Network connection weights unsigned int* source_indices # Which nodes provide input + double external_input # external input int ninputs # Number of inputs unsigned int node_index # This node's index (for self-reference) +cdef struct processed_inputs_t: + double generic + double excitatory + double inhibitory + double cholinergic + double phase_coupling + + # Input transfer function # Receives n-inputs and produces one output to be fed into ode/output_tf -cdef double base_input_tf( +cdef processed_inputs_t base_input_tf( double time, const double* states, const node_inputs_t inputs, @@ -27,7 +36,7 @@ cdef void base_ode( double time, const double* states, double* derivatives, - double input_val, + processed_inputs_t input_vals, double noise, const node_t* node, ) @@ -37,7 +46,7 @@ cdef void base_ode( cdef double base_output_tf( double time, const double* states, - double input_val, + processed_inputs_t input_vals, double noise, const node_t* node, ) @@ -58,28 +67,28 @@ cdef struct node_t: void* params # Pointer to the parameters of the node. # Functions - double input_tf( + processed_inputs_t input_tf( double time, const double* states, const node_inputs_t inputs, const node_t* node, const edge_t** edges, - ) + ) noexcept void ode( double time, const double* states, double* derivatives, - double input, + processed_inputs_t input_vals, double noise, const node_t* node, - ) + ) noexcept double output_tf( double time, const double* states, - double input, + processed_inputs_t input_vals, double noise, const node_t* node, - ) + ) noexcept cdef class NodeCy: diff --git a/farms_network/core/node_cy.pyx b/farms_network/core/node_cy.pyx index b88f591..68354e1 100644 --- a/farms_network/core/node_cy.pyx +++ b/farms_network/core/node_cy.pyx @@ -12,7 +12,7 @@ from farms_network.models import Models # Input transfer function # Receives n-inputs and produces one output to be fed into ode/output_tf -cdef double base_input_tf( +cdef processed_inputs_t base_input_tf( double time, const double* states, const node_inputs_t inputs, @@ -26,7 +26,7 @@ cdef void base_ode( double time, const double* states, double* derivatives, - double input, + processed_inputs_t input_vals, double noise, const node_t* node, ) noexcept: @@ -37,7 +37,7 @@ cdef void base_ode( cdef double base_output_tf( double time, const double* states, - double input, + processed_inputs_t input_vals, double noise, const node_t* node, ) noexcept: From 18593dad66e17358cd2aef1f87a731e8bae3c5f0 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Thu, 24 Jul 2025 15:58:08 -0400 Subject: [PATCH 249/316] [CORE] Updated options for OscillatorEdge --- farms_network/core/options.py | 54 +++++++++++++++++++++++++++++------ 1 file changed, 45 insertions(+), 9 deletions(-) diff --git a/farms_network/core/options.py b/farms_network/core/options.py index 5e9c88d..3ba0b3b 100644 --- a/farms_network/core/options.py +++ b/farms_network/core/options.py @@ -144,6 +144,8 @@ def from_options(cls, kwargs: Dict): class EdgeOptions(Options): """ Base class for defining edge options between nodes """ + MODEL = Models.BASE + def __init__(self, **kwargs): """ Initialize """ super().__init__() @@ -151,9 +153,8 @@ def __init__(self, **kwargs): self.target: str = kwargs.pop("target") self.weight: float = kwargs.pop("weight") self.type = EdgeTypes.to_str(kwargs.pop("type")) - self.parameters: EdgeParameterOptions = kwargs.pop( - "parameters", EdgeParameterOptions() - ) + self.model = kwargs.pop("model", EdgeOptions.MODEL) + self.parameters: EdgeParameterOptions = kwargs.pop("parameters", EdgeParameterOptions()) self.visual: EdgeVisualOptions = kwargs.pop("visual") if kwargs: @@ -197,7 +198,6 @@ class EdgeParameterOptions(Options): def __init__(self, **kwargs): super().__init__() - self.model: str = kwargs.pop("model", None) @classmethod def from_options(cls, kwargs: Dict): @@ -461,12 +461,14 @@ def defaults(cls, **kwargs): class OscillatorNodeOptions(NodeOptions): """ Class to define the properties of Oscillator node model """ + MODEL = Models.OSCILLATOR + def __init__(self, **kwargs): """ Initialize """ - model = "oscillator" + super().__init__( name=kwargs.pop("name"), - model=model, + model=OscillatorNodeOptions.MODEL, parameters=kwargs.pop("parameters"), visual=kwargs.pop("visual"), state=kwargs.pop("state"), @@ -537,13 +539,47 @@ def __init__(self, **kwargs): assert len(self.initial) == 3, f"Number of initial states {len(self.initial)} should be 3" +class OscillatorEdgeOptions(EdgeOptions): + """ Oscillator Edge Options """ + MODEL = Models.OSCILLATOR + + def __init__(self, **kwargs): + parameters = kwargs.pop("parameters") + assert isinstance(parameters, OscillatorEdgeParameterOptions) + super().__init__( + model=OscillatorEdgeOptions.MODEL, + source=kwargs.pop("source"), + target=kwargs.pop("target"), + weight=kwargs.pop("weight"), + type=kwargs.pop("type"), + parameters=parameters, + visual=kwargs.pop("visual"), + ) + self._nparameters = 1 + + if kwargs: + raise Exception(f'Unknown kwargs: {kwargs}') + + @classmethod + def from_options(cls, kwargs: Dict): + """ Load from options """ + options = {} + options["source"] = kwargs["source"] + options["target"] = kwargs["target"] + options["weight"] = kwargs["weight"] + options["type"] = kwargs["type"] + options["parameters"] = OscillatorEdgeParameterOptions.from_options( + kwargs["parameters"] + ) + options["visual"] = EdgeVisualOptions.from_options(kwargs["visual"]) + return cls(**options) + + class OscillatorEdgeParameterOptions(EdgeParameterOptions): """ Oscillator edge parameter options """ - MODEL = Models.OSCILLATOR - def __init__(self, **kwargs): - super().__init__(model=OscillatorEdgeParameterOptions.MODEL) + super().__init__() self.phase_difference = kwargs.pop("phase_difference") # radians if kwargs: From 83fdcbec8da49f837138a9e933fff3f08969387c Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Thu, 24 Jul 2025 15:58:35 -0400 Subject: [PATCH 250/316] [MODELS] Updated ReLU with processed_inputs in input_tf --- farms_network/models/relu_cy.pxd | 8 ++++---- farms_network/models/relu_cy.pyx | 26 +++++++++++++++++--------- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/farms_network/models/relu_cy.pxd b/farms_network/models/relu_cy.pxd index c57ef86..6c6e237 100644 --- a/farms_network/models/relu_cy.pxd +++ b/farms_network/models/relu_cy.pxd @@ -1,7 +1,7 @@ """ Rectified Linear Unit """ -from ..core.node_cy cimport node_t, node_inputs_t, NodeCy +from ..core.node_cy cimport node_t, node_inputs_t, processed_inputs_t, NodeCy from ..core.edge_cy cimport edge_t @@ -16,7 +16,7 @@ cdef packed struct relu_params_t: double offset -cdef double relu_input_tf( +cdef processed_inputs_t relu_input_tf( double time, const double* states, const node_inputs_t inputs, @@ -29,7 +29,7 @@ cdef void relu_ode( double time, const double* states, double* derivatives, - double input_val, + processed_inputs_t input_val, double noise, const node_t* node, ) noexcept @@ -38,7 +38,7 @@ cdef void relu_ode( cdef double relu_output_tf( double time, const double* states, - double input_val, + processed_inputs_t input_val, double noise, const node_t* node, ) noexcept diff --git a/farms_network/models/relu_cy.pyx b/farms_network/models/relu_cy.pyx index a25ebbb..e6ce02a 100644 --- a/farms_network/models/relu_cy.pyx +++ b/farms_network/models/relu_cy.pyx @@ -11,7 +11,7 @@ cpdef enum STATE: nstates = NSTATES -cdef double relu_input_tf( +cdef processed_inputs_t relu_input_tf( double time, const double* states, const node_inputs_t inputs, @@ -19,24 +19,33 @@ cdef double relu_input_tf( const edge_t** edges, ) noexcept: cdef relu_params_t params = ( node[0].params)[0] + cdef processed_inputs_t processed_inputs = { + 'generic': 0.0, + 'excitatory': 0.0, + 'inhibitory': 0.0, + 'cholinergic': 0.0, + 'phase_coupling': 0.0 + } cdef: double _sum = 0.0 - unsigned int j + unsigned int j, ninputs double _input, _weight - for j in range(inputs.ninputs): + ninputs = inputs.ninputs + + for j in range(ninputs): _input = inputs.network_outputs[inputs.source_indices[j]] _weight = inputs.weights[j] - _sum += _weight*_input - return _sum + processed_inputs.generic += _weight*_input + return processed_inputs cdef void relu_ode( double time, const double* states, double* derivatives, - double input_val, + processed_inputs_t input_vals, double noise, const node_t* node, ) noexcept: @@ -46,11 +55,12 @@ cdef void relu_ode( cdef double relu_output_tf( double time, const double* states, - double input_val, + processed_inputs_t input_vals, double noise, const node_t* node, ) noexcept: cdef relu_params_t params = ( node[0].params)[0] + cdef double input_val = input_vals.generic cdef double res = max(0.0, params.gain*(params.sign*input_val + params.offset)) return res @@ -75,8 +85,6 @@ cdef class ReLUNodeCy(NodeCy): def __init__(self, **kwargs): super().__init__() - printf("Node %p\n", self._node) - # Set node parameters self.params.gain = kwargs.pop("gain") self.params.sign = kwargs.pop("sign") From b3e9f05fc0ef08319767c02d258e0fda65600d71 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Thu, 24 Jul 2025 16:22:05 -0400 Subject: [PATCH 251/316] [MODELS] Added refactored Linear model --- farms_network/models/linear.pxd | 54 -------------- farms_network/models/linear.py | 16 ++++ farms_network/models/linear.pyx | 87 ---------------------- farms_network/models/linear_cy.pxd | 50 +++++++++++++ farms_network/models/linear_cy.pyx | 116 +++++++++++++++++++++++++++++ 5 files changed, 182 insertions(+), 141 deletions(-) delete mode 100644 farms_network/models/linear.pxd create mode 100644 farms_network/models/linear.py delete mode 100644 farms_network/models/linear.pyx create mode 100644 farms_network/models/linear_cy.pxd create mode 100644 farms_network/models/linear_cy.pyx diff --git a/farms_network/models/linear.pxd b/farms_network/models/linear.pxd deleted file mode 100644 index d473c7f..0000000 --- a/farms_network/models/linear.pxd +++ /dev/null @@ -1,54 +0,0 @@ -""" ------------------------------------------------------------------------ -Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty -Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. ------------------------------------------------------------------------ - -Linear model -""" - - -from ..core.node cimport NodeCy, Node -from ..core.edge cimport EdgeCy, Edge - - -cdef enum: - #STATES - NSTATES = 0 - - -cdef packed struct LinearNodeParameters: - double slope - double bias - - -cdef: - double output( - double time, - double* states, - double external_input, - double* network_outputs, - unsigned int* inputs, - double* weights, - NodeCy* c_node, - EdgeCy** c_edges, - ) noexcept - - -cdef class LinearNode(Node): - """ Python interface to Linear Node C-Structure """ - - cdef: - LinearNodeParameters parameters diff --git a/farms_network/models/linear.py b/farms_network/models/linear.py new file mode 100644 index 0000000..0805b5b --- /dev/null +++ b/farms_network/models/linear.py @@ -0,0 +1,16 @@ +""" Linear """ + + +from farms_network.core.options import LinearNodeOptions +from farms_network.models.linear_cy import LinearNodeCy +from farms_network.core.node import Node +from farms_network.models import Models + + +class LinearNode(Node): + """ Linear node Cy """ + + CY_NODE_CLASS = LinearNodeCy + + def __init__(self, name: str, **kwargs): + super().__init__(name=name, model=Models.LINEAR, **kwargs) diff --git a/farms_network/models/linear.pyx b/farms_network/models/linear.pyx deleted file mode 100644 index ac655d0..0000000 --- a/farms_network/models/linear.pyx +++ /dev/null @@ -1,87 +0,0 @@ -""" ------------------------------------------------------------------------ -Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty -Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. ------------------------------------------------------------------------ - -Linear model -""" - -from libc.stdio cimport printf -from libc.stdlib cimport free, malloc -from libc.string cimport strdup - - -cpdef enum STATE: - - #STATES - nstates = NSTATES - - -cdef double output( - double time, - double* states, - double external_input, - double* network_outputs, - unsigned int* inputs, - double* weights, - NodeCy* c_node, - EdgeCy** c_edges, -) noexcept: - """ Node output. """ - cdef LinearNodeParameters params = ( c_node[0].parameters)[0] - - cdef: - double _sum = 0.0 - unsigned int j - double _input, _weight - cdef unsigned int ninputs = c_node.ninputs - _sum += external_input - for j in range(ninputs): - _input = network_outputs[inputs[j]] - _weight = weights[j] - _sum += _weight*_input - - return (params.slope*_sum + params.bias) - - -cdef class LinearNode(Node): - """ Python interface to Linear Node C-Structure """ - - def __cinit__(self): - self.c_node.model_type = strdup("LINEAR".encode('UTF-8')) - # override default ode and out methods - self.c_node.is_statefull = False - self.c_node.output = output - # parameters - self.c_node.parameters = malloc(sizeof(LinearNodeParameters)) - if self.c_node.parameters is NULL: - raise MemoryError("Failed to allocate memory for node parameters") - - def __init__(self, name: str, **kwargs): - super().__init__(name) - - # Set node parameters - cdef LinearNodeParameters* param = (self.c_node.parameters) - param.slope = kwargs.pop("slope") - param.bias = kwargs.pop("bias") - if kwargs: - raise Exception(f'Unknown kwargs: {kwargs}') - - @property - def parameters(self): - """ Parameters in the network """ - cdef LinearNodeParameters params = ( self.c_node.parameters)[0] - return params diff --git a/farms_network/models/linear_cy.pxd b/farms_network/models/linear_cy.pxd new file mode 100644 index 0000000..cd4b331 --- /dev/null +++ b/farms_network/models/linear_cy.pxd @@ -0,0 +1,50 @@ +""" Linear model """ + + +from ..core.node_cy cimport node_t, node_inputs_t, processed_inputs_t, NodeCy +from ..core.edge_cy cimport edge_t + + +cdef enum: + #STATES + NSTATES = 0 + + +cdef packed struct linear_node_params_t: + double slope + double bias + + +cdef processed_inputs_t linear_input_tf( + double time, + const double* states, + const node_inputs_t inputs, + const node_t* node, + const edge_t** edges, +) noexcept + + +cdef void linear_ode( + double time, + const double* states, + double* derivatives, + processed_inputs_t input_val, + double noise, + const node_t* node, +) noexcept + + +cdef double linear_output_tf( + double time, + const double* states, + processed_inputs_t input_val, + double noise, + const node_t* node, +) noexcept + + +cdef class LinearNodeCy(NodeCy): + """ Python interface to Linear Node C-Structure """ + + cdef: + linear_node_params_t params diff --git a/farms_network/models/linear_cy.pyx b/farms_network/models/linear_cy.pyx new file mode 100644 index 0000000..32d9aa3 --- /dev/null +++ b/farms_network/models/linear_cy.pyx @@ -0,0 +1,116 @@ +""" Linear model """ + +from libc.stdio cimport printf +from libc.stdlib cimport free + + +cpdef enum STATE: + #STATES + nstates = NSTATES + + +cdef processed_inputs_t linear_input_tf( + double time, + const double* states, + const node_inputs_t inputs, + const node_t* node, + const edge_t** edges, +) noexcept: + cdef linear_params_t params = ( node[0].params)[0] + cdef processed_inputs_t processed_inputs = { + 'generic': 0.0, + 'excitatory': 0.0, + 'inhibitory': 0.0, + 'cholinergic': 0.0, + 'phase_coupling': 0.0 + } + + cdef: + double _sum = 0.0 + unsigned int j, ninputs + double _input, _weight + + ninputs = inputs.ninputs + + for j in range(ninputs): + _input = inputs.network_outputs[inputs.source_indices[j]] + _weight = inputs.weights[j] + processed_inputs.generic += _weight*_input + return processed_inputs + + +cdef void linear_ode( + double time, + const double* states, + double* derivatives, + processed_inputs_t input_vals, + double noise, + const node_t* node, +) noexcept: + raise NotImplementedError("ode must be implemented by node type") + + +cdef double linear_output_tf( + double time, + const double* states, + processed_inputs_t input_vals, + double noise, + const node_t* node, +) noexcept: + cdef linear_params_t params = ( node[0].params)[0] + cdef double input_val = input_vals.generic + cdef double res = params.slope*input_val + params.bias + return res + + +cdef class LinearNodeCy(NodeCy): + """ Python interface to Linear Node C-Structure """ + + def __cinit__(self): + # override default ode and out methods + self._node.nstates = 0 + self._node.nparams = 3 + + self._node.is_statefull = False + self._node.input_tf = linear_input_tf + self._node.output_tf = linear_output_tf + # parameters + self.params = linear_params_t() + self._node.params = &self.params + if self._node.params is NULL: + raise MemoryError("Failed to allocate memory for node parameters") + + def __init__(self, **kwargs): + super().__init__() + + # Set node parameters + self.params.slope = kwargs.pop("slope") + self.params.bias = kwargs.pop("bias") + if kwargs: + raise Exception(f'Unknown kwargs: {kwargs}') + + @property + def slope(self): + """ Slope property """ + return ( self._node.params)[0].slope + + @gain.setter + def slope(self, value): + """ Set slope """ + ( self._node.params)[0].slope = value + + @property + def bias(self): + """ Bias property """ + return ( self._node.params)[0].bias + + @gain.setter + def bias(self, value): + """ Set bias """ + ( self._node.params)[0].bias = value + + @property + def parameters(self): + """ Parameters in the network """ + cdef linear_params_t params = ( self._node.params)[0] + return params From a72c9102c92bb6fa048315473f12327138481cbc Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Thu, 24 Jul 2025 16:31:48 -0400 Subject: [PATCH 252/316] [MODELS] Added refactored relay model --- farms_network/models/relay.py | 6 ---- farms_network/models/relay_cy.pxd | 59 ++++++++++++++++--------------- farms_network/models/relay_cy.pyx | 57 ++++++++++++++++++++++------- 3 files changed, 75 insertions(+), 47 deletions(-) diff --git a/farms_network/models/relay.py b/farms_network/models/relay.py index da60e7b..7df98b8 100644 --- a/farms_network/models/relay.py +++ b/farms_network/models/relay.py @@ -14,9 +14,3 @@ class RelayNode(Node): def __init__(self, name: str, **kwargs): super().__init__(name=name, model=Models.RELAY, **kwargs) - - @classmethod - def from_options(cls, node_options: RelayNodeOptions): - """ Instantiate relay node from options """ - name: str = node_options.name - return cls(name) diff --git a/farms_network/models/relay_cy.pxd b/farms_network/models/relay_cy.pxd index d299300..345c109 100644 --- a/farms_network/models/relay_cy.pxd +++ b/farms_network/models/relay_cy.pxd @@ -1,40 +1,41 @@ -""" ------------------------------------------------------------------------ -Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty -Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne +""" Relay model """ -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 +from ..core.node_cy cimport node_t, node_inputs_t, processed_inputs_t, NodeCy +from ..core.edge_cy cimport edge_t -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. ------------------------------------------------------------------------ -Relay model -""" +cdef enum: + #STATES + NSTATES = 0 -from ..core.node_cy cimport NodeCy, node_t -from ..core.edge_cy cimport edge_t +cdef processed_inputs_t relay_input_tf( + double time, + const double* states, + const node_inputs_t inputs, + const node_t* node, + const edge_t** edges, +) noexcept + + +cdef void relay_ode( + double time, + const double* states, + double* derivatives, + processed_inputs_t input_val, + double noise, + const node_t* node, +) noexcept -cdef: - double output( - double time, - double* states, - double external_input, - double* network_outputs, - unsigned int* inputs, - double* weights, - node_t* c_node, - edge_t** c_edges, - ) noexcept +cdef double relay_output_tf( + double time, + const double* states, + processed_inputs_t input_val, + double noise, + const node_t* node, +) noexcept cdef class RelayNodeCy(NodeCy): diff --git a/farms_network/models/relay_cy.pyx b/farms_network/models/relay_cy.pyx index d61b740..ca84e15 100644 --- a/farms_network/models/relay_cy.pyx +++ b/farms_network/models/relay_cy.pyx @@ -1,21 +1,50 @@ """ Relay model """ from libc.stdio cimport printf -from libc.stdlib cimport malloc -cdef double output( +cpdef enum STATE: + #STATES + nstates = NSTATES + + +cdef processed_inputs_t relay_input_tf( + double time, + const double* states, + const node_inputs_t inputs, + const node_t* node, + const edge_t** edges, +) noexcept: + cdef processed_inputs_t processed_inputs = { + 'generic': 0.0, + 'excitatory': 0.0, + 'inhibitory': 0.0, + 'cholinergic': 0.0, + 'phase_coupling': 0.0 + } + processed_inputs.generic = inputs.external_input + return processed_inputs + + +cdef void relay_ode( double time, - double* states, - double external_input, - double* network_outputs, - unsigned int* inputs, - double* weights, - node_t* c_node, - edge_t** c_edges, + const double* states, + double* derivatives, + processed_inputs_t input_vals, + double noise, + const node_t* node, ) noexcept: - """ Node output. """ - return external_input + pass + + +cdef double relay_output_tf( + double time, + const double* states, + processed_inputs_t input_vals, + double noise, + const node_t* node, +) noexcept: + return input_vals.generic cdef class RelayNodeCy(NodeCy): @@ -23,8 +52,12 @@ cdef class RelayNodeCy(NodeCy): def __cinit__(self): # override default ode and out methods + self._node.nstates = 0 + self._node.nparams = 0 + self._node.is_statefull = False - self._node.output = output + self._node.input_tf = relay_input_tf + self._node.output_tf = relay_output_tf def __init__(self, **kwargs): super().__init__() From 79aec55a50924a3b024058ecd66222c473ee3f98 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Thu, 24 Jul 2025 16:32:03 -0400 Subject: [PATCH 253/316] [MODELS] Minor typos fixed in relu and linear --- farms_network/models/linear_cy.pxd | 4 ++-- farms_network/models/linear_cy.pyx | 4 ++-- farms_network/models/relu.py | 2 ++ farms_network/models/relu_cy.pyx | 2 +- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/farms_network/models/linear_cy.pxd b/farms_network/models/linear_cy.pxd index cd4b331..9609e0b 100644 --- a/farms_network/models/linear_cy.pxd +++ b/farms_network/models/linear_cy.pxd @@ -10,7 +10,7 @@ cdef enum: NSTATES = 0 -cdef packed struct linear_node_params_t: +cdef packed struct linear_params_t: double slope double bias @@ -47,4 +47,4 @@ cdef class LinearNodeCy(NodeCy): """ Python interface to Linear Node C-Structure """ cdef: - linear_node_params_t params + linear_params_t params diff --git a/farms_network/models/linear_cy.pyx b/farms_network/models/linear_cy.pyx index 32d9aa3..7cd9249 100644 --- a/farms_network/models/linear_cy.pyx +++ b/farms_network/models/linear_cy.pyx @@ -94,7 +94,7 @@ cdef class LinearNodeCy(NodeCy): """ Slope property """ return ( self._node.params)[0].slope - @gain.setter + @slope.setter def slope(self, value): """ Set slope """ ( self._node.params)[0].slope = value @@ -104,7 +104,7 @@ cdef class LinearNodeCy(NodeCy): """ Bias property """ return ( self._node.params)[0].bias - @gain.setter + @bias.setter def bias(self, value): """ Set bias """ ( self._node.params)[0].bias = value diff --git a/farms_network/models/relu.py b/farms_network/models/relu.py index 2296753..2744085 100644 --- a/farms_network/models/relu.py +++ b/farms_network/models/relu.py @@ -1,3 +1,5 @@ +""" ReLU """ + from farms_network.core.node import Node from farms_network.models import Models from farms_network.core.options import ReLUNodeOptions diff --git a/farms_network/models/relu_cy.pyx b/farms_network/models/relu_cy.pyx index e6ce02a..b4cbeae 100644 --- a/farms_network/models/relu_cy.pyx +++ b/farms_network/models/relu_cy.pyx @@ -2,7 +2,7 @@ from libc.stdio cimport printf -from libc.stdlib cimport free, malloc +from libc.stdlib cimport free cpdef enum STATE: From 3978dd13207b9c2f9dcdaedd706dd6e57e2df16e Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Thu, 24 Jul 2025 16:33:12 -0400 Subject: [PATCH 254/316] [MODELS] Changed default edge type open to generic --- farms_network/models/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/farms_network/models/__init__.py b/farms_network/models/__init__.py index ff00c36..d3ecbbf 100644 --- a/farms_network/models/__init__.py +++ b/farms_network/models/__init__.py @@ -37,7 +37,8 @@ class Models(str, BaseTypes): @unique class EdgeTypes(str, BaseTypes): - OPEN = "open" + GENERIC = "generic" EXCITATORY = "excitatory" INHIBITORY = "inhibitory" CHOLINERGIC = "cholinergic" + PHASE_COUPLING = "phase_coupling" From d39b7a77af4952014de94ceda59b9fe83f64ddf8 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Sun, 27 Jul 2025 17:05:50 -0400 Subject: [PATCH 255/316] [MODELS] Added refactored hopf_oscillator --- farms_network/models/hopf_oscillator.pxd | 72 ----------- farms_network/models/hopf_oscillator.py | 14 +++ farms_network/models/hopf_oscillator.pyx | 127 -------------------- farms_network/models/hopf_oscillator_cy.pxd | 55 +++++++++ farms_network/models/hopf_oscillator_cy.pyx | 122 +++++++++++++++++++ 5 files changed, 191 insertions(+), 199 deletions(-) delete mode 100644 farms_network/models/hopf_oscillator.pxd create mode 100644 farms_network/models/hopf_oscillator.py delete mode 100644 farms_network/models/hopf_oscillator.pyx create mode 100644 farms_network/models/hopf_oscillator_cy.pxd create mode 100644 farms_network/models/hopf_oscillator_cy.pyx diff --git a/farms_network/models/hopf_oscillator.pxd b/farms_network/models/hopf_oscillator.pxd deleted file mode 100644 index 1a7fc32..0000000 --- a/farms_network/models/hopf_oscillator.pxd +++ /dev/null @@ -1,72 +0,0 @@ -""" ------------------------------------------------------------------------ -Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty -Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. ------------------------------------------------------------------------ - -Hopf-Oscillator model -""" - - -from ..core.node cimport NodeCy, Node -from ..core.edge cimport EdgeCy, Edge - - -cdef enum: - - #STATES - NSTATES = 2 - STATE_X = 0 - STATE_Y= 1 - - -cdef packed struct HopfOscillatorNodeParameters: - - double mu - double omega - double alpha - double beta - - -cdef: - void ode( - double time, - double* states, - double* derivatives, - double external_input, - double* network_outputs, - unsigned int* inputs, - double* weights, - double noise, - NodeCy* c_node, - EdgeCy** c_edges, - ) noexcept - double output( - double time, - double* states, - double external_input, - double* network_outputs, - unsigned int* inputs, - double* weights, - NodeCy* c_node, - EdgeCy** c_edges, - ) noexcept - - -cdef class HopfOscillatorNode(Node): - """ Python interface to HopfOscillator Node C-Structure """ - - cdef: - HopfOscillatorNodeParameters parameters diff --git a/farms_network/models/hopf_oscillator.py b/farms_network/models/hopf_oscillator.py new file mode 100644 index 0000000..7927dd3 --- /dev/null +++ b/farms_network/models/hopf_oscillator.py @@ -0,0 +1,14 @@ +from farms_network.core.node import Node +from farms_network.models import Models +from farms_network.core.options import HopfOscillatorNodeOptions +from farms_network.models.hopf_oscillator_cy import HopfOscillatorNodeCy + + +class HopfOscillatorNode(Node): + + CY_NODE_CLASS = HopfOscillatorNodeCy + + def __init__(self, name: str, **kwargs): + super().__init__(name=name, model=Models.OSCILLATOR, **kwargs) + + # Hopf Oscillator-specific properties diff --git a/farms_network/models/hopf_oscillator.pyx b/farms_network/models/hopf_oscillator.pyx deleted file mode 100644 index c3ba230..0000000 --- a/farms_network/models/hopf_oscillator.pyx +++ /dev/null @@ -1,127 +0,0 @@ -""" ------------------------------------------------------------------------ -Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty -Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. ------------------------------------------------------------------------ - -Hopf Oscillator - -[1]L. Righetti and A. J. Ijspeert, “Pattern generators with sensory -feedback for the control of quadruped locomotion,” in 2008 IEEE -International Conference on Robotics and Automation, May 2008, -pp. 819–824. doi: 10.1109/ROBOT.2008.4543306. - -""" - -from libc.stdio cimport printf -from libc.stdlib cimport malloc -from libc.string cimport strdup - - -cpdef enum STATE: - - #STATES - nstates = NSTATES - x = STATE_X - y = STATE_Y - - -cdef void ode( - double time, - double* states, - double* derivatives, - double external_input, - double* network_outputs, - unsigned int* inputs, - double* weights, - double noise, - NodeCy* c_node, - EdgeCy** c_edges, -) noexcept: - """ ODE """ - # Parameters - cdef HopfOscillatorNodeParameters params = ( c_node[0].parameters)[0] - - # States - cdef double state_x = states[STATE.x] - cdef double state_y = states[STATE.y] - - cdef: - double _sum = 0.0 - unsigned int j - double _input, _weight - - cdef unsigned int ninputs = c_node.ninputs - for j in range(ninputs): - _input = network_outputs[inputs[j]] - _weight = weights[j] - _sum += (_weight*_input) - - r_square = (state_x**2 + state_y**2) - # xdot : x_dot - derivatives[STATE.x] = ( - params.alpha*(params.mu - r_square)*state_x - params.omega*state_y - ) - # ydot : y_dot - derivatives[STATE.y] = ( - params.beta*(params.mu - r_square)*state_y + params.omega*state_x + (_sum) - ) - - -cdef double output( - double time, - double* states, - double external_input, - double* network_outputs, - unsigned int* inputs, - double* weights, - NodeCy* c_node, - EdgeCy** c_edges, -) noexcept: - """ Node output. """ - return states[STATE.y] - - -cdef class HopfOscillatorNode(Node): - """ Python interface to HopfOscillator Node C-Structure """ - - def __cinit__(self): - self.c_node.model_type = strdup("HOPF_OSCILLATOR".encode('UTF-8')) - # override default ode and out methods - self.c_node.is_statefull = True - self.c_node.ode = ode - self.c_node.output = output - # parameters - self.c_node.parameters = malloc(sizeof(HopfOscillatorNodeParameters)) - if self.c_node.parameters is NULL: - raise MemoryError("Failed to allocate memory for node parameters") - - def __init__(self, name: str, **kwargs): - super().__init__(name) - - # Set node parameters - cdef HopfOscillatorNodeParameters* params = (self.c_node.parameters) - params.mu = kwargs.pop("mu") - params.omega = kwargs.pop("omega") - params.alpha = kwargs.pop("alpha") - params.beta = kwargs.pop("beta") - if kwargs: - raise Exception(f'Unknown kwargs: {kwargs}') - - @property - def parameters(self): - """ Parameters in the network """ - cdef HopfOscillatorNodeParameters params = ( self.c_node.parameters)[0] - return params diff --git a/farms_network/models/hopf_oscillator_cy.pxd b/farms_network/models/hopf_oscillator_cy.pxd new file mode 100644 index 0000000..c8e584f --- /dev/null +++ b/farms_network/models/hopf_oscillator_cy.pxd @@ -0,0 +1,55 @@ +""" Hopf-Oscillator model """ + + +from ..core.node_cy cimport node_t, node_inputs_t, processed_inputs_t, NodeCy +from ..core.edge_cy cimport edge_t + + +cdef enum: + #STATES + NSTATES = 2 + STATE_X = 0 + STATE_Y= 1 + + +cdef packed struct hopf_oscillator_params_t: + + double mu + double omega + double alpha + double beta + + +cdef processed_inputs_t hopf_oscillator_input_tf( + double time, + const double* states, + const node_inputs_t inputs, + const node_t* node, + const edge_t** edges, +) noexcept + + +cdef void hopf_oscillator_ode( + double time, + const double* states, + double* derivatives, + processed_inputs_t input_vals, + double noise, + const node_t* node, +) noexcept + + +cdef double hopf_oscillator_output_tf( + double time, + const double* states, + processed_inputs_t input_vals, + double noise, + const node_t* node, +) noexcept + + +cdef class HopfOscillatorNodeCy(NodeCy): + """ Python interface to HopfOscillator Node C-Structure """ + + cdef: + hopf_oscillator_params_t params diff --git a/farms_network/models/hopf_oscillator_cy.pyx b/farms_network/models/hopf_oscillator_cy.pyx new file mode 100644 index 0000000..87fa0d1 --- /dev/null +++ b/farms_network/models/hopf_oscillator_cy.pyx @@ -0,0 +1,122 @@ +""" Hopf Oscillator + +[1]L. Righetti and A. J. Ijspeert, “Pattern generators with sensory +feedback for the control of quadruped locomotion,” in 2008 IEEE +International Conference on Robotics and Automation, May 2008, +pp. 819–824. doi: 10.1109/ROBOT.2008.4543306. +""" + +from libc.stdio cimport printf + + +cpdef enum STATE: + + #STATES + nstates = NSTATES + x = STATE_X + y = STATE_Y + + +cdef processed_inputs_t hopf_oscillator_input_tf( + double time, + const double* states, + const node_inputs_t inputs, + const node_t* node, + const edge_t** edges, +) noexcept: + + # Parameters + cdef hopf_oscillator_params_t params = ( node[0].params)[0] + + # States + cdef double state_x = states[STATE.x] + cdef double state_y = states[STATE.y] + + cdef processed_inputs_t processed_inputs = { + 'generic': 0.0, + 'excitatory': 0.0, + 'inhibitory': 0.0, + 'cholinergic': 0.0, + 'phase_coupling': 0.0 + } + + for j in range(inputs.ninputs): + _input = inputs.network_outputs[inputs.source_indices[j]] + _weight = inputs.weights[j] + processed_inputs.generic += (_weight*_input) + + return processed_inputs + + +cdef void hopf_oscillator_ode( + double time, + const double* states, + double* derivatives, + processed_inputs_t input_vals, + double noise, + const node_t* node, +) noexcept: + # Parameters + cdef hopf_oscillator_params_t params = ( node[0].params)[0] + + # States + cdef double state_x = states[STATE.x] + cdef double state_y = states[STATE.y] + + cdef double input_val = input_vals.generic + + r_square = (state_x**2 + state_y**2) + # xdot : x_dot + derivatives[STATE.x] = ( + params.alpha*(params.mu - r_square)*state_x - params.omega*state_y + ) + # ydot : y_dot + derivatives[STATE.y] = ( + params.beta*(params.mu - r_square)*state_y + params.omega*state_x + (input_val) + ) + + +cdef double hopf_oscillator_output_tf( + double time, + const double* states, + processed_inputs_t input_val, + double noise, + const node_t* node, +) noexcept: + return states[STATE.y] + + +cdef class HopfOscillatorNodeCy(NodeCy): + """ Python interface to HopfOscillator Node C-Structure """ + + def __cinit__(self): + # override default ode and out methods + self._node.nstates = 2 + self._node.nparams = 4 + + self._node.is_statefull = True + self._node.input_tf = hopf_oscillator_input_tf + self._node.ode = hopf_oscillator_ode + self._node.output_tf = hopf_oscillator_output_tf + # parameters + self.params = hopf_oscillator_params_t() + self._node.params = &self.params + if self._node.params is NULL: + raise MemoryError("Failed to allocate memory for node parameters") + + def __init__(self, **kwargs): + super().__init__() + + # Set node parameters + self.params.mu = kwargs.pop("mu") + self.params.omega = kwargs.pop("omega") + self.params.alpha = kwargs.pop("alpha") + self.params.beta = kwargs.pop("beta") + if kwargs: + raise Exception(f'Unknown kwargs: {kwargs}') + + @property + def parameters(self): + """ Parameters in the network """ + cdef hopf_oscillator_params_t params = ( self._edge.params)[0] + return params From 23fa417398deef555926a420bbdd36093a60abdb Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Sun, 27 Jul 2025 17:07:15 -0400 Subject: [PATCH 256/316] [MODELS] Added parameters method to oscillator CY --- farms_network/models/oscillator_cy.pyx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/farms_network/models/oscillator_cy.pyx b/farms_network/models/oscillator_cy.pyx index bd8c437..0396e8f 100644 --- a/farms_network/models/oscillator_cy.pyx +++ b/farms_network/models/oscillator_cy.pyx @@ -122,6 +122,12 @@ cdef class OscillatorNodeCy(NodeCy): raise Exception(f'Unknown kwargs: {kwargs}') + def parameters(self): + """ Parameters in the network """ + cdef oscillator_params_t params = ( self._node.params)[0] + return params + + cdef class OscillatorEdgeCy(EdgeCy): """ Python interface to Oscillator Edge C-Structure """ From 99b9982268e30bfb171ae6e73d261669cf69911b Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Mon, 28 Jul 2025 12:36:30 -0400 Subject: [PATCH 257/316] [DATA] Changed network data to be a timeseries This avoid unnecessary copying of data but at the cost of fine control over actual data --- farms_network/core/data.py | 43 +++++++++++++++++----------------- farms_network/core/data_cy.pxd | 6 ++--- farms_network/core/data_cy.pyx | 4 ++-- 3 files changed, 27 insertions(+), 26 deletions(-) diff --git a/farms_network/core/data.py b/farms_network/core/data.py index e75379b..3c01075 100644 --- a/farms_network/core/data.py +++ b/farms_network/core/data.py @@ -1,20 +1,4 @@ """ ------------------------------------------------------------------------ -Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty -Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. ------------------------------------------------------------------------ Main data structure for the network @@ -84,20 +68,24 @@ def from_options(cls, network_options: NetworkOptions): derivatives = NetworkStates.from_options(network_options) connectivity = NetworkConnectivity.from_options(network_options) noise = NetworkNoise.from_options(network_options) - outputs = DoubleArray1D( + outputs = DoubleArray2D( array=np.full( - shape=len(network_options.nodes), + shape=(buffer_size, len(network_options.nodes)), fill_value=0, dtype=NPDTYPE, ) ) - external_inputs = DoubleArray1D( + external_inputs = DoubleArray2D( array=np.full( - shape=len(network_options.nodes), + shape=(buffer_size, len(network_options.nodes)), fill_value=0, dtype=NPDTYPE, ) ) + # nodes = [ + # NodeStates(states, node_index) + # for node_index, node_options in enumerate(network_options.nodes) + # ] nodes = np.array( [ NodeData.from_options( @@ -156,7 +144,7 @@ def from_options(cls, network_options: NetworkOptions): nstates += node._nstates indices.append(nstates) return cls( - array=np.array(np.zeros((nstates,)), dtype=NPDTYPE), + array=np.array(np.zeros((network_options.logs.buffer_size, nstates)), dtype=NPDTYPE), indices=np.array(indices) ) @@ -284,6 +272,19 @@ def to_dict(self, iteration: int = None) -> Dict: 'outputs': to_array(self.outputs), } +class NodeStates: + def __init__(self, network_states, node_index): + self._network_states = network_states + self._node_index = node_index + + @property + def array(self): + start = self._network_states.indices[self._node_index] + end = self._network_states.indices[self._node_index + 1] + if start == end: + return None + return self._network_states.array[start:end] + class NodeData(NodeDataCy): """ Base class for representing an arbitrary node data """ diff --git a/farms_network/core/data_cy.pxd b/farms_network/core/data_cy.pxd index cc3b24d..c91bda3 100644 --- a/farms_network/core/data_cy.pxd +++ b/farms_network/core/data_cy.pxd @@ -29,15 +29,15 @@ cdef class NetworkDataCy: cdef: public NetworkStatesCy states public NetworkStatesCy derivatives - public DoubleArray1D external_inputs - public DoubleArray1D outputs + public DoubleArray2D external_inputs + public DoubleArray2D outputs public NetworkConnectivityCy connectivity public NetworkNoiseCy noise public NodeDataCy[:] nodes -cdef class NetworkStatesCy(DoubleArray1D): +cdef class NetworkStatesCy(DoubleArray2D): """ State array """ cdef public UITYPEv1 indices diff --git a/farms_network/core/data_cy.pyx b/farms_network/core/data_cy.pyx index f7e9d70..cd83a9d 100644 --- a/farms_network/core/data_cy.pyx +++ b/farms_network/core/data_cy.pyx @@ -36,12 +36,12 @@ cdef class NetworkDataCy: super().__init__() -cdef class NetworkStatesCy(DoubleArray1D): +cdef class NetworkStatesCy(DoubleArray2D): """ State array """ def __init__( self, - array: NDArray[(Any,), np.double], + array: NDArray[(Any, Any), np.double], indices: NDArray[(Any,), np.uintc], ): super().__init__(array) From 8933d051d7f78e1a0db5594d6d5ded6a2905293d Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Mon, 28 Jul 2025 12:37:57 -0400 Subject: [PATCH 258/316] [CORE] Fixed edge class instantiation --- farms_network/core/network.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/farms_network/core/network.py b/farms_network/core/network.py index befc11a..674d497 100644 --- a/farms_network/core/network.py +++ b/farms_network/core/network.py @@ -6,7 +6,7 @@ from .network_cy import NetworkCy from .options import (EdgeOptions, IntegrationOptions, NetworkOptions, NodeOptions) -from ..models.factory import NodeFactory +from ..models.factory import NodeFactory, EdgeFactory from .edge import Edge from .node import Node from typing import Optional @@ -92,7 +92,8 @@ def _generate_node(node_options: NodeOptions) -> Node: @staticmethod def _generate_edge(edge_options: EdgeOptions) -> Edge: """ Generate an edge from options """ - return Edge.from_options(edge_options) + EdgeClass = EdgeFactory.create(edge_options.model) + return EdgeClass.from_options(edge_options) # Delegate properties to Cython implementation @property From 8988abc9caaf774dd3974a381e8e2e22fffce86c Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Mon, 28 Jul 2025 12:40:53 -0400 Subject: [PATCH 259/316] [CORE] Updated network with new refactored nodes and data --- farms_network/core/network_cy.pxd | 1 - farms_network/core/network_cy.pyx | 33 +++++++++++++++++-------------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/farms_network/core/network_cy.pxd b/farms_network/core/network_cy.pxd index 459a325..56e880a 100644 --- a/farms_network/core/network_cy.pxd +++ b/farms_network/core/network_cy.pxd @@ -58,6 +58,5 @@ cdef class NetworkCy(ODESystem): list nodes_output_data - # cpdef void step(self) cpdef void evaluate(self, double time, double[:] states, double[:] derivatives) noexcept cpdef void step(self) diff --git a/farms_network/core/network_cy.pyx b/farms_network/core/network_cy.pyx index 8517580..608797c 100644 --- a/farms_network/core/network_cy.pyx +++ b/farms_network/core/network_cy.pyx @@ -11,6 +11,7 @@ from libc.string cimport strdup from ..models.factory import NodeFactory from ..noise.ornstein_uhlenbeck import OrnsteinUhlenbeck from .data import NetworkData, NetworkStates +from .node_cy cimport processed_inputs_t from .data_cy cimport (NetworkConnectivityCy, NetworkDataCy, NetworkNoiseCy, NetworkStatesCy) @@ -24,7 +25,6 @@ from .options import (EdgeOptions, IntegrationOptions, NetworkOptions, cdef inline void ode( double time, double[:] states_arr, - NetworkDataCy data, network_t* c_network, double[:] node_outputs_tmp, ) noexcept: @@ -36,13 +36,14 @@ cdef inline void ode( cdef edge_t** c_edges = c_network.edges nnodes = c_network.nnodes - # It is important to use the states passed to the function and not from the data.states cdef double* node_outputs_tmp_ptr = &node_outputs_tmp[0] cdef node_inputs_t node_inputs node_inputs.network_outputs = c_network.outputs - cdef total_input_val = 0.0 + # It is important to use the states passed to the function and not from the data.states + c_network.states = &states_arr[0] + cdef processed_inputs_t processed_inputs for j in range(nnodes): total_input_val = 0.0 @@ -56,7 +57,7 @@ cdef inline void ode( node_inputs.node_index = j if __node.is_statefull: # Compute the inputs from all nodes - total_input_val = __node.input_tf( + processed_inputs = __node.input_tf( time, c_network.states + c_network.states_indices[j], node_inputs, @@ -68,7 +69,7 @@ cdef inline void ode( time, c_network.states + c_network.states_indices[j], c_network.derivatives + c_network.derivatives_indices[j], - total_input_val, + processed_inputs, 0.0, c_nodes[j] ) @@ -76,12 +77,12 @@ cdef inline void ode( node_outputs_tmp_ptr[j] = __node.output_tf( time, c_network.states + c_network.states_indices[j], - total_input_val, + processed_inputs, 0.0, c_nodes[j], ) else: - total_input_val = __node.input_tf( + processed_inputs = __node.input_tf( time, NULL, node_inputs, @@ -92,7 +93,7 @@ cdef inline void ode( node_outputs_tmp_ptr[j] = __node.output_tf( time, NULL, - total_input_val, + processed_inputs, 0.0, c_nodes[j], ) @@ -163,7 +164,7 @@ cdef class NetworkCy(ODESystem): # Initialize network context self.data = data if self.data.states.array.size > 0: - self._network.states = &self.data.states.array[0] + self._network.states = &self.data.states.array[0][0] else: self._network.states = NULL # No stateful @@ -173,7 +174,7 @@ cdef class NetworkCy(ODESystem): self._network.states_indices = NULL if self.data.derivatives.array.size > 0: - self._network.derivatives = &self.data.derivatives.array[0] + self._network.derivatives = &self.data.derivatives.array[0][0] else: self._network.derivatives = NULL @@ -183,12 +184,12 @@ cdef class NetworkCy(ODESystem): self._network.derivatives_indices = NULL if self.data.external_inputs.array.size > 0: - self._network.external_inputs = &self.data.external_inputs.array[0] + self._network.external_inputs = &self.data.external_inputs.array[0][0] else: self._network.external_inputs = NULL if self.data.outputs.array.size > 0: - self._network.outputs = &self.data.outputs.array[0] + self._network.outputs = &self.data.outputs.array[0][0] else: self._network.outputs = NULL @@ -218,6 +219,7 @@ cdef class NetworkCy(ODESystem): def __init__(self, nnodes, nedges, data: NetworkDataCy): """ Initialize """ super().__init__() + self.iteration = 0 def __dealloc__(self): @@ -263,9 +265,10 @@ cdef class NetworkCy(ODESystem): # Update noise model cdef NetworkDataCy data = self.data - ode(time, states, data, self._network, self.__tmp_node_outputs) - # data.outputs.array[:] = self.__tmp_node_outputs - # derivatives[:] = data.derivatives.array + ode(time, states, self._network, self.__tmp_node_outputs) + data.states.array[self.iteration, :] = states + data.outputs.array[self.iteration, :] = self.__tmp_node_outputs + derivatives[:] = data.derivatives.array[self.iteration, :] cpdef void step(self): """ Step the network state """ From a72b7ddcdbc5f7a55927fcbdf6a83ba440aaa61d Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Mon, 28 Jul 2025 12:43:06 -0400 Subject: [PATCH 260/316] [MODELS] Renamed cython files to new architecture --- ...hugh_nagumo.pxd => fitzhugh_nagumo_cy.pxd} | 0 ...hugh_nagumo.pyx => fitzhugh_nagumo_cy.pyx} | 0 ...toneuron.pxd => hh_daun_motoneuron_cy.pxd} | 0 ...toneuron.pyx => hh_daun_motoneuron_cy.pyx} | 0 .../{izhikevich.pyx => izhikevich_cy.pxd} | 0 ...integrator.pxd => leaky_integrator_cy.pxd} | 0 ...integrator.pyx => leaky_integrator_cy.pyx} | 0 farms_network/models/li_daun_interneuron.pyx | 0 .../models/li_daun_interneuron_cy.pxd | 28 +++++++++ ...rneuron.pxd => li_daun_interneuron_cy.pyx} | 0 farms_network/models/matsuoka.pxd | 19 ------ farms_network/models/matsuoka.pyx | 19 ------ farms_network/models/matsuoka_cy.pxd | 57 +++++++++++++++++ farms_network/models/matsuoka_cy.pyx | 63 +++++++++++++++++++ ...cillator.pxd => morphed_oscillator_cy.pxd} | 0 ...cillator.pyx => morphed_oscillator_cy.pyx} | 0 farms_network/models/morris_lecar.pyx | 20 ------ .../{morris_lecar.pxd => morris_lecar_cy.pxd} | 0 farms_network/models/morris_lecar_cy.pyx | 1 + 19 files changed, 149 insertions(+), 58 deletions(-) rename farms_network/models/{fitzhugh_nagumo.pxd => fitzhugh_nagumo_cy.pxd} (100%) rename farms_network/models/{fitzhugh_nagumo.pyx => fitzhugh_nagumo_cy.pyx} (100%) rename farms_network/models/{hh_daun_motoneuron.pxd => hh_daun_motoneuron_cy.pxd} (100%) rename farms_network/models/{hh_daun_motoneuron.pyx => hh_daun_motoneuron_cy.pyx} (100%) rename farms_network/models/{izhikevich.pyx => izhikevich_cy.pxd} (100%) rename farms_network/models/{leaky_integrator.pxd => leaky_integrator_cy.pxd} (100%) rename farms_network/models/{leaky_integrator.pyx => leaky_integrator_cy.pyx} (100%) delete mode 100644 farms_network/models/li_daun_interneuron.pyx create mode 100644 farms_network/models/li_daun_interneuron_cy.pxd rename farms_network/models/{li_daun_interneuron.pxd => li_daun_interneuron_cy.pyx} (100%) delete mode 100644 farms_network/models/matsuoka.pxd delete mode 100644 farms_network/models/matsuoka.pyx create mode 100644 farms_network/models/matsuoka_cy.pxd create mode 100644 farms_network/models/matsuoka_cy.pyx rename farms_network/models/{morphed_oscillator.pxd => morphed_oscillator_cy.pxd} (100%) rename farms_network/models/{morphed_oscillator.pyx => morphed_oscillator_cy.pyx} (100%) delete mode 100644 farms_network/models/morris_lecar.pyx rename farms_network/models/{morris_lecar.pxd => morris_lecar_cy.pxd} (100%) create mode 100644 farms_network/models/morris_lecar_cy.pyx diff --git a/farms_network/models/fitzhugh_nagumo.pxd b/farms_network/models/fitzhugh_nagumo_cy.pxd similarity index 100% rename from farms_network/models/fitzhugh_nagumo.pxd rename to farms_network/models/fitzhugh_nagumo_cy.pxd diff --git a/farms_network/models/fitzhugh_nagumo.pyx b/farms_network/models/fitzhugh_nagumo_cy.pyx similarity index 100% rename from farms_network/models/fitzhugh_nagumo.pyx rename to farms_network/models/fitzhugh_nagumo_cy.pyx diff --git a/farms_network/models/hh_daun_motoneuron.pxd b/farms_network/models/hh_daun_motoneuron_cy.pxd similarity index 100% rename from farms_network/models/hh_daun_motoneuron.pxd rename to farms_network/models/hh_daun_motoneuron_cy.pxd diff --git a/farms_network/models/hh_daun_motoneuron.pyx b/farms_network/models/hh_daun_motoneuron_cy.pyx similarity index 100% rename from farms_network/models/hh_daun_motoneuron.pyx rename to farms_network/models/hh_daun_motoneuron_cy.pyx diff --git a/farms_network/models/izhikevich.pyx b/farms_network/models/izhikevich_cy.pxd similarity index 100% rename from farms_network/models/izhikevich.pyx rename to farms_network/models/izhikevich_cy.pxd diff --git a/farms_network/models/leaky_integrator.pxd b/farms_network/models/leaky_integrator_cy.pxd similarity index 100% rename from farms_network/models/leaky_integrator.pxd rename to farms_network/models/leaky_integrator_cy.pxd diff --git a/farms_network/models/leaky_integrator.pyx b/farms_network/models/leaky_integrator_cy.pyx similarity index 100% rename from farms_network/models/leaky_integrator.pyx rename to farms_network/models/leaky_integrator_cy.pyx diff --git a/farms_network/models/li_daun_interneuron.pyx b/farms_network/models/li_daun_interneuron.pyx deleted file mode 100644 index e69de29..0000000 diff --git a/farms_network/models/li_daun_interneuron_cy.pxd b/farms_network/models/li_daun_interneuron_cy.pxd new file mode 100644 index 0000000..3b93a21 --- /dev/null +++ b/farms_network/models/li_daun_interneuron_cy.pxd @@ -0,0 +1,28 @@ +""" Leaky Integrate and Fire InterNeuron Based on Daun et.al. """ + + +from ..core.node_cy cimport node_t, node_inputs_t, processed_inputs_t, NodeCy +from ..core.edge_cy cimport edge_t, EXCITATORY, INHIBITORY, CHOLINERGIC + + +cdef enum: + #STATES + NSTATES = 2 + STATE_V = 0 + STATE_H = 1 + + +cdef packed struct li_daun_params_t: + + double c_m + double g_nap + double e_nap + double v_h_h + double gamma_h + double v_t_h + double eps + double gamma_t + double v_h_m + double gamma_m + double g_leak + double e_leak diff --git a/farms_network/models/li_daun_interneuron.pxd b/farms_network/models/li_daun_interneuron_cy.pyx similarity index 100% rename from farms_network/models/li_daun_interneuron.pxd rename to farms_network/models/li_daun_interneuron_cy.pyx diff --git a/farms_network/models/matsuoka.pxd b/farms_network/models/matsuoka.pxd deleted file mode 100644 index 2f21108..0000000 --- a/farms_network/models/matsuoka.pxd +++ /dev/null @@ -1,19 +0,0 @@ -""" ------------------------------------------------------------------------ -Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty -Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. ------------------------------------------------------------------------ -Matsuoka Neuron model -""" diff --git a/farms_network/models/matsuoka.pyx b/farms_network/models/matsuoka.pyx deleted file mode 100644 index 2f21108..0000000 --- a/farms_network/models/matsuoka.pyx +++ /dev/null @@ -1,19 +0,0 @@ -""" ------------------------------------------------------------------------ -Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty -Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. ------------------------------------------------------------------------ -Matsuoka Neuron model -""" diff --git a/farms_network/models/matsuoka_cy.pxd b/farms_network/models/matsuoka_cy.pxd new file mode 100644 index 0000000..dab2aea --- /dev/null +++ b/farms_network/models/matsuoka_cy.pxd @@ -0,0 +1,57 @@ +""" Matsuoka Neuron model """ + + +from ..core.node_cy cimport node_t, node_inputs_t, processed_inputs_t, NodeCy +from ..core.edge_cy cimport edge_t, EdgeCy + + +cdef enum: + #STATES + NSTATES = 2 + STATE_V = 0 + STATE_W= 1 + + +cdef packed struct matsuoka_params_t: + + double c # + double b # + double tau # + double T # + double theta # + double nu # + + +cdef processed_inputs_t matsuoka_input_tf( + double time, + const double* states, + const node_inputs_t inputs, + const node_t* node, + const edge_t** edges, +) noexcept + + +cdef void matsuoka_ode( + double time, + const double* states, + double* derivatives, + processed_inputs_t input_vals, + double noise, + const node_t* node, +) noexcept + + +cdef double matsuoka_output_tf( + double time, + const double* states, + processed_inputs_t input_vals, + double noise, + const node_t* node, +) noexcept + + +cdef class MatsuokaNodeCy(NodeCy): + """ Python interface to Matsuoka Node C-Structure """ + + cdef: + matsuoka_params_t params diff --git a/farms_network/models/matsuoka_cy.pyx b/farms_network/models/matsuoka_cy.pyx new file mode 100644 index 0000000..af9968d --- /dev/null +++ b/farms_network/models/matsuoka_cy.pyx @@ -0,0 +1,63 @@ +""" Matsuoka Neuron model """ + +from libc.stdio cimport printf + + +cpdef enum STATE: + #STATES + nstates = NSTATES + v = STATE_V + w = STATE_W + + +cdef processed_inputs_t matsuoka_input_tf( + double time, + const double* states, + const node_inputs_t inputs, + const node_t* node, + const edge_t** edges, +) noexcept: + # Parameters + cdef oscillator_params_t params = ( node[0].params)[0] + + # States + cdef double state_v = states[STATE.v] + cdef double state_w = states[STATE.w] + + cdef processed_inputs_t processed_inputs = { + 'generic': 0.0, + 'excitatory': 0.0, + 'inhibitory': 0.0, + 'cholinergic': 0.0, + 'phase_coupling': 0.0 + } + + cdef: + double _sum = 0.0 + unsigned int j + double _input, _weight + + for j in range(inputs.ninputs): + _input = inputs.network_outputs[inputs.source_indices[j]] + _weight = inputs.weights[j] + + +cdef void matsuoka_ode( + double time, + const double* states, + double* derivatives, + processed_inputs_t input_vals, + double noise, + const node_t* node, +) noexcept: + pass + + +cdef double matsuoka_output_tf( + double time, + const double* states, + processed_inputs_t input_vals, + double noise, + const node_t* node, +) noexcept: + pass diff --git a/farms_network/models/morphed_oscillator.pxd b/farms_network/models/morphed_oscillator_cy.pxd similarity index 100% rename from farms_network/models/morphed_oscillator.pxd rename to farms_network/models/morphed_oscillator_cy.pxd diff --git a/farms_network/models/morphed_oscillator.pyx b/farms_network/models/morphed_oscillator_cy.pyx similarity index 100% rename from farms_network/models/morphed_oscillator.pyx rename to farms_network/models/morphed_oscillator_cy.pyx diff --git a/farms_network/models/morris_lecar.pyx b/farms_network/models/morris_lecar.pyx deleted file mode 100644 index 7fe32ef..0000000 --- a/farms_network/models/morris_lecar.pyx +++ /dev/null @@ -1,20 +0,0 @@ -""" ------------------------------------------------------------------------ -Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty -Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. ------------------------------------------------------------------------ - -Morris Lecar Neuron model. -""" diff --git a/farms_network/models/morris_lecar.pxd b/farms_network/models/morris_lecar_cy.pxd similarity index 100% rename from farms_network/models/morris_lecar.pxd rename to farms_network/models/morris_lecar_cy.pxd diff --git a/farms_network/models/morris_lecar_cy.pyx b/farms_network/models/morris_lecar_cy.pyx new file mode 100644 index 0000000..da9f621 --- /dev/null +++ b/farms_network/models/morris_lecar_cy.pyx @@ -0,0 +1 @@ +""" Morris Lecar Neuron model. """ From e2fdb13afcbd906e96f0bebbbb4af8122fbf53a9 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Mon, 28 Jul 2025 12:56:45 -0400 Subject: [PATCH 261/316] [NUMERIC] Renamed system and integrators to new architecture --- farms_network/numeric/integrators.py | 0 farms_network/numeric/system.py | 0 farms_network/numeric/{system.pxd => system_cy.pxd} | 0 farms_network/numeric/{system.pyx => system_cy.pyx} | 0 4 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 farms_network/numeric/integrators.py create mode 100644 farms_network/numeric/system.py rename farms_network/numeric/{system.pxd => system_cy.pxd} (100%) rename farms_network/numeric/{system.pyx => system_cy.pyx} (100%) diff --git a/farms_network/numeric/integrators.py b/farms_network/numeric/integrators.py new file mode 100644 index 0000000..e69de29 diff --git a/farms_network/numeric/system.py b/farms_network/numeric/system.py new file mode 100644 index 0000000..e69de29 diff --git a/farms_network/numeric/system.pxd b/farms_network/numeric/system_cy.pxd similarity index 100% rename from farms_network/numeric/system.pxd rename to farms_network/numeric/system_cy.pxd diff --git a/farms_network/numeric/system.pyx b/farms_network/numeric/system_cy.pyx similarity index 100% rename from farms_network/numeric/system.pyx rename to farms_network/numeric/system_cy.pyx From de31e9d0078a045dda37fd19598b62bb5187f607 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Mon, 28 Jul 2025 14:47:07 -0400 Subject: [PATCH 262/316] [DUCKS] Added core and introduction chapters --- ducks/source/core/data.rst | 8 + ducks/source/core/node.rst | 67 +- ducks/source/core/options.rst | 847 +++++++++++++++--- ducks/source/index.rst | 4 +- ducks/source/introduction/concepts.rst | 123 +++ ducks/source/introduction/getting-started.rst | 52 ++ ducks/source/introduction/index.rst | 2 + ducks/source/introduction/installation.rst | 96 +- 8 files changed, 1023 insertions(+), 176 deletions(-) create mode 100644 ducks/source/core/data.rst create mode 100644 ducks/source/introduction/concepts.rst create mode 100644 ducks/source/introduction/getting-started.rst diff --git a/ducks/source/core/data.rst b/ducks/source/core/data.rst new file mode 100644 index 0000000..bebb24b --- /dev/null +++ b/ducks/source/core/data.rst @@ -0,0 +1,8 @@ +.. automodule:: farms_network.core.data + :platform: Unix, Windows + :synopsis: Provides Node C-Structure and Python interface for nodes in a dynamical system. + :members: + :show-inheritance: + :private-members: + :special-members: + :noindex: diff --git a/ducks/source/core/node.rst b/ducks/source/core/node.rst index 891bc00..532d7ed 100644 --- a/ducks/source/core/node.rst +++ b/ducks/source/core/node.rst @@ -1,13 +1,13 @@ Node ==== -This documentation describes the Node structure and Python interface provided in the `node.pyx` and `node.pxd` files. +This documentation describes the Node structure and Python interface provided in the `node_cy.pyx` and `node_cy.pxd` files. Contents -------- - Node C Structure -- PyNode Python Class +- Node Python Class - Functions (ODE and Output) Node C Structure @@ -20,11 +20,66 @@ The simplest case would be a node with one input and one input. A node can have N-states that will be integrated by a numerical integrator over time. A stateless node will have zero states and is useful in using the node as a transfer function. -.. automodule:: farms_network.core.node + +Node +==== + +.. autoclass:: farms_network.node.Node + :members: + +The Node class provides a high-level interface for neural network nodes. + +Constructor +---------- + +.. code-block:: python + + Node(name: str, **kwargs) + +**Parameters:** + +- ``name`` (str): Unique identifier for the node +- ``**kwargs``: Additional configuration parameters passed to NodeCy + +Class Methods +------------ + +from_options +^^^^^^^^^^^ + +Create a node from configuration options. + +.. code-block:: python + + @classmethod + def from_options(cls, node_options: NodeOptions) -> Node + +**Parameters:** + +- ``node_options`` (NodeOptions): Configuration options + +**Returns:** + +- Node: Configured node instance + +Examples +-------- + +Creating a node: + +.. code-block:: python + + # Direct instantiation + node = Node("neuron1") + + # From options + options = NodeOptions(name="neuron1") + node = Node.from_options(options) + + +.. automodule:: farms_network.core.node_cy :platform: Unix, Windows - :synopsis: Provides Node C-Structure and Python interface for nodes in a dynamical system. + :synopsis: Provides Node C-Structure for nodes in a dynamical system. :members: :show-inheritance: - :private-members: - :special-members: :noindex: diff --git a/ducks/source/core/options.rst b/ducks/source/core/options.rst index 09d2a61..304993e 100644 --- a/ducks/source/core/options.rst +++ b/ducks/source/core/options.rst @@ -1,47 +1,8 @@ -Options -======= +Configuration Options +==================== -This module contains the configuration options for neural network models, including options for nodes, edges, integration, and visualization. +This module provides configuration options for neural network components and simulations. -NetworkOptions Class --------------------- -.. autoclass:: farms_network.core.options.NetworkOptions - :members: - :undoc-members: - - **Attributes:** - - - **directed** (bool): Whether the network is directed. Default is `True`. - - **multigraph** (bool): Whether the network allows multiple edges between nodes. Default is `False`. - - **graph** (dict): Graph properties (e.g., name). Default is `{"name": ""}`. - - **units** (optional): Units for the network. Default is `None`. - - **integration** (:class:`IntegrationOptions`): Options for numerical integration. Default values shown in the table below. - -IntegrationOptions Class ------------------------- -.. autoclass:: farms_network.core.options.IntegrationOptions - :members: - :undoc-members: - - The default values for `IntegrationOptions` are as follows: - - +------------+-------------------+ - | Parameter | Default Value | - +------------+-------------------+ - | timestep | ``1e-3`` | - +------------+-------------------+ - | integrator | ``"dopri5"`` | - +------------+-------------------+ - | method | ``"adams"`` | - +------------+-------------------+ - | atol | ``1e-12`` | - +------------+-------------------+ - | rtol | ``1e-6`` | - +------------+-------------------+ - | max_step | ``0.0`` | - +------------+-------------------+ - | checks | ``True`` | - +------------+-------------------+ NodeOptions Class ----------------- @@ -62,30 +23,6 @@ NodeParameterOptions Class :members: :undoc-members: - The default values for `NodeParameterOptions` are as follows: - - +----------------+----------------+ - | Parameter | Default Value | - +================+================+ - | c_m | ``10.0`` pF | - +----------------+----------------+ - | g_leak | ``2.8`` nS | - +----------------+----------------+ - | e_leak | ``-60.0`` mV | - +----------------+----------------+ - | v_max | ``0.0`` mV | - +----------------+----------------+ - | v_thr | ``-50.0`` mV | - +----------------+----------------+ - | g_syn_e | ``10.0`` nS | - +----------------+----------------+ - | g_syn_i | ``10.0`` nS | - +----------------+----------------+ - | e_syn_e | ``-10.0`` mV | - +----------------+----------------+ - | e_syn_i | ``-75.0`` mV | - +----------------+----------------+ - NodeStateOptions Class ---------------------- .. autoclass:: farms_network.core.options.NodeStateOptions @@ -121,83 +58,707 @@ EdgeVisualOptions Class - **color** (list of float): Color of the edge. - **label** (str): Label for the edge. - **layer** (str): Layer in which the edge is displayed. +.. -LIDannerParameterOptions Class ------------------------------- -.. autoclass:: farms_network.core.options.LIDannerParameterOptions - :members: - :undoc-members: + Network Options + ------------- - The default values for `LIDannerParameterOptions` are as follows: - - +----------------+----------------+ - | Parameter | Default Value | - +================+================+ - | c_m | ``10.0`` pF | - +----------------+----------------+ - | g_leak | ``2.8`` nS | - +----------------+----------------+ - | e_leak | ``-60.0`` mV | - +----------------+----------------+ - | v_max | ``0.0`` mV | - +----------------+----------------+ - | v_thr | ``-50.0`` mV | - +----------------+----------------+ - | g_syn_e | ``10.0`` nS | - +----------------+----------------+ - | g_syn_i | ``10.0`` nS | - +----------------+----------------+ - | e_syn_e | ``-10.0`` mV | - +----------------+----------------+ - | e_syn_i | ``-75.0`` mV | - +----------------+----------------+ - -LINaPDannerParameterOptions Class ---------------------------------- -.. autoclass:: farms_network.core.options.LINaPDannerParameterOptions - :members: - :undoc-members: + .. autoclass:: farms_network.options.NetworkOptions + :members: + :undoc-members: + + The main configuration class for neural networks. Controls network structure, simulation parameters, and logging. + + **Key Attributes:** + + - ``directed``: Network directionality (default: True) + - ``multigraph``: Allow multiple edges between nodes (default: False) + - ``nodes``: List of :class:`NodeOptions` + - ``edges``: List of :class:`EdgeOptions` + - ``integration``: :class:`IntegrationOptions` for simulation settings + - ``logs``: :class:`NetworkLogOptions` for data collection + - ``random_seed``: Seed for reproducibility + + Node Options + ----------- + + .. autoclass:: farms_network.options.NodeOptions + :members: + :undoc-members: + + Base class for neuron configuration. + + **Key Attributes:** + + - ``name``: Unique identifier + - ``model``: Neural model type + - ``parameters``: Model-specific parameters + - ``state``: Initial state variables + - ``visual``: Visualization settings + - ``noise``: Noise configuration + + Available Node Models + ^^^^^^^^^^^^^^^^^^^ + + * :class:`RelayNodeOptions`: Simple signal relay + * :class:`LinearNodeOptions`: Linear transformation + * :class:`ReLUNodeOptions`: Rectified linear unit + * :class:`OscillatorNodeOptions`: Phase-amplitude oscillator + * :class:`LIDannerNodeOptions`: Leaky integrator (Danner model) + * :class:`LINaPDannerNodeOptions`: Leaky integrator with NaP + + Edge Options + ----------- + + .. autoclass:: farms_network.options.EdgeOptions + :members: + :undoc-members: + + Configuration for synaptic connections. + + **Key Attributes:** + + - ``source``: Source node name + - ``target``: Target node name + - ``weight``: Connection strength + - ``type``: Synapse type (e.g., excitatory/inhibitory) + - ``parameters``: Model-specific parameters + - ``visual``: Visualization settings + + Integration Options + ----------------- + + .. autoclass:: farms_network.options.IntegrationOptions + :members: + :undoc-members: + + Numerical integration settings. + + **Key Attributes:** + + - ``timestep``: Integration step size + - ``n_iterations``: Number of iterations + - ``integrator``: Integration method (e.g., 'rk4') + - ``method``: Solver method + - ``atol``: Absolute tolerance + - ``rtol``: Relative tolerance + + Example Usage + ----------- + + Basic network configuration: + + .. code-block:: python + + from farms_network.options import NetworkOptions, LIDannerNodeOptions, EdgeOptions + + # Create network options + net_opts = NetworkOptions( + directed=True, + integration=IntegrationOptions.defaults(timestep=0.1), + logs=NetworkLogOptions(n_iterations=1000) + ) + + # Add nodes + node1 = LIDannerNodeOptions( + name="neuron1", + parameters=LIDannerNodeParameterOptions.defaults() + ) + net_opts.add_node(node1) + + # Add edges + edge = EdgeOptions( + source="neuron1", + target="neuron2", + weight=0.5, + type="excitatory" + ) + net_opts.add_edge(edge) + + + Configuration Options + ==================== + + Base Options + ----------- + + Node Options + ^^^^^^^^^^^ + + .. autoclass:: farms_network.options.NodeOptions + :members: + + Base class for all node configurations. + + **Attributes:** + + - ``name`` (str): Unique identifier + - ``model`` (str): Neural model type + - ``parameters`` (NodeParameterOptions): Model-specific parameters + - ``state`` (NodeStateOptions): Initial state variables + - ``visual`` (NodeVisualOptions): Visualization settings + - ``noise`` (NoiseOptions): Noise configuration + + Node Visual Options + ^^^^^^^^^^^^^^^^^ + + .. autoclass:: farms_network.options.NodeVisualOptions + :members: + + Visualization settings for nodes. + + **Default Values:** + + .. list-table:: + :header-rows: 1 + + * - Parameter + - Default Value + - Description + * - position + - [0.0, 0.0, 0.0] + - 3D coordinates + * - radius + - 1.0 + - Node size + * - color + - [1.0, 0.0, 0.0] + - RGB values + * - label + - "n" + - Display label + * - layer + - "background" + - Rendering layer + * - latex + - "{}" + - LaTeX formatting + + Node State Options + ^^^^^^^^^^^^^^^^^ + + .. autoclass:: farms_network.options.NodeStateOptions + :members: + + Base class for node states. + + **Attributes:** + + - ``initial`` (List[float]): Initial state values + - ``names`` (List[str]): State variable names + + Node Models + ----------- + + Relay Node + ^^^^^^^^^ + + .. autoclass:: farms_network.options.RelayNodeOptions + :members: + + Simple signal relay node. + + Linear Node + ^^^^^^^^^^ + + .. autoclass:: farms_network.options.LinearNodeOptions + :members: + + Linear transformation node. + + **Default Parameters:** + + .. list-table:: + :header-rows: 1 + + * - Parameter + - Default Value + - Description + * - slope + - 1.0 + - Linear transformation slope + * - bias + - 0.0 + - Constant offset + + ReLU Node + ^^^^^^^^ + + .. autoclass:: farms_network.options.ReLUNodeOptions + :members: + + Rectified Linear Unit node. + + **Default Parameters:** + + .. list-table:: + :header-rows: 1 + + * - Parameter + - Default Value + - Description + * - gain + - 1.0 + - Amplification factor + * - sign + - 1 + - Direction (+1/-1) + * - offset + - 0.0 + - Activation threshold + + Oscillator Node + ^^^^^^^^^^^^^ + + .. autoclass:: farms_network.options.OscillatorNodeOptions + :members: + + Phase-amplitude oscillator. + + **Default Parameters:** + + .. list-table:: + :header-rows: 1 + + * - Parameter + - Default Value + - Description + * - intrinsic_frequency + - 1.0 + - Base frequency (Hz) + * - nominal_amplitude + - 1.0 + - Base amplitude + * - amplitude_rate + - 1.0 + - Amplitude change rate + + Leaky Integrator Node (Danner) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + .. autoclass:: farms_network.options.LIDannerNodeOptions + :members: + + Leaky integrator with Danner dynamics. + + **Default Parameters:** + + .. list-table:: + :header-rows: 1 + + * - Parameter + - Default Value + - Description + * - c_m + - 10.0 + - Membrane capacitance (pF) + * - g_leak + - 2.8 + - Leak conductance (nS) + * - e_leak + - -60.0 + - Leak reversal potential (mV) + * - v_max + - 0.0 + - Maximum voltage (mV) + * - v_thr + - -50.0 + - Threshold voltage (mV) + * - g_syn_e + - 10.0 + - Excitatory synaptic conductance (nS) + * - g_syn_i + - 10.0 + - Inhibitory synaptic conductance (nS) + * - e_syn_e + - -10.0 + - Excitatory synaptic reversal potential (mV) + * - e_syn_i + - -75.0 + - Inhibitory synaptic reversal potential (mV) + * - tau_ch + - 5.0 + - Cholinergic time constant (ms) + + LINaP Node (Danner) + ^^^^^^^^^^^^^^^^^ + + .. autoclass:: farms_network.options.LINaPDannerNodeOptions + :members: + + Leaky integrator with persistent sodium current. + + **Default Parameters:** + + .. list-table:: + :header-rows: 1 + + * - Parameter + - Default Value + - Description + * - c_m + - 10.0 + - Membrane capacitance (pF) + * - g_nap + - 4.5 + - NaP conductance (nS) + * - e_na + - 50.0 + - Sodium reversal potential (mV) + * - v1_2_m + - -40.0 + - Half-activation voltage (mV) + * - k_m + - -6.0 + - Activation slope + * - v1_2_h + - -45.0 + - Half-inactivation voltage (mV) + * - k_h + - 4.0 + - Inactivation slope + * - v1_2_t + - -35.0 + - Threshold half-activation (mV) + * - k_t + - 15.0 + - Threshold slope + * - g_leak + - 4.5 + - Leak conductance (nS) + * - e_leak + - -62.5 + - Leak reversal potential (mV) + * - tau_0 + - 80.0 + - Base time constant (ms) + * - tau_max + - 160.0 + - Maximum time constant (ms) + + Edge Options + ----------- + + .. autoclass:: farms_network.options.EdgeOptions + :members: + + Configuration for synaptic connections. + + **Attributes:** + + - ``source`` (str): Source node name + - ``target`` (str): Target node name + - ``weight`` (float): Connection strength + - ``type`` (str): Synapse type + - ``parameters`` (EdgeParameterOptions): Model-specific parameters + - ``visual`` (EdgeVisualOptions): Visualization settings + + Edge Visual Options + ^^^^^^^^^^^^^^^^^ + + .. autoclass:: farms_network.options.EdgeVisualOptions + :members: + + **Default Values:** + + .. list-table:: + :header-rows: 1 + + * - Parameter + - Default Value + - Description + * - color + - [1.0, 0.0, 0.0] + - RGB values + * - alpha + - 1.0 + - Transparency + * - label + - "" + - Display label + * - layer + - "background" + - Rendering layer + * - arrowstyle + - "->" + - Arrow appearance + * - connectionstyle + - "arc3,rad=0.1" + - Connection curve + * - linewidth + - 1.5 + - Line thickness + * - edgecolor + - [0.0, 0.0, 0.0] + - Border color + + Integration Options + ----------------- + + .. autoclass:: farms_network.options.IntegrationOptions + :members: + + Numerical integration settings. + + **Default Values:** + + .. list-table:: + :header-rows: 1 + + * - Parameter + - Default Value + - Description + * - timestep + - 0.001 + - Integration step (s) + * - n_iterations + - 1000 + - Number of steps + * - integrator + - "rk4" + - Integration method + * - method + - "adams" + - Solver method + * - atol + - 1e-12 + - Absolute tolerance + * - rtol + - 1e-6 + - Relative tolerance + * - max_step + - 0.0 + - Maximum step size + * - checks + - True + - Enable validation + + Network Log Options + ----------------- + + .. autoclass:: farms_network.options.NetworkLogOptions + :members: + + Logging configuration. + + **Default Values:** + + .. list-table:: + :header-rows: 1 + + * - Parameter + - Default Value + - Description + * - n_iterations + - Required + - Number of iterations to log + * - buffer_size + - n_iterations + - Log buffer size + * - nodes_all + - False + - Log all nodes + + Options + ======= + + This module contains the configuration options for neural network models, including options for nodes, edges, integration, and visualization. + + NetworkOptions Class + -------------------- + .. autoclass:: farms_network.core.options.NetworkOptions + :members: + :undoc-members: + + **Attributes:** + + - **directed** (bool): Whether the network is directed. Default is `True`. + - **multigraph** (bool): Whether the network allows multiple edges between nodes. Default is `False`. + - **graph** (dict): Graph properties (e.g., name). Default is `{"name": ""}`. + - **units** (optional): Units for the network. Default is `None`. + - **integration** (:class:`IntegrationOptions`): Options for numerical integration. Default values shown in the table below. + + IntegrationOptions Class + ------------------------ + .. autoclass:: farms_network.core.options.IntegrationOptions + :members: + :undoc-members: + + The default values for `IntegrationOptions` are as follows: + + +------------+-------------------+ + | Parameter | Default Value | + +------------+-------------------+ + | timestep | ``1e-3`` | + +------------+-------------------+ + | integrator | ``"dopri5"`` | + +------------+-------------------+ + | method | ``"adams"`` | + +------------+-------------------+ + | atol | ``1e-12`` | + +------------+-------------------+ + | rtol | ``1e-6`` | + +------------+-------------------+ + | max_step | ``0.0`` | + +------------+-------------------+ + | checks | ``True`` | + +------------+-------------------+ + + NodeOptions Class + ----------------- + .. autoclass:: farms_network.core.options.NodeOptions + :members: + :undoc-members: + + **Attributes:** + + - **name** (str): Name of the node. + - **model** (str): Node model type. + - **parameters** (:class:`NodeParameterOptions`): Node-specific parameters. + - **state** (:class:`NodeStateOptions`): Node state options. + + NodeParameterOptions Class + -------------------------- + .. autoclass:: farms_network.core.options.NodeParameterOptions + :members: + :undoc-members: + + The default values for `NodeParameterOptions` are as follows: + + +----------------+----------------+ + | Parameter | Default Value | + +================+================+ + | c_m | ``10.0`` pF | + +----------------+----------------+ + | g_leak | ``2.8`` nS | + +----------------+----------------+ + | e_leak | ``-60.0`` mV | + +----------------+----------------+ + | v_max | ``0.0`` mV | + +----------------+----------------+ + | v_thr | ``-50.0`` mV | + +----------------+----------------+ + | g_syn_e | ``10.0`` nS | + +----------------+----------------+ + | g_syn_i | ``10.0`` nS | + +----------------+----------------+ + | e_syn_e | ``-10.0`` mV | + +----------------+----------------+ + | e_syn_i | ``-75.0`` mV | + +----------------+----------------+ + + NodeStateOptions Class + ---------------------- + .. autoclass:: farms_network.core.options.NodeStateOptions + :members: + :undoc-members: + + **Attributes:** + + - **initial** (list of float): Initial state values. + - **names** (list of str): State variable names. + + EdgeOptions Class + ----------------- + .. autoclass:: farms_network.core.options.EdgeOptions + :members: + :undoc-members: + + **Attributes:** + + - **from_node** (str): Source node of the edge. + - **to_node** (str): Target node of the edge. + - **weight** (float): Weight of the edge. + - **type** (str): Edge type (e.g., excitatory, inhibitory). + + EdgeVisualOptions Class + ----------------------- + .. autoclass:: farms_network.core.options.EdgeVisualOptions + :members: + :undoc-members: + + **Attributes:** + + - **color** (list of float): Color of the edge. + - **label** (str): Label for the edge. + - **layer** (str): Layer in which the edge is displayed. + + LIDannerParameterOptions Class + ------------------------------ + .. autoclass:: farms_network.core.options.LIDannerParameterOptions + :members: + :undoc-members: + + The default values for `LIDannerParameterOptions` are as follows: + + +----------------+----------------+ + | Parameter | Default Value | + +================+================+ + | c_m | ``10.0`` pF | + +----------------+----------------+ + | g_leak | ``2.8`` nS | + +----------------+----------------+ + | e_leak | ``-60.0`` mV | + +----------------+----------------+ + | v_max | ``0.0`` mV | + +----------------+----------------+ + | v_thr | ``-50.0`` mV | + +----------------+----------------+ + | g_syn_e | ``10.0`` nS | + +----------------+----------------+ + | g_syn_i | ``10.0`` nS | + +----------------+----------------+ + | e_syn_e | ``-10.0`` mV | + +----------------+----------------+ + | e_syn_i | ``-75.0`` mV | + +----------------+----------------+ + + LINaPDannerParameterOptions Class + --------------------------------- + .. autoclass:: farms_network.core.options.LINaPDannerParameterOptions + :members: + :undoc-members: + + The default values for `LIDannerNaPParameterOptions` are as follows: - The default values for `LIDannerNaPParameterOptions` are as follows: - - +----------------+----------------+ - | Parameter | Default Value | - +================+================+ - | c_m | ``10.0`` pF | - +----------------+----------------+ - | g_nap | ``4.5`` nS | - +----------------+----------------+ - | e_na | ``50.0`` mV | - +----------------+----------------+ - | v1_2_m | ``-40.0`` mV | - +----------------+----------------+ - | k_m | ``-6.0`` | - +----------------+----------------+ - | v1_2_h | ``-45.0`` mV | - +----------------+----------------+ - | k_h | ``4.0`` | - +----------------+----------------+ - | v1_2_t | ``-35.0`` mV | - +----------------+----------------+ - | k_t | ``15.0`` | - +----------------+----------------+ - | g_leak | ``4.5`` nS | - +----------------+----------------+ - | e_leak | ``-62.5`` mV | - +----------------+----------------+ - | tau_0 | ``80.0`` ms | - +----------------+----------------+ - | tau_max | ``160.0`` ms | - +----------------+----------------+ - | v_max | ``0.0`` mV | - +----------------+----------------+ - | v_thr | ``-50.0`` mV | - +----------------+----------------+ - | g_syn_e | ``10.0`` nS | - +----------------+----------------+ - | g_syn_i | ``10.0`` nS | - +----------------+----------------+ - | e_syn_e | ``-10.0`` mV | - +----------------+----------------+ - | e_syn_i | ``-75.0`` mV | - +----------------+----------------+ + +----------------+----------------+ + | Parameter | Default Value | + +================+================+ + | c_m | ``10.0`` pF | + +----------------+----------------+ + | g_nap | ``4.5`` nS | + +----------------+----------------+ + | e_na | ``50.0`` mV | + +----------------+----------------+ + | v1_2_m | ``-40.0`` mV | + +----------------+----------------+ + | k_m | ``-6.0`` | + +----------------+----------------+ + | v1_2_h | ``-45.0`` mV | + +----------------+----------------+ + | k_h | ``4.0`` | + +----------------+----------------+ + | v1_2_t | ``-35.0`` mV | + +----------------+----------------+ + | k_t | ``15.0`` | + +----------------+----------------+ + | g_leak | ``4.5`` nS | + +----------------+----------------+ + | e_leak | ``-62.5`` mV | + +----------------+----------------+ + | tau_0 | ``80.0`` ms | + +----------------+----------------+ + | tau_max | ``160.0`` ms | + +----------------+----------------+ + | v_max | ``0.0`` mV | + +----------------+----------------+ + | v_thr | ``-50.0`` mV | + +----------------+----------------+ + | g_syn_e | ``10.0`` nS | + +----------------+----------------+ + | g_syn_i | ``10.0`` nS | + +----------------+----------------+ + | e_syn_e | ``-10.0`` mV | + +----------------+----------------+ + | e_syn_i | ``-75.0`` mV | + +----------------+----------------+ diff --git a/ducks/source/index.rst b/ducks/source/index.rst index f6337ec..62cc0dc 100644 --- a/ducks/source/index.rst +++ b/ducks/source/index.rst @@ -9,9 +9,7 @@ .. warning:: Farmers are currently busy! Documentation is work in progress!! -farms network provides commonly used neural models for locomotion circuits. - -A neural network simulation library designed for simulating sparse neural networks with models such as leaky integrators with efficient computing through Cython and future GPU integration. +A neural network simulation library designed for simulating sparse neural networks written primrarily in python with efficient computing handled through Cython. Farms network provides commonly used neural models for locomotion circuits. ============= Introduction diff --git a/ducks/source/introduction/concepts.rst b/ducks/source/introduction/concepts.rst new file mode 100644 index 0000000..c212118 --- /dev/null +++ b/ducks/source/introduction/concepts.rst @@ -0,0 +1,123 @@ +Core Concepts +============= + +Network Components +------------------ + +Node +^^^^ +Basic unit representing a computational node, a neuron is a form of a node. A node receives n inputs and has n outputs. + +Properties: + +* Dynamics (ode)/computation +* Input integration +* Output generation + +Available dynamics models: + * Base + * Relay + * Linear + * Relu + * Oscillator + * Hopf oscillator + * Morphed oscillator + * Matsuoka oscillator + * Fitzhugh nagumo oscillator + * Morris lecar oscillator + * Leaky integrator + * Leaky integrator (danner) + * Leaky integrator with persistence sodium (danner) + * Leaky integrator (daun) + * Hodgkin-Huxley (daun) + * Izhikevich + * Hodgkin-Huxley + * Custom (user-defined) + +Edge +^^^^ +Connection between any two nodes. Characteristics: + +* Source node +* Target node +* Weight (connection strength) +* Type (excitatory/inhibitory/cholinergic) +* Parameters + +Network +^^^^^^^ +The primary container for the circuit simulation. Networks manage: + +* Component organization and connectivity +* Simulation configuration +* State tracking and data collection +* Input/output handling + +Simulation Elements +------------------- + +Time Management +^^^^^^^^^^^^^^^ +* ``dt``: Integration time step +* ``simulation_duration``: Total simulation time +* ``sampling_rate``: Data collection frequency + +State Variables +^^^^^^^^^^^^^^^ +* Membrane potentials +* Synaptic currents +* Ionic concentrations +* Firing rates +* Custom variables + +Input Handling +^^^^^^^^^^^^^^ +* External current injection +* Spike trains +* Continuous signals +* Stochastic inputs + +Output and Analysis +^^^^^^^^^^^^^^^^^^^ +* Membrane potential traces +* Spike times +* Population activity +* Network statistics +* Custom metrics + +Configuration +------------- + +Network Setup +^^^^^^^^^^^^^ +.. code-block:: python + + net.configure( + dt=0.1, # Time step (ms) + simulation_duration=1000.0, # Duration (ms) + sampling_rate=1.0, # Recording frequency (ms) + backend='cpu' # backend + ) + +Node Configuration +^^^^^^^^^^^^^^^^^^ +.. code node:: python + + neuron.configure( + threshold=-55.0, # Firing threshold (mV) + reset_potential=-70.0, # Reset potential (mV) + refractory_period=2.0, # Refractory period (ms) + capacitance=1.0, # Membrane capacitance (pF) + leak_conductance=0.1 # Leak conductance (nS) + ) + +Synapse Configuration +^^^^^^^^^^^^^^^^^^^^^ +.. code-block:: python + +Edge + + delay=1.0, # Transmission delay (ms) + plasticity='stdp', # Plasticity rule + learning_rate=0.01 # Learning rate + ) diff --git a/ducks/source/introduction/getting-started.rst b/ducks/source/introduction/getting-started.rst new file mode 100644 index 0000000..8f494ab --- /dev/null +++ b/ducks/source/introduction/getting-started.rst @@ -0,0 +1,52 @@ +Getting Started +=============== + +This guide demonstrates basic usage of farms_network through a simple example. + +Basic Example +------------ + +Let's create a simple neural network that simulates a basic reflex circuit: + +.. code-block:: python + + from farms_network import Network, Node, Edge + import numpy as np + + # Create network + net = Network('reflex_circuit') + + # Add neurons + sensory = Node('sensory', dynamics='leaky_integrator') + inter = Node('interneuron', dynamics='leaky_integrator') + motor = Node('motor', dynamics='leaky_integrator') + + # Add neurons to network + net.add_neurons([sensory, inter, motor]) + + # Create synaptic connections + syn1 = Synapse('sensory_to_inter', sensory, inter, weight=0.5) + syn2 = Synapse('inter_to_motor', inter, motor, weight=0.8) + + # Add synapses to network + net.add_synapses([syn1, syn2]) + + # Configure simulation parameters + net.configure(dt=0.1, simulation_duration=10.0) + + # Add input stimulus + stimulus = np.sin(np.linspace(0, 10, 100)) + net.set_external_input(sensory, stimulus) + + # Run simulation + results = net.simulate() + + # Plot results + net.plot_results(results) + +Key Concepts +----------- + +* **Network**: The main container for your neural circuit +* **Node**: Represents a neural unit with specific dynamics +* **Synapse**: Defines connections between neur diff --git a/ducks/source/introduction/index.rst b/ducks/source/introduction/index.rst index b0aa207..ae9472d 100644 --- a/ducks/source/introduction/index.rst +++ b/ducks/source/introduction/index.rst @@ -4,3 +4,5 @@ Introduction .. toctree:: installation + getting-started + concepts diff --git a/ducks/source/introduction/installation.rst b/ducks/source/introduction/installation.rst index 0cf2322..f97427c 100644 --- a/ducks/source/introduction/installation.rst +++ b/ducks/source/introduction/installation.rst @@ -2,52 +2,100 @@ Installation ============== +Code is currently tested with Python3 version ["3.11", "3.13"] on ubuntu-latest, macos-latest, windows-latest using docker images on GitHub. +We recommend using a virtual environment to avoid conflicts with other Python packages. +This guide will help you install farms_network and its dependencies. -Requirements -============ +Prerequisites +------------ +Before installing farms_network, ensure you have the following prerequisites: -"Code is only currently tested with Python3" +* Python 3.11 or higher +* pip (Python package installer) +* A C++ compiler (for building extensions) -The installation requires Cython. To install Cython, -.. code-block:: console +Basic Installation +---------------- - $ pip install cython +You can install farms_network using pip: + +.. code-block:: bash + + pip git+install https://github.com/farmsim/farms_network.git + +(You may use `--user` option with the above command if you want install only for a user) (**NOTE** : *Depending on your system installation you may want to use pip3 instead of pip to use python3*) -Installation -^^^^^^^^^^^^^ +Development Installation +---------------------- + +For development purposes, you can install farms_network from source: + +.. code-block:: bash + + git clone https://github.com/username/farms_network.git + cd farms_network + pip install -e .[dev] + +The `[dev]` flag will install additional dependencies needed for development. + +Optional Dependencies +------------------- + +farms_network has several optional dependencies for extended functionality: + +.. + * **GPU Support**: For GPU acceleration + + .. code-block:: bash + + pip install farms_network[gpu] + +* **Visualization**: For advanced visualization features + + .. code-block:: bash + + pip install farms_network[viz] + -- Navigate to the root of the directory: +Updating farms_network +-------------------- - .. code-block:: console +To update an existing installation to the latest version: - $ cd farms_network +.. code-block:: bash -- Install system wide with pip: + pip install --upgrade farms_network - .. code-block:: console +To update to a specific version: - $ pip install . +.. code-block:: bash -- Install for user with pip: + pip install --upgrade farms_network== - .. code-block:: console +Note: After updating, it's recommended to restart any running applications or kernels using farms_network. - $ pip install . --user +Troubleshooting +-------------- -- Install in developer mode so that you don't have to install every time you make changes to the repository: +Common Installation Issues +^^^^^^^^^^^^^^^^^^^^^^^^ - .. code-block:: console +1. **Compiler errors**: Ensure you have a compatible C++ compiler installed +2. **Missing dependencies**: Try installing the package with all optional dependencies: - $ pip install -e . + .. code-block:: bash - - (You may use `--user` option with the above command if you want install only for a user): + pip install farms_network[all] -- To only compile the module: +3. **Version conflicts**: If you encounter dependency conflicts, try creating a fresh virtual environment: - .. code-block:: console + .. code-block:: bash - $ python setup.py build_ext -i + python -m venv farms_env + source farms_env/bin/activate # On Unix + # or + farms_env\Scripts\activate # On Windows From 1308aef2e05fa501c490d91e2e071aa84be847df Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Tue, 29 Jul 2025 10:29:47 -0400 Subject: [PATCH 263/316] [MODELS] Oscillator added missing property decorator --- farms_network/models/oscillator_cy.pyx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/farms_network/models/oscillator_cy.pyx b/farms_network/models/oscillator_cy.pyx index 0396e8f..bf50711 100644 --- a/farms_network/models/oscillator_cy.pyx +++ b/farms_network/models/oscillator_cy.pyx @@ -63,6 +63,7 @@ cdef void oscillator_ode( double noise, const node_t* node, ) noexcept: + # Parameters cdef oscillator_params_t params = ( node[0].params)[0] cdef oscillator_edge_params_t edge_params @@ -121,7 +122,7 @@ cdef class OscillatorNodeCy(NodeCy): if kwargs: raise Exception(f'Unknown kwargs: {kwargs}') - + @property def parameters(self): """ Parameters in the network """ cdef oscillator_params_t params = ( self._node.params)[0] From 3d4a3d3519baa95313342be8facf6f7ae7f9d7e4 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Tue, 29 Jul 2025 10:30:15 -0400 Subject: [PATCH 264/316] [CORE] Fixed instantiation of edge class from options --- farms_network/core/options.py | 51 +++++++++++------------------------ 1 file changed, 15 insertions(+), 36 deletions(-) diff --git a/farms_network/core/options.py b/farms_network/core/options.py index 3ba0b3b..4a52ebd 100644 --- a/farms_network/core/options.py +++ b/farms_network/core/options.py @@ -153,7 +153,14 @@ def __init__(self, **kwargs): self.target: str = kwargs.pop("target") self.weight: float = kwargs.pop("weight") self.type = EdgeTypes.to_str(kwargs.pop("type")) - self.model = kwargs.pop("model", EdgeOptions.MODEL) + model = kwargs.pop("model", Models.BASE) + if isinstance(model, Models): + model = Models.to_str(model) + elif not isinstance(model, str): + raise TypeError( + f"{model} is of {type(model)}. Needs to {type(Models)} or {type(str)}" + ) + self.model: str = model self.parameters: EdgeParameterOptions = kwargs.pop("parameters", EdgeParameterOptions()) self.visual: EdgeVisualOptions = kwargs.pop("visual") @@ -547,9 +554,9 @@ def __init__(self, **kwargs): parameters = kwargs.pop("parameters") assert isinstance(parameters, OscillatorEdgeParameterOptions) super().__init__( - model=OscillatorEdgeOptions.MODEL, source=kwargs.pop("source"), target=kwargs.pop("target"), + model=OscillatorEdgeOptions.MODEL, weight=kwargs.pop("weight"), type=kwargs.pop("type"), parameters=parameters, @@ -876,39 +883,6 @@ def __init__(self, **kwargs): # assert len(self.initial) == 2, f"Number of initial states {len(self.initial)} should be 2" -class LIDannerEdgeOptions(EdgeOptions): - """ LIDanner edge options """ - - MODEL = Models.LI_DANNER - - def __init__(self, **kwargs): - """ Initialize """ - super().__init__( - model=LIDannerEdgeOptions.MODEL, - source=kwargs.pop("source"), - target=kwargs.pop("target"), - weight=kwargs.pop("weight"), - type=kwargs.pop("type"), - parameters=kwargs.pop("parameters"), - visual=kwargs.pop("visual"), - ) - if kwargs: - raise Exception(f'Unknown kwargs: {kwargs}') - - @classmethod - def from_options(cls, kwargs: Dict): - """ From options """ - - options = {} - options["source"] = kwargs["source"] - options["target"] = kwargs["target"] - options["weight"] = kwargs["weight"] - options["type"] = kwargs["type"] - options["parameters"] = None - options["visual"] = EdgeVisualOptions.from_options(kwargs["visual"]) - return cls(**options) - - ################################################## # Leaky Integrator With NaP Danner Model Options # ################################################## @@ -1109,6 +1083,11 @@ class NetworkOptions(Options): # Models.HH_DAUN: HHDaunNodeOptions, } + EDGE_TYPES: Dict[Models, Type] = { + Models.BASE: EdgeOptions, + Models.OSCILLATOR: OscillatorEdgeOptions, + } + def __init__(self, **kwargs): super().__init__() @@ -1150,7 +1129,7 @@ def from_options(cls, kwargs): ] # Edges options["edges"] = [ - EdgeOptions.from_options(edge) + cls.EDGE_TYPES[edge["model"]].from_options(edge) for edge in kwargs["edges"] ] return cls(**options) From d74c6332de8bff8be3a814375067c3fed594ab72 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Wed, 30 Jul 2025 14:48:44 -0400 Subject: [PATCH 265/316] [CORE] Addressed major edge indexing issue --- farms_network/core/data.py | 50 +++++---- farms_network/core/data_cy.pxd | 5 +- farms_network/core/data_cy.pyx | 10 +- farms_network/core/network.py | 7 +- farms_network/core/network_cy.pxd | 13 +-- farms_network/core/network_cy.pyx | 114 ++++++++------------ farms_network/core/node_cy.pxd | 3 +- farms_network/models/hopf_oscillator_cy.pyx | 2 +- farms_network/models/li_danner_cy.pyx | 11 +- farms_network/models/li_nap_danner_cy.pyx | 15 +-- farms_network/models/linear_cy.pyx | 2 +- farms_network/models/oscillator_cy.pyx | 4 +- farms_network/models/relu_cy.pyx | 2 +- 13 files changed, 113 insertions(+), 125 deletions(-) diff --git a/farms_network/core/data.py b/farms_network/core/data.py index 3c01075..f89ce1c 100644 --- a/farms_network/core/data.py +++ b/farms_network/core/data.py @@ -158,8 +158,8 @@ def to_dict(self, iteration: int = None) -> Dict: class NetworkConnectivity(NetworkConnectivityCy): - def __init__(self, sources, weights, indices): - super().__init__(sources, weights, indices) + def __init__(self, node_indices, edge_indices, weights, index_offsets): + super().__init__(node_indices, edge_indices, weights, index_offsets) @classmethod def from_options(cls, network_options: NetworkOptions): @@ -168,7 +168,7 @@ def from_options(cls, network_options: NetworkOptions): edges = network_options.edges connectivity = np.full( - shape=(len(edges), 3), + shape=(len(edges), 4), fill_value=0, dtype=NPDTYPE, ) @@ -178,9 +178,10 @@ def from_options(cls, network_options: NetworkOptions): connectivity[index][0] = int(node_names.index(edge.source)) connectivity[index][1] = int(node_names.index(edge.target)) connectivity[index][2] = edge.weight + connectivity[index][3] = index connectivity = np.array(sorted(connectivity, key=lambda col: col[1])) - sources = np.full( + node_indices = np.full( shape=len(edges), fill_value=0, dtype=NPDTYPE, @@ -190,29 +191,38 @@ def from_options(cls, network_options: NetworkOptions): fill_value=0, dtype=NPDTYPE, ) + edge_indices = np.full( + shape=len(edges), + fill_value=0, + dtype=NPDTYPE, + ) nedges = 0 - indices = [] + index_offsets = [] if len(edges) > 0: - indices.append(0) + index_offsets.append(0) for index, node in enumerate(nodes): - node_sources = connectivity[connectivity[:, 1] == index][:, 0].tolist() - node_weights = connectivity[connectivity[:, 1] == index][:, 2].tolist() - nedges += len(node_sources) - indices.append(nedges) - sources[indices[index]:indices[index+1]] = node_sources - weights[indices[index]:indices[index+1]] = node_weights + _node_indices = connectivity[connectivity[:, 1] == index][:, 0].tolist() + _weights = connectivity[connectivity[:, 1] == index][:, 2].tolist() + _edge_indices = connectivity[connectivity[:, 1] == index][:, 3].tolist() + nedges += len(_node_indices) + index_offsets.append(nedges) + node_indices[index_offsets[index]:index_offsets[index+1]] = _node_indices + edge_indices[index_offsets[index]:index_offsets[index+1]] = _edge_indices + weights[index_offsets[index]:index_offsets[index+1]] = _weights return cls( - sources=np.array(sources, dtype=NPUITYPE), + node_indices=np.array(node_indices, dtype=NPUITYPE), + edge_indices=np.array(edge_indices, dtype=NPUITYPE), weights=np.array(weights, dtype=NPDTYPE), - indices=np.array(indices, dtype=NPUITYPE) + index_offsets=np.array(index_offsets, dtype=NPUITYPE) ) def to_dict(self, iteration: int = None) -> Dict: """Convert data to dictionary""" return { - 'sources': to_array(self.sources), + 'node_indices': to_array(self.node_indices), + 'edge_indices': to_array(self.edge_indices), 'weights': to_array(self.weights), - 'indices': to_array(self.indices), + 'index_offsets': to_array(self.index_offsets), } @@ -230,10 +240,10 @@ def from_options(cls, network_options: NetworkOptions): n_nodes = len(nodes) indices = [] - for index, node in enumerate(nodes): - if node.noise and node.noise.is_stochastic: - n_noise_states += 1 - indices.append(index) + # for index, node in enumerate(nodes): + # if node.noise and node.noise.is_stochastic: + # n_noise_states += 1 + # indices.append(index) return cls( states=np.full( diff --git a/farms_network/core/data_cy.pxd b/farms_network/core/data_cy.pxd index c91bda3..7082a1e 100644 --- a/farms_network/core/data_cy.pxd +++ b/farms_network/core/data_cy.pxd @@ -48,8 +48,9 @@ cdef class NetworkConnectivityCy: cdef: public DTYPEv1 weights - public UITYPEv1 sources - public UITYPEv1 indices + public UITYPEv1 node_indices + public UITYPEv1 edge_indices + public UITYPEv1 index_offsets cdef class NetworkNoiseCy: diff --git a/farms_network/core/data_cy.pyx b/farms_network/core/data_cy.pyx index cd83a9d..0422831 100644 --- a/farms_network/core/data_cy.pyx +++ b/farms_network/core/data_cy.pyx @@ -53,14 +53,16 @@ cdef class NetworkConnectivityCy: def __init__( self, - sources: NDArray[(Any,), np.uintc], + node_indices: NDArray[(Any,), np.uintc], + edge_indices: NDArray[(Any,), np.uintc], weights: NDArray[(Any,), np.double], - indices: NDArray[(Any,), np.uintc], + index_offsets: NDArray[(Any,), np.uintc], ): super().__init__() - self.sources = np.array(sources, dtype=np.uintc) + self.node_indices = np.array(node_indices, dtype=np.uintc) + self.edge_indices = np.array(edge_indices, dtype=np.uintc) self.weights = np.array(weights, dtype=np.double) - self.indices = np.array(indices, dtype=np.uintc) + self.index_offsets = np.array(index_offsets, dtype=np.uintc) cdef class NetworkNoiseCy: diff --git a/farms_network/core/network.py b/farms_network/core/network.py index 674d497..c8d5265 100644 --- a/farms_network/core/network.py +++ b/farms_network/core/network.py @@ -28,7 +28,6 @@ def __init__(self, network_options: NetworkOptions): data=self.data ) - # Python-level collections self.nodes: List[Node] = [] self.edges: List[Edge] = [] @@ -50,10 +49,10 @@ def _setup_network(self): for index, node_options in enumerate(self.options.nodes): python_node = self._generate_node(node_options) python_node._node_cy.ninputs = len( - self.data.connectivity.sources[ - self.data.connectivity.indices[index]:self.data.connectivity.indices[index+1] + self.data.connectivity.node_indices[ + self.data.connectivity.index_offsets[index]:self.data.connectivity.index_offsets[index+1] ] - ) if self.data.connectivity.indices else 0 + ) if self.data.connectivity.index_offsets else 0 nstates += python_node.nstates self.nodes.append(python_node) diff --git a/farms_network/core/network_cy.pxd b/farms_network/core/network_cy.pxd index 56e880a..cb5dce3 100644 --- a/farms_network/core/network_cy.pxd +++ b/farms_network/core/network_cy.pxd @@ -20,20 +20,21 @@ cdef struct network_t: # ODE double* states - unsigned int* states_indices + const unsigned int* states_indices double* derivatives - unsigned int* derivatives_indices + const unsigned int* derivatives_indices double* outputs - double* external_inputs + const double* external_inputs double* noise - unsigned int* input_neurons - double* weights - unsigned int* input_neurons_indices + const unsigned int* node_indices + const unsigned int* edge_indices + const double* weights + const unsigned int* index_offsets cdef class NetworkCy(ODESystem): diff --git a/farms_network/core/network_cy.pyx b/farms_network/core/network_cy.pyx index 608797c..182c7f4 100644 --- a/farms_network/core/network_cy.pyx +++ b/farms_network/core/network_cy.pyx @@ -45,25 +45,29 @@ cdef inline void ode( cdef processed_inputs_t processed_inputs + cdef unsigned int j + for j in range(nnodes): - total_input_val = 0.0 __node = c_nodes[j][0] # Prepare node context - node_inputs.source_indices = c_network.input_neurons + c_network.input_neurons_indices[j] - node_inputs.weights = c_network.weights + c_network.input_neurons_indices[j] + node_inputs.node_indices = c_network.node_indices + c_network.index_offsets[j] + node_inputs.edge_indices = c_network.edge_indices + c_network.index_offsets[j] + node_inputs.weights = c_network.weights + c_network.index_offsets[j] node_inputs.external_input = c_network.external_inputs[j] node_inputs.ninputs = __node.ninputs node_inputs.node_index = j + + # Compute the inputs from all nodes + processed_inputs = __node.input_tf( + time, + c_network.states + c_network.states_indices[j], + node_inputs, + c_nodes[j], + c_edges, + ) + if __node.is_statefull: - # Compute the inputs from all nodes - processed_inputs = __node.input_tf( - time, - c_network.states + c_network.states_indices[j], - node_inputs, - c_nodes[j], - c_edges + c_network.input_neurons_indices[j], - ) # Compute the ode __node.ode( time, @@ -73,60 +77,24 @@ cdef inline void ode( 0.0, c_nodes[j] ) - # Compute all the node outputs based on the current state - node_outputs_tmp_ptr[j] = __node.output_tf( - time, - c_network.states + c_network.states_indices[j], - processed_inputs, - 0.0, - c_nodes[j], - ) - else: - processed_inputs = __node.input_tf( - time, - NULL, - node_inputs, - c_nodes[j], - c_edges + c_network.input_neurons_indices[j], - ) - # Compute all the node outputs based on the current state - node_outputs_tmp_ptr[j] = __node.output_tf( - time, - NULL, - processed_inputs, - 0.0, - c_nodes[j], - ) - -# cdef inline void logger( -# int iteration, -# NetworkDataCy data, -# network_t* c_network -# ) noexcept: -# cdef unsigned int nnodes = c_network.nnodes -# cdef unsigned int j -# cdef double* states_ptr = &data.states.array[0] -# cdef unsigned int[:] state_indices = data.states.indices -# cdef double[:] outputs = data.outputs.array -# cdef double* outputs_ptr = &data.outputs.array[0] -# cdef double[:] external_inputs = data.external_inputs.array -# cdef NodeDataCy node_data -# cdef double[:] node_states -# cdef int state_idx, start_idx, end_idx, state_iteration -# cdef NodeDataCy[:] nodes_data = data.nodes -# for j in range(nnodes): -# # Log states -# start_idx = state_indices[j] -# end_idx = state_indices[j+1] -# state_iteration = 0 -# node_states = nodes_data[j].states.array[iteration] -# for state_idx in range(start_idx, end_idx): -# node_states[state_iteration] = states_ptr[state_idx] -# state_iteration += 1 -# nodes_data[j].output.array[iteration] = outputs_ptr[j] -# nodes_data[j].external_input.array[iteration] = external_inputs[j] + for j in range(nnodes): + __node = c_nodes[j][0] + # Prepare node context + node_inputs.node_indices = c_network.node_indices + c_network.index_offsets[j] + node_inputs.edge_indices = c_network.edge_indices + c_network.index_offsets[j] + node_inputs.weights = c_network.weights + c_network.index_offsets[j] + node_inputs.external_input = c_network.external_inputs[j] + node_inputs.ninputs = __node.ninputs + node_inputs.node_index = j + c_network.outputs[j] = __node.output_tf( + time, + c_network.states + c_network.states_indices[j], + processed_inputs, + 0.0, + c_nodes[j], + ) # cdef inline void _noise_states_to_output( # double[:] states, @@ -198,30 +166,32 @@ cdef class NetworkCy(ODESystem): else: self._network.noise = NULL - if self.data.connectivity.sources.size > 0: - self._network.input_neurons = &self.data.connectivity.sources[0] + if self.data.connectivity.node_indices.size > 0: + self._network.node_indices = &self.data.connectivity.node_indices[0] else: - self._network.input_neurons = NULL + self._network.node_indices = NULL + + if self.data.connectivity.edge_indices.size > 0: + self._network.edge_indices = &self.data.connectivity.edge_indices[0] + else: + self._network.edge_indices = NULL if self.data.connectivity.weights.size > 0: self._network.weights = &self.data.connectivity.weights[0] else: self._network.weights = NULL - if self.data.connectivity.indices.size > 0: - self._network.input_neurons_indices = &self.data.connectivity.indices[0] - print("indices size", self.data.connectivity.indices.size) + if self.data.connectivity.index_offsets.size > 0: + self._network.index_offsets = &self.data.connectivity.index_offsets[0] else: - self._network.input_neurons_indices = NULL + self._network.index_offsets = NULL - # cdef double* node_outputs_tmp_ptr = &node_outputs_tmp[0] def __init__(self, nnodes, nedges, data: NetworkDataCy): """ Initialize """ super().__init__() self.iteration = 0 - def __dealloc__(self): """ Deallocate any manual memory as part of clean up """ if self._network.nodes is not NULL: diff --git a/farms_network/core/node_cy.pxd b/farms_network/core/node_cy.pxd index 0166b72..8a0cac1 100644 --- a/farms_network/core/node_cy.pxd +++ b/farms_network/core/node_cy.pxd @@ -6,7 +6,8 @@ from farms_network.core.edge_cy cimport edge_t cdef struct node_inputs_t: double* network_outputs # Network level outputs double* weights # Network connection weights - unsigned int* source_indices # Which nodes provide input + unsigned int* node_indices # Which nodes provide input + unsigned int* edge_indices # Which edges provide input double external_input # external input int ninputs # Number of inputs unsigned int node_index # This node's index (for self-reference) diff --git a/farms_network/models/hopf_oscillator_cy.pyx b/farms_network/models/hopf_oscillator_cy.pyx index 87fa0d1..bd49d38 100644 --- a/farms_network/models/hopf_oscillator_cy.pyx +++ b/farms_network/models/hopf_oscillator_cy.pyx @@ -41,7 +41,7 @@ cdef processed_inputs_t hopf_oscillator_input_tf( } for j in range(inputs.ninputs): - _input = inputs.network_outputs[inputs.source_indices[j]] + _input = inputs.network_outputs[inputs.node_indices[j]] _weight = inputs.weights[j] processed_inputs.generic += (_weight*_input) diff --git a/farms_network/models/li_danner_cy.pyx b/farms_network/models/li_danner_cy.pyx index e843764..d5f580f 100644 --- a/farms_network/models/li_danner_cy.pyx +++ b/farms_network/models/li_danner_cy.pyx @@ -8,7 +8,6 @@ from farms_network.models import Models cpdef enum STATE: - #STATES nstates = NSTATES v = STATE_V @@ -43,18 +42,20 @@ cdef processed_inputs_t li_danner_input_tf( double _cholinergic_sum = 0.0 unsigned int j double _node_out, res, _input, _weight + edge_t* _edge cdef unsigned int ninputs = inputs.ninputs for j in range(ninputs): - _input = inputs.network_outputs[inputs.source_indices[j]] + _input = inputs.network_outputs[inputs.node_indices[j]] _weight = inputs.weights[j] - if edges[j].type == EXCITATORY: + _edge = edges[inputs.edge_indices[j]] + if _edge.type == EXCITATORY: # Excitatory Synapse processed_inputs.excitatory += params.g_syn_e*cfabs(_weight)*_input*(state_v - params.e_syn_e) - elif edges[j].type == INHIBITORY: + elif _edge.type == INHIBITORY: # Inhibitory Synapse processed_inputs.inhibitory += params.g_syn_i*cfabs(_weight)*_input*(state_v - params.e_syn_i) - elif edges[j].type == CHOLINERGIC: + elif _edge.type == CHOLINERGIC: processed_inputs.cholinergic += cfabs(_weight)*_input return processed_inputs diff --git a/farms_network/models/li_nap_danner_cy.pyx b/farms_network/models/li_nap_danner_cy.pyx index 79142c6..b0de03c 100644 --- a/farms_network/models/li_nap_danner_cy.pyx +++ b/farms_network/models/li_nap_danner_cy.pyx @@ -3,12 +3,12 @@ from libc.math cimport exp as cexp from libc.math cimport fabs as cfabs from libc.stdio cimport printf from libc.string cimport strdup +import numpy as np from farms_network.models import Models cpdef enum STATE: - #STATES nstates = NSTATES v = STATE_V @@ -42,15 +42,18 @@ cdef processed_inputs_t li_nap_danner_input_tf( double _sum = 0.0 unsigned int j double _input, _weight + edge_t* _edge cdef unsigned int ninputs = inputs.ninputs for j in range(ninputs): - _input = inputs.network_outputs[inputs.source_indices[j]] + _input = inputs.network_outputs[inputs.node_indices[j]] _weight = inputs.weights[j] - if edges[j].type == EXCITATORY: + _edge = edges[inputs.edge_indices[j]] + if _edge.type == EXCITATORY: # Excitatory Synapse processed_inputs.excitatory += params.g_syn_e*cfabs(_weight)*_input*(state_v - params.e_syn_e) - elif edges[j].type == INHIBITORY: + elif _edge.type == INHIBITORY: + # print(_input, _weight, inputs.source_indices[j], edges[j].type) # Inhibitory Synapse processed_inputs.inhibitory += params.g_syn_i*cfabs(_weight)*_input*(state_v - params.e_syn_i) return processed_inputs @@ -137,8 +140,8 @@ cdef class LINaPDannerNodeCy(NodeCy): if self._node.params is NULL: raise MemoryError("Failed to allocate memory for node parameters") - def __init__(self, name: str, **kwargs): - super().__init__(name) + def __init__(self, **kwargs): + super().__init__() # Set node parameters self.params.c_m = kwargs.pop("c_m") diff --git a/farms_network/models/linear_cy.pyx b/farms_network/models/linear_cy.pyx index 7cd9249..88f3ff2 100644 --- a/farms_network/models/linear_cy.pyx +++ b/farms_network/models/linear_cy.pyx @@ -33,7 +33,7 @@ cdef processed_inputs_t linear_input_tf( ninputs = inputs.ninputs for j in range(ninputs): - _input = inputs.network_outputs[inputs.source_indices[j]] + _input = inputs.network_outputs[inputs.node_indices[j]] _weight = inputs.weights[j] processed_inputs.generic += _weight*_input return processed_inputs diff --git a/farms_network/models/oscillator_cy.pyx b/farms_network/models/oscillator_cy.pyx index bf50711..f899759 100644 --- a/farms_network/models/oscillator_cy.pyx +++ b/farms_network/models/oscillator_cy.pyx @@ -47,9 +47,9 @@ cdef processed_inputs_t oscillator_input_tf( double _input, _weight for j in range(inputs.ninputs): - _input = inputs.network_outputs[inputs.source_indices[j]] + _input = inputs.network_outputs[inputs.node_indices[j]] _weight = inputs.weights[j] - edge_params = ( edges[j].params)[0] + edge_params = ( edges[inputs.edge_indices[j]].params)[0] processed_inputs.generic += _weight*state_amplitude*csin(_input - state_phase - edge_params.phase_difference) return processed_inputs diff --git a/farms_network/models/relu_cy.pyx b/farms_network/models/relu_cy.pyx index b4cbeae..0ae048f 100644 --- a/farms_network/models/relu_cy.pyx +++ b/farms_network/models/relu_cy.pyx @@ -35,7 +35,7 @@ cdef processed_inputs_t relu_input_tf( ninputs = inputs.ninputs for j in range(ninputs): - _input = inputs.network_outputs[inputs.source_indices[j]] + _input = inputs.network_outputs[inputs.node_indices[j]] _weight = inputs.weights[j] processed_inputs.generic += _weight*_input return processed_inputs From 8ae79ef2cfbb7296793d9ffd4c4da9e47733b355 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Wed, 30 Jul 2025 14:49:15 -0400 Subject: [PATCH 266/316] [CORE] Removed requirement for tmp_node_outputs storage --- farms_network/core/network_cy.pxd | 18 +++++------------- farms_network/core/network_cy.pyx | 17 +++++------------ 2 files changed, 10 insertions(+), 25 deletions(-) diff --git a/farms_network/core/network_cy.pxd b/farms_network/core/network_cy.pxd index cb5dce3..06fdd04 100644 --- a/farms_network/core/network_cy.pxd +++ b/farms_network/core/network_cy.pxd @@ -1,7 +1,7 @@ cimport numpy as cnp from ..numeric.integrators_cy cimport EulerMaruyamaSolver, RK4Solver -from ..numeric.system cimport ODESystem, SDESystem +from ..numeric.system_cy cimport ODESystem, SDESystem from .data_cy cimport NetworkDataCy, NodeDataCy from .edge_cy cimport EdgeCy, edge_t from .node_cy cimport NodeCy, node_t, node_inputs_t @@ -44,20 +44,12 @@ cdef class NetworkCy(ODESystem): network_t *_network public list nodes public list edges - public NetworkDataCy data - double[:] __tmp_node_outputs + NetworkDataCy data unsigned int iteration - unsigned int n_iterations - unsigned int buffer_size - double timestep - - public RK4Solver ode_integrator - public EulerMaruyamaSolver sde_integrator - - SDESystem sde_system - - list nodes_output_data + const unsigned int n_iterations + const unsigned int buffer_size + const double timestep cpdef void evaluate(self, double time, double[:] states, double[:] derivatives) noexcept cpdef void step(self) diff --git a/farms_network/core/network_cy.pyx b/farms_network/core/network_cy.pyx index 182c7f4..01dc317 100644 --- a/farms_network/core/network_cy.pyx +++ b/farms_network/core/network_cy.pyx @@ -25,23 +25,21 @@ from .options import (EdgeOptions, IntegrationOptions, NetworkOptions, cdef inline void ode( double time, double[:] states_arr, + double[:] derivatives_arr, network_t* c_network, - double[:] node_outputs_tmp, ) noexcept: """ C Implementation to compute full network state """ - cdef unsigned int j, nnodes cdef node_t __node cdef node_t** c_nodes = c_network.nodes cdef edge_t** c_edges = c_network.edges - nnodes = c_network.nnodes - - cdef double* node_outputs_tmp_ptr = &node_outputs_tmp[0] + cdef unsigned int nnodes = c_network.nnodes cdef node_inputs_t node_inputs node_inputs.network_outputs = c_network.outputs # It is important to use the states passed to the function and not from the data.states c_network.states = &states_arr[0] + c_network.derivatives = &derivatives_arr[0] cdef processed_inputs_t processed_inputs @@ -119,7 +117,6 @@ cdef class NetworkCy(ODESystem): self._network.nnodes = nnodes self._network.nedges = nedges - self.__tmp_node_outputs = np.zeros((nnodes,)) # Allocate C arrays self._network.nodes = malloc(self.nnodes * sizeof(node_t*)) @@ -233,12 +230,8 @@ cdef class NetworkCy(ODESystem): cpdef void evaluate(self, double time, double[:] states, double[:] derivatives) noexcept: """ Evaluate the ODE """ # Update noise model - cdef NetworkDataCy data = self.data - - ode(time, states, self._network, self.__tmp_node_outputs) - data.states.array[self.iteration, :] = states - data.outputs.array[self.iteration, :] = self.__tmp_node_outputs - derivatives[:] = data.derivatives.array[self.iteration, :] + ode(time, states, derivatives, self._network) + self.data.derivatives.array[self.iteration, :] = derivatives[:] cpdef void step(self): """ Step the network state """ From aa9857503a6753e3b54546832a1805c7c633e1a3 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Wed, 30 Jul 2025 15:04:32 -0400 Subject: [PATCH 267/316] [CORE] Added simpler interface to accessing node specific data views --- farms_network/core/data.py | 257 +++++++++++++++++------------- farms_network/core/data_cy.pxd | 16 +- farms_network/core/data_cy.pyx | 10 +- farms_network/core/network_cy.pxd | 2 +- 4 files changed, 163 insertions(+), 122 deletions(-) diff --git a/farms_network/core/data.py b/farms_network/core/data.py index f89ce1c..faf063c 100644 --- a/farms_network/core/data.py +++ b/farms_network/core/data.py @@ -17,7 +17,7 @@ from farms_core.io.hdf5 import dict_to_hdf5, hdf5_to_dict from .data_cy import (NetworkConnectivityCy, NetworkDataCy, NetworkNoiseCy, - NetworkStatesCy, NodeDataCy) + NetworkStatesCy) from .options import NetworkOptions, NodeOptions, NodeStateOptions NPDTYPE = np.float64 @@ -82,20 +82,25 @@ def from_options(cls, network_options: NetworkOptions): dtype=NPDTYPE, ) ) - # nodes = [ - # NodeStates(states, node_index) - # for node_index, node_options in enumerate(network_options.nodes) - # ] - nodes = np.array( - [ - NodeData.from_options( - node_options, - buffer_size=network_options.logs.buffer_size - ) - for node_options in network_options.nodes - ], - dtype=NodeDataCy - ) + nodes = [ + NodeData( + node_options.name, + NodeStates(states, node_index,), + NodeOutput(outputs, node_index,), + NodeExternalInput(external_inputs, node_index,), + ) + for node_index, node_options in enumerate(network_options.nodes) + ] + # nodes = np.array( + # [ + # NodeData.from_options( + # node_options, + # buffer_size=network_options.logs.buffer_size + # ) + # for node_options in network_options.nodes + # ], + # dtype=NodeDataCy + # ) return cls( times=times, states=states, @@ -293,113 +298,149 @@ def array(self): end = self._network_states.indices[self._node_index + 1] if start == end: return None - return self._network_states.array[start:end] + return self._network_states.array[:, start:end] -class NodeData(NodeDataCy): - """ Base class for representing an arbitrary node data """ +class NodeOutput: + def __init__(self, network_outputs, node_index): + self._network_outputs = network_outputs + self._node_index = node_index + + @property + def array(self): + return self._network_outputs.array[:, self._node_index] + + +class NodeExternalInput: + def __init__(self, network_external_inputs, node_index): + self._network_external_inputs = network_external_inputs + self._node_index = node_index + + @property + def array(self): + return self._network_external_inputs.array[:, self._node_index] + +class NodeData: + """ Accesssor for Node Data """ def __init__( self, name: str, - states: "NodeStatesArray", - output: "NodeOutputArray", - external_input: "NodeExternalInputArray", + states: "NodeStates", + output: "NodeOutput", + external_input: "NodeExternalInput", ): - """ Node data initialization """ - super().__init__() self.name = name self.states = states self.output = output self.external_input = external_input - @classmethod - def from_options(cls, options: NodeOptions, buffer_size: int): - """ Node data from class """ - return cls( - name=options.name, - states=NodeStatesArray.from_options(options, buffer_size), - output=NodeOutputArray.from_options(options, buffer_size), - external_input=NodeExternalInputArray.from_options(options, buffer_size), - ) - def to_dict(self, iteration: int = None) -> Dict: - """ Concert data to dictionary """ - return { - 'states': self.states.to_dict(iteration), - 'output': to_array(self.output.array), - 'external_input': to_array(self.output.array), - } - - -class NodeStatesArray(DoubleArray2D): - """ State array data """ - - def __init__(self, array: NDARRAY_V2_D, names: List): - super().__init__(array) - self.names = names - - @classmethod - def from_options(cls, options: NodeOptions, buffer_size: int): - """ State options """ - nstates = options._nstates - if nstates > 0: - names = options.state.names - array = np.full( - shape=[buffer_size, nstates], - fill_value=0, - dtype=NPDTYPE, - ) - else: - names = [] - array = np.full( - shape=[buffer_size, 0], - fill_value=0, - dtype=NPDTYPE, - ) - return cls(array=array, names=names) - - def to_dict(self, iteration: int = None) -> Dict: - """ Concert data to dictionary """ - return { - 'names': self.names, - 'array': to_array(self.array) - } - - -class NodeOutputArray(DoubleArray1D): - """ Output array data """ - - def __init__(self, array: NDARRAY_V1_D): - super().__init__(array) - - @classmethod - def from_options(cls, options: NodeOptions, buffer_size: int): - """ State options """ - array = np.full( - shape=buffer_size, - fill_value=0, - dtype=NPDTYPE, - ) - return cls(array=array) - - -class NodeExternalInputArray(DoubleArray1D): - """ ExternalInput array data """ - - def __init__(self, array: NDARRAY_V1_D): - super().__init__(array) - - @classmethod - def from_options(cls, options: NodeOptions, buffer_size: int): - """ State options """ - array = np.full( - shape=buffer_size, - fill_value=0, - dtype=NPDTYPE, - ) - return cls(array=array) +# class NodeData(NodeDataCy): +# """ Base class for representing an arbitrary node data """ + +# def __init__( +# self, +# name: str, +# states: "NodeStatesArray", +# output: "NodeOutputArray", +# external_input: "NodeExternalInputArray", +# ): +# """ Node data initialization """ + +# super().__init__() +# self.name = name +# self.states = states +# self.output = output +# self.external_input = external_input + +# @classmethod +# def from_options(cls, options: NodeOptions, buffer_size: int): +# """ Node data from class """ +# return cls( +# name=options.name, +# states=NodeStatesArray.from_options(options, buffer_size), +# output=NodeOutputArray.from_options(options, buffer_size), +# external_input=NodeExternalInputArray.from_options(options, buffer_size), +# ) + +# def to_dict(self, iteration: int = None) -> Dict: +# """ Concert data to dictionary """ +# return { +# 'states': self.states.to_dict(iteration), +# 'output': to_array(self.output.array), +# 'external_input': to_array(self.output.array), +# } + + +# class NodeStatesArray(DoubleArray2D): +# """ State array data """ + +# def __init__(self, array: NDARRAY_V2_D, names: List): +# super().__init__(array) +# self.names = names + +# @classmethod +# def from_options(cls, options: NodeOptions, buffer_size: int): +# """ State options """ +# nstates = options._nstates +# if nstates > 0: +# names = options.state.names +# array = np.full( +# shape=[buffer_size, nstates], +# fill_value=0, +# dtype=NPDTYPE, +# ) +# else: +# names = [] +# array = np.full( +# shape=[buffer_size, 0], +# fill_value=0, +# dtype=NPDTYPE, +# ) +# return cls(array=array, names=names) + +# def to_dict(self, iteration: int = None) -> Dict: +# """ Concert data to dictionary """ +# return { +# 'names': self.names, +# 'array': to_array(self.array) +# } + + +# class NodeOutputArray(DoubleArray1D): +# """ Output array data """ + +# def __init__(self, array: NDARRAY_V1_D): +# super().__init__(array) + +# @classmethod +# def from_options(cls, options: NodeOptions, buffer_size: int): +# """ State options """ +# array = np.full( +# shape=buffer_size, +# fill_value=0, +# dtype=NPDTYPE, +# ) +# return cls(array=array) + + +# class NodeExternalInputArray(DoubleArray1D): +# """ ExternalInput array data """ + +# def __init__(self, array: NDARRAY_V1_D): +# super().__init__(array) + +# @classmethod +# def from_options(cls, options: NodeOptions, buffer_size: int): +# """ State options """ +# array = np.full( +# shape=buffer_size, +# fill_value=0, +# dtype=NPDTYPE, +# ) +# return cls(array=array) def main(): diff --git a/farms_network/core/data_cy.pxd b/farms_network/core/data_cy.pxd index 7082a1e..7e437ce 100644 --- a/farms_network/core/data_cy.pxd +++ b/farms_network/core/data_cy.pxd @@ -34,7 +34,7 @@ cdef class NetworkDataCy: public NetworkConnectivityCy connectivity public NetworkNoiseCy noise - public NodeDataCy[:] nodes + # public NodeDataCy[:] nodes cdef class NetworkStatesCy(DoubleArray2D): @@ -64,10 +64,10 @@ cdef class NetworkNoiseCy: public DTYPEv1 outputs -cdef class NodeDataCy: - """ Node data """ - cdef: - public DoubleArray2D states - public DoubleArray2D derivatives - public DoubleArray1D output - public DoubleArray1D external_input +# cdef class NodeDataCy: +# """ Node data """ +# cdef: +# public DoubleArray2D states +# public DoubleArray2D derivatives +# public DoubleArray1D output +# public DoubleArray1D external_input diff --git a/farms_network/core/data_cy.pyx b/farms_network/core/data_cy.pyx index 0422831..36854c2 100644 --- a/farms_network/core/data_cy.pyx +++ b/farms_network/core/data_cy.pyx @@ -87,10 +87,10 @@ cdef class NetworkNoiseCy: ############# # Node Data # ############# -cdef class NodeDataCy: - """ Node data """ +# cdef class NodeDataCy: +# """ Node data """ - def __init__(self): - """ nodes data initialization """ +# def __init__(self): +# """ nodes data initialization """ - super().__init__() +# super().__init__() diff --git a/farms_network/core/network_cy.pxd b/farms_network/core/network_cy.pxd index 06fdd04..fc0c1bd 100644 --- a/farms_network/core/network_cy.pxd +++ b/farms_network/core/network_cy.pxd @@ -2,7 +2,7 @@ cimport numpy as cnp from ..numeric.integrators_cy cimport EulerMaruyamaSolver, RK4Solver from ..numeric.system_cy cimport ODESystem, SDESystem -from .data_cy cimport NetworkDataCy, NodeDataCy +from .data_cy cimport NetworkDataCy from .edge_cy cimport EdgeCy, edge_t from .node_cy cimport NodeCy, node_t, node_inputs_t From 18163afb396f5dc74a1eae4285efccd9dd3281b2 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Thu, 14 Aug 2025 13:52:14 -0400 Subject: [PATCH 268/316] [NUMERIC] Updated integrators to use renamed system_cy --- farms_network/noise/ornstein_uhlenbeck.pxd | 2 +- farms_network/numeric/integrators.py | 9 +++++++++ farms_network/numeric/integrators_cy.pxd | 4 ++-- farms_network/numeric/integrators_cy.pyx | 9 +++++---- 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/farms_network/noise/ornstein_uhlenbeck.pxd b/farms_network/noise/ornstein_uhlenbeck.pxd index c332cf2..c92630d 100644 --- a/farms_network/noise/ornstein_uhlenbeck.pxd +++ b/farms_network/noise/ornstein_uhlenbeck.pxd @@ -3,7 +3,7 @@ from libc.math cimport sqrt as csqrt from libc.stdint cimport uint_fast32_t, uint_fast64_t -from ..numeric.system cimport SDESystem +from ..numeric.system_cy cimport SDESystem cdef extern from "" namespace "std" nogil: diff --git a/farms_network/numeric/integrators.py b/farms_network/numeric/integrators.py index e69de29..23bbe67 100644 --- a/farms_network/numeric/integrators.py +++ b/farms_network/numeric/integrators.py @@ -0,0 +1,9 @@ +""" Integrators """ + + +class RK4: + """ RK4 Integrator """ + + def __init__(self, system, integration_options): + " Integration " + self._rk4_integrator = None diff --git a/farms_network/numeric/integrators_cy.pxd b/farms_network/numeric/integrators_cy.pxd index c4c98d6..bb94b4b 100644 --- a/farms_network/numeric/integrators_cy.pxd +++ b/farms_network/numeric/integrators_cy.pxd @@ -1,7 +1,7 @@ from farms_core.array.array_cy cimport DoubleArray1D from libc.math cimport sqrt as csqrt -from .system cimport ODESystem, SDESystem +from .system_cy cimport ODESystem, SDESystem include 'types.pxd' @@ -17,7 +17,7 @@ cdef class RK4Solver: unsigned int dim double dt - cdef void step(self, ODESystem sys, double time, double[:] state) noexcept + cdef void _step(self, ODESystem sys, double time, double[:] state) noexcept cdef class EulerMaruyamaSolver: diff --git a/farms_network/numeric/integrators_cy.pyx b/farms_network/numeric/integrators_cy.pyx index 9f4abbb..0c9434e 100644 --- a/farms_network/numeric/integrators_cy.pyx +++ b/farms_network/numeric/integrators_cy.pyx @@ -30,7 +30,7 @@ cdef class RK4Solver: array=np.full(shape=dim, fill_value=0.0, dtype=NPDTYPE,) ) - cdef void step(self, ODESystem sys, double time, double[:] states) noexcept: + cdef void _step(self, ODESystem sys, double time, double[:] states) noexcept: cdef unsigned int i cdef double dt2 = self.dt / 2.0 cdef double dt6 = self.dt / 6.0 @@ -60,9 +60,10 @@ cdef class RK4Solver: # Update y: y = y + (k1 + 2*k2 + 2*k3 + k4) / 6 for i in range(self.dim): - states[i] = states[i] + dt6 * ( - k1[i] + 2.0 * k2[i] + 2.0 * k3[i] + k4[i] - ) + states[i] = states[i] + dt6 * (k1[i] + 2.0 * k2[i] + 2.0 * k3[i] + k4[i]) + + def step(self, ODESystem sys, double time, double[:] states): + self._step(sys, time, states) cdef class EulerMaruyamaSolver: From 2bf11e3cf3877b90e9d5fbbab8629d98b38bad97 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Thu, 21 Aug 2025 17:27:43 -0400 Subject: [PATCH 269/316] [CORE] Added checks for network data with additional temp buffers --- farms_network/core/data.py | 27 +++++++++++++++++++--- farms_network/core/data_cy.pxd | 42 +++++++++++++++++++--------------- farms_network/core/data_cy.pyx | 40 ++++++++------------------------ 3 files changed, 57 insertions(+), 52 deletions(-) diff --git a/farms_network/core/data.py b/farms_network/core/data.py index faf063c..a23e860 100644 --- a/farms_network/core/data.py +++ b/farms_network/core/data.py @@ -20,6 +20,7 @@ NetworkStatesCy) from .options import NetworkOptions, NodeOptions, NodeStateOptions + NPDTYPE = np.float64 NPUITYPE = np.uintc @@ -34,6 +35,7 @@ def __init__( derivatives, connectivity, outputs, + curr_outputs, external_inputs, noise, nodes, @@ -42,15 +44,23 @@ def __init__( """ Network data structure """ super().__init__() + self.times = times self.states = states self.derivatives = derivatives self.connectivity = connectivity self.outputs = outputs + self.curr_outputs = curr_outputs self.external_inputs = external_inputs self.noise = noise - self.nodes: np.ndarray[NodeDataCy] = nodes + self.nodes: List[NodeData] = nodes + + # assert that the data created is c-contiguous + assert self.states.array.is_c_contig() + assert self.derivatives.array.is_c_contig() + assert self.outputs.array.is_c_contig() + assert self.external_inputs.array.is_c_contig() @classmethod def from_options(cls, network_options: NetworkOptions): @@ -75,6 +85,13 @@ def from_options(cls, network_options: NetworkOptions): dtype=NPDTYPE, ) ) + curr_outputs = DoubleArray1D( + array=np.full( + shape=(len(network_options.nodes),), + fill_value=0, + dtype=NPDTYPE, + ) + ) external_inputs = DoubleArray2D( array=np.full( shape=(buffer_size, len(network_options.nodes)), @@ -107,6 +124,7 @@ def from_options(cls, network_options: NetworkOptions): derivatives=derivatives, connectivity=connectivity, outputs=outputs, + curr_outputs=curr_outputs, external_inputs=external_inputs, noise=noise, nodes=nodes, @@ -120,6 +138,7 @@ def to_dict(self, iteration: int = None) -> Dict: 'derivatives': self.derivatives.to_dict(), 'connectivity': self.connectivity.to_dict(), 'outputs': to_array(self.outputs.array), + 'curr_outputs': to_array(self.curr_outputs.array), 'external_inputs': to_array(self.external_inputs.array), 'noise': self.noise.to_dict(), 'nodes': {node.name: node.to_dict() for node in self.nodes}, @@ -136,8 +155,8 @@ def to_file(self, filename: str, iteration: int = None): class NetworkStates(NetworkStatesCy): - def __init__(self, array, indices): - super().__init__(array, indices) + def __init__(self, array, current, indices): + super().__init__(array, current, indices) @classmethod def from_options(cls, network_options: NetworkOptions): @@ -150,6 +169,7 @@ def from_options(cls, network_options: NetworkOptions): indices.append(nstates) return cls( array=np.array(np.zeros((network_options.logs.buffer_size, nstates)), dtype=NPDTYPE), + current=np.array(np.zeros((nstates,)), dtype=NPDTYPE), indices=np.array(indices) ) @@ -157,6 +177,7 @@ def to_dict(self, iteration: int = None) -> Dict: """Convert data to dictionary""" return { 'array': to_array(self.array), + 'current': to_array(self.current.array), 'indices': to_array(self.indices), } diff --git a/farms_network/core/data_cy.pxd b/farms_network/core/data_cy.pxd index 7e437ce..cca7aca 100644 --- a/farms_network/core/data_cy.pxd +++ b/farms_network/core/data_cy.pxd @@ -1,27 +1,30 @@ -""" ------------------------------------------------------------------------ -Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty -Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne +""" Network Data """ -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 +from farms_core.array.array_cy cimport (DoubleArray1D, DoubleArray2D, IntegerArray1D) -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. ------------------------------------------------------------------------ -""" +include 'types.pxd' -from farms_core.array.array_cy cimport (DoubleArray1D, DoubleArray2D, - IntegerArray1D) +# cdef class NetworkComputeData: -include 'types.pxd' +# cdef: +# # States +# public DoubleArray1D curr_states +# public DoubleArray1D tmp_states +# public UITYPEv1 state_indices +# # Derivatives +# public DoubleArray1D curr_derivatives +# public DoubleArray1D tmp_derivatives +# # Outputs +# public DoubleArray1D curr_outputs +# public DoubleArray1D tmp_outputs +# # External inputs +# public DoubleArray2D external_inputs +# # Network connectivity +# public NetworkConnectivityCy connectivity +# # Noise +# public NetworkNoiseCy noise cdef class NetworkDataCy: @@ -31,9 +34,9 @@ cdef class NetworkDataCy: public NetworkStatesCy derivatives public DoubleArray2D external_inputs public DoubleArray2D outputs + public DoubleArray1D curr_outputs public NetworkConnectivityCy connectivity public NetworkNoiseCy noise - # public NodeDataCy[:] nodes @@ -41,6 +44,7 @@ cdef class NetworkStatesCy(DoubleArray2D): """ State array """ cdef public UITYPEv1 indices + cdef public DTYPEv1 current cdef class NetworkConnectivityCy: diff --git a/farms_network/core/data_cy.pyx b/farms_network/core/data_cy.pyx index 36854c2..e540e59 100644 --- a/farms_network/core/data_cy.pyx +++ b/farms_network/core/data_cy.pyx @@ -1,21 +1,4 @@ -""" ------------------------------------------------------------------------ -Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty -Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. ------------------------------------------------------------------------ -""" +""" Core Data """ from typing import Dict, Iterable, List @@ -42,10 +25,15 @@ cdef class NetworkStatesCy(DoubleArray2D): def __init__( self, array: NDArray[(Any, Any), np.double], + current: NDArray[(Any,), np.double], indices: NDArray[(Any,), np.uintc], ): super().__init__(array) + assert self.array.is_c_contig() self.indices = np.array(indices, dtype=np.uintc) + assert self.indices.is_c_contig() + self.current = np.array(current, dtype=np.double) + assert self.current.is_c_contig() cdef class NetworkConnectivityCy: @@ -60,9 +48,13 @@ cdef class NetworkConnectivityCy: ): super().__init__() self.node_indices = np.array(node_indices, dtype=np.uintc) + assert self.node_indices.is_c_contig() self.edge_indices = np.array(edge_indices, dtype=np.uintc) + assert self.edge_indices.is_c_contig() self.weights = np.array(weights, dtype=np.double) + assert self.weights.is_c_contig() self.index_offsets = np.array(index_offsets, dtype=np.uintc) + assert self.index_offsets.is_c_contig() cdef class NetworkNoiseCy: @@ -82,15 +74,3 @@ cdef class NetworkNoiseCy: self.drift = np.array(drift, dtype=np.double) self.diffusion = np.array(diffusion, dtype=np.double) self.outputs = np.array(outputs, dtype=np.double) - - -############# -# Node Data # -############# -# cdef class NodeDataCy: -# """ Node data """ - -# def __init__(self): -# """ nodes data initialization """ - -# super().__init__() From 7355a432cd11d37025c82ff17f27f4558312324a Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Fri, 22 Aug 2025 20:53:33 -0400 Subject: [PATCH 270/316] [CORE] Refactored and separated code data and logs --- farms_network/core/data.py | 168 +++++++++++++++++++++++++++--- farms_network/core/data_cy.pxd | 84 ++++++++------- farms_network/core/data_cy.pyx | 28 ++++- farms_network/core/network.py | 37 ++++--- farms_network/core/network_cy.pxd | 16 +-- farms_network/core/network_cy.pyx | 156 ++++++++++++--------------- 6 files changed, 317 insertions(+), 172 deletions(-) diff --git a/farms_network/core/data.py b/farms_network/core/data.py index a23e860..66f9200 100644 --- a/farms_network/core/data.py +++ b/farms_network/core/data.py @@ -16,8 +16,8 @@ NDARRAY_V3_D) from farms_core.io.hdf5 import dict_to_hdf5, hdf5_to_dict -from .data_cy import (NetworkConnectivityCy, NetworkDataCy, NetworkNoiseCy, - NetworkStatesCy) +from .data_cy import (NetworkConnectivityCy, NetworkDataCy, NetworkLogCy, NetworkNoiseCy, + NetworkStatesCy, NetworkLogStatesCy) from .options import NetworkOptions, NodeOptions, NodeStateOptions @@ -35,7 +35,7 @@ def __init__( derivatives, connectivity, outputs, - curr_outputs, + tmp_outputs, external_inputs, noise, nodes, @@ -50,7 +50,7 @@ def __init__( self.derivatives = derivatives self.connectivity = connectivity self.outputs = outputs - self.curr_outputs = curr_outputs + self.tmp_outputs = tmp_outputs self.external_inputs = external_inputs self.noise = noise @@ -60,6 +60,7 @@ def __init__( assert self.states.array.is_c_contig() assert self.derivatives.array.is_c_contig() assert self.outputs.array.is_c_contig() + assert self.tmp_outputs.array.is_c_contig() assert self.external_inputs.array.is_c_contig() @classmethod @@ -78,23 +79,26 @@ def from_options(cls, network_options: NetworkOptions): derivatives = NetworkStates.from_options(network_options) connectivity = NetworkConnectivity.from_options(network_options) noise = NetworkNoise.from_options(network_options) - outputs = DoubleArray2D( + + outputs = DoubleArray1D( array=np.full( - shape=(buffer_size, len(network_options.nodes)), + shape=(len(network_options.nodes),), fill_value=0, dtype=NPDTYPE, ) ) - curr_outputs = DoubleArray1D( + + tmp_outputs = DoubleArray1D( array=np.full( shape=(len(network_options.nodes),), fill_value=0, dtype=NPDTYPE, ) ) - external_inputs = DoubleArray2D( + + external_inputs = DoubleArray1D( array=np.full( - shape=(buffer_size, len(network_options.nodes)), + shape=len(network_options.nodes), fill_value=0, dtype=NPDTYPE, ) @@ -124,7 +128,7 @@ def from_options(cls, network_options: NetworkOptions): derivatives=derivatives, connectivity=connectivity, outputs=outputs, - curr_outputs=curr_outputs, + tmp_outputs=tmp_outputs, external_inputs=external_inputs, noise=noise, nodes=nodes, @@ -138,7 +142,7 @@ def to_dict(self, iteration: int = None) -> Dict: 'derivatives': self.derivatives.to_dict(), 'connectivity': self.connectivity.to_dict(), 'outputs': to_array(self.outputs.array), - 'curr_outputs': to_array(self.curr_outputs.array), + 'tmp_outputs': to_array(self.tmp_outputs.array), 'external_inputs': to_array(self.external_inputs.array), 'noise': self.noise.to_dict(), 'nodes': {node.name: node.to_dict() for node in self.nodes}, @@ -155,8 +159,8 @@ def to_file(self, filename: str, iteration: int = None): class NetworkStates(NetworkStatesCy): - def __init__(self, array, current, indices): - super().__init__(array, current, indices) + def __init__(self, array, indices): + super().__init__(array, indices) @classmethod def from_options(cls, network_options: NetworkOptions): @@ -168,8 +172,7 @@ def from_options(cls, network_options: NetworkOptions): nstates += node._nstates indices.append(nstates) return cls( - array=np.array(np.zeros((network_options.logs.buffer_size, nstates)), dtype=NPDTYPE), - current=np.array(np.zeros((nstates,)), dtype=NPDTYPE), + array=np.array(np.zeros((nstates,)), dtype=NPDTYPE), indices=np.array(indices) ) @@ -177,7 +180,6 @@ def to_dict(self, iteration: int = None) -> Dict: """Convert data to dictionary""" return { 'array': to_array(self.array), - 'current': to_array(self.current.array), 'indices': to_array(self.indices), } @@ -308,6 +310,140 @@ def to_dict(self, iteration: int = None) -> Dict: 'outputs': to_array(self.outputs), } + +class NetworkLogStates(NetworkLogStatesCy): + + def __init__(self, array, indices): + super().__init__(array, indices) + + @classmethod + def from_options(cls, network_options: NetworkOptions): + + nodes = network_options.nodes + nstates = 0 + indices = [0,] + buffer_size = network_options.logs.buffer_size + for index, node in enumerate(nodes): + nstates += node._nstates + indices.append(nstates) + return cls( + array=np.array(np.zeros((buffer_size, nstates)), dtype=NPDTYPE), + indices=np.array(indices) + ) + + def to_dict(self, iteration: int = None) -> Dict: + """Convert data to dictionary""" + return { + 'array': to_array(self.array), + 'indices': to_array(self.indices), + } + + + +class NetworkLog(NetworkLogCy): + """ Network Logs """ + + def __init__( + self, + times, + states, + connectivity, + outputs, + external_inputs, + noise, + nodes, + **kwargs, + ): + """ Network data structure """ + + super().__init__() + + self.times = times + self.states = states + self.connectivity = connectivity + self.outputs = outputs + self.external_inputs = external_inputs + self.noise = noise + + self.nodes: List[NodeData] = nodes + + # assert that the data created is c-contiguous + assert self.states.array.is_c_contig() + assert self.outputs.array.is_c_contig() + assert self.external_inputs.array.is_c_contig() + + @classmethod + def from_options(cls, network_options: NetworkOptions): + """ From options """ + + buffer_size = network_options.logs.buffer_size + times = DoubleArray1D( + array=np.full( + shape=buffer_size, + fill_value=0, + dtype=NPDTYPE, + ) + ) + states = NetworkLogStates.from_options(network_options) + connectivity = NetworkConnectivity.from_options(network_options) + noise = NetworkNoise.from_options(network_options) + + outputs = DoubleArray2D( + array=np.full( + shape=(buffer_size, len(network_options.nodes)), + fill_value=0, + dtype=NPDTYPE, + ) + ) + + external_inputs = DoubleArray2D( + array=np.full( + shape=(buffer_size, len(network_options.nodes)), + fill_value=0, + dtype=NPDTYPE, + ) + ) + nodes = [ + NodeData( + node_options.name, + NodeStates(states, node_index,), + NodeOutput(outputs, node_index,), + NodeExternalInput(external_inputs, node_index,), + ) + for node_index, node_options in enumerate(network_options.nodes) + ] + + return cls( + times=times, + states=states, + connectivity=connectivity, + outputs=outputs, + external_inputs=external_inputs, + noise=noise, + nodes=nodes, + ) + + def to_dict(self, iteration: int = None) -> Dict: + """Convert data to dictionary""" + return { + 'times': to_array(self.times.array), + 'states': self.states.to_dict(), + 'connectivity': self.connectivity.to_dict(), + 'outputs': to_array(self.outputs.array), + 'external_inputs': to_array(self.external_inputs.array), + 'noise': self.noise.to_dict(), + 'nodes': {node.name: node.to_dict() for node in self.nodes}, + } + + def to_file(self, filename: str, iteration: int = None): + """Save data to file""" + pylog.info('Exporting to dictionary') + data_dict = self.to_dict(iteration) + pylog.info('Saving data to %s', filename) + dict_to_hdf5(filename=filename, data=data_dict) + pylog.info('Saved data to %s', filename) + + class NodeStates: def __init__(self, network_states, node_index): self._network_states = network_states diff --git a/farms_network/core/data_cy.pxd b/farms_network/core/data_cy.pxd index cca7aca..1ae87c0 100644 --- a/farms_network/core/data_cy.pxd +++ b/farms_network/core/data_cy.pxd @@ -6,50 +6,42 @@ from farms_core.array.array_cy cimport (DoubleArray1D, DoubleArray2D, IntegerArr include 'types.pxd' -# cdef class NetworkComputeData: - -# cdef: -# # States -# public DoubleArray1D curr_states -# public DoubleArray1D tmp_states -# public UITYPEv1 state_indices -# # Derivatives -# public DoubleArray1D curr_derivatives -# public DoubleArray1D tmp_derivatives -# # Outputs -# public DoubleArray1D curr_outputs -# public DoubleArray1D tmp_outputs -# # External inputs -# public DoubleArray2D external_inputs -# # Network connectivity -# public NetworkConnectivityCy connectivity -# # Noise -# public NetworkNoiseCy noise - - cdef class NetworkDataCy: - cdef: + public DoubleArray1D times public NetworkStatesCy states public NetworkStatesCy derivatives + public DoubleArray1D external_inputs + public DoubleArray1D outputs + public DoubleArray1D tmp_outputs + public NetworkConnectivityCy connectivity + public NetworkNoiseCy noise + + +cdef class NetworkLogCy: + cdef: + public DoubleArray1D times + public NetworkLogStatesCy states public DoubleArray2D external_inputs public DoubleArray2D outputs - public DoubleArray1D curr_outputs public NetworkConnectivityCy connectivity public NetworkNoiseCy noise - # public NodeDataCy[:] nodes -cdef class NetworkStatesCy(DoubleArray2D): +cdef class NetworkStatesCy(DoubleArray1D): """ State array """ + cdef: + public UITYPEv1 indices - cdef public UITYPEv1 indices - cdef public DTYPEv1 current + +cdef class NetworkLogStatesCy(DoubleArray2D): + """ State array for logging """ + cdef: + public UITYPEv1 indices cdef class NetworkConnectivityCy: """ Network connectivity array """ - cdef: public DTYPEv1 weights public UITYPEv1 node_indices @@ -59,7 +51,6 @@ cdef class NetworkConnectivityCy: cdef class NetworkNoiseCy: """ Noise data array """ - cdef: public DTYPEv1 states public UITYPEv1 indices @@ -68,10 +59,31 @@ cdef class NetworkNoiseCy: public DTYPEv1 outputs -# cdef class NodeDataCy: -# """ Node data """ -# cdef: -# public DoubleArray2D states -# public DoubleArray2D derivatives -# public DoubleArray1D output -# public DoubleArray1D external_input +# Network data will hold the necessary computational data +cdef class NetworkData: + + cdef: + # Time + public DoubleArray1D times + + # States + public DoubleArray1D curr_states + DoubleArray1D tmp_states + public UITYPEv1 state_indices + + # Derivatives + public DoubleArray1D curr_derivatives + DoubleArray1D tmp_derivatives + + # Outputs + public DoubleArray1D curr_outputs + DoubleArray1D tmp_outputs + + # External inputs + public DoubleArray1D external_inputs + + # Network connectivity + public NetworkConnectivityCy connectivity + + # Noise + public NetworkNoiseCy noise diff --git a/farms_network/core/data_cy.pyx b/farms_network/core/data_cy.pyx index e540e59..4fa337e 100644 --- a/farms_network/core/data_cy.pyx +++ b/farms_network/core/data_cy.pyx @@ -19,21 +19,41 @@ cdef class NetworkDataCy: super().__init__() -cdef class NetworkStatesCy(DoubleArray2D): +cdef class NetworkLogCy: + """ Network Log """ + + def __init__(self): + """ Network Logs initialization """ + + super().__init__() + + +cdef class NetworkStatesCy(DoubleArray1D): + """ State array """ + + def __init__( + self, + array: NDArray[(Any,), np.double], + indices: NDArray[(Any,), np.uintc], + ): + super().__init__(array) + assert self.array.is_c_contig() + self.indices = np.array(indices, dtype=np.uintc) + assert self.indices.is_c_contig() + + +cdef class NetworkLogStatesCy(DoubleArray2D): """ State array """ def __init__( self, array: NDArray[(Any, Any), np.double], - current: NDArray[(Any,), np.double], indices: NDArray[(Any,), np.uintc], ): super().__init__(array) assert self.array.is_c_contig() self.indices = np.array(indices, dtype=np.uintc) assert self.indices.is_c_contig() - self.current = np.array(current, dtype=np.double) - assert self.current.is_c_contig() cdef class NetworkConnectivityCy: diff --git a/farms_network/core/network.py b/farms_network/core/network.py index c8d5265..f206c52 100644 --- a/farms_network/core/network.py +++ b/farms_network/core/network.py @@ -1,15 +1,16 @@ """ Network """ +from typing import List, Optional + import numpy as np -from .data import NetworkData +from ..models.factory import EdgeFactory, NodeFactory +from .data import NetworkData, NetworkLog +from .edge import Edge from .network_cy import NetworkCy +from .node import Node from .options import (EdgeOptions, IntegrationOptions, NetworkOptions, NodeOptions) -from ..models.factory import NodeFactory, EdgeFactory -from .edge import Edge -from .node import Node -from typing import Optional class Network: @@ -21,11 +22,13 @@ def __init__(self, network_options: NetworkOptions): # Core network data and Cython implementation self.data = NetworkData.from_options(network_options) + self.log = NetworkLog.from_options(network_options) self._network_cy = NetworkCy( nnodes=len(network_options.nodes), nedges=len(network_options.edges), - data=self.data + data=self.data, + log=self.log ) # Python-level collections @@ -36,12 +39,15 @@ def __init__(self, network_options: NetworkOptions): self._setup_network() # self._setup_integrator() - # Simulation parameters - self.n_iterations: int = network_options.integration.n_iterations - self.timestep: float = network_options.integration.timestep - self.iteration: int = 0 + # Logs self.buffer_size: int = network_options.logs.buffer_size + # Iteration + if network_options.integration: + self.timestep: float = network_options.integration.timestep + self.iteration: int = 0 + self.n_iterations: int = network_options.integration.n_iterations + def _setup_network(self): """ Setup network nodes and edges """ # Create Python nodes @@ -94,6 +100,10 @@ def _generate_edge(edge_options: EdgeOptions) -> Edge: EdgeClass = EdgeFactory.create(edge_options.model) return EdgeClass.from_options(edge_options) + def get_ode_func(self): + """ Get ODE function for external integration """ + return self._network_cy.ode_func + # Delegate properties to Cython implementation @property def nnodes(self) -> int: @@ -107,13 +117,6 @@ def nedges(self) -> int: def nstates(self) -> int: return self._network_cy.nstates - # Delegate methods to Cython implementation - def evaluate(self, time, states): - """ Evaluate the ODE """ - derivatives = np.zeros(np.size(states)) - self._network_cy.evaluate(time, states, derivatives) - return derivatives - def step(self): """ Step the network simulation """ self._network_cy.step() diff --git a/farms_network/core/network_cy.pxd b/farms_network/core/network_cy.pxd index fc0c1bd..8899aff 100644 --- a/farms_network/core/network_cy.pxd +++ b/farms_network/core/network_cy.pxd @@ -2,16 +2,16 @@ cimport numpy as cnp from ..numeric.integrators_cy cimport EulerMaruyamaSolver, RK4Solver from ..numeric.system_cy cimport ODESystem, SDESystem -from .data_cy cimport NetworkDataCy +from .data_cy cimport NetworkDataCy, NetworkLogCy from .edge_cy cimport EdgeCy, edge_t from .node_cy cimport NodeCy, node_t, node_inputs_t cdef struct network_t: # info - unsigned long int nnodes - unsigned long int nedges - unsigned long int nstates + unsigned int nnodes + unsigned int nedges + unsigned int nstates # nodes list node_t** nodes @@ -26,6 +26,7 @@ cdef struct network_t: const unsigned int* derivatives_indices double* outputs + double* tmp_outputs const double* external_inputs @@ -45,11 +46,12 @@ cdef class NetworkCy(ODESystem): public list nodes public list edges NetworkDataCy data + NetworkLogCy log - unsigned int iteration + public unsigned int iteration const unsigned int n_iterations const unsigned int buffer_size const double timestep - cpdef void evaluate(self, double time, double[:] states, double[:] derivatives) noexcept - cpdef void step(self) + cdef void evaluate(self, double time, double[:] states, double[:] derivatives) noexcept + # cpdef void update_iteration(self) diff --git a/farms_network/core/network_cy.pyx b/farms_network/core/network_cy.pyx index 01dc317..829ed38 100644 --- a/farms_network/core/network_cy.pyx +++ b/farms_network/core/network_cy.pyx @@ -24,8 +24,8 @@ from .options import (EdgeOptions, IntegrationOptions, NetworkOptions, cdef inline void ode( double time, - double[:] states_arr, - double[:] derivatives_arr, + double[:] states, + double[:] derivatives, network_t* c_network, ) noexcept: """ C Implementation to compute full network state """ @@ -34,16 +34,14 @@ cdef inline void ode( cdef node_t** c_nodes = c_network.nodes cdef edge_t** c_edges = c_network.edges cdef unsigned int nnodes = c_network.nnodes - + cdef unsigned int j + cdef processed_inputs_t processed_inputs cdef node_inputs_t node_inputs + node_inputs.network_outputs = c_network.outputs # It is important to use the states passed to the function and not from the data.states - c_network.states = &states_arr[0] - c_network.derivatives = &derivatives_arr[0] - - cdef processed_inputs_t processed_inputs - - cdef unsigned int j + cdef double* states_ptr = &states[0] + cdef double* derivatives_ptr = &derivatives[0] for j in range(nnodes): __node = c_nodes[j][0] @@ -59,7 +57,7 @@ cdef inline void ode( # Compute the inputs from all nodes processed_inputs = __node.input_tf( time, - c_network.states + c_network.states_indices[j], + states_ptr + c_network.states_indices[j], node_inputs, c_nodes[j], c_edges, @@ -69,30 +67,23 @@ cdef inline void ode( # Compute the ode __node.ode( time, - c_network.states + c_network.states_indices[j], - c_network.derivatives + c_network.derivatives_indices[j], + states_ptr + c_network.states_indices[j], + derivatives_ptr + c_network.states_indices[j], processed_inputs, 0.0, c_nodes[j] ) - - for j in range(nnodes): - __node = c_nodes[j][0] - # Prepare node context - node_inputs.node_indices = c_network.node_indices + c_network.index_offsets[j] - node_inputs.edge_indices = c_network.edge_indices + c_network.index_offsets[j] - node_inputs.weights = c_network.weights + c_network.index_offsets[j] - node_inputs.external_input = c_network.external_inputs[j] - - node_inputs.ninputs = __node.ninputs - node_inputs.node_index = j - c_network.outputs[j] = __node.output_tf( + # Check for writing to proper outputs array + c_network.tmp_outputs[j] = __node.output_tf( time, - c_network.states + c_network.states_indices[j], + states_ptr + c_network.states_indices[j], processed_inputs, 0.0, c_nodes[j], ) + # # Swap pointers for the outputs (Maybe!) + # c_network.outputs, c_network.tmp_outputs = c_network.tmp_outputs, c_network.outputs + # cdef inline void _noise_states_to_output( # double[:] states, @@ -109,7 +100,7 @@ cdef inline void ode( cdef class NetworkCy(ODESystem): """ Python interface to Network ODE """ - def __cinit__(self, nnodes: int, nedges: int, data: NetworkDataCy): + def __cinit__(self, nnodes: int, nedges: int, data: NetworkDataCy, log: NetworkLogCy): # Memory allocation only self._network = malloc(sizeof(network_t)) if self._network is NULL: @@ -129,34 +120,42 @@ cdef class NetworkCy(ODESystem): # Initialize network context self.data = data if self.data.states.array.size > 0: - self._network.states = &self.data.states.array[0][0] + self._network.states = &self.data.states.array[0] else: self._network.states = NULL # No stateful if self.data.states.indices.size > 0: self._network.states_indices = &self.data.states.indices[0] else: + assert self._network.states == NULL self._network.states_indices = NULL - if self.data.derivatives.array.size > 0: - self._network.derivatives = &self.data.derivatives.array[0][0] - else: - self._network.derivatives = NULL + # if self.data.derivatives.array.size > 0: + # self._network.derivatives = &self.data.derivatives.array[0] + # else: + # assert self._network.states == NULL + self._network.derivatives = NULL - if self.data.derivatives.indices.size > 0: - self._network.derivatives_indices = &self.data.derivatives.indices[0] - else: - self._network.derivatives_indices = NULL + # if self.data.derivatives.indices.size > 0: + # self._network.derivatives_indices = &self.data.derivatives.indices[0] + # else: + # assert self._network.derivatives == NULL + self._network.derivatives_indices = NULL if self.data.external_inputs.array.size > 0: - self._network.external_inputs = &self.data.external_inputs.array[0][0] + self._network.external_inputs = &self.data.external_inputs.array[0] else: - self._network.external_inputs = NULL + raise ValueError("External inputs array cannot be of size 0") if self.data.outputs.array.size > 0: - self._network.outputs = &self.data.outputs.array[0][0] + self._network.outputs = &self.data.outputs.array[0] + else: + raise ValueError("Outputs array cannot be of size 0") + + if self.data.tmp_outputs.array.size > 0: + self._network.tmp_outputs = &self.data.tmp_outputs.array[0] else: - self._network.outputs = NULL + raise ValueError("Temp Outputs array cannot be of size 0") if self.data.noise.outputs.size > 0: self._network.noise = &self.data.noise.outputs[0] @@ -166,37 +165,41 @@ cdef class NetworkCy(ODESystem): if self.data.connectivity.node_indices.size > 0: self._network.node_indices = &self.data.connectivity.node_indices[0] else: - self._network.node_indices = NULL + raise ValueError("Connectivity array cannot be of size 0") if self.data.connectivity.edge_indices.size > 0: self._network.edge_indices = &self.data.connectivity.edge_indices[0] else: - self._network.edge_indices = NULL + raise ValueError("Connectivity array cannot be of size 0") if self.data.connectivity.weights.size > 0: self._network.weights = &self.data.connectivity.weights[0] else: - self._network.weights = NULL + raise ValueError("Connectivity array cannot be of size 0") if self.data.connectivity.index_offsets.size > 0: self._network.index_offsets = &self.data.connectivity.index_offsets[0] else: - self._network.index_offsets = NULL + raise ValueError("Connectivity array cannot be of size 0") - def __init__(self, nnodes, nedges, data: NetworkDataCy): + def __init__(self, nnodes, nedges, data: NetworkDataCy, log: NetworkLogCy): """ Initialize """ super().__init__() + self.log = log self.iteration = 0 def __dealloc__(self): """ Deallocate any manual memory as part of clean up """ if self._network.nodes is not NULL: free(self._network.nodes) + self._network.nodes = NULL if self._network.edges is not NULL: free(self._network.edges) + self._network.edges = NULL if self._network is not NULL: free(self._network) + self._network = NULL def setup_network(self, options: NetworkOptions, data: NetworkData, nodes: List[NodeCy], edges: List[EdgeCy]): """ Setup network """ @@ -207,6 +210,23 @@ cdef class NetworkCy(ODESystem): for index, edge in enumerate(edges): self._network.edges[index] = ((edge._edge_cy)._edge) + cdef void evaluate(self, double time, double[:] states, double[:] derivatives) noexcept: + """ Evaluate the ODE """ + ode(time, states, derivatives, self._network) + # Swap the temporary outputs + self.data.outputs.array[:] = self.data.tmp_outputs.array[:] + + def ode_func(self, double time, double[:] states): + """ Evaluate the ODE """ + self.evaluate(time, states, self.data.derivatives.array) + return self.data.derivatives.array + + def update_logs(self, iteration: int): + """ Updated logs to copy current iteration data into logs """ + self.log.states.array[iteration, :] = self.data.states.array[:] + self.log.external_inputs.array[iteration, :] = self.data.external_inputs.array[:] + self.log.outputs.array[iteration, :] = self.data.outputs.array[:] + @property def nnodes(self): """ Number of nodes in the network """ @@ -226,51 +246,3 @@ cdef class NetworkCy(ODESystem): def nstates(self, value: int): """ Number of network states """ self._network.nstates = value - - cpdef void evaluate(self, double time, double[:] states, double[:] derivatives) noexcept: - """ Evaluate the ODE """ - # Update noise model - ode(time, states, derivatives, self._network) - self.data.derivatives.array[self.iteration, :] = derivatives[:] - - cpdef void step(self): - """ Step the network state """ - cdef NetworkDataCy data = self.data - cdef SDESystem sde_system = self.sde_system - cdef EulerMaruyamaSolver sde_integrator = self.sde_integrator - - # sde_integrator.step( - # sde_system, - # (self.iteration%self.buffer_size)*self.timestep, - # self.data.noise.states - # ) - # _noise_states_to_output( - # self.data.noise.states, - # self.data.noise.indices, - # self.data.noise.outputs - # ) - # self.ode_integrator.step( - # self, - # (self.iteration%self.buffer_size)*self.timestep, - # self.data.states.array - # ) - # Logging - # TODO: Use network options to check global logging flag - # logger((self.iteration%self.buffer_size), self.data, self._network) - self.iteration += 1 - - def setup_integrator(self, network_options: NetworkOptions): - """ Setup integrator for neural network """ - # Setup ODE numerical integrator - integration_options = network_options.integration - timestep = integration_options.timestep - self.ode_integrator = RK4Solver(self._network.nstates, timestep) - # Setup SDE numerical integrator for noise models if any - noise_options = [] - for node in network_options.nodes: - if node.noise is not None: - if node.noise.is_stochastic: - noise_options.append(node.noise) - - self.sde_system = OrnsteinUhlenbeck(noise_options) - self.sde_integrator = EulerMaruyamaSolver(len(noise_options), timestep) From d30123be1c24a167ce95ca9f75bdd736009c4d13 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Fri, 22 Aug 2025 20:54:35 -0400 Subject: [PATCH 271/316] [MAIN] Improved pyproject setup --- pyproject.toml | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 41997f5..a0a6e04 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,23 +3,19 @@ requires = [ "setuptools", "Cython >= 0.15.1", "numpy", - "farms_core @ git+https://github.com/ShravanTata/farms_core.git@pyproject-toml-setup", ] build-backend = "setuptools.build_meta" [project] +dynamic = ["version"] name = "farms_network" -version = "0.1" description = "Module to generate, develop and visualize neural networks" -license = {file = "LICENSE"} +readme = "README.md" +license = "Apache-2.0" +license-files = ["LICENSE"] dependencies = [ "tqdm", - "farms_core @ git+https://github.com/ShravanTata/farms_core.git@pyproject-toml-setup", ] -# authors = [ -# {name = "Jonathan Arreguit", email = ""}, -# {name = "Shravan Tata Ramalingasetty", email = ""}, -# ] classifiers = [ "Development Status :: 3 - Beta", # Indicate who your project is intended for @@ -28,11 +24,7 @@ classifiers = [ "Topic :: Scientific/Engineering :: Artificial Intelligence", "Topic :: Scientific/Engineering :: Artificial Life", # Specify the Python versions you support here. - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.11", ] [project.urls] @@ -50,8 +42,18 @@ gui = [ "PyQt5", "networkx", "pydot", + "farms_core", ] cli = ["rich",] [project.scripts] -farms_network = "farms_network.utils.run:main" \ No newline at end of file +farms_network = "farms_network.utils.run:main" + +[tool.setuptools.package-data] +farms_muscle = [ + "*.pxd", + "core/*.pxd", + "models/*.pxd", + "numeric/*.pxd", + "noise/*.pxd" +] \ No newline at end of file From 1e25d4fba0390c09a47f73aa16cef3720178ecf7 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Fri, 22 Aug 2025 22:39:05 -0400 Subject: [PATCH 272/316] [CORE] Updated LIDanner state options node default parameters --- farms_network/core/node.py | 2 ++ farms_network/core/options.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/farms_network/core/node.py b/farms_network/core/node.py index e1d564a..cc71a04 100644 --- a/farms_network/core/node.py +++ b/farms_network/core/node.py @@ -62,6 +62,8 @@ def from_options(cls, node_options: NodeOptions): """ From node options """ name: str = node_options.name parameters = node_options.parameters + if parameters is None: + parameters = {} return cls(name, **parameters) def to_options(self): diff --git a/farms_network/core/options.py b/farms_network/core/options.py index 4a52ebd..98b7207 100644 --- a/farms_network/core/options.py +++ b/farms_network/core/options.py @@ -880,7 +880,7 @@ def __init__(self, **kwargs): initial=kwargs.pop("initial", [-60.0, 0.0]), names=LIDannerStateOptions.STATE_NAMES ) - # assert len(self.initial) == 2, f"Number of initial states {len(self.initial)} should be 2" + assert len(self.initial) == 2, f"Number of initial states {len(self.initial)} should be 2" ################################################## From f82a32618472811f7f3b597625bda5a69e9e2969 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Fri, 22 Aug 2025 22:40:03 -0400 Subject: [PATCH 273/316] [MAIN] Fixed license file in pyproject --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index a0a6e04..bd6a635 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,10 +11,10 @@ dynamic = ["version"] name = "farms_network" description = "Module to generate, develop and visualize neural networks" readme = "README.md" -license = "Apache-2.0" -license-files = ["LICENSE"] +license = {file = "LICENSE"} dependencies = [ "tqdm", + "farms_core", ] classifiers = [ "Development Status :: 3 - Beta", From f2b9bd189c071429bff2c78d4a901adfd0a1b4a1 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Fri, 22 Aug 2025 22:42:34 -0400 Subject: [PATCH 274/316] [MODELS] Refactored factory module --- farms_network/models/factory.py | 185 +++++++++++++------------------- 1 file changed, 73 insertions(+), 112 deletions(-) diff --git a/farms_network/models/factory.py b/farms_network/models/factory.py index 33dedaf..a2b49bb 100644 --- a/farms_network/models/factory.py +++ b/farms_network/models/factory.py @@ -1,33 +1,15 @@ -""" ------------------------------------------------------------------------ -Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty -Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne +""" Factory class for generating the node and edges. """ -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at +from abc import ABC +from typing import Dict, Type, Union - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. ------------------------------------------------------------------------ - -Factory class for generating the node model. -""" - -from typing import Dict, Optional, Type - -from farms_network.core.edge import Edge from farms_network.core.node import Node -from farms_network.models.external_relay import ExternalRelayNode +from farms_network.core.edge import Edge +from farms_network.models import Models # from farms_network.models.fitzhugh_nagumo import FitzhughNagumo # from farms_network.models.hh_daun_motoneuron import HHDaunMotoneuron from farms_network.models.hopf_oscillator import HopfOscillatorNode -from farms_network.models.leaky_integrator import LeakyIntegratorNode +# from farms_network.models.leaky_integrator import LeakyIntegratorNode from farms_network.models.li_danner import LIDannerNode from farms_network.models.li_nap_danner import LINaPDannerNode from farms_network.models.linear import LinearNode @@ -35,30 +17,16 @@ # from farms_network.models.matsuoka_node import MatsuokaNode # from farms_network.models.morphed_oscillator import MorphedOscillator # from farms_network.models.morris_lecar import MorrisLecarNode -from farms_network.models.oscillator import OscillatorEdge, OscillatorNode +from farms_network.models.oscillator import OscillatorNode +from farms_network.models.oscillator import OscillatorEdge +from farms_network.models.relay import RelayNode from farms_network.models.relu import ReLUNode -class NodeFactory: - """Implementation of Factory Node class. - """ - _nodes: Dict[str, Type[Edge]] = { - # 'if': PyIntegrateAndFire, - 'oscillator': OscillatorNode, - 'hopf_oscillator': HopfOscillatorNode, - # 'morphed_oscillator': MorphedOscillator, - 'leaky_integrator': LeakyIntegratorNode, - 'external_relay': ExternalRelayNode, - 'linear': LinearNode, - 'li_nap_danner': LINaPDannerNode, - 'li_danner': LIDannerNode, - # 'lif_daun_interneuron': LIFDaunInterneuron, - # 'hh_daun_motoneuron': HHDaunMotoneuron, - # 'fitzhugh_nagumo': FitzhughNagumo, - # 'matsuoka_node': MatsuokaNode, - # 'morris_lecar': MorrisLecarNode, - 'relu': ReLUNode, - } +class BaseFactory(ABC): + """ Base Factory implementation """ + + _registry: Dict = {} @classmethod def available_types(cls) -> list[str]: @@ -67,103 +35,96 @@ def available_types(cls) -> list[str]: Returns: Sorted list of registered node type identifiers """ - return sorted(cls._nodes.keys()) + return list(cls._registry.keys()) @classmethod - def create(cls, node_type: str) -> Node: - """Create a node instance of the specified type. + def create(cls, item_type: Union[str, Models]) -> Node: + """Create a item instance of the specified type. Args: - node_type: Type identifier of node to create + item_type: Type identifier of item to create Returns: - Instance of requested node class + Instance of requested item class Raises: - KeyError: If node_type is not registered + KeyError: If item_type is not registered """ try: - node_class = cls._nodes[node_type] - return node_class + item_class = cls._registry[item_type] + return item_class except KeyError: - available = ', '.join(sorted(cls._nodes.keys())) + available = ', '.join(cls._registry.keys()) raise KeyError( - f"Unknown node type: {node_type}. " + f"Unknown item type: {item_type}. " f"Available types: {available}" ) @classmethod - def register(cls, node_type: str, node_class: Type[Node]) -> None: - """Register a new node type. + def register(cls, item_type, item_class) -> None: + """Register a new item type. Args: - node_type: Unique identifier for the node - node_class: Node class to register, must inherit from Node + item_type: Unique identifier for the item + item_class: Node class to register, must inherit from Node Raises: - TypeError: If node_class doesn't inherit from Node - ValueError: If node_type is already registered + TypeError: If item_class doesn't inherit from Node + ValueError: If item_type is already registered """ - if not issubclass(node_class, Node): - raise TypeError(f"Node class must inherit from Node: {node_class}") + if not issubclass(item_class, cls.get_base_type()): + raise TypeError( + f"Class must inherit from {cls.get_base_type()}: {item_class}" + ) + if item_type in cls._registry: + raise ValueError(f"Type already registered: {item_type}") + cls._registry[item_type] = item_class + + @classmethod + def get_base_type(cls): + """Get the base type for factory products. - if node_type in cls._nodes: - raise ValueError(f"Node type already registered: {node_type}") + Must be implemented by subclasses. - cls._nodes[node_type] = node_class + Returns: + Base type that all products must inherit from + """ + raise NotImplementedError -class EdgeFactory: - """Implementation of Factory Edge class. +class NodeFactory(BaseFactory): + """Implementation of Factory Node class. """ - _edges: Dict[str, Type[Edge]] = { - 'oscillator': OscillatorEdge, + _registry: Dict[Models, Type[Node]] = { + Models.BASE: Node, + Models.RELAY: RelayNode, + Models.LINEAR: LinearNode, + Models.RELU: ReLUNode, + Models.OSCILLATOR: OscillatorNode, + Models.HOPF_OSCILLATOR: HopfOscillatorNode, + # Models.MORPHED_OSCILLATOR: MorphedOscillatorNode, + # Models.MATSUOKA: MatsuokaNode, + # Models.FITZHUGH_NAGUMO: FitzhughNagumoNode, + # Models.MORRIS_LECAR: MorrisLecarNode, + # Models.LEAKY_INTEGRATOR: LeakyIntegratorNode, + Models.LI_DANNER: LIDannerNode, + Models.LI_NAP_DANNER: LINaPDannerNode, + # Models.LI_DAUN: LIDaunNode, + # Models.HH_DAUN: HHDaunNode, } @classmethod - def available_types(cls) -> list[str]: - """Get list of registered edge types.""" - return sorted(cls._edges.keys()) + def get_base_type(cls) -> Type[Node]: + return Node - @classmethod - def create(cls, edge_type: str) -> Edge: - """Create an edge instance of the specified type. - Args: - edge_type: Type identifier of edge to create - - Returns: - Instance of requested edge class - - Raises: - KeyError: If edge_type is not registered - """ - try: - edge_class = cls._edges.get(edge_type, Edge) - return edge_class - except KeyError: - available = ', '.join(sorted(cls._edges.keys())) - raise KeyError( - f"Unknown edge type: {edge_type}. " - f"Available types: {available}" - ) +class EdgeFactory(BaseFactory): + """Implementation of Factory Edge class.""" + _registry: Dict[Models, Type[Edge]] = { + Models.BASE: Edge, + Models.OSCILLATOR: OscillatorEdge, + } @classmethod - def register(cls, edge_type: str, edge_class: Type[Edge]) -> None: - """Register a new edge type. - - Args: - edge_type: Unique identifier for the edge - edge_class: Edge class to register, must inherit from Edge - - Raises: - TypeError: If edge_class doesn't inherit from Edge - ValueError: If edge_type is already registered - """ - if not issubclass(edge_class, Edge): - raise TypeError(f"Edge class must inherit from Edge: {edge_class}") - - if edge_type in cls._edges: - raise ValueError(f"Edge type already registered: {edge_type}") - - cls._edges[edge_type] = edge_class + def get_base_type(cls) -> Type[Edge]: + return Edge From 03adb3957935a4cffef5a12152eccf5f64837e5a Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Fri, 22 Aug 2025 22:43:11 -0400 Subject: [PATCH 275/316] [EX] Updated examples for testing current changes --- examples/ijspeert07/run.py | 75 +++- examples/rhythm_generator/run.py | 743 +++++++++++++++++++++++++++++++ 2 files changed, 798 insertions(+), 20 deletions(-) create mode 100644 examples/rhythm_generator/run.py diff --git a/examples/ijspeert07/run.py b/examples/ijspeert07/run.py index c0430e2..6ab9bae 100644 --- a/examples/ijspeert07/run.py +++ b/examples/ijspeert07/run.py @@ -8,9 +8,12 @@ import seaborn as sns from farms_core.utils import profile from farms_network.core import options +from farms_core.io.yaml import read_yaml from farms_network.core.data import NetworkData from farms_network.core.network import Network from tqdm import tqdm +from farms_network.numeric.integrators_cy import RK4Solver +from scipy.integrate import ode plt.rcParams['text.usetex'] = False @@ -28,7 +31,7 @@ def oscillator_chain(network_options, n_oscillators, name_prefix, **kwargs): for n in range(n_oscillators) ] # Oscillators - intrinsic_frequency = kwargs.get('intrinsic_frequency', 0.2) + intrinsic_frequency = kwargs.get('intrinsic_frequency', 1.0) nominal_amplitude = kwargs.get('nominal_amplitude', 1.0) amplitude_rate = kwargs.get('amplitude_rate', 20.0) @@ -54,7 +57,7 @@ def oscillator_chain(network_options, n_oscillators, name_prefix, **kwargs): ) ) # Connect - phase_diff = kwargs.get('axial_phi', 2*np.pi/8) + phase_diff = kwargs.get('axial_phi', -np.pi/2) weight = kwargs.get('axial_w', 5) connections = np.vstack( (np.arange(n_oscillators), @@ -97,7 +100,7 @@ def oscillator_double_chain(network_options, n_oscillators, **kwargs): # Connect double chain phase_diff = kwargs.get('anti_phi', np.pi) - weight = kwargs.get('anti_w', 50) + weight = kwargs.get('anti_w', 5) for n in range(n_oscillators): network_options.add_edge( options.OscillatorEdgeOptions( @@ -153,7 +156,7 @@ def nodes(self): return nodes -def generate_network(iterations=2000): +def generate_network(iterations=30000): """ Generate network """ # Main network @@ -172,51 +175,85 @@ def generate_network(iterations=2000): ) # Generate rhythm centers - n_oscillators = 6 + n_oscillators = 10 network_options = oscillator_double_chain(network_options, n_oscillators) + graph = nx.node_link_graph( + network_options, + directed=True, + multigraph=False, + link="edges", + name="name", + source="source", + target="target" + ) + node_positions = nx.spring_layout(graph) + for index, node in enumerate(network_options.nodes): + node.visual.position[:2] = node_positions[node.name] + network_options.save("/tmp/network_options.yaml") + # network_options = options.NetworkOptions.from_options( + # read_yaml("/tmp/rhythm.yaml") + # ) data = NetworkData.from_options(network_options) network = Network.from_options(network_options) - network.setup_integrator(network_options) - # nnodes = len(network_options.nodes) - # integrator.set_initial_value(np.zeros(len(data.states.array),), 0.0) + # network.setup_integrator(network_options) + rk4solver = RK4Solver(network.nstates, 1e-3) + + integrator = ode(network.get_ode_func()).set_integrator( + u'dopri5', + method=u'adams', + max_step=0.0, + # nsteps=0 + ) + nnodes = len(network_options.nodes) + integrator.set_initial_value(np.zeros(len(data.states.array[:]),), 0.0) # print("Data ------------", np.array(network.data.states.array)) # data.to_file("/tmp/sim.hdf5") - # integrator.integrate(integrator.t + 1e-3) - # # Integrate - states = np.ones((iterations+1, len(data.states.array)))*0.0 - outputs = np.ones((iterations, len(data.outputs.array)))*1.0 + states = np.ones((iterations+1, len(data.states.array[:])))*1.0 + outputs = np.ones((iterations, len(data.outputs.array[:])))*1.0 # states[0, 2] = -1.0 # for index, node in enumerate(network_options.nodes): # print(index, node.name) # network.data.external_inputs.array[:] = np.ones((1,))*(iteration/iterations)*1.0 for iteration in tqdm(range(0, iterations), colour='green', ascii=' >='): + time = iteration*1e-3 + network.data.times.array[iteration] = time # network.step(network.ode, iteration*1e-3, network.data.states.array) # network.step() # states[iteration+1, :] = network.data.states.array - network.step() - network.data.times.array[iteration] = iteration*1e-3 + # network.step() + # network.evaluate(iteration*1e-3, states[iteration, :]) + + # integrator.set_initial_value(integrator.y, integrator.t) + # integrator.integrate(integrator.t+1e-3) + + rk4solver.step(network._network_cy, time, network.data.states.array) + outputs[iteration, :] = network.data.outputs.array + states[iteration, :] = network.data.states.array + + outputs[iteration, :] = network.data.outputs.array[:] + states[iteration, :] = network.data.states.array[:] # network.data.to_file("/tmp/network.h5") plt.figure() - for j in range(int(n_oscillators/2)+1): + for j in range(int(n_oscillators/2)): plt.fill_between( np.array(network.data.times.array), - 2*j + (1 + np.sin(network.data.nodes[j].output.array)), + 2*j + (1 + np.sin(outputs[:, j])), 2*j, alpha=0.2, lw=1.0, ) plt.plot( np.array(network.data.times.array), - 2*j + (1 + np.sin(network.data.nodes[j].output.array)), + 2*j + (1 + np.sin(outputs[:, j])), label=f"{j}" ) plt.legend() @@ -232,12 +269,10 @@ def generate_network(iterations=2000): ) plt.figure() node_positions = nx.circular_layout(graph) - node_positions = nx.spring_layout(graph) + node_positions = nx.forceatlas2_layout(graph) for index, node in enumerate(network_options.nodes): node.visual.position[:2] = node_positions[node.name] - network_options.save("/tmp/network_options.yaml") - _ = nx.draw_networkx_nodes( graph, pos=node_positions, diff --git a/examples/rhythm_generator/run.py b/examples/rhythm_generator/run.py new file mode 100644 index 0000000..2d568fe --- /dev/null +++ b/examples/rhythm_generator/run.py @@ -0,0 +1,743 @@ +import matplotlib.pyplot as plt +import networkx as nx +import numpy as np +import seaborn as sns +from typing import Iterable, List +from farms_core.io.yaml import read_yaml +from farms_core.utils import profile +from farms_network.core import options +from farms_network.core.data import NetworkData +from farms_network.core.network import Network +from farms_network.numeric.integrators_cy import RK4Solver +from scipy.integrate import ode +from tqdm import tqdm + +plt.rcParams['text.usetex'] = False + + +def calculate_arc_rad(source_pos, target_pos, base_rad=-0.1): + """Calculate arc3 radius for edge based on node positions.""" + dx = target_pos[0] - source_pos[0] + dy = target_pos[1] - source_pos[1] + + # Set curvature to zero if nodes are aligned horizontally or vertically + if dx == 0 or dy == 0: + return 0.0 + + # Decide on curvature based on position differences + if abs(dx) > abs(dy): + # Horizontal direction - positive rad for up, negative for down + return -base_rad if dy >= 0 else base_rad + else: + # Vertical direction - positive rad for right, negative for left + return base_rad if dx >= 0 else base_rad + + +def update_edge_visuals(network_options): + """ Update edge options """ + + nodes = network_options.nodes + edges = network_options.edges + for edge in edges: + base_rad = calculate_arc_rad( + nodes[nodes.index(edge.source)].visual.position, + nodes[nodes.index(edge.target)].visual.position, + ) + edge.visual.connectionstyle = f"arc3,rad={base_rad*0.0}" + return network_options + + +def join_str(strings): + return "_".join(filter(None, strings)) + + +def multiply_transform(vec: np.ndarray, transform_mat: np.ndarray) -> np.ndarray: + """ + Multiply a 2D vector with a 2D transformation matrix (3x3). + + Parameters: + vec (np.ndarray): A 2D vector (shape (2,) or (3,)) + transform_mat (np.ndarray): A 3x3 transformation matrix. + + Returns: + np.ndarray: The transformed vector. + """ + + assert transform_mat.shape == (3, 3), "Transformation matrix must be 3x3" + + # Ensure vec is in homogeneous coordinates (i.e., 3 elements). + if vec.shape == (2,): + vec = np.append(vec, 1) + elif vec.shape != (3,): + raise ValueError("Input vector must have shape (2,) or (3,)") + + # Perform the multiplication + return transform_mat @ vec + + +def get_scale_matrix(scale: float) -> np.ndarray: + """Return a scaling matrix.""" + return np.array([[scale, 0, 0], [0, scale, 0], [0, 0, 1]]) + + +def get_mirror_matrix(mirror_x: bool = False, mirror_y: bool = False) -> np.ndarray: + """Return a mirror matrix based on the mirror flags.""" + mirror_matrix = np.identity(3) + if mirror_x: + mirror_matrix = np.array([[1, 0, 0], [0, -1, 0], [0, 0, 1]]) + if mirror_y: + mirror_matrix = np.array([[-1, 0, 0], [0, 1, 0], [0, 0, 1]]) + return mirror_matrix + + +def get_translation_matrix(off_x: float, off_y: float) -> np.ndarray: + """Return a translation matrix.""" + return np.array([[1, 0, off_x], [0, 1, off_y], [0, 0, 1]]) + + +def get_rotation_matrix(angle: float) -> np.ndarray: + """Return a rotation matrix for the given angle in degrees.""" + angle_rad = np.radians(angle) + return np.array( + [ + [np.cos(angle_rad), -np.sin(angle_rad), 0], + [np.sin(angle_rad), np.cos(angle_rad), 0], + [0, 0, 1], + ] + ) + + +def get_transform_mat( + angle: float, + off_x: float, + off_y: float, + mirror_x: bool = False, + mirror_y: bool = False, + scale: float = 2.5, +) -> np.ndarray: + """Return a complete transformation matrix based on input parameters.""" + scale_matrix = get_scale_matrix(scale) + mirror_matrix = get_mirror_matrix(mirror_x, mirror_y) + translation_matrix = get_translation_matrix(off_x, off_y) + rotation_matrix = get_rotation_matrix(angle) + + # Combine the transformations in the correct order: translation -> rotation -> mirror -> scale + transform_matrix = translation_matrix @ rotation_matrix @ mirror_matrix + transform_matrix = scale_matrix @ transform_matrix + + return transform_matrix + + +def create_node( + base_name: str, + node_id: str, + node_type: str, + position_vec: np.ndarray, + label: str, + color: list, + transform_mat: np.ndarray, + states: dict, + parameters: dict, +) -> options.LIDannerNodeOptions: + """ + Function to create a node with visual and state options. + + Parameters: + base_name (str): The base name to prepend to node_id. + node_id (str): Unique identifier for the node. + position_vec (np.ndarray): The position of the node. + label (str): The visual label for the node. + color (list): RGB color values for the node. + node_type (str): Type of the node ('LINaPDanner' or 'LIDanner'). + transform_mat (np.ndarray): Transformation matrix for positioning. + v0 (float): Initial value for the state option 'v0'. + h0 (float, optional): Initial value for the state option 'h0', only used for some node types. + + Returns: + options.LIDannerNodeOptions: The configured node options object. + """ + # Generate the full name and position + full_name = join_str((base_name, node_id)) + position = multiply_transform(position_vec, transform_mat).tolist() + + # Determine node type and state options + visual_options = options.NodeVisualOptions( + position=position, + label=label, + color=color, + ) + if node_type == "LINaPDanner": + state_options = options.LINaPDannerStateOptions.from_kwargs(**states) + parameters = options.LINaPDannerNodeParameterOptions.defaults(**parameters) + noise = options.OrnsteinUhlenbeckOptions.defaults() + node_options_class = options.LINaPDannerNodeOptions + elif node_type == "LIDanner": + state_options = options.LIDannerStateOptions.from_kwargs(**states) + parameters = options.LIDannerNodeParameterOptions.defaults(**parameters) + noise = options.OrnsteinUhlenbeckOptions.defaults() + node_options_class = options.LIDannerNodeOptions + elif node_type == "Linear": + state_options = None + parameters = options.LinearParameterOptions.defaults(**parameters) + noise = None + node_options_class = options.LinearNodeOptions + elif node_type == "ReLU": + state_options = None + parameters = options.ReLUParameterOptions.defaults(**parameters) + noise = None + node_options_class = options.ReLUNodeOptions + elif node_type == "ExternalRelay": + state_options = None + parameters = options.NodeParameterOptions() + noise = None + visual_options.radius = 0.0 + node_options_class = options.RelayNodeOptions + else: + raise ValueError(f"Unknown node type: {node_type}") + + # Create and return the node options + return node_options_class( + name=full_name, + parameters=parameters, + visual=visual_options, + state=state_options, + noise=noise, + ) + + +def create_nodes( + node_specs: Iterable, + base_name: str, + transform_mat: np.ndarray, +) -> options.NodeOptions: + """Create node using create_method""" + nodes = {} + for ( + node_id, + node_type, + position_vec, + label, + color, + states, + parameters, + ) in node_specs: + nodes[node_id] = create_node( + base_name, + node_id, + node_type, + position_vec, + label, + color, + transform_mat, + states, + parameters, + ) + return nodes + + +def create_edges( + edge_specs: Iterable, + base_name: str, + visual_options: options.EdgeVisualOptions = options.EdgeVisualOptions(), +) -> options.EdgeOptions: + """Create edges from specs""" + edges = {} + for source_tuple, target_tuple, weight, edge_type in edge_specs: + source = join_str((base_name, *source_tuple)) + target = join_str((base_name, *target_tuple)) + edges[join_str((source, "to", target))] = options.EdgeOptions( + source=source, + target=target, + weight=weight, + type=edge_type, + visual=options.EdgeVisualOptions(**visual_options), + ) + return edges + + +class BrainStemDrive: + """ Generate Brainstem drive network """ + + def __init__(self, name="", transform_mat=np.identity(3)): + """Initialization.""" + super().__init__() + self.name = name + self.transform_mat = transform_mat + + def nodes(self): + """Add nodes.""" + node_specs = [ + ( + join_str(("BS", "input")), + "Relay", + np.array((3.0, 0.0)), + "A", + [0.0, 0.0, 0.0], + {}, + {}, + ), + ( + join_str(("BS", "DR")), + "Linear", + np.array((3.0, -1.0)), + "A", + [0.0, 0.0, 0.0], + None, + {"slope": 1.0, "bias": 0.0}, + ), + ] + + # Loop through the node specs to create each node using the create_node function + nodes = create_nodes(node_specs, self.name, self.transform_mat) + return nodes + + def edges(self): + """Add edges.""" + + # Define edge details in a list for easier iteration + edge_specs = [ + (("BS", "input"), ("BS", "DR"), 1.0, "excitatory"), + ] + + # Loop through the edge specs to create each edge + edges = create_edges(edge_specs, self.name) + return edges + + +class RhythmGenerator: + """Generate RhythmGenerator Network""" + + def __init__(self, name="", transform_mat=np.identity(3)): + """Initialization.""" + super().__init__() + self.name = name + self.transform_mat = transform_mat + + def nodes(self): + """Add nodes.""" + node_specs = [ + ( + join_str(("RG", "F")), + "LINaPDanner", + np.array((3.0, 0.0)), + "F", + [1.0, 0.0, 0.0], + {"v": -62.5, "h": np.random.uniform(0, 1)}, + {}, + ), + ( + join_str(("RG", "E")), + "LINaPDanner", + np.array((-3.0, 0.0)), + "E", + [0.0, 1.0, 0.0], + {"v": -62.5, "h": np.random.uniform(0, 1)}, + {}, + ), + ( + join_str(("RG", "In", "F")), + "LIDanner", + np.array((1.0, -1.5)), + "In", + [0.2, 0.2, 0.2], + {"v": -60.0, "a": 0.0}, + {}, + ), + ( + join_str(("RG", "In", "E")), + "LIDanner", + np.array((-1.0, 1.5)), + "In", + [0.2, 0.2, 0.2], + {"v": -60.0, "a": 0.0}, + {}, + ), + # ( + # join_str(("RG", "In", "E2")), + # "LIDanner", + # np.array((-5.0, 1.0)), + # "In", + # [0.2, 0.2, 0.2], + # {"v": -60.0, "a": 0.0}, + # {}, + # ), + # ( + # join_str(("RG", "F", "DR")), + # "Linear", + # np.array((3.0, 2.0)), + # "d", + # [0.5, 0.5, 0.5], # Default visual color if needed + # None, + # {"slope": 0.1, "bias": 0.0}, + # ), + # ( + # join_str(("RG", "E", "DR")), + # "Linear", + # np.array((-3.0, 2.0)), + # "d", + # [0.5, 0.5, 0.5], # Default visual color if needed + # None, + # {"slope": 0.0, "bias": 0.1}, + # ), + ] + + # Loop through the node specs to create each node using the create_node function + nodes = create_nodes(node_specs, self.name, self.transform_mat) + return nodes + + def edges(self): + """Add edges.""" + + # Define edge details in a list for easier iteration + edge_specs = [ + (("RG", "F"), ("RG", "In", "F"), 0.4, "excitatory"), + (("RG", "In", "F"), ("RG", "E"), -1.0, "inhibitory"), + (("RG", "E"), ("RG", "In", "E"), 0.4, "excitatory"), + (("RG", "In", "E"), ("RG", "F"), -0.08, "inhibitory"), + # (("RG", "In", "E2"), ("RG", "F"), -0.04, "inhibitory"), + # (("RG", "F", "DR"), ("RG", "F"), 1.0, "excitatory"), + # (("RG", "E", "DR"), ("RG", "E"), 1.0, "excitatory"), + ] + + # Loop through the edge specs to create each edge + edges = create_edges(edge_specs, self.name) + return edges + + +# class RhythmGenerator: +# """Generate RhythmGenerator Network""" + +# def __init__(self, name="", anchor_x=0.0, anchor_y=0.0): +# """Initialization.""" +# super().__init__() +# self.name = name + +# def nodes(self): +# """Add nodes.""" +# nodes = {} +# nodes["RG-F"] = options.LINaPDannerNodeOptions( +# name=self.name + "-RG-F", +# parameters=options.LINaPDannerNodeParameterOptions.defaults(), +# visual=options.NodeVisualOptions(label="F", color=[1.0, 0.0, 0.0]), +# state=options.LINaPDannerStateOptions.from_kwargs( +# v=-60.5, h=np.random.uniform(0, 1) +# ), +# noise=None +# ) + +# nodes["RG-E"] = options.LINaPDannerNodeOptions( +# name=self.name + "-RG-E", +# parameters=options.LINaPDannerNodeParameterOptions.defaults(), +# visual=options.NodeVisualOptions(label="E", color=[0.0, 1.0, 0.0]), +# state=options.LINaPDannerStateOptions.from_kwargs( +# v=-62.5, h=np.random.uniform(0, 1) +# ), +# noise=None +# ) + +# nodes["In-F"] = options.LIDannerNodeOptions( +# name=self.name + "-In-F", +# parameters=options.LIDannerNodeParameterOptions.defaults(), +# visual=options.NodeVisualOptions(label="In", color=[0.2, 0.2, 0.2]), +# state=options.LIDannerStateOptions.from_kwargs(v=-60.0, a=0.0), +# noise=None +# ) + +# nodes["In-E"] = options.LIDannerNodeOptions( +# name=self.name + "-In-E", +# parameters=options.LIDannerNodeParameterOptions.defaults(), +# visual=options.NodeVisualOptions(label="In", color=[0.2, 0.2, 0.2]), +# state=options.LIDannerStateOptions.from_kwargs(v=-60.0, a=0.0), +# noise=None +# ) +# return nodes + +# def edges(self): +# edges = {} +# edges["RG-F-to-In-F"] = options.EdgeOptions( +# source=self.name + "-RG-F", +# target=self.name + "-In-F", +# weight=0.4, +# type="excitatory", +# visual=options.EdgeVisualOptions(), +# ) +# edges["In-F-to-RG-E"] = options.EdgeOptions( +# source=self.name + "-In-F", +# target=self.name + "-RG-E", +# weight=-1.0, +# type="inhibitory", +# visual=options.EdgeVisualOptions(), +# ) +# edges["In-E-to-RG-F"] = options.EdgeOptions( +# source=self.name + "-In-E", +# target=self.name + "-RG-F", +# weight=-0.08, +# type="inhibitory", +# visual=options.EdgeVisualOptions(), +# ) +# edges["RG-E-to-In-E"] = options.EdgeOptions( +# source=self.name + "-RG-E", +# target=self.name + "-In-E", +# weight=0.4, +# type="excitatory", +# visual=options.EdgeVisualOptions(), +# ) +# return edges + + +def generate_network(iterations=1000): + """ Generate network """ + + # Main network + network_options = options.NetworkOptions( + directed=True, + multigraph=False, + graph={"name": "rhythm_generator"}, + integration=options.IntegrationOptions.defaults( + n_iterations=iterations, + timestep=float(1.0), + ), + logs=options.NetworkLogOptions( + n_iterations=iterations, + buffer_size=iterations, + ) + ) + + # Generate rhythm center + rhythm = RhythmGenerator(name="") + network_options.add_nodes((rhythm.nodes()).values()) + network_options.add_edges((rhythm.edges()).values()) + + flexor_drive = options.LinearNodeOptions( + name="FD", + parameters=options.LinearParameterOptions.defaults(slope=0.1, bias=0.0), + visual=options.NodeVisualOptions(position=(1.0, 0.0)), + noise=None + ) + extensor_drive = options.LinearNodeOptions( + name="ED", + parameters=options.LinearParameterOptions.defaults(slope=0.0, bias=0.1), + visual=options.NodeVisualOptions(position=(1.0, 1.0)), + noise=None + ) + + drive = options.RelayNodeOptions( + name="D", + visual=options.NodeVisualOptions(position=(5.0, 5.0)), + parameters=None, + noise=None + ) + + network_options.add_node(flexor_drive) + network_options.add_node(extensor_drive) + network_options.add_node(drive) + + network_options.add_edge( + options.EdgeOptions( + source="FD", + target="RG_F", + weight=1.0, + type="excitatory", + visual=options.EdgeVisualOptions(), + ) + ) + network_options.add_edge( + options.EdgeOptions( + source="ED", + target="RG_E", + weight=1.0, + type="excitatory", + visual=options.EdgeVisualOptions(), + ) + ) + network_options.add_edge( + options.EdgeOptions( + source="ED", + target="RG_In_E", + weight=1.0, + type="excitatory", + visual=options.EdgeVisualOptions(), + ) + ) + network_options.add_edge( + options.EdgeOptions( + source="D", + target="ED", + weight=1.0, + type="excitatory", + visual=options.EdgeVisualOptions(), + ) + ) + network_options.add_edge( + options.EdgeOptions( + source="D", + target="FD", + weight=1.0, + type="excitatory", + visual=options.EdgeVisualOptions(), + ) + ) + + + # network_options = options.NetworkOptions.from_options( + # read_yaml("/Users/tatarama/projects/work/farms/farms_network/examples/mouse/config/network_options.yaml") + # ) + graph = nx.node_link_graph( + network_options, + directed=True, + multigraph=False, + link="edges", + name="name", + source="source", + target="target" + ) + # node_positions = nx.spring_layout(graph) + # network_options.save("/tmp/rhythm.yaml") + # for index, node in enumerate(network_options.nodes): + # node.visual.position[:2] = node_positions[node.name] + + data = NetworkData.from_options(network_options) + + network = Network.from_options(network_options) + + # network.setup_integrator(network_options) + rk4solver = RK4Solver(network.nstates, network_options.integration.timestep) + + integrator = ode(network.get_ode_func()).set_integrator( + u'dopri5', + method=u'adams', + max_step=0.0, + # nsteps=0 + ) + nnodes = len(network_options.nodes) + integrator.set_initial_value(np.zeros(len(data.states.array[:]),), 0.0) + + # print("Data ------------", np.array(network.data.states.array)) + + # data.to_file("/tmp/sim.hdf5") + + # # Integrate + states = np.ones((iterations, len(data.states.array[:])))*1.0 + states_tmp = np.zeros((len(data.states.array[:],))) + outputs = np.ones((iterations, len(data.outputs.array[:])))*1.0 + # states[0, 2] = -1.0 + + # for index, node in enumerate(network_options.nodes): + # print(index, node.name) + # network.data.external_inputs.array[:] = np.ones((1,))*(iteration/iterations)*1.0 + inputs = np.ones(np.shape(network.data.external_inputs.array[:])) + # print(np.array(network.data.connectivity.weights), np.array(network.data.connectivity.edge_indices), np.array(network.data.connectivity.node_indices), np.array(network.data.connectivity.index_offsets)) + for iteration in tqdm(range(0, iterations), colour='green', ascii=' >='): + time = iteration + # network.step(network.ode, iteration*1e-3, network.data.states.array) + # network.step() + # states[iteration+1, :] = network.data.states.array + # network.step() + # network.evaluate(iteration*1e-3, states[iteration, :]) + + _iter = network._network_cy.iteration + network.data.times.array[_iter] = time + network.data.external_inputs.array[:] = inputs*0.5 + # integrator.set_initial_value(integrator.y, integrator.t) + # integrator.integrate(integrator.t+1.0) + # network.data.states.array[:] = integrator.y + rk4solver.step(network._network_cy, time, network.data.states.array) + # outputs[iteration, :] = network.data.outputs.array + # states[iteration, :] = integrator.y# network.data.states.array + # network._network_cy.update_iteration() + network._network_cy.iteration += 1 + network._network_cy.update_logs(network._network_cy.iteration) + + # network.data.to_file("/tmp/network.h5") + nodes_data = network.log.nodes + plt.figure() + plt.plot( + np.linspace(0.0, iterations*1e-3, iterations), states[:, :], + ) + plt.figure() + plt.fill_between( + np.linspace(0.0, iterations*1e-3, iterations), np.asarray(nodes_data[0].output.array), + alpha=0.2, lw=1.0, + ) + plt.plot( + np.linspace(0.0, iterations*1e-3, iterations), np.asarray(nodes_data[0].output.array), + label="RG-F" + ) + plt.fill_between( + np.linspace(0.0, iterations*1e-3, iterations), np.asarray(nodes_data[1].output.array), + alpha=0.2, lw=1.0, + ) + plt.plot( + np.linspace(0.0, iterations*1e-3, iterations), np.asarray(nodes_data[1].output.array), label="RG-E" + ) + plt.legend() + + plt.figure() + # node_positions = nx.circular_layout(graph) + # node_positions = nx.forceatlas2_layout(graph) + node_positions = {} + for index, node in enumerate(network_options.nodes): + node_positions[node.name] = node.visual.position[:2] + + _ = nx.draw_networkx_nodes( + graph, + pos=node_positions, + node_color=[data["visual"]["color"] for node, data in graph.nodes.items()], + alpha=0.25, + edgecolors='k', + linewidths=2.0, + ) + nx.draw_networkx_labels( + graph, + pos=node_positions, + labels={node: data["visual"]["label"] for node, data in graph.nodes.items()}, + font_size=11.0, + font_weight='bold', + font_family='sans-serif', + alpha=1.0, + ) + nx.draw_networkx_edges( + graph, + pos=node_positions, + edge_color=[ + [0.0, 1.0, 0.0] if data["type"] == "excitatory" else [1.0, 0.0, 0.0] + for edge, data in graph.edges.items() + ], + width=1., + arrowsize=10, + style='dashed', + arrows=True, + min_source_margin=5, + min_target_margin=5, + connectionstyle="arc3,rad=-0.2", + ) + # plt.figure() + # sparse_array = nx.to_scipy_sparse_array(graph) + # sns.heatmap( + # sparse_array.todense(), cbar=False, square=True, + # linewidths=0.5, + # annot=True + # ) + plt.show() + + # generate_tikz_figure( + # graph, + # paths.get_project_data_path().joinpath("templates", "network",), + # "tikz-full-network.tex", + # paths.get_project_images_path().joinpath("quadruped_network.tex") + # ) + + +def main(): + """Main.""" + + # Generate the network + # profile.profile(generate_network) + generate_network() + + # Run the network + # run_network() + + +if __name__ == "__main__": + main() From 73fff2ebb9d71e2e7fc2f7973a379af47993fd24 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Sat, 23 Aug 2025 14:41:19 -0400 Subject: [PATCH 276/316] [CORE] Updated function signature for input_tf and all associated nodes --- farms_network/core/network_cy.pyx | 22 ++++++-- farms_network/core/node_cy.pxd | 6 +- farms_network/core/node_cy.pyx | 61 +-------------------- farms_network/models/hopf_oscillator_cy.pxd | 3 +- farms_network/models/hopf_oscillator_cy.pyx | 19 ++----- farms_network/models/li_danner_cy.pxd | 3 +- farms_network/models/li_danner_cy.pyx | 24 +++----- farms_network/models/li_nap_danner_cy.pxd | 3 +- farms_network/models/li_nap_danner_cy.pyx | 22 +++----- farms_network/models/linear_cy.pxd | 3 +- farms_network/models/linear_cy.pyx | 17 ++---- farms_network/models/oscillator_cy.pxd | 3 +- farms_network/models/oscillator_cy.pyx | 19 ++----- farms_network/models/relay_cy.pxd | 3 +- farms_network/models/relay_cy.pyx | 14 ++--- farms_network/models/relu_cy.pxd | 3 +- farms_network/models/relu_cy.pyx | 17 ++---- 17 files changed, 77 insertions(+), 165 deletions(-) diff --git a/farms_network/core/network_cy.pyx b/farms_network/core/network_cy.pyx index 829ed38..8dd8c90 100644 --- a/farms_network/core/network_cy.pyx +++ b/farms_network/core/network_cy.pyx @@ -30,12 +30,18 @@ cdef inline void ode( ) noexcept: """ C Implementation to compute full network state """ - cdef node_t __node + cdef node_t* __node cdef node_t** c_nodes = c_network.nodes cdef edge_t** c_edges = c_network.edges cdef unsigned int nnodes = c_network.nnodes cdef unsigned int j - cdef processed_inputs_t processed_inputs + cdef processed_inputs_t processed_inputs = { + 'generic': 0.0, + 'excitatory': 0.0, + 'inhibitory': 0.0, + 'cholinergic': 0.0, + 'phase_coupling': 0.0 + } cdef node_inputs_t node_inputs node_inputs.network_outputs = c_network.outputs @@ -44,7 +50,8 @@ cdef inline void ode( cdef double* derivatives_ptr = &derivatives[0] for j in range(nnodes): - __node = c_nodes[j][0] + __node = c_nodes[j] + # Prepare node context node_inputs.node_indices = c_network.node_indices + c_network.index_offsets[j] node_inputs.edge_indices = c_network.edge_indices + c_network.index_offsets[j] @@ -55,12 +62,19 @@ cdef inline void ode( node_inputs.node_index = j # Compute the inputs from all nodes - processed_inputs = __node.input_tf( + processed_inputs.generic = 0.0 + processed_inputs.excitatory = 0.0 + processed_inputs.inhibitory = 0.0 + processed_inputs.cholinergic = 0.0 + processed_inputs.phase_coupling = 0.0 + + __node.input_tf( time, states_ptr + c_network.states_indices[j], node_inputs, c_nodes[j], c_edges, + &processed_inputs ) if __node.is_statefull: diff --git a/farms_network/core/node_cy.pxd b/farms_network/core/node_cy.pxd index 8a0cac1..da37a95 100644 --- a/farms_network/core/node_cy.pxd +++ b/farms_network/core/node_cy.pxd @@ -23,12 +23,13 @@ cdef struct processed_inputs_t: # Input transfer function # Receives n-inputs and produces one output to be fed into ode/output_tf -cdef processed_inputs_t base_input_tf( +cdef void base_input_tf( double time, const double* states, const node_inputs_t inputs, const node_t* node, const edge_t** edges, + processed_inputs_t* out ) @@ -68,12 +69,13 @@ cdef struct node_t: void* params # Pointer to the parameters of the node. # Functions - processed_inputs_t input_tf( + void input_tf( double time, const double* states, const node_inputs_t inputs, const node_t* node, const edge_t** edges, + processed_inputs_t* out ) noexcept void ode( double time, diff --git a/farms_network/core/node_cy.pyx b/farms_network/core/node_cy.pyx index 68354e1..a74323a 100644 --- a/farms_network/core/node_cy.pyx +++ b/farms_network/core/node_cy.pyx @@ -12,12 +12,13 @@ from farms_network.models import Models # Input transfer function # Receives n-inputs and produces one output to be fed into ode/output_tf -cdef processed_inputs_t base_input_tf( +cdef void base_input_tf( double time, const double* states, const node_inputs_t inputs, const node_t* node, const edge_t** edges, + processed_inputs_t* out ) noexcept: raise NotImplementedError("input_tf must be implemented by node type") @@ -98,61 +99,3 @@ cdef class NodeCy: """ Call C node output """ cdef double* states_ptr = &states[0] return self._node.output_tf(time, states_ptr, input_val, noise, self._node) - - # Methods to wrap the ODE and output functions - # def ode( - # self, - # double time, - # double[:] states, - # double[:] derivatives, - # unsigned int[:] inputs, - # double noise, - # ): - # cdef double* states_ptr = &states[0] - # cdef double* derivatives_ptr = &derivatives[0] - # cdef double* network_outputs_ptr = &network_outputs[0] - # cdef unsigned int* inputs_ptr = &inputs[0] - # cdef double* weights_ptr = &weights[0] - - # cdef edge_t** c_edges = NULL - - # # Call the C function directly - # if self._node.ode is not NULL: - # self._node.ode( - # time, - # states_ptr, - # derivatives_ptr, - # external_input, - # network_outputs_ptr, - # inputs_ptr, - # weights_ptr, - # noise, - # self._node, - # c_edges - # ) - - # def output( - # self, - # double time, - # double[:] states, - # double external_input, - # double[:] network_outputs, - # unsigned int[:] inputs, - # double[:] weights, - # ): - # # Call the C function and return its result - # cdef double* states_ptr = &states[0] - # cdef double* network_outputs_ptr = &network_outputs[0] - # cdef unsigned int* inputs_ptr = &inputs[0] - # cdef double* weights_ptr = &weights[0] - # cdef edge_t** c_edges = NULL - # return self._node.output( - # time, - # states_ptr, - # external_input, - # network_outputs_ptr, - # inputs_ptr, - # weights_ptr, - # self._node, - # c_edges - # ) diff --git a/farms_network/models/hopf_oscillator_cy.pxd b/farms_network/models/hopf_oscillator_cy.pxd index c8e584f..333412e 100644 --- a/farms_network/models/hopf_oscillator_cy.pxd +++ b/farms_network/models/hopf_oscillator_cy.pxd @@ -20,12 +20,13 @@ cdef packed struct hopf_oscillator_params_t: double beta -cdef processed_inputs_t hopf_oscillator_input_tf( +cdef void hopf_oscillator_input_tf( double time, const double* states, const node_inputs_t inputs, const node_t* node, const edge_t** edges, + processed_inputs_t* out ) noexcept diff --git a/farms_network/models/hopf_oscillator_cy.pyx b/farms_network/models/hopf_oscillator_cy.pyx index bd49d38..7397234 100644 --- a/farms_network/models/hopf_oscillator_cy.pyx +++ b/farms_network/models/hopf_oscillator_cy.pyx @@ -17,35 +17,26 @@ cpdef enum STATE: y = STATE_Y -cdef processed_inputs_t hopf_oscillator_input_tf( +cdef void hopf_oscillator_input_tf( double time, const double* states, const node_inputs_t inputs, const node_t* node, const edge_t** edges, + processed_inputs_t* out ) noexcept: # Parameters - cdef hopf_oscillator_params_t params = ( node[0].params)[0] + cdef hopf_oscillator_params_t* params = ( node[0].params) # States cdef double state_x = states[STATE.x] cdef double state_y = states[STATE.y] - cdef processed_inputs_t processed_inputs = { - 'generic': 0.0, - 'excitatory': 0.0, - 'inhibitory': 0.0, - 'cholinergic': 0.0, - 'phase_coupling': 0.0 - } - for j in range(inputs.ninputs): _input = inputs.network_outputs[inputs.node_indices[j]] _weight = inputs.weights[j] - processed_inputs.generic += (_weight*_input) - - return processed_inputs + out.generic += (_weight*_input) cdef void hopf_oscillator_ode( @@ -57,7 +48,7 @@ cdef void hopf_oscillator_ode( const node_t* node, ) noexcept: # Parameters - cdef hopf_oscillator_params_t params = ( node[0].params)[0] + cdef hopf_oscillator_params_t* params = ( node[0].params) # States cdef double state_x = states[STATE.x] diff --git a/farms_network/models/li_danner_cy.pxd b/farms_network/models/li_danner_cy.pxd index 9851d73..3ad22d6 100644 --- a/farms_network/models/li_danner_cy.pxd +++ b/farms_network/models/li_danner_cy.pxd @@ -26,12 +26,13 @@ cdef packed struct li_danner_params_t: double tau_ch # ms -cdef processed_inputs_t li_danner_input_tf( +cdef void li_danner_input_tf( double time, const double* states, const node_inputs_t inputs, const node_t* node, const edge_t** edges, + processed_inputs_t* out ) noexcept diff --git a/farms_network/models/li_danner_cy.pyx b/farms_network/models/li_danner_cy.pyx index d5f580f..01884eb 100644 --- a/farms_network/models/li_danner_cy.pyx +++ b/farms_network/models/li_danner_cy.pyx @@ -14,28 +14,21 @@ cpdef enum STATE: a = STATE_A -cdef processed_inputs_t li_danner_input_tf( +cdef void li_danner_input_tf( double time, const double* states, const node_inputs_t inputs, const node_t* node, const edge_t** edges, + processed_inputs_t* out ) noexcept: # Parameters - cdef li_danner_params_t params = ( node[0].params)[0] + cdef li_danner_params_t* params = ( node[0].params) # States cdef double state_v = states[STATE.v] cdef double state_a = states[STATE.a] - cdef processed_inputs_t processed_inputs = { - 'generic': 0.0, - 'excitatory': 0.0, - 'inhibitory': 0.0, - 'cholinergic': 0.0, - 'phase_coupling': 0.0 - } - # Node inputs cdef: double _sum = 0.0 @@ -51,13 +44,12 @@ cdef processed_inputs_t li_danner_input_tf( _edge = edges[inputs.edge_indices[j]] if _edge.type == EXCITATORY: # Excitatory Synapse - processed_inputs.excitatory += params.g_syn_e*cfabs(_weight)*_input*(state_v - params.e_syn_e) + out.excitatory += params.g_syn_e*cfabs(_weight)*_input*(state_v - params.e_syn_e) elif _edge.type == INHIBITORY: # Inhibitory Synapse - processed_inputs.inhibitory += params.g_syn_i*cfabs(_weight)*_input*(state_v - params.e_syn_i) + out.inhibitory += params.g_syn_i*cfabs(_weight)*_input*(state_v - params.e_syn_i) elif _edge.type == CHOLINERGIC: - processed_inputs.cholinergic += cfabs(_weight)*_input - return processed_inputs + out.cholinergic += cfabs(_weight)*_input cdef void li_danner_ode( @@ -68,7 +60,7 @@ cdef void li_danner_ode( double noise, const node_t* node, ) noexcept: - cdef li_danner_params_t params = ( node[0].params)[0] + cdef li_danner_params_t* params = ( node[0].params) # States cdef double state_v = states[STATE.v] @@ -96,7 +88,7 @@ cdef double li_danner_output_tf( double noise, const node_t* node, ) noexcept: - cdef li_danner_params_t params = ( node.params)[0] + cdef li_danner_params_t* params = ( node.params) cdef double _n_out = 0.0 cdef double cholinergic_gain = 1.0 diff --git a/farms_network/models/li_nap_danner_cy.pxd b/farms_network/models/li_nap_danner_cy.pxd index 1498667..f8efe56 100644 --- a/farms_network/models/li_nap_danner_cy.pxd +++ b/farms_network/models/li_nap_danner_cy.pxd @@ -37,12 +37,13 @@ cdef packed struct li_nap_danner_params_t: double e_syn_i # mV -cdef processed_inputs_t li_nap_danner_input_tf( +cdef void li_nap_danner_input_tf( double time, const double* states, const node_inputs_t inputs, const node_t* node, const edge_t** edges, + processed_inputs_t* out ) noexcept diff --git a/farms_network/models/li_nap_danner_cy.pyx b/farms_network/models/li_nap_danner_cy.pyx index b0de03c..9dbfd56 100644 --- a/farms_network/models/li_nap_danner_cy.pyx +++ b/farms_network/models/li_nap_danner_cy.pyx @@ -15,28 +15,21 @@ cpdef enum STATE: h = STATE_H -cdef processed_inputs_t li_nap_danner_input_tf( +cdef void li_nap_danner_input_tf( double time, const double* states, const node_inputs_t inputs, const node_t* node, const edge_t** edges, + processed_inputs_t* out ) noexcept: - cdef li_nap_danner_params_t params = ( node[0].params)[0] + cdef li_nap_danner_params_t* params = ( node[0].params) # States cdef double state_v = states[STATE.v] cdef double state_h = states[STATE.h] - cdef processed_inputs_t processed_inputs = { - 'generic': 0.0, - 'excitatory': 0.0, - 'inhibitory': 0.0, - 'cholinergic': 0.0, - 'phase_coupling': 0.0 - } - # Neuron inputs cdef: double _sum = 0.0 @@ -51,12 +44,11 @@ cdef processed_inputs_t li_nap_danner_input_tf( _edge = edges[inputs.edge_indices[j]] if _edge.type == EXCITATORY: # Excitatory Synapse - processed_inputs.excitatory += params.g_syn_e*cfabs(_weight)*_input*(state_v - params.e_syn_e) + out.excitatory += params.g_syn_e*cfabs(_weight)*_input*(state_v - params.e_syn_e) elif _edge.type == INHIBITORY: # print(_input, _weight, inputs.source_indices[j], edges[j].type) # Inhibitory Synapse - processed_inputs.inhibitory += params.g_syn_i*cfabs(_weight)*_input*(state_v - params.e_syn_i) - return processed_inputs + out.inhibitory += params.g_syn_i*cfabs(_weight)*_input*(state_v - params.e_syn_i) cdef void li_nap_danner_ode( @@ -67,7 +59,7 @@ cdef void li_nap_danner_ode( double noise, const node_t* node, ) noexcept: - cdef li_nap_danner_params_t params = ( node[0].params)[0] + cdef li_nap_danner_params_t* params = ( node[0].params) # States cdef double state_v = states[STATE.v] @@ -109,7 +101,7 @@ cdef double li_nap_danner_output_tf( double noise, const node_t* node, ) noexcept: - cdef li_nap_danner_params_t params = ( node[0].params)[0] + cdef li_nap_danner_params_t* params = ( node[0].params) cdef double _n_out = 0.0 cdef double state_v = states[STATE.v] diff --git a/farms_network/models/linear_cy.pxd b/farms_network/models/linear_cy.pxd index 9609e0b..d574c6d 100644 --- a/farms_network/models/linear_cy.pxd +++ b/farms_network/models/linear_cy.pxd @@ -15,12 +15,13 @@ cdef packed struct linear_params_t: double bias -cdef processed_inputs_t linear_input_tf( +cdef void linear_input_tf( double time, const double* states, const node_inputs_t inputs, const node_t* node, const edge_t** edges, + processed_inputs_t* out ) noexcept diff --git a/farms_network/models/linear_cy.pyx b/farms_network/models/linear_cy.pyx index 88f3ff2..7f5794a 100644 --- a/farms_network/models/linear_cy.pyx +++ b/farms_network/models/linear_cy.pyx @@ -9,21 +9,15 @@ cpdef enum STATE: nstates = NSTATES -cdef processed_inputs_t linear_input_tf( +cdef void linear_input_tf( double time, const double* states, const node_inputs_t inputs, const node_t* node, const edge_t** edges, + processed_inputs_t* out ) noexcept: - cdef linear_params_t params = ( node[0].params)[0] - cdef processed_inputs_t processed_inputs = { - 'generic': 0.0, - 'excitatory': 0.0, - 'inhibitory': 0.0, - 'cholinergic': 0.0, - 'phase_coupling': 0.0 - } + cdef linear_params_t* params = ( node[0].params) cdef: double _sum = 0.0 @@ -35,8 +29,7 @@ cdef processed_inputs_t linear_input_tf( for j in range(ninputs): _input = inputs.network_outputs[inputs.node_indices[j]] _weight = inputs.weights[j] - processed_inputs.generic += _weight*_input - return processed_inputs + out.generic += _weight*_input cdef void linear_ode( @@ -57,7 +50,7 @@ cdef double linear_output_tf( double noise, const node_t* node, ) noexcept: - cdef linear_params_t params = ( node[0].params)[0] + cdef linear_params_t* params = ( node[0].params) cdef double input_val = input_vals.generic cdef double res = params.slope*input_val + params.bias return res diff --git a/farms_network/models/oscillator_cy.pxd b/farms_network/models/oscillator_cy.pxd index cea4490..1f98034 100644 --- a/farms_network/models/oscillator_cy.pxd +++ b/farms_network/models/oscillator_cy.pxd @@ -25,12 +25,13 @@ cdef packed struct oscillator_edge_params_t: double phase_difference # radians -cdef processed_inputs_t oscillator_input_tf( +cdef void oscillator_input_tf( double time, const double* states, const node_inputs_t inputs, const node_t* node, const edge_t** edges, + processed_inputs_t* out ) noexcept diff --git a/farms_network/models/oscillator_cy.pyx b/farms_network/models/oscillator_cy.pyx index f899759..1d16a8c 100644 --- a/farms_network/models/oscillator_cy.pyx +++ b/farms_network/models/oscillator_cy.pyx @@ -17,30 +17,23 @@ cpdef enum STATE: amplitude_0 = STATE_AMPLITUDE_0 -cdef processed_inputs_t oscillator_input_tf( +cdef void oscillator_input_tf( double time, const double* states, const node_inputs_t inputs, const node_t* node, const edge_t** edges, + processed_inputs_t* out ) noexcept: # Parameters - cdef oscillator_params_t params = ( node[0].params)[0] + cdef oscillator_params_t* params = ( node[0].params) cdef oscillator_edge_params_t edge_params # States cdef double state_phase = states[STATE.phase] cdef double state_amplitude = states[STATE.amplitude] - cdef processed_inputs_t processed_inputs = { - 'generic': 0.0, - 'excitatory': 0.0, - 'inhibitory': 0.0, - 'cholinergic': 0.0, - 'phase_coupling': 0.0 - } - cdef: double _sum = 0.0 unsigned int j @@ -50,9 +43,7 @@ cdef processed_inputs_t oscillator_input_tf( _input = inputs.network_outputs[inputs.node_indices[j]] _weight = inputs.weights[j] edge_params = ( edges[inputs.edge_indices[j]].params)[0] - processed_inputs.generic += _weight*state_amplitude*csin(_input - state_phase - edge_params.phase_difference) - - return processed_inputs + out.generic += _weight*state_amplitude*csin(_input - state_phase - edge_params.phase_difference) cdef void oscillator_ode( @@ -65,7 +56,7 @@ cdef void oscillator_ode( ) noexcept: # Parameters - cdef oscillator_params_t params = ( node[0].params)[0] + cdef oscillator_params_t* params = ( node[0].params) cdef oscillator_edge_params_t edge_params # States diff --git a/farms_network/models/relay_cy.pxd b/farms_network/models/relay_cy.pxd index 345c109..1ed2122 100644 --- a/farms_network/models/relay_cy.pxd +++ b/farms_network/models/relay_cy.pxd @@ -10,12 +10,13 @@ cdef enum: NSTATES = 0 -cdef processed_inputs_t relay_input_tf( +cdef void relay_input_tf( double time, const double* states, const node_inputs_t inputs, const node_t* node, const edge_t** edges, + processed_inputs_t* out ) noexcept diff --git a/farms_network/models/relay_cy.pyx b/farms_network/models/relay_cy.pyx index ca84e15..40028e6 100644 --- a/farms_network/models/relay_cy.pyx +++ b/farms_network/models/relay_cy.pyx @@ -8,22 +8,16 @@ cpdef enum STATE: nstates = NSTATES -cdef processed_inputs_t relay_input_tf( +cdef void relay_input_tf( double time, const double* states, const node_inputs_t inputs, const node_t* node, const edge_t** edges, + processed_inputs_t* out ) noexcept: - cdef processed_inputs_t processed_inputs = { - 'generic': 0.0, - 'excitatory': 0.0, - 'inhibitory': 0.0, - 'cholinergic': 0.0, - 'phase_coupling': 0.0 - } - processed_inputs.generic = inputs.external_input - return processed_inputs + + out.generic = inputs.external_input cdef void relay_ode( diff --git a/farms_network/models/relu_cy.pxd b/farms_network/models/relu_cy.pxd index 6c6e237..e8826dd 100644 --- a/farms_network/models/relu_cy.pxd +++ b/farms_network/models/relu_cy.pxd @@ -16,12 +16,13 @@ cdef packed struct relu_params_t: double offset -cdef processed_inputs_t relu_input_tf( +cdef void relu_input_tf( double time, const double* states, const node_inputs_t inputs, const node_t* node, const edge_t** edges, + processed_inputs_t* out ) noexcept diff --git a/farms_network/models/relu_cy.pyx b/farms_network/models/relu_cy.pyx index 0ae048f..1fceb0f 100644 --- a/farms_network/models/relu_cy.pyx +++ b/farms_network/models/relu_cy.pyx @@ -11,21 +11,15 @@ cpdef enum STATE: nstates = NSTATES -cdef processed_inputs_t relu_input_tf( +cdef void relu_input_tf( double time, const double* states, const node_inputs_t inputs, const node_t* node, const edge_t** edges, + processed_inputs_t* out ) noexcept: - cdef relu_params_t params = ( node[0].params)[0] - cdef processed_inputs_t processed_inputs = { - 'generic': 0.0, - 'excitatory': 0.0, - 'inhibitory': 0.0, - 'cholinergic': 0.0, - 'phase_coupling': 0.0 - } + cdef relu_params_t* params = ( node[0].params) cdef: double _sum = 0.0 @@ -37,8 +31,7 @@ cdef processed_inputs_t relu_input_tf( for j in range(ninputs): _input = inputs.network_outputs[inputs.node_indices[j]] _weight = inputs.weights[j] - processed_inputs.generic += _weight*_input - return processed_inputs + out.generic += _weight*_input cdef void relu_ode( @@ -59,7 +52,7 @@ cdef double relu_output_tf( double noise, const node_t* node, ) noexcept: - cdef relu_params_t params = ( node[0].params)[0] + cdef relu_params_t* params = ( node[0].params) cdef double input_val = input_vals.generic cdef double res = max(0.0, params.gain*(params.sign*input_val + params.offset)) return res From 7998f9f5249c982719eb55799ff4fa2770a59520 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Fri, 5 Sep 2025 12:48:02 +0530 Subject: [PATCH 277/316] [CORE] Made visuals optional to have lighter configs --- farms_network/core/options.py | 70 +++++++++++++++++------------------ 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/farms_network/core/options.py b/farms_network/core/options.py index 98b7207..091c74b 100644 --- a/farms_network/core/options.py +++ b/farms_network/core/options.py @@ -2,7 +2,7 @@ import time -from typing import Any, Dict, Iterable, List, Self, Type +from typing import Any, Dict, Iterable, List, Self, Type, Union from farms_core import pylog from farms_core.options import Options @@ -16,11 +16,18 @@ class NodeOptions(Options): """ Base class for defining node options """ MODEL = Models.BASE - def __init__(self, **kwargs: Any): + def __init__( + self, + name: str, + model: Union[str, Models] = MODEL, + parameters: "NodeParameterOptions" = None, + visual: "NodeVisualOptions" = None, + state: "NodeStateOptions" = None, + noise: "NoiseOptions" = None, + ): """ Initialize """ super().__init__() - self.name: str = kwargs.pop("name") - model = kwargs.pop("model", NodeOptions.MODEL) + self.name = name if isinstance(model, Models): model = Models.to_str(model) elif not isinstance(model, str): @@ -28,23 +35,23 @@ def __init__(self, **kwargs: Any): f"{model} is of {type(model)}. Needs to {type(Models)} or {type(str)}" ) self.model: str = model - self.parameters: NodeParameterOptions = kwargs.pop("parameters", None) - self.visual: NodeVisualOptions = NodeVisualOptions.from_options( - kwargs.pop("visual") - ) - self.state: NodeStateOptions = kwargs.pop("state", None) - self.noise: NoiseOptions = kwargs.pop("noise", None) + self.parameters = parameters + self.visual = visual + self.state = state + self.noise = noise @classmethod - def from_options(cls, kwargs: Dict): + def from_options(cls, options: Dict): """ Load from options """ - options = {} - options["name"] = kwargs.pop("name") - options["parameters"] = kwargs.pop("parameters") - options["visual"] = kwargs.pop("visual") - options["state"] = kwargs.pop("state") - options["noise"] = kwargs.pop("noise") - return cls(**options) + visual = options.get("visual") + return cls( + name=options["name"], + model=options.get("model", cls.MODEL), + parameters=options.get("parameters"), + state=options.get("state"), + noise=options.get("noise"), + visual=NodeVisualOptions.from_options(visual) if visual else None, + ) def __eq__(self, other): if isinstance(other, NodeOptions): @@ -163,7 +170,7 @@ def __init__(self, **kwargs): self.model: str = model self.parameters: EdgeParameterOptions = kwargs.pop("parameters", EdgeParameterOptions()) - self.visual: EdgeVisualOptions = kwargs.pop("visual") + self.visual: EdgeVisualOptions = kwargs.pop("visual", None) if kwargs: raise Exception(f'Unknown kwargs: {kwargs}') @@ -187,9 +194,10 @@ def from_options(cls, kwargs: Dict): options["parameters"] = EdgeParameterOptions.from_options( kwargs["parameters"] ) - options["visual"] = EdgeVisualOptions.from_options( - kwargs["visual"] - ) + if visual := kwargs.get("visual"): + options["visual"] = EdgeVisualOptions.from_options(visual) + else: + options["visual"] = None return cls(**options) def __str__(self) -> str: @@ -331,7 +339,7 @@ def from_options(cls, kwargs: Dict): options = {} options["name"] = kwargs.pop("name") options["parameters"] = None - options["visual"] = NodeVisualOptions.from_options(kwargs["visual"]) + options["visual"] = kwargs.get("visual", None) options["noise"] = kwargs.pop("noise", None) return cls(**options) @@ -370,9 +378,7 @@ def from_options(cls, kwargs: Dict): options["parameters"] = LinearParameterOptions.from_options( kwargs["parameters"] ) - options["visual"] = NodeVisualOptions.from_options( - kwargs["visual"] - ) + options["visual"] = kwargs.get("visual", None) options["noise"] = kwargs.pop("noise", None) return cls(**options) @@ -432,9 +438,7 @@ def from_options(cls, kwargs: Dict): options["parameters"] = ReLUParameterOptions.from_options( kwargs["parameters"] ) - options["visual"] = NodeVisualOptions.from_options( - kwargs["visual"] - ) + options["visual"] = kwargs.get("visual", None) options["state"] = None options["noise"] = kwargs.pop("noise", None) return cls(**options) @@ -806,9 +810,7 @@ def from_options(cls, kwargs: Dict): options["parameters"] = LIDannerNodeParameterOptions.from_options( kwargs["parameters"] ) - options["visual"] = NodeVisualOptions.from_options( - kwargs["visual"] - ) + options["visual"] = kwargs.get("visual", None) options["state"] = LIDannerStateOptions.from_options( kwargs["state"] ) @@ -914,9 +916,7 @@ def from_options(cls, kwargs: Dict): options["parameters"] = LINaPDannerNodeParameterOptions.from_options( kwargs["parameters"] ) - options["visual"] = NodeVisualOptions.from_options( - kwargs["visual"] - ) + options["visual"] = kwargs.get("visual", None) options["state"] = LINaPDannerStateOptions.from_options( kwargs["state"] ) From 0700c450816bf5aaf309ef61cd3d47bec206873f Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Fri, 5 Sep 2025 12:49:27 +0530 Subject: [PATCH 278/316] [EX] Fixed danner components with latest refactor --- examples/mouse/components.py | 116 ++++++++++++++++++----------------- 1 file changed, 59 insertions(+), 57 deletions(-) diff --git a/examples/mouse/components.py b/examples/mouse/components.py index f388819..94bc65a 100644 --- a/examples/mouse/components.py +++ b/examples/mouse/components.py @@ -4,14 +4,14 @@ from pprint import pprint from typing import Iterable, List -import farms_pylog as pylog +from farms_core import pylog import matplotlib.pyplot as plt import networkx as nx import numpy as np from farms_core.io.yaml import read_yaml from farms_network.core import options from farms_network.core.data import NetworkData -from farms_network.core.network import PyNetwork +from farms_network.core.network import Network from jinja2 import Environment, FileSystemLoader from tqdm import tqdm @@ -171,12 +171,12 @@ def create_node( ) if node_type == "LINaPDanner": state_options = options.LINaPDannerStateOptions.from_kwargs(**states) - parameters = options.LINaPDannerParameterOptions.defaults(**parameters) + parameters = options.LINaPDannerNodeParameterOptions.defaults(**parameters) noise = options.OrnsteinUhlenbeckOptions.defaults() node_options_class = options.LINaPDannerNodeOptions elif node_type == "LIDanner": state_options = options.LIDannerStateOptions.from_kwargs(**states) - parameters = options.LIDannerParameterOptions.defaults(**parameters) + parameters = options.LIDannerNodeParameterOptions.defaults(**parameters) noise = options.OrnsteinUhlenbeckOptions.defaults() node_options_class = options.LIDannerNodeOptions elif node_type == "Linear": @@ -189,12 +189,12 @@ def create_node( parameters = options.ReLUParameterOptions.defaults(**parameters) noise = None node_options_class = options.ReLUNodeOptions - elif node_type == "ExternalRelay": + elif node_type == "Relay": state_options = None - parameters = options.NodeParameterOptions() + parameters = None noise = None visual_options.radius = 0.0 - node_options_class = options.ExternalRelayNodeOptions + node_options_class = options.RelayNodeOptions else: raise ValueError(f"Unknown node type: {node_type}") @@ -202,7 +202,7 @@ def create_node( return node_options_class( name=full_name, parameters=parameters, - visual=visual_options, + visual=None, state=state_options, noise=noise, ) @@ -212,7 +212,7 @@ def create_nodes( node_specs: Iterable, base_name: str, transform_mat: np.ndarray, -) -> options.NodeOptions: +) -> dict[str, options.NodeOptions]: """Create node using create_method""" nodes = {} for ( @@ -242,9 +242,10 @@ def create_edges( edge_specs: Iterable, base_name: str, visual_options: options.EdgeVisualOptions = options.EdgeVisualOptions(), -) -> options.EdgeOptions: +) -> dict[str, options.EdgeOptions]: """Create edges from specs""" edges = {} + visual_options = options.EdgeVisualOptions(**visual_options) for source_tuple, target_tuple, weight, edge_type in edge_specs: source = join_str((base_name, *source_tuple)) target = join_str((base_name, *target_tuple)) @@ -253,7 +254,7 @@ def create_edges( target=target, weight=weight, type=edge_type, - visual=options.EdgeVisualOptions(**visual_options), + visual=visual_options, ) return edges @@ -272,7 +273,7 @@ def nodes(self): node_specs = [ ( join_str(("BS", "input")), - "ExternalRelay", + "Relay", np.array((3.0, 0.0)), "A", [0.0, 0.0, 0.0], @@ -343,7 +344,7 @@ def nodes(self): np.array((1.0, -1.5)), "In", [0.2, 0.2, 0.2], - {"v": -60.0}, + {"v": -60.0, "a": 0.0}, {}, ), ( @@ -352,7 +353,7 @@ def nodes(self): np.array((-1.0, 1.5)), "In", [0.2, 0.2, 0.2], - {"v": -60.0}, + {"v": -60.0, "a": 0.0}, {}, ), ( @@ -361,7 +362,7 @@ def nodes(self): np.array((-5.0, 1.0)), "In", [0.2, 0.2, 0.2], - {"v": -60.0}, + {"v": -60.0, "a": 0.0}, {}, ), ( @@ -445,7 +446,7 @@ def nodes(self): np.array((-5.0, -1.5)), "In\\textsubscript{A}", [0.2, 0.2, 0.2], - {"v": -60.0}, + {"v": -60.0, "a": 0.0}, {}, ), ( @@ -454,7 +455,7 @@ def nodes(self): np.array((-7.0, 1.5)), "In\\textsubscript{A}", [0.2, 0.2, 0.2], - {"v": -60.0}, + {"v": -60.0, "a": 0.0}, {}, ), ( @@ -481,7 +482,7 @@ def nodes(self): np.array((7.0, -1.5)), "In\\textsubscript{B}", [0.2, 0.2, 0.2], - {"v": -60.0}, + {"v": -60.0, "a": 0.0}, {}, ), ( @@ -490,7 +491,7 @@ def nodes(self): np.array((5.0, 1.5)), "In\\textsubscript{B}", [0.2, 0.2, 0.2], - {"v": -60.0}, + {"v": -60.0, "a": 0.0}, {}, ), ( @@ -499,7 +500,7 @@ def nodes(self): np.array((9.0, -3.0)), "In\\textsubscript{2F}", [0.2, 0.2, 0.2], - {"v": -60.0}, + {"v": -60.0, "a": 0.0}, {"g_leak": 5.0}, ), ( @@ -508,7 +509,7 @@ def nodes(self): np.array((3.0, -3.0)), "In\\textsubscript{2E}", [0.2, 0.2, 0.2], - {"v": -60.0}, + {"v": -60.0, "a": 0.0}, {"g_leak": 5.0}, ), ( @@ -611,7 +612,7 @@ def nodes(self): np.array((0.0, 2.0, 1.0)), "V2\\textsubscript{a}", [0.0, 1.0, 0.0], - {"v": -60.0}, + {"v": -60.0, "a": 0.0}, {}, ), ( @@ -620,7 +621,7 @@ def nodes(self): np.array((0.0, 0.0, 1.0)), "In\\textsubscript{i}", [1.0, 0.0, 1.0], - {"v": -60.0}, + {"v": -60.0, "a": 0.0}, {}, ), ( @@ -629,7 +630,7 @@ def nodes(self): np.array((2.0, 0.5, 1.0)), "V0\\textsubscript{V}", [0.0, 1.0, 0.0], - {"v": -60.0}, + {"v": -60.0, "a": 0.0}, {}, ), ( @@ -638,7 +639,7 @@ def nodes(self): np.array((2.0, -2.0, 1.0)), "V0\\textsubscript{D}", [1.0, 0.0, 1.0], - {"v": -60.0}, + {"v": -60.0, "a": 0.0}, {}, ), ( @@ -647,7 +648,7 @@ def nodes(self): np.array((2.0, 3.0, 1.0)), "V3\\textsubscript{E}", [0.0, 1.0, 0.0], - {"v": -60.0}, + {"v": -60.0, "a": 0.0}, {}, ), ( @@ -656,7 +657,7 @@ def nodes(self): np.array((2.0, -4.0, 1.0)), "V3\\textsubscript{F}", [0.0, 1.0, 0.0], - {"v": -60.0}, + {"v": -60.0, "a": 0.0}, {}, ), ( @@ -718,7 +719,7 @@ def nodes(self): np.array((0.0, 0.0, 1.0)), "V0\\textsubscript{D}", [0.5, 0.0, 0.5], - {"v": -60.0}, + {"v": -60.0, "a": 0.0}, {}, ), ( @@ -727,7 +728,7 @@ def nodes(self): np.array((0.0, -1.25, 1.0)), "V0\\textsubscript{V}", [0.0, 1.0, 0.0], - {"v": -60.0}, + {"v": -60.0, "a": 0.0}, {}, ), ( @@ -736,7 +737,7 @@ def nodes(self): np.array((0.0, -4.0, 1.0)), "V3\\textsubscript{a}", [0.0, 1.0, 0.0], - {"v": -60.0}, + {"v": -60.0, "a": 0.0}, {}, ), ( @@ -745,7 +746,7 @@ def nodes(self): np.array((-4.0, 0.0, 1.0)), "LPN\\textsubscript{i}", [0.0, 1.0, 0.0], - {"v": -60.0}, + {"v": -60.0, "a": 0.0}, {}, ), ( @@ -754,7 +755,7 @@ def nodes(self): np.array((-8.0, 0.0, 1.0)), "Sh\\textsubscript{2}", [0.0, 1.0, 0.0], - {"v": -60.0}, + {"v": -60.0, "a": 0.0}, {}, ), ( @@ -763,7 +764,7 @@ def nodes(self): np.array((-8.0, -4.0, 1.0)), "Sh\\textsubscript{2}", [0.0, 1.0, 0.0], - {"v": -60.0}, + {"v": -60.0, "a": 0.0}, {}, ), ( @@ -837,7 +838,7 @@ def nodes(self): np.array((IaIn_x_positions[0], y_off, 1.0)), "Ia\\textsubscript{ea}", [0.0, 0.0, 1.0], - {"v": -60.0}, + {"v": -60.0, "a": 0.0}, {}, ), ( @@ -846,7 +847,7 @@ def nodes(self): np.array((IaIn_x_positions[1], y_off, 1.0)), "Ia\\textsubscript{eb}", [0.0, 0.0, 1.0], - {"v": -60.0}, + {"v": -60.0, "a": 0.0}, {}, ), ( @@ -855,7 +856,7 @@ def nodes(self): np.array((IaIn_x_positions[2], y_off, 1.0)), "Ia\\textsubscript{fa}", [0.0, 0.0, 1.0], - {"v": -60.0}, + {"v": -60.0, "a": 0.0}, {}, ), ( @@ -864,7 +865,7 @@ def nodes(self): np.array((IaIn_x_positions[3], y_off, 1.0)), "Ia\\textsubscript{fb}", [0.0, 0.0, 1.0], - {"v": -60.0}, + {"v": -60.0, "a": 0.0}, {}, ), ( @@ -873,7 +874,7 @@ def nodes(self): np.array((np.mean(IaIn_x_positions), y_off - spacing, 1.0)), "Ib\\textsubscript{rg}", [0.0, 0.0, 1.0], - {"v": -60.0}, + {"v": -60.0, "a": 0.0}, {}, ), ] @@ -914,12 +915,12 @@ def _get_muscle_neurons(self, muscle, x_off, y_off, mirror_y=False): np.array((x_off, y_off, 1.0)), "Mn", [1.0, 0.0, 1.0], - {"v": -60.0}, + {"v": -60.0, "a": 0.0}, {"e_leak": -52.5, "g_leak": 1.0}, ), ( join_str((muscle["name"], "Ia")), - "ExternalRelay", + "Relay", np.array((x_off - 0.5, y_off + 0.75 * mirror_y_sign, 1.0)), "Ia", [1.0, 0.0, 0.0], @@ -928,7 +929,7 @@ def _get_muscle_neurons(self, muscle, x_off, y_off, mirror_y=False): ), ( join_str((muscle["name"], "II")), - "ExternalRelay", + "Relay", np.array((x_off, y_off + 0.75 * mirror_y_sign, 1.0)), "II", [1.0, 0.0, 0.0], @@ -937,7 +938,7 @@ def _get_muscle_neurons(self, muscle, x_off, y_off, mirror_y=False): ), ( join_str((muscle["name"], "Ib")), - "ExternalRelay", + "Relay", np.array((x_off + 0.5, y_off + 0.75 * mirror_y_sign, 1.0)), "Ib", [1.0, 0.0, 0.0], @@ -950,7 +951,7 @@ def _get_muscle_neurons(self, muscle, x_off, y_off, mirror_y=False): np.array((x_off + 0.5, y_off - 1.0 * mirror_y_sign, 1.0)), "Rn", [1.0, 0.0, 1.0], - {"v": -60.0}, + {"v": -60.0, "a": 0.0}, {}, ), ( @@ -959,7 +960,7 @@ def _get_muscle_neurons(self, muscle, x_off, y_off, mirror_y=False): np.array((x_off + 1.0, y_off, 1.0)), "Ib\\textsubscript{i}", [0.0, 0.0, 1.0], - {"v": -60.0}, + {"v": -60.0, "a": 0.0}, {}, ), ( @@ -968,7 +969,7 @@ def _get_muscle_neurons(self, muscle, x_off, y_off, mirror_y=False): np.array((x_off + 1.0, y_off + 1.5 * mirror_y_sign, 1.0)), "Ib\\textsubscript{e}", [0.0, 0.0, 1.0], - {"v": -60.0}, + {"v": -60.0, "a": 0.0}, {}, ), ( @@ -977,7 +978,7 @@ def _get_muscle_neurons(self, muscle, x_off, y_off, mirror_y=False): np.array((x_off - 1.0, y_off, 1.0)), "II\\textsubscript{RG}", [0.0, 0.0, 1.0], - {"v": -60.0}, + {"v": -60.0, "a": 0.0}, {}, ), ] @@ -1030,7 +1031,7 @@ def nodes(self): node_specs = [ ( join_str((self.side, self.rate, self.axis, "Vn")), - "ExternalRelay", + "Relay", np.array((0.0, 0.0)), "Vn", [1.0, 1.0, 0.0], # Yellow for sensory neuron @@ -1060,7 +1061,7 @@ def nodes(self): np.array((0.0 - x_offset, 2.0)), "In", [0.5, 0.5, 0.5], # Gray for inhibitory interneurons - {"v": -60.0}, + {"v": -60.0, "a": 0.0}, {}, ) ) @@ -1117,7 +1118,7 @@ def nodes(self): node_specs.append( ( join_str((contact, "cut")), - "ExternalRelay", + "Relay", np.array((0.0, y_pos)), "C", [1.0, 0.0, 0.0], @@ -1133,7 +1134,7 @@ def nodes(self): np.array((-1.0, np.mean(node_y_pos))), "In\\textsubscript{C}", [1.0, 0.0, 0.0], - {"v": -60.0}, + {"v": -60.0, "a": 0.0}, {} ) ) @@ -1475,7 +1476,7 @@ def define_muscle_patterns() -> dict: "tbo": ["EA", "EB"], "bbs": ["FA", "FB"], "bra": ["FA", "FB"], - "eip": ["FA", "FB"], + "ecu": ["FA", "FB"], "fcu": ["EA", "EB"], } } @@ -1611,13 +1612,13 @@ def limb_circuit( }, "fore": { "spd": muscles_patterns["fore"]["spd"], - "eip": muscles_patterns["fore"]["eip"], + "ecu": muscles_patterns["fore"]["ecu"], } } II_feedback_to_rg = { "hind": ["ip", "ta",], - "fore": ["ssp", "bra", "eip"] + "fore": ["ssp", "bra", "ecu"] } Ia_reciprocal_inhibition_extensor2flexor = { @@ -1627,7 +1628,7 @@ def limb_circuit( }, "fore": { "extensors": ["ssp", "tbl", "tbo", "fcu"], - "flexors": ["spd", "bra", "bbs", "eip"], + "flexors": ["spd", "bra", "bbs", "ecu"], } } @@ -1638,7 +1639,7 @@ def limb_circuit( }, "fore": { "extensors": ["ssp", "tbl", "tbo", "fcu"], - "flexors": ["spd", "bra", "bbs", "eip"], + "flexors": ["spd", "bra", "bbs", "ecu"], } } @@ -1649,7 +1650,7 @@ def limb_circuit( }, "fore": { "extensors": ["ssp", "tbl", "tbo", "fcu"], - "flexors": ["spd", "bra", "bbs", "eip"], + "flexors": ["spd", "bra", "bbs", "ecu"], } } @@ -1660,7 +1661,7 @@ def limb_circuit( }, "fore": { "extensors": ["ssp", "tbl", "tbo", "fcu"], - "flexors": ["spd", "bra", "bbs", "eip"], + "flexors": ["spd", "bra", "bbs", "ecu"], } } # Type II connections @@ -1838,6 +1839,7 @@ def brain_stem_circuit( "excitatory", ) ) + print(edge_specs) edges = create_edges(edge_specs, base_name="") return edges From 9783d1c6fc3820fb7274a0066cfd22e2042c6b54 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Sat, 6 Sep 2025 12:56:07 +0530 Subject: [PATCH 279/316] [MODELS] Removed content from to be refactored models --- farms_network/models/leaky_integrator_cy.pxd | 49 --------- farms_network/models/leaky_integrator_cy.pyx | 108 ------------------- farms_network/models/matsuoka_cy.pyx | 4 +- 3 files changed, 2 insertions(+), 159 deletions(-) diff --git a/farms_network/models/leaky_integrator_cy.pxd b/farms_network/models/leaky_integrator_cy.pxd index a1c9153..bd4aff4 100644 --- a/farms_network/models/leaky_integrator_cy.pxd +++ b/farms_network/models/leaky_integrator_cy.pxd @@ -18,52 +18,3 @@ limitations under the License. Leaky Integrator Neuron. """ - -from ..core.node cimport NodeCy, Node -from ..core.edge cimport EdgeCy - - -cdef enum: - - #STATES - NSTATES = 1 - STATE_M = 0 - - -cdef packed struct LeakyIntegratorNodeParameters: - - double tau - double bias - double D - - -cdef: - void ode( - double time, - double* states, - double* derivatives, - double external_input, - double* network_outputs, - unsigned int* inputs, - double* weights, - double noise, - NodeCy* c_node, - EdgeCy** c_edges, - ) noexcept - double output( - double time, - double* states, - double external_input, - double* network_outputs, - unsigned int* inputs, - double* weights, - NodeCy* c_node, - EdgeCy** c_edges, - ) noexcept - - -cdef class LeakyIntegratorNode(Node): - """ Python interface to Leaky Integrator Node C-Structure """ - - cdef: - LeakyIntegratorNodeParameters parameters diff --git a/farms_network/models/leaky_integrator_cy.pyx b/farms_network/models/leaky_integrator_cy.pyx index bbea734..bd4aff4 100644 --- a/farms_network/models/leaky_integrator_cy.pyx +++ b/farms_network/models/leaky_integrator_cy.pyx @@ -18,111 +18,3 @@ limitations under the License. Leaky Integrator Neuron. """ - - -from libc.math cimport exp as cexp -from libc.stdio cimport printf -from libc.stdlib cimport malloc -from libc.string cimport strdup - -from ..core.options import LeakyIntegratorParameterOptions - - -cpdef enum STATE: - - #STATES - nstates = NSTATES - m = STATE_M - - -cdef void ode( - double time, - double* states, - double* derivatives, - double external_input, - double* network_outputs, - unsigned int* inputs, - double* weights, - double noise, - NodeCy* c_node, - EdgeCy** c_edges, -) noexcept: - """ ODE """ - # Parameters - cdef LeakyIntegratorNodeParameters params = ( - c_node[0].parameters - )[0] - - # States - cdef double state_m = states[STATE.m] - - # Node inputs - cdef: - double _sum = 0.0 - unsigned int j - double _node_out, res, _input, _weight - - cdef unsigned int ninputs = c_node.ninputs - for j in range(ninputs): - _input = network_outputs[inputs[j]] - _weight = weights[j] - _sum += _input*_weight - - # noise current - cdef double i_noise = noise - - # dV - derivatives[STATE.m] = (-state_m + _sum + i_noise)/params.tau - - -cdef double output( - double time, - double* states, - double external_input, - double* network_outputs, - unsigned int* inputs, - double* weights, - NodeCy* c_node, - EdgeCy** c_edges, -) noexcept: - """ Node output. """ - - cdef LeakyIntegratorNodeParameters params = ( - c_node[0].parameters - )[0] - - cdef double state_m = states[STATE.m] - cdef double _n_out = 1.0 / (1.0 + cexp(-params.D * (state_m + params.bias))) - return _n_out - - -cdef class LeakyIntegratorNode(Node): - """ Python interface to Leaky Integrator Node C-Structure """ - - def __cinit__(self): - self.c_node.model_type = strdup("LEAKY_INTEGRATOR".encode('UTF-8')) - # override default ode and out methods - self.c_node.is_statefull = True - self.c_node.ode = ode - self.c_node.output = output - # parameters - self.c_node.parameters = malloc(sizeof(LeakyIntegratorNodeParameters)) - if self.c_node.parameters is NULL: - raise MemoryError("Failed to allocate memory for node parameters") - - def __init__(self, name: str, **kwargs): - super().__init__(name) - - # Set node parameters - cdef LeakyIntegratorNodeParameters* param = (self.c_node.parameters) - param.tau = kwargs.pop("tau") - param.bias = kwargs.pop("bias") - param.D = kwargs.pop("D") - if kwargs: - raise Exception(f'Unknown kwargs: {kwargs}') - - @property - def parameters(self): - """ Parameters in the network """ - cdef LeakyIntegratorNodeParameters params = ( self.c_node.parameters)[0] - return params diff --git a/farms_network/models/matsuoka_cy.pyx b/farms_network/models/matsuoka_cy.pyx index af9968d..3f85fa1 100644 --- a/farms_network/models/matsuoka_cy.pyx +++ b/farms_network/models/matsuoka_cy.pyx @@ -18,7 +18,7 @@ cdef processed_inputs_t matsuoka_input_tf( const edge_t** edges, ) noexcept: # Parameters - cdef oscillator_params_t params = ( node[0].params)[0] + cdef matsuoka_params_t params = ( node[0].params)[0] # States cdef double state_v = states[STATE.v] @@ -38,7 +38,7 @@ cdef processed_inputs_t matsuoka_input_tf( double _input, _weight for j in range(inputs.ninputs): - _input = inputs.network_outputs[inputs.source_indices[j]] + _input = inputs.network_outputs[inputs.node_indices[j]] _weight = inputs.weights[j] From 64096f6ffb50955da56d625983c9f1c657f70d66 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Sat, 6 Sep 2025 12:58:00 +0530 Subject: [PATCH 280/316] [MAIN] Refactored setup py to compile all core modules --- setup.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/setup.py b/setup.py index 53074cb..047c8ad 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ Options.generate_cleanup_code = False Options.clear_to_none = True Options.annotate = True -Options.fast_fail = False +Options.fast_fail = True Options.warning_errors = False Options.error_on_unknown_names = True Options.error_on_uninitialized = True @@ -32,20 +32,15 @@ [f"farms_network/{subpackage}/*.pyx"], include_dirs=[numpy.get_include(),], # libraries=["c", "stdc++"], - extra_compile_args=['-O3'], + extra_compile_args=['-O3',], extra_link_args=['-O3'], ) - for subpackage in ('core', 'models', 'numeric', 'noise') + for subpackage in ('core', 'models', 'noise', 'numeric') ] setup( name='farms_network', version='0.1', - description='Module to generate, develop and visualize neural networks', - url='https://gitlab.com/FARMSIM/farms_network.git', - author="Jonathan Arreguit & Shravan Tata Ramalingasetty", - author_email='biorob-farms@groupes.epfl.ch', - license='Apache-2.0', packages=find_packages(exclude=['tests*']), package_dir={'farms_network': 'farms_network'}, package_data={'farms_network': [ From e5994643180f909fd9407018f1ea9e350b2d5656 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Mon, 15 Sep 2025 14:19:07 -0400 Subject: [PATCH 281/316] [CORE] Added _noise_states_to_output method --- farms_network/core/network_cy.pyx | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/farms_network/core/network_cy.pyx b/farms_network/core/network_cy.pyx index 8dd8c90..1a35944 100644 --- a/farms_network/core/network_cy.pyx +++ b/farms_network/core/network_cy.pyx @@ -99,16 +99,16 @@ cdef inline void ode( # c_network.outputs, c_network.tmp_outputs = c_network.tmp_outputs, c_network.outputs -# cdef inline void _noise_states_to_output( -# double[:] states, -# unsigned int[:] indices, -# double[:] outputs, -# ) noexcept: -# """ Copy noise states data to noise outputs """ -# cdef int n_indices = indices.shape[0] -# cdef int index -# for index in range(n_indices): -# outputs[indices[index]] = states[index] +cdef inline void _noise_states_to_output( + double[:] states, + unsigned int[:] indices, + double[:] outputs, +) noexcept: + """ Copy noise states data to noise outputs """ + cdef int n_indices = indices.shape[0] + cdef int index + for index in range(n_indices): + outputs[indices[index]] = states[index] cdef class NetworkCy(ODESystem): From c6371d1b383dd09ddf1d0e1c71cc4608f52ea296 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Tue, 16 Sep 2025 09:30:37 -0400 Subject: [PATCH 282/316] [EX][WIP] Added work in progress mouse running example network --- examples/mouse/run.py | 126 ++++++++++++++++++++++++++++++------------ 1 file changed, 91 insertions(+), 35 deletions(-) diff --git a/examples/mouse/run.py b/examples/mouse/run.py index ffdfcee..91dfba9 100644 --- a/examples/mouse/run.py +++ b/examples/mouse/run.py @@ -8,6 +8,10 @@ from components import * from components import limb_circuit +from farms_network.core.network import Network +from farms_network.numeric.integrators_cy import RK4Solver +from scipy.integrate import ode +from tqdm import tqdm def generate_network(n_iterations: int): @@ -388,35 +392,85 @@ def generate_quadruped_circuit( def run_network(*args): network_options = args[0] - network = PyNetwork.from_options(network_options) - network.setup_integrator(network_options) + network = Network.from_options(network_options) + iterations = network_options.integration.n_iterations + rk4solver = RK4Solver(network.nstates, network_options.integration.timestep) - # data.to_file("/tmp/sim.hdf5") + integrator = ode(network.get_ode_func()).set_integrator( + u'dopri5', + method=u'adams', + max_step=0.0, + # nsteps=0 + ) + nnodes = len(network_options.nodes) + integrator.set_initial_value(np.zeros(len(network.data.states.array[:]),), 0.0) + + # print("Data ------------", np.array(network.network.data.states.array)) - # Integrate - N_ITERATIONS = network_options.integration.n_iterations - # states = np.ones((len(network.data.states.array),)) * 1.0 + # data.to_file("/tmp/sim.hdf5") - # network_gui = NetworkGUI(data=data) - # network_gui.run() + # # Integrate + states = np.ones((iterations, len(network.data.states.array[:])))*1.0 + states_tmp = np.zeros((len(network.data.states.array[:],))) + outputs = np.ones((iterations, len(network.data.outputs.array[:])))*1.0 + # states[0, 2] = -1.0 - inputs_view = network.data.external_inputs.array + # for index, node in enumerate(network_options.nodes): + # print(index, node.name) + # network.data.external_inputs.array[:] = np.ones((1,))*(iteration/iterations)*1.0 drive_input_indices = [ index for index, node in enumerate(network_options.nodes) - if "DR" in node.name and node.model == "linear" + if "BS_input" in node.name and node.model == "relay" ] - inputs = np.zeros((len(inputs_view),)) - for iteration in tqdm(range(0, N_ITERATIONS), colour="green", ascii=" >="): - inputs[drive_input_indices] = 0.02 - inputs_view[:] = inputs - # states = rk4(iteration * 1e-3, states, network.ode, step_size=1) - # states = network.integrator.step(network, iteration * 1e-3, states) - network.step() - # states = network.ode(iteration*1e-3, states) - # print(np.array(states)[0], network.data.states.array[0], network.data.derivatives.array[0]) - network.data.times.array[iteration] = iteration*1e-3 - # network.logging(iteration) + inputs = np.zeros(np.shape(network.data.external_inputs.array[:])) + # print(np.array(network.data.connectivity.weights), np.array(network.data.connectivity.edge_indices), np.array(network.data.connectivity.node_indices), np.array(network.data.connectivity.index_offsets)) + for iteration in tqdm(range(0, iterations), colour='green', ascii=' >='): + time = iteration + # network.step(network.ode, iteration*1e-3, network.data.states.array) + # network.step() + # states[iteration+1, :] = network.data.states.array + # network.step() + # network.evaluate(iteration*1e-3, states[iteration, :]) + + _iter = network._network_cy.iteration + network.log.times.array[_iter] = time + inputs[drive_input_indices] = 0.5 + network.data.external_inputs.array[:] = inputs + # integrator.set_initial_value(integrator.y, integrator.t) + # integrator.integrate(integrator.t+1.0) + # network.data.states.array[:] = integrator.y + rk4solver.step(network._network_cy, time, network.data.states.array) + # outputs[iteration, :] = network.data.outputs.array + # states[iteration, :] = integrator.y# network.data.states.array + # network._network_cy.update_iteration() + network._network_cy.update_logs(network._network_cy.iteration) + network._network_cy.iteration += 1 + + # # Integrate + # N_ITERATIONS = network_options.integration.n_iterations + # # states = np.ones((len(network.data.states.array),)) * 1.0 + + # # network_gui = NetworkGUI(data=data) + # # network_gui.run() + + # inputs_view = network.data.external_inputs.array + # drive_input_indices = [ + # index + # for index, node in enumerate(network_options.nodes) + # if "DR" in node.name and node.model == "linear" + # ] + # inputs = np.zeros((len(inputs_view),)) + # for iteration in tqdm(range(0, N_ITERATIONS), colour="green", ascii=" >="): + # inputs[drive_input_indices] = 0.02 + # inputs_view[:] = inputs + # # states = rk4(iteration * 1e-3, states, network.ode, step_size=1) + # # states = network.integrator.step(network, iteration * 1e-3, states) + # network.step() + # # states = network.ode(iteration*1e-3, states) + # # print(np.array(states)[0], network.data.states.array[0], network.data.derivatives.array[0]) + # network.data.times.array[iteration] = iteration*1e-3 + # # network.logging(iteration) # network.data.to_file("/tmp/network.h5") network_options.save("/tmp/network_options.yaml") @@ -504,39 +558,40 @@ def plot_data(network, network_options): ] plt.figure() + for index, node_index in enumerate(plot_nodes): plt.fill_between( - np.array(network.data.times.array), - index + np.array(network.data.nodes[node_index].output.array), + np.array(network.log.times.array)*1e-3, + index + np.array(network.log.nodes[node_index].output.array), index, alpha=0.2, lw=1.0, ) plt.plot( - np.array(network.data.times.array), - index + np.array(network.data.nodes[node_index].output.array), - label=network.data.nodes[node_index].name, + np.array(network.log.times.array)*1e-3, + index + np.array(network.log.nodes[node_index].output.array), + label=network.log.nodes[node_index].name, ) plt.legend() plot_nodes = [ index - for index, node in enumerate(network.data.nodes) + for index, node in enumerate(network.log.nodes) if ("Mn" in node.name) ] plt.figure() for index, node_index in enumerate(plot_nodes): plt.fill_between( - np.array(network.data.times.array), - index + np.array(network.data.nodes[node_index].output.array), + np.array(network.log.times.array)*1e-3, + index + np.array(network.log.nodes[node_index].output.array), index, alpha=0.2, lw=1.0, ) plt.plot( - np.array(network.data.times.array), - index + np.array(network.data.nodes[node_index].output.array), - label=network.data.nodes[node_index].name, + np.array(network.log.times.array)*1e-3, + index + np.array(network.log.nodes[node_index].output.array), + label=network.log.nodes[node_index].name, ) plt.legend() plt.show() @@ -548,9 +603,9 @@ def main(): # Generate the network # network_options = generate_network(int(1e4)) # network_options = generate_limb_circuit(int(5e4)) - network_options = generate_quadruped_circuit((5e4)) + network_options = generate_quadruped_circuit((1e4)) - plot_network(network_options) + # plot_network(network_options) network = run_network(network_options) plot_data(network, network_options) @@ -591,4 +646,5 @@ def main(): if __name__ == "__main__": - main() + profile.profile(main) + # main() From 80df37e526b14b99c1e2450548b4c750c2ad0d66 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Tue, 16 Sep 2025 09:56:02 -0400 Subject: [PATCH 283/316] [CORE] Refactored state options base class Breaking change! --- farms_network/core/options.py | 81 ++++++++++++----------------------- 1 file changed, 27 insertions(+), 54 deletions(-) diff --git a/farms_network/core/options.py b/farms_network/core/options.py index 091c74b..4ec9e40 100644 --- a/farms_network/core/options.py +++ b/farms_network/core/options.py @@ -81,33 +81,30 @@ def from_options(cls, kwargs: Dict): class NodeStateOptions(Options): - """ Base class for node specific state options """ + """Base class for node-specific state options.""" STATE_NAMES: List[str] = [] # Override in subclasses - def __init__(self, **kwargs): + def __init__(self, initial: List[float]): super().__init__() - self.initial: List[float] = kwargs.pop("initial") - self.names: List[str] = kwargs.pop("names") + self.initial = list(initial) - if kwargs: - raise ValueError(f'Unknown kwargs: {kwargs}') + if len(self.initial) != len(self.STATE_NAMES): + raise ValueError( + f"Length mismatch: expected {len(self.STATE_NAMES)} values for {self.STATE_NAMES}, got {len(self.initial)}" + ) @classmethod - def from_kwargs(cls, **kwargs): - """ From node specific name-value kwargs """ - initial = [ - kwargs.pop(name) - for name in cls.STATE_NAMES - ] - if kwargs: - raise ValueError(f'Unknown kwargs: {kwargs}') + def from_options(cls, options: Dict) -> "NodeStateOptions": + """Create from a dict of options.""" + initial = options.get("initial") + if initial is None: + raise ValueError("Missing required 'initial' values in options") return cls(initial=initial) - @classmethod - def from_options(cls, kwargs: Dict): - """ From options """ - return cls(**kwargs) + def __repr__(self): + pairs = ", ".join(f"{n}={v}" for n, v in zip(self.STATE_NAMES, self.initial)) + return f"{self.__class__.__name__}({pairs})" class NodeLogOptions(Options): @@ -542,12 +539,8 @@ class OscillatorStateOptions(NodeStateOptions): STATE_NAMES = ["phase", "amplitude_0", "amplitude"] - def __init__(self, **kwargs): - super().__init__( - initial=kwargs.pop("initial"), - names=OscillatorStateOptions.STATE_NAMES - ) - assert len(self.initial) == 3, f"Number of initial states {len(self.initial)} should be 3" + def __init__(self, initial): + super().__init__(initial=initial) class OscillatorEdgeOptions(EdgeOptions): @@ -681,12 +674,8 @@ class HopfOscillatorStateOptions(NodeStateOptions): STATE_NAMES = ["x", "y"] - def __init__(self, **kwargs): - super().__init__( - initial=kwargs.pop("initial"), - names=HopfOscillatorStateOptions.STATE_NAMES - ) - assert len(self.initial) == 2, f"Number of initial states {len(self.initial)} should be 2" + def __init__(self, initial): + super().__init__(initial) ################################## @@ -770,12 +759,8 @@ class LeakyIntegratorStateOptions(NodeStateOptions): STATE_NAMES = ["m",] - def __init__(self, **kwargs): - super().__init__( - initial=kwargs.pop("initial"), - names=LeakyIntegratorStateOptions.STATE_NAMES - ) - assert len(self.initial) == 1, f"Number of initial states {len(self.initial)} should be 1" + def __init__(self, initial): + super().__init__(initial) ######################################### @@ -877,12 +862,8 @@ class LIDannerStateOptions(NodeStateOptions): STATE_NAMES = ["v", "a"] - def __init__(self, **kwargs): - super().__init__( - initial=kwargs.pop("initial", [-60.0, 0.0]), - names=LIDannerStateOptions.STATE_NAMES - ) - assert len(self.initial) == 2, f"Number of initial states {len(self.initial)} should be 2" + def __init__(self, initial): + super().__init__(initial=initial) ################################################## @@ -988,12 +969,8 @@ class LINaPDannerStateOptions(NodeStateOptions): STATE_NAMES = ["v", "h"] - def __init__(self, **kwargs): - super().__init__( - initial=kwargs.pop("initial"), - names=LINaPDannerStateOptions.STATE_NAMES - ) - assert len(self.initial) == 2, f"Number of initial states {len(self.initial)} should be 2" + def __init__(self, initial): + super().__init__(initial) #################### @@ -1052,12 +1029,8 @@ class IzhikevichStateOptions(NodeStateOptions): STATE_NAMES = ["v", "u"] - def __init__(self, **kwargs): - super().__init__( - initial=kwargs.pop("initial"), - names=IzhikevichStateOptions.STATE_NAMES - ) - assert len(self.initial) == 2, f"Number of initial states {len(self.initial)} should be 2" + def __init__(self, initial): + super().__init__(initial) ############################## From bc61d0a0853bb8a71ff91369abc4b1783eea054c Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Tue, 16 Sep 2025 10:03:34 -0400 Subject: [PATCH 284/316] [EX] Fixed for changes in state options initialization --- examples/ijspeert07/run.py | 38 ++++++++++++++++++++++++++---------- examples/mouse/components.py | 4 ++-- 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/examples/ijspeert07/run.py b/examples/ijspeert07/run.py index 6ab9bae..a58daf8 100644 --- a/examples/ijspeert07/run.py +++ b/examples/ijspeert07/run.py @@ -13,7 +13,7 @@ from farms_network.core.network import Network from tqdm import tqdm from farms_network.numeric.integrators_cy import RK4Solver -from scipy.integrate import ode +from scipy.integrate import ode, RK45, RK23 plt.rcParams['text.usetex'] = False @@ -48,10 +48,12 @@ def oscillator_chain(network_options, n_oscillators, name_prefix, **kwargs): visual=options.NodeVisualOptions( label=f"{j}", color=[1.0, 0.0, 0.0] ), - state=options.OscillatorStateOptions.from_kwargs( - phase=np.random.uniform(-np.pi, np.pi), - amplitude=np.random.uniform(0, 1), - amplitude_0=np.random.uniform(0, 1) + state=options.OscillatorStateOptions( + initial=[ + np.random.uniform(-np.pi, np.pi), + np.random.uniform(0, 1), + np.random.uniform(0, 1) + ] ), noise=None, ) @@ -156,7 +158,7 @@ def nodes(self): return nodes -def generate_network(iterations=30000): +def generate_network(iterations=3000): """ Generate network """ # Main network @@ -201,12 +203,27 @@ def generate_network(iterations=30000): # network.setup_integrator(network_options) rk4solver = RK4Solver(network.nstates, 1e-3) + sc_integrator = RK23( + network.get_ode_func(), + t0=0.0, + y0=np.zeros(len(data.states.array[:]),), + t_bound=3000*1e-3, + max_step=1e-3, + first_step=1e-3, + # rtol=1e-2, + # atol=1e2, + ) + integrator = ode(network.get_ode_func()).set_integrator( - u'dopri5', - method=u'adams', - max_step=0.0, - # nsteps=0 + 'dopri5', + max_step=1e-3, # your RK4 step ) + # set_integrator( + # u'dopri5', + # method=u'adams', + # max_step=0.0, + # # nsteps=0 + # ) nnodes = len(network_options.nodes) integrator.set_initial_value(np.zeros(len(data.states.array[:]),), 0.0) @@ -233,6 +250,7 @@ def generate_network(iterations=30000): # integrator.set_initial_value(integrator.y, integrator.t) # integrator.integrate(integrator.t+1e-3) + # sc_integrator.step() rk4solver.step(network._network_cy, time, network.data.states.array) outputs[iteration, :] = network.data.outputs.array diff --git a/examples/mouse/components.py b/examples/mouse/components.py index 94bc65a..b278ae0 100644 --- a/examples/mouse/components.py +++ b/examples/mouse/components.py @@ -170,12 +170,12 @@ def create_node( color=color, ) if node_type == "LINaPDanner": - state_options = options.LINaPDannerStateOptions.from_kwargs(**states) + state_options = options.LINaPDannerStateOptions(list(states.values())) parameters = options.LINaPDannerNodeParameterOptions.defaults(**parameters) noise = options.OrnsteinUhlenbeckOptions.defaults() node_options_class = options.LINaPDannerNodeOptions elif node_type == "LIDanner": - state_options = options.LIDannerStateOptions.from_kwargs(**states) + state_options = options.LIDannerStateOptions(list(states.values())) parameters = options.LIDannerNodeParameterOptions.defaults(**parameters) noise = options.OrnsteinUhlenbeckOptions.defaults() node_options_class = options.LIDannerNodeOptions From 2552d040cb7e300ba376d9d645214fc5de07a078 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Wed, 17 Sep 2025 11:53:27 -0400 Subject: [PATCH 285/316] [PYPROJECT] Added networkx dependency --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index bd6a635..ae48609 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,6 +14,7 @@ readme = "README.md" license = {file = "LICENSE"} dependencies = [ "tqdm", + "networkx", "farms_core", ] classifiers = [ From 4e7becf6ccd4865b3687f91686720b2519984584 Mon Sep 17 00:00:00 2001 From: ShravanTata Date: Fri, 19 Sep 2025 15:30:09 -0400 Subject: [PATCH 286/316] [CORE] Fixed gcc const pointer explicit casting issue GCC 14.0 onwards the compiler is strict about casting pointers to const to match the function signatures --- farms_network/core/network_cy.pyx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/farms_network/core/network_cy.pyx b/farms_network/core/network_cy.pyx index 1a35944..cf4cd85 100644 --- a/farms_network/core/network_cy.pyx +++ b/farms_network/core/network_cy.pyx @@ -71,9 +71,9 @@ cdef inline void ode( __node.input_tf( time, states_ptr + c_network.states_indices[j], - node_inputs, - c_nodes[j], - c_edges, + node_inputs, + c_nodes[j], + c_edges, &processed_inputs ) @@ -81,19 +81,19 @@ cdef inline void ode( # Compute the ode __node.ode( time, - states_ptr + c_network.states_indices[j], + states_ptr + c_network.states_indices[j], derivatives_ptr + c_network.states_indices[j], processed_inputs, 0.0, - c_nodes[j] + c_nodes[j] ) # Check for writing to proper outputs array c_network.tmp_outputs[j] = __node.output_tf( time, - states_ptr + c_network.states_indices[j], + states_ptr + c_network.states_indices[j], processed_inputs, 0.0, - c_nodes[j], + c_nodes[j], ) # # Swap pointers for the outputs (Maybe!) # c_network.outputs, c_network.tmp_outputs = c_network.tmp_outputs, c_network.outputs From 8331ed94f0127e84c3a79b1fc9d349192817a331 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Mon, 22 Sep 2025 13:42:19 -0400 Subject: [PATCH 287/316] [MAIN] Removed farms_core from dependencies Currently farms_core needs to be installed from Git and for the time being it will be removed as part of the pyproject and a user is expected to farms_core installed before installing farms_network --- pyproject.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index ae48609..7363e6d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,7 +15,7 @@ license = {file = "LICENSE"} dependencies = [ "tqdm", "networkx", - "farms_core", + "numpy" ] classifiers = [ "Development Status :: 3 - Beta", @@ -43,7 +43,6 @@ gui = [ "PyQt5", "networkx", "pydot", - "farms_core", ] cli = ["rich",] From 088d943a81d95c81433b46cdd94b61db6b5d7090 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Mon, 22 Sep 2025 14:06:03 -0400 Subject: [PATCH 288/316] [EX] Refactored ijspeert07 example --- examples/ijspeert07/run.py | 127 +++++++++++-------------------------- 1 file changed, 38 insertions(+), 89 deletions(-) diff --git a/examples/ijspeert07/run.py b/examples/ijspeert07/run.py index a58daf8..b539870 100644 --- a/examples/ijspeert07/run.py +++ b/examples/ijspeert07/run.py @@ -60,7 +60,7 @@ def oscillator_chain(network_options, n_oscillators, name_prefix, **kwargs): ) # Connect phase_diff = kwargs.get('axial_phi', -np.pi/2) - weight = kwargs.get('axial_w', 5) + weight = kwargs.get('axial_w', 1e4) connections = np.vstack( (np.arange(n_oscillators), np.roll(np.arange(n_oscillators), -1)))[:, :-1] @@ -102,7 +102,7 @@ def oscillator_double_chain(network_options, n_oscillators, **kwargs): # Connect double chain phase_diff = kwargs.get('anti_phi', np.pi) - weight = kwargs.get('anti_w', 5) + weight = kwargs.get('anti_w', 1e4) for n in range(n_oscillators): network_options.add_edge( options.OscillatorEdgeOptions( @@ -131,34 +131,7 @@ def oscillator_double_chain(network_options, n_oscillators, **kwargs): return network_options -class RhythmDrive: - """ Generate Drive Network """ - - def __init__(self, name="", anchor_x=0.0, anchor_y=0.0): - """Initialization.""" - super().__init__() - self.name = name - - def nodes(self): - """Add nodes.""" - nodes = {} - name = join_strings((self.name, "RG", "F", "DR")) - nodes[name] = options.LinearNodeOptions( - name=name, - parameters=options.LinearParameterOptions.defaults(slope=0.1, bias=0.0), - visual=options.NodeVisualOptions(), - ) - name = join_strings((self.name, "RG", "E", "DR")) - nodes[name] = options.LinearNodeOptions( - name=name, - parameters=options.LinearParameterOptions.defaults(slope=0.0, bias=0.1), - visual=options.NodeVisualOptions(), - ) - - return nodes - - -def generate_network(iterations=3000): +def generate_network(iterations=10000): """ Generate network """ # Main network @@ -191,87 +164,67 @@ def generate_network(iterations=3000): node_positions = nx.spring_layout(graph) for index, node in enumerate(network_options.nodes): node.visual.position[:2] = node_positions[node.name] - network_options.save("/tmp/network_options.yaml") - # network_options = options.NetworkOptions.from_options( - # read_yaml("/tmp/rhythm.yaml") - # ) + return network_options + - data = NetworkData.from_options(network_options) +def run_network(network_options: options.NetworkOptions): + """ Run network """ network = Network.from_options(network_options) + iterations = network_options.integration.n_iterations + timestep = network_options.integration.timestep - # network.setup_integrator(network_options) - rk4solver = RK4Solver(network.nstates, 1e-3) + # Setup integrators + rk4solver = RK4Solver(network.nstates, timestep) - sc_integrator = RK23( + sc_integrator = RK45( network.get_ode_func(), t0=0.0, - y0=np.zeros(len(data.states.array[:]),), - t_bound=3000*1e-3, - max_step=1e-3, - first_step=1e-3, + y0=np.zeros(network.nstates,), + t_bound=iterations*timestep, + # max_step=timestep, + # first_step=timestep, # rtol=1e-2, # atol=1e2, ) integrator = ode(network.get_ode_func()).set_integrator( 'dopri5', - max_step=1e-3, # your RK4 step + max_step=timestep, # your RK4 step + nsteps=10000, ) - # set_integrator( - # u'dopri5', - # method=u'adams', - # max_step=0.0, - # # nsteps=0 - # ) - nnodes = len(network_options.nodes) - integrator.set_initial_value(np.zeros(len(data.states.array[:]),), 0.0) - # print("Data ------------", np.array(network.data.states.array)) + nnodes = len(network_options.nodes) + integrator.set_initial_value(np.zeros(network.nstates,), 0.0) - # data.to_file("/tmp/sim.hdf5") + # Integrate + states = np.ones((iterations+1, network.nstates))*1.0 + outputs = np.ones((iterations, network.nnodes))*1.0 + for iteration in tqdm(range(0, iterations), colour='green', ascii=' >='): + network.data.times.array[iteration] = iteration*timestep - # # Integrate - states = np.ones((iterations+1, len(data.states.array[:])))*1.0 - outputs = np.ones((iterations, len(data.outputs.array[:])))*1.0 - # states[0, 2] = -1.0 + integrator.set_initial_value(integrator.y, integrator.t) + integrator.integrate(integrator.t+timestep) - # for index, node in enumerate(network_options.nodes): - # print(index, node.name) - # network.data.external_inputs.array[:] = np.ones((1,))*(iteration/iterations)*1.0 - for iteration in tqdm(range(0, iterations), colour='green', ascii=' >='): - time = iteration*1e-3 - network.data.times.array[iteration] = time - # network.step(network.ode, iteration*1e-3, network.data.states.array) - # network.step() - # states[iteration+1, :] = network.data.states.array - # network.step() - # network.evaluate(iteration*1e-3, states[iteration, :]) - - # integrator.set_initial_value(integrator.y, integrator.t) - # integrator.integrate(integrator.t+1e-3) # sc_integrator.step() - rk4solver.step(network._network_cy, time, network.data.states.array) - outputs[iteration, :] = network.data.outputs.array - states[iteration, :] = network.data.states.array + # rk4solver.step(network._network_cy, iteration*timestep, network.data.states.array) - outputs[iteration, :] = network.data.outputs.array[:] - states[iteration, :] = network.data.states.array[:] + network._network_cy.update_logs(network._network_cy.iteration) + network._network_cy.iteration += 1 - # network.data.to_file("/tmp/network.h5") plt.figure() - for j in range(int(n_oscillators/2)): + for j in range(int(network.nnodes/2)): plt.fill_between( - np.array(network.data.times.array), - 2*j + (1 + np.sin(outputs[:, j])), + np.array(network.log.times.array), + 2*j + (1 + np.sin(np.array(network.log.outputs.array[:, j]))), 2*j, alpha=0.2, lw=1.0, ) plt.plot( np.array(network.data.times.array), - 2*j + (1 + np.sin(outputs[:, j])), + 2*j + (1 + np.sin(network.log.outputs.array[:, j])), label=f"{j}" ) plt.legend() @@ -285,7 +238,9 @@ def generate_network(iterations=3000): source="source", target="target" ) + plt.figure() + node_positions = nx.circular_layout(graph) node_positions = nx.forceatlas2_layout(graph) for index, node in enumerate(network_options.nodes): @@ -332,19 +287,13 @@ def generate_network(iterations=3000): ) plt.show() - # generate_tikz_figure( - # graph, - # paths.get_project_data_path().joinpath("templates", "network",), - # "tikz-full-network.tex", - # paths.get_project_images_path().joinpath("quadruped_network.tex") - # ) - def main(): """Main.""" # Generate the network - profile.profile(generate_network) + network = generate_network() + profile.profile(run_network, network) # Run the network # run_network() From 0a69a79138342049323bde8e493e86d20db6eb40 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Tue, 23 Sep 2025 08:49:19 -0400 Subject: [PATCH 289/316] [MAIN] Deleted deprecated legacy code --- farms_network/fitzhugh_nagumo.pxd | 66 ----- farms_network/fitzhugh_nagumo.pyx | 161 ------------ farms_network/hh_daun_motorneuron.pxd | 113 --------- farms_network/hh_daun_motorneuron.pyx | 334 ------------------------- farms_network/hopf_oscillator.pxd | 67 ----- farms_network/hopf_oscillator.pyx | 146 ----------- farms_network/integrators.pxd | 4 - farms_network/integrators.pyx | 11 - farms_network/leaky_integrator.pxd | 59 ----- farms_network/leaky_integrator.pyx | 126 ---------- farms_network/lif_danner.pxd | 85 ------- farms_network/lif_danner.pyx | 226 ----------------- farms_network/lif_danner_nap.pxd | 98 -------- farms_network/lif_danner_nap.pyx | 277 -------------------- farms_network/lif_daun_interneuron.pxd | 75 ------ farms_network/lif_daun_interneuron.pyx | 231 ----------------- farms_network/matsuoka_neuron.pxd | 67 ----- farms_network/matsuoka_neuron.pyx | 168 ------------- farms_network/morphed_oscillator.pxd | 69 ----- farms_network/morphed_oscillator.pyx | 177 ------------- farms_network/morris_lecar.pxd | 75 ------ farms_network/morris_lecar.pyx | 185 -------------- farms_network/network_generator.pxd | 40 --- farms_network/network_generator.pyx | 136 ---------- farms_network/networkx_model.py | 240 ------------------ farms_network/neural_system.py | 123 --------- farms_network/neuron.pxd | 31 --- farms_network/neuron.pyx | 85 ------- farms_network/neuron_factory.py | 97 ------- farms_network/oscillator.pxd | 64 ----- farms_network/oscillator.pyx | 161 ------------ farms_network/relu.pxd | 53 ---- farms_network/relu.pyx | 132 ---------- farms_network/sensory_neuron.pxd | 37 --- farms_network/sensory_neuron.pyx | 78 ------ 35 files changed, 4097 deletions(-) delete mode 100644 farms_network/fitzhugh_nagumo.pxd delete mode 100644 farms_network/fitzhugh_nagumo.pyx delete mode 100644 farms_network/hh_daun_motorneuron.pxd delete mode 100644 farms_network/hh_daun_motorneuron.pyx delete mode 100644 farms_network/hopf_oscillator.pxd delete mode 100644 farms_network/hopf_oscillator.pyx delete mode 100644 farms_network/integrators.pxd delete mode 100644 farms_network/integrators.pyx delete mode 100644 farms_network/leaky_integrator.pxd delete mode 100644 farms_network/leaky_integrator.pyx delete mode 100644 farms_network/lif_danner.pxd delete mode 100644 farms_network/lif_danner.pyx delete mode 100644 farms_network/lif_danner_nap.pxd delete mode 100644 farms_network/lif_danner_nap.pyx delete mode 100644 farms_network/lif_daun_interneuron.pxd delete mode 100644 farms_network/lif_daun_interneuron.pyx delete mode 100644 farms_network/matsuoka_neuron.pxd delete mode 100644 farms_network/matsuoka_neuron.pyx delete mode 100644 farms_network/morphed_oscillator.pxd delete mode 100644 farms_network/morphed_oscillator.pyx delete mode 100644 farms_network/morris_lecar.pxd delete mode 100644 farms_network/morris_lecar.pyx delete mode 100644 farms_network/network_generator.pxd delete mode 100644 farms_network/network_generator.pyx delete mode 100644 farms_network/networkx_model.py delete mode 100644 farms_network/neural_system.py delete mode 100644 farms_network/neuron.pxd delete mode 100644 farms_network/neuron.pyx delete mode 100644 farms_network/neuron_factory.py delete mode 100644 farms_network/oscillator.pxd delete mode 100644 farms_network/oscillator.pyx delete mode 100644 farms_network/relu.pxd delete mode 100644 farms_network/relu.pyx delete mode 100644 farms_network/sensory_neuron.pxd delete mode 100644 farms_network/sensory_neuron.pyx diff --git a/farms_network/fitzhugh_nagumo.pxd b/farms_network/fitzhugh_nagumo.pxd deleted file mode 100644 index 6a73c04..0000000 --- a/farms_network/fitzhugh_nagumo.pxd +++ /dev/null @@ -1,66 +0,0 @@ -""" ----------------------------------------------------------------------- -Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty -Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. ------------------------------------------------------------------------ - -Fitzhugh Nagumo model. - -""" - -from farms_container.parameter cimport Parameter -from farms_network.neuron cimport Neuron - -cdef struct FNNeuronInput: - int neuron_idx - int weight_idx - int phi_idx - -cdef class FitzhughNagumo(Neuron): - cdef: - readonly str n_id - - unsigned int num_inputs - - # parameters - # constants - double a - double b - double tau - double internal_curr - - # states - Parameter V - Parameter w - - # inputs - Parameter ext_in - - # ode - Parameter V_dot - Parameter w_dot - - # Ouputs - Parameter nout - - # neuron connenctions - FNNeuronInput[:] neuron_inputs - - cdef: - void c_ode_rhs(self, double[:] _y, double[:] _w, double[:] _p) - void c_output(self) - cdef double c_neuron_inputs_eval( - self, double _neuron_out, double _weight, double _phi, - double _V, double _w) diff --git a/farms_network/fitzhugh_nagumo.pyx b/farms_network/fitzhugh_nagumo.pyx deleted file mode 100644 index 0395af5..0000000 --- a/farms_network/fitzhugh_nagumo.pyx +++ /dev/null @@ -1,161 +0,0 @@ -""" ----------------------------------------------------------------------- -Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty -Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. ------------------------------------------------------------------------ - -Fitzhugh Nagumo model - -""" -from libc.stdio cimport printf -import farms_pylog as pylog -import numpy as np -cimport numpy as cnp - - -cdef class FitzhughNagumo(Neuron): - - def __init__(self, n_id, num_inputs, neural_container, **kwargs): - """Initialize. - - Parameters - ---------- - n_id: str - Unique ID for the neuron in the network. - """ - super(FitzhughNagumo, self).__init__('leaky') - - # Neuron ID - self.n_id = n_id - - # Initialize parameters - (_, self.a) = neural_container.constants.add_parameter( - 'a_' + self.n_id, kwargs.get('a', 0.7)) - - (_, self.b) = neural_container.constants.add_parameter( - 'b_' + self.n_id, kwargs.get('b', 0.8)) - - (_, self.tau) = neural_container.constants.add_parameter( - 'tau_' + self.n_id, kwargs.get('tau', 1/0.08)) - - (_, self.internal_curr) = neural_container.constants.add_parameter( - 'I_' + self.n_id, kwargs.get('I', 1)) - - # Initialize states - self.V = neural_container.states.add_parameter( - 'V_' + self.n_id, kwargs.get('V0', 0.0))[0] - self.w = neural_container.states.add_parameter( - 'w_' + self.n_id, kwargs.get('w0', 0.0))[0] - - # External inputs - self.ext_in = neural_container.inputs.add_parameter( - 'ext_in_' + self.n_id)[0] - - # ODE RHS - self.V_dot = neural_container.dstates.add_parameter( - 'V_dot_' + self.n_id, 0.0)[0] - self.w_dot = neural_container.dstates.add_parameter( - 'w_dot_' + self.n_id, 0.0)[0] - - # Output - self.nout = neural_container.outputs.add_parameter( - 'nout_' + self.n_id, 0.0)[0] - - # Neuron inputs - self.neuron_inputs = cnp.ndarray((num_inputs,), - dtype=[('neuron_idx', 'i'), - ('weight_idx', 'i'), - ('phi_idx', 'i')]) - - self.num_inputs = num_inputs - - def add_ode_input(self, int idx, neuron, neural_container, **kwargs): - """ Add relevant external inputs to the ode.""" - # Create a struct to store the inputs and weights to the neuron - cdef FNNeuronInput n - # Get the neuron parameter - neuron_idx = neural_container.outputs.get_parameter_index( - 'nout_'+neuron.n_id) - - # Add the weight parameter - weight = neural_container.weights.add_parameter( - 'w_' + neuron.n_id + '_to_' + self.n_id, - kwargs.get('weight', 0.0))[0] - phi = neural_container.parameters.add_parameter( - 'phi_' + neuron.n_id + '_to_' + self.n_id, - kwargs.get('phi', 0.0))[0] - - weight_idx = neural_container.weights.get_parameter_index( - 'w_' + neuron.n_id + '_to_' + self.n_id) - phi_idx = neural_container.parameters.get_parameter_index( - 'phi_' + neuron.n_id + '_to_' + self.n_id) - - n.neuron_idx = neuron_idx - n.weight_idx = weight_idx - n.phi_idx = phi_idx - cdef double x = self.a - # Append the struct to the list - self.neuron_inputs[idx] = n - - def output(self): - """Neuron activation function. - Parameters - ---------- - m_potential: float - Neuron membrane potential - """ - return self.c_output() - - def ode_rhs(self, y, w, p): - """ Python interface to the ode_rhs computation.""" - self.c_ode_rhs(y, w, p) - - #################### C-FUNCTIONS #################### - cdef void c_ode_rhs(self, double[:] _y, double[:] _w, double[:] _p): - """ Compute the ODE. Internal Setup Function.""" - - # Current state - cdef double _V = self.V.c_get_value() - cdef double _W = self.w.c_get_value() - - # Neuron inputs - cdef double _sum = 0.0 - cdef unsigned int j - cdef double _neuron_out - cdef double _weight - cdef double _phi - - for j in range(self.num_inputs): - _neuron_out = _y[self.neuron_inputs[j].neuron_idx] - _weight = _w[self.neuron_inputs[j].weight_idx] - _phi = _p[self.neuron_inputs[j].phi_idx] - _sum += self.c_neuron_inputs_eval(_neuron_out, - _weight, _phi, _V, _W) - - # phidot : V_dot - self.V_dot.c_set_value(_V - _V**3/3 - _W + self.internal_curr + _sum) - - # wdot - self.w_dot.c_set_value((1/self.tau)*(_V + self.a - self.b*_W)) - - cdef void c_output(self): - """ Neuron output. """ - self.nout.c_set_value(self.V.c_get_value()) - - cdef double c_neuron_inputs_eval( - self, double _neuron_out, double _weight, double _phi, - double _V, double _w): - """ Evaluate neuron inputs.""" - return _weight*(_neuron_out - _V - _phi) diff --git a/farms_network/hh_daun_motorneuron.pxd b/farms_network/hh_daun_motorneuron.pxd deleted file mode 100644 index 0c0519c..0000000 --- a/farms_network/hh_daun_motorneuron.pxd +++ /dev/null @@ -1,113 +0,0 @@ -""" ----------------------------------------------------------------------- -Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty -Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. ------------------------------------------------------------------------ - -Hodgkin Huxley Motor Neuron Based on Daun et.al. -""" - -from farms_container.parameter cimport Parameter -from farms_network.neuron cimport Neuron - -cdef struct DaunMotorNeuronInput: - int neuron_idx - int g_syn_idx - int e_syn_idx - int gamma_s_idx - int v_h_s_idx - -cdef class HHDaunMotorneuron(Neuron): - cdef: - readonly str n_id - - unsigned int num_inputs - - # parameters - # constants - double g_nap - double e_nap - double am1_nap - double am2_nap - double am3_nap - double bm1_nap - double bm2_nap - double bm3_nap - double ah1_nap - double ah2_nap - double ah3_nap - double bh1_nap - double bh2_nap - double bh3_nap - - # Parameters of IK - double g_k - double e_k - double am1_k - double am2_k - double am3_k - double bm1_k - double bm2_k - double bm3_k - - # Parameters of Iq - double g_q - double e_q - double gamma_q - double r_q - double v_m_q - - # Parameters of Ileak - double g_leak - double e_leak - - # Parameters of Isyn - double g_syn - double e_syn - double v_hs - double gamma_s - - # Other constants - double c_m - - # State Variables - Parameter v - Parameter m_na - Parameter h_na - Parameter m_k - Parameter m_q - - # ODE - Parameter vdot - Parameter m_na_dot - Parameter h_na_dot - Parameter m_k_dot - Parameter m_q_dot - - # External Input - Parameter g_app - Parameter e_app - - # Output - Parameter nout - - # neuron connenctions - DaunMotorNeuronInput[:] neuron_inputs - - cdef: - void c_ode_rhs(self, double[:] _y, double[:] _w, double[:] _p) - void c_output(self) - double c_neuron_inputs_eval(self, double _neuron_out, double _g_syn, double _e_syn, - double _gamma_s, double _v_h_s) diff --git a/farms_network/hh_daun_motorneuron.pyx b/farms_network/hh_daun_motorneuron.pyx deleted file mode 100644 index be2a19d..0000000 --- a/farms_network/hh_daun_motorneuron.pyx +++ /dev/null @@ -1,334 +0,0 @@ -""" ----------------------------------------------------------------------- -Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty -Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. ------------------------------------------------------------------------ - -HH-Daun Motor neuron -""" -from libc.stdio cimport printf -import numpy as np -from libc.math cimport exp as cexp -from libc.math cimport cosh as ccosh -from libc.math cimport fabs as cfabs -cimport numpy as cnp - - -cdef class HHDaunMotorneuron(Neuron): - """Hodgkin Huxley Neuron Model - Based on Silvia Daun and Tbor's model. - """ - - def __init__(self, n_id, num_inputs, neural_container, **kwargs): - super(HHDaunMotorneuron, self).__init__('hh_daun_motorneuron') - - self.n_id = n_id # Unique neuron identifier - # Constants - # Neuron constants - # Parameters of INaP - (_, self.g_nap) = neural_container.constants.add_parameter( - 'g_nap' + self.n_id, kwargs.get('g_nap', 10.0)) - (_, self.e_nap) = neural_container.constants.add_parameter( - 'e_nap' + self.n_id, kwargs.get('e_nap', 55.0)) - (_, self.am1_nap) = neural_container.constants.add_parameter( - 'am1_nap' + self.n_id, kwargs.get('am1_nap', 0.32)) - (_, self.am2_nap) = neural_container.constants.add_parameter( - 'am2_nap' + self.n_id, kwargs.get('am2_nap', -51.90)) - (_, self.am3_nap) = neural_container.constants.add_parameter( - 'am3_nap' + self.n_id, kwargs.get('am3_nap', 0.25)) - (_, self.bm1_nap) = neural_container.constants.add_parameter( - 'bm1_nap' + self.n_id, kwargs.get('bm1_nap', -0.280)) - (_, self.bm2_nap) = neural_container.constants.add_parameter( - 'bm2_nap' + self.n_id, kwargs.get('bm2_nap', -24.90)) - (_, self.bm3_nap) = neural_container.constants.add_parameter( - 'bm3_nap' + self.n_id, kwargs.get('bm3_nap', -0.2)) - (_, self.ah1_nap) = neural_container.constants.add_parameter( - 'ah1_nap' + self.n_id, kwargs.get('ah1_nap', 0.1280)) - (_, self.ah2_nap) = neural_container.constants.add_parameter( - 'ah2_nap' + self.n_id, kwargs.get('ah2_nap', -48.0)) - (_, self.ah3_nap) = neural_container.constants.add_parameter( - 'ah3_nap' + self.n_id, kwargs.get('ah3_nap', 0.0556)) - (_, self.bh1_nap) = neural_container.constants.add_parameter( - 'bh1_nap' + self.n_id, kwargs.get('bh1_nap', 4.0)) - (_, self.bh2_nap) = neural_container.constants.add_parameter( - 'bh2_nap' + self.n_id, kwargs.get('bh2_nap', -25.0)) - (_, self.bh3_nap) = neural_container.constants.add_parameter( - 'bh3_nap' + self.n_id, kwargs.get('bh3_nap', 0.20)) - - # Parameters of IK - (_, self.g_k) = neural_container.constants.add_parameter( - 'g_k' + self.n_id, kwargs.get('g_k', 2.0)) - (_, self.e_k) = neural_container.constants.add_parameter( - 'e_k' + self.n_id, kwargs.get('e_k', -80.0)) - (_, self.am1_k) = neural_container.constants.add_parameter( - 'am1_k' + self.n_id, kwargs.get('am1_k', 0.0160)) - (_, self.am2_k) = neural_container.constants.add_parameter( - 'am2_k' + self.n_id, kwargs.get('am2_k', -29.90)) - (_, self.am3_k) = neural_container.constants.add_parameter( - 'am3_k' + self.n_id, kwargs.get('am3_k', 0.20)) - (_, self.bm1_k) = neural_container.constants.add_parameter( - 'bm1_k' + self.n_id, kwargs.get('bm1_k', 0.250)) - (_, self.bm2_k) = neural_container.constants.add_parameter( - 'bm2_k' + self.n_id, kwargs.get('bm2_k', -45.0)) - (_, self.bm3_k) = neural_container.constants.add_parameter( - 'bm3_k' + self.n_id, kwargs.get('bm3_k', 0.025)) - - # Parameters of Iq - (_, self.g_q) = neural_container.constants.add_parameter( - 'g_q' + self.n_id, kwargs.get('g_q', 12.0)) - (_, self.e_q) = neural_container.constants.add_parameter( - 'e_q' + self.n_id, kwargs.get('e_q', -80.0)) - (_, self.gamma_q) = neural_container.constants.add_parameter( - 'gamma_q' + self.n_id, kwargs.get('gamma_q', -0.6)) - (_, self.r_q) = neural_container.constants.add_parameter( - 'r_q' + self.n_id, kwargs.get('r_q', 0.0005)) - (_, self.v_m_q) = neural_container.constants.add_parameter( - 'v_m_q' + self.n_id, kwargs.get('v_m_q', -30.0)) - - # Parameters of Ileak - (_, self.g_leak) = neural_container.constants.add_parameter( - 'g_leak' + self.n_id, kwargs.get('g_leak', 0.8)) - (_, self.e_leak) = neural_container.constants.add_parameter( - 'e_leak' + self.n_id, kwargs.get('e_leak', -70.0)) - - # Parameters of Isyn - (_, self.g_syn) = neural_container.constants.add_parameter( - 'g_syn' + self.n_id, kwargs.get('g_syn', 0.1)) - (_, self.e_syn) = neural_container.constants.add_parameter( - 'e_syn' + self.n_id, kwargs.get('e_syn', 0.0)) - (_, self.v_hs) = neural_container.constants.add_parameter( - 'v_hs' + self.n_id, kwargs.get('v_hs', -43.0)) - (_, self.gamma_s) = neural_container.constants.add_parameter( - 'gamma_s' + self.n_id, kwargs.get('gamma_s', -0.42)) - - # Other constants - (_, self.c_m) = neural_container.constants.add_parameter( - 'c_m' + self.n_id, kwargs.get('c_m', 1.0)) - - # State Variables - # pylint: disable=invalid-name - # Membrane potential - self.v = neural_container.states.add_parameter( - 'V_' + self.n_id, kwargs.get('v0', -65.0))[0] - self.m_na = neural_container.states.add_parameter( - 'm_na_' + self.n_id, kwargs.get('m_na0', 0.9))[0] - self.h_na = neural_container.states.add_parameter( - 'h_na_' + self.n_id, kwargs.get('h_na0', 0.0))[0] - self.m_k = neural_container.states.add_parameter( - 'm_k_' + self.n_id, kwargs.get('m_k0', 0.0))[0] - self.m_q = neural_container.states.add_parameter( - 'm_q_' + self.n_id, kwargs.get('m_q0', 0.0))[0] - - # ODE - self.vdot = neural_container.dstates.add_parameter( - 'vdot_' + self.n_id, 0.0)[0] - self.m_na_dot = neural_container.dstates.add_parameter( - 'm_na_dot_' + self.n_id, 0.0)[0] - self.h_na_dot = neural_container.dstates.add_parameter( - 'h_na_dot_' + self.n_id, 0.0)[0] - self.m_k_dot = neural_container.dstates.add_parameter( - 'm_k_dot_' + self.n_id, 0.0)[0] - self.m_q_dot = neural_container.dstates.add_parameter( - 'm_q_dot_' + self.n_id, 0.0)[0] - - # External Input - self.g_app = neural_container.inputs.add_parameter( - 'g_app_' + self.n_id, kwargs.get('g_app', 0.19))[0] - self.e_app = neural_container.inputs.add_parameter( - 'e_app_' + self.n_id, kwargs.get('e_app', 0.0))[0] - - # Output - self.nout = neural_container.outputs.add_parameter( - 'nout_' + self.n_id, 0.0)[0] - - # Neuron inputs - self.num_inputs = num_inputs - self.neuron_inputs = cnp.ndarray((num_inputs,), - dtype=[('neuron_idx', 'i'), - ('g_syn_idx', 'i'), - ('e_syn_idx', 'i'), - ('gamma_s_idx', 'i'), - ('v_h_s_idx', 'i')]) - - def add_ode_input(self, int idx, neuron, neural_container, **kwargs): - """ Add relevant external inputs to the ode. - Parameters - ---------- - neuron : - Neuron model from which the input is received. - weight : - Strength of the synapse between the two neurons""" - - # Create a struct to store the inputs and weights to the neuron - cdef DaunMotorNeuronInput n - # Get the neuron parameter - neuron_idx = neural_container.outputs.get_parameter_index( - 'nout_'+neuron.n_id) - - g_syn = neural_container.parameters.add_parameter( - 'g_syn_' + self.n_id, kwargs.pop('g_syn', 0.0))[0] - e_syn = neural_container.parameters.add_parameter( - 'e_syn_' + self.n_id, kwargs.pop('e_syn', 0.0))[0] - gamma_s = neural_container.parameters.add_parameter( - 'gamma_s_' + self.n_id, kwargs.pop('gamma_s', 0.0))[0] - v_h_s = neural_container.parameters.add_parameter( - 'v_h_s_' + self.n_id, kwargs.pop('v_h_s', 0.0))[0] - - # Get neuron parameter indices - g_syn_idx = neural_container.parameters.get_parameter_index( - 'g_syn_' + self.n_id) - e_syn_idx = neural_container.parameters.get_parameter_index( - 'e_syn_' + self.n_id) - gamma_s_idx = neural_container.parameters.get_parameter_index( - 'gamma_s_' + self.n_id) - v_h_s_idx = neural_container.parameters.get_parameter_index( - 'v_h_s_' + self.n_id) - - # Add the indices to the struct - n.neuron_idx = neuron_idx - n.g_syn_idx = g_syn_idx - n.e_syn_idx = e_syn_idx - n.gamma_s_idx = gamma_s_idx - n.v_h_s_idx = v_h_s_idx - - # Append the struct to the list - self.neuron_inputs[idx] = n - - def output(self): - """Neuron activation function. - Parameters - ---------- - m_potential: float - Neuron membrane potential - """ - return self.c_output() - - def ode_rhs(self, y, w, p): - """ Python interface to the ode_rhs computation.""" - self.c_ode_rhs(y, w, p) - - #################### C-FUNCTIONS #################### - cdef void c_ode_rhs(self, double[:] _y, double[:] _w, double[:] _p): - """ Compute the ODE. Internal Setup Function.""" - - # States - cdef double _v = self.v.c_get_value() - cdef double _m_na = self.m_na.c_get_value() - cdef double _h_na = self.h_na.c_get_value() - cdef double _m_k = self.m_k.c_get_value() - cdef double _m_q = self.m_q.c_get_value() - - # alpha_m_Na(V) - cdef double a_m_nap = (self.am1_nap * (self.am2_nap - _v)) / ( - cexp(self.am3_nap * (self.am2_nap - _v)) - 1) - - # beta_m_Na(V) - cdef double b_m_nap = (self.bm1_nap * (self.bm2_nap - _v)) / ( - cexp(self.bm3_nap * (self.bm2_nap - _v)) - 1) - - # alpha_m_Na(V) - cdef double a_h_nap = self.ah1_nap * cexp( - self.ah3_nap * (self.ah2_nap - _v)) - - # beta_m_Na(V) - cdef double b_h_nap = (self.bh1_nap) / ( - cexp(self.bh3_nap * (self.bh2_nap - _v)) + 1) - - # Inap - # pylint: disable=no-member - cdef double i_nap = self.g_nap * _m_na * _h_na * ( - _v - self.e_nap) - - # alpha_m_K - cdef double a_m_k = (self.am1_k * (self.am2_k - _v)) / ( - cexp(self.am3_k * (self.am2_k - _v)) - 1) - - # beta_m_K - cdef double b_m_k = self.bm1_k * cexp(self.bm3_k * (self.bm2_k - _v)) - - # Ik - # pylint: disable=no-member - cdef double i_k = self.g_k * _m_k * (_v - self.e_k) - - # m_q_inf - cdef double m_q_inf = 1./(1 + cexp(self.gamma_q * (_v - self.v_m_q))) - - # alpha_m_q - cdef double a_m_q = m_q_inf * self.r_q - - # beta_m_q - cdef double b_m_q = (1 - m_q_inf) * self.r_q - - # Ileak - cdef double i_leak = self.g_leak * (_v - self.e_leak) - - # Iapp - cdef double i_app = self.g_app.c_get_value() * ( - _v - self.e_app.c_get_value()) - - # m_na_dot - self.m_na_dot.c_set_value(a_m_nap*(1 - _m_na) - b_m_nap*_m_na) - - # h_na_dot - self.h_na_dot.c_set_value(a_h_nap*(1 - _h_na) - b_h_nap*_h_na) - - # m_k_dot - self.m_k_dot.c_set_value(a_m_k*(1 - _m_k) - b_m_k*_m_k) - - # m_q_dot - self.m_q_dot.c_set_value(a_m_q * (1 - _m_q) - b_m_q * _m_q) - - # Iq - # pylint: disable=no-member - cdef double i_q = self.g_q * self.m_q_dot.c_get_value() * (_v - self.e_q) - - # Neuron inputs - cdef double _sum = 0.0 - cdef unsigned int j - cdef double _neuron_out - cdef double _g_syn - cdef double _e_syn - cdef double _gamma_s - cdef double _v_h_s - cdef DaunMotorNeuronInput _neuron - - for j in range(self.num_inputs): - _neuron = self.neuron_inputs[j] - _neuron_out = _y[_neuron.neuron_idx] - _g_syn = _p[_neuron.g_syn_idx] - _e_syn = _p[_neuron.e_syn_idx] - _gamma_s = _p[_neuron.gamma_s_idx] - _v_h_s = _p[_neuron.v_h_s_idx] - _sum += self.c_neuron_inputs_eval( - _neuron_out, _g_syn, _e_syn, _gamma_s, _v_h_s) - - # dV - self.vdot.c_set_value(( - -i_nap - i_k - i_q - i_leak - i_app - _sum)/self.c_m) - - cdef void c_output(self): - """ Neuron output. """ - # Set the neuron output - self.nout.c_set_value(self.v.c_get_value()) - - cdef double c_neuron_inputs_eval( - self, double _neuron_out, double _g_syn, double _e_syn, - double _gamma_s, double _v_h_s): - """ Evaluate neuron inputs.""" - cdef double _v = self.v.c_get_value() - - cdef double _s_inf = 1./(1. + cexp(_gamma_s*(_neuron_out - _v_h_s))) - - return _g_syn*_s_inf*(_v - _e_syn) diff --git a/farms_network/hopf_oscillator.pxd b/farms_network/hopf_oscillator.pxd deleted file mode 100644 index 96e9fa6..0000000 --- a/farms_network/hopf_oscillator.pxd +++ /dev/null @@ -1,67 +0,0 @@ -""" ------------------------------------------------------------------------ -Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty -Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. ------------------------------------------------------------------------ - -Hopf oscillator model - -[1]L. Righetti and A. J. Ijspeert, “Pattern generators with sensory -feedback for the control of quadruped locomotion,” in 2008 IEEE -International Conference on Robotics and Automation, May 2008, -pp. 819–824. doi: 10.1109/ROBOT.2008.4543306. -""" - -from farms_container.parameter cimport Parameter -from farms_network.neuron cimport Neuron - -cdef struct HopfOscillatorNeuronInput: - int neuron_idx - int weight_idx - -cdef class HopfOscillator(Neuron): - cdef: - readonly str n_id - - unsigned int num_inputs - - # parameters - # constants - double mu - double omega - double alpha - double beta - - # states - Parameter x - Parameter y - - # inputs - Parameter ext_in - - # ode - Parameter xdot - Parameter ydot - - # Ouputs - Parameter nout - - # neuron connenctions - HopfOscillatorNeuronInput[:] neuron_inputs - - cdef: - void c_ode_rhs(self, double[:] _y, double[:] _w, double[:] _p) - void c_output(self) - double c_neuron_inputs_eval(self, double _neuron_out, double _weight) diff --git a/farms_network/hopf_oscillator.pyx b/farms_network/hopf_oscillator.pyx deleted file mode 100644 index d21ae52..0000000 --- a/farms_network/hopf_oscillator.pyx +++ /dev/null @@ -1,146 +0,0 @@ -""" ------------------------------------------------------------------------ -Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty -Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. ------------------------------------------------------------------------ - -Hopf Oscillator - -[1]L. Righetti and A. J. Ijspeert, “Pattern generators with sensory -feedback for the control of quadruped locomotion,” in 2008 IEEE -International Conference on Robotics and Automation, May 2008, -pp. 819–824. doi: 10.1109/ROBOT.2008.4543306. - -""" -from libc.math cimport exp -import numpy as np -cimport numpy as cnp - -cdef class HopfOscillator(Neuron): - - def __init__(self, n_id, num_inputs, neural_container, **kwargs): - """Initialize. - - Parameters - ---------- - n_id: str - Unique ID for the neuron in the network. - """ - super(HopfOscillator, self).__init__('leaky', n_id) - # Neuron ID - self.n_id = n_id - # Initialize parameters - (_, self.mu) = neural_container.constants.add_parameter( - 'mu_' + self.n_id, kwargs.get('mu', 0.1)) - (_, self.omega) = neural_container.constants.add_parameter( - 'omega_' + self.n_id, kwargs.get('omega', 0.1)) - (_, self.alpha) = neural_container.constants.add_parameter( - 'alpha_' + self.n_id, kwargs.get('alpha', 1.0)) - (_, self.beta) = neural_container.constants.add_parameter( - 'beta_' + self.n_id, kwargs.get('beta', 1.0)) - - # Initialize states - self.x = neural_container.states.add_parameter( - 'x_' + self.n_id, kwargs.get('x0', 0.0))[0] - self.y = neural_container.states.add_parameter( - 'y_' + self.n_id, kwargs.get('y0', 0.0))[0] - # External inputs - self.ext_in = neural_container.inputs.add_parameter( - 'ext_in_' + self.n_id)[0] - - # ODE RHS - self.xdot = neural_container.dstates.add_parameter( - 'xdot_' + self.n_id, 0.0)[0] - self.ydot = neural_container.dstates.add_parameter( - 'ydot_' + self.n_id, 0.0)[0] - - # Output - self.nout = neural_container.outputs.add_parameter( - 'nout_' + self.n_id, 0.0)[0] - - # Neuron inputs - self.neuron_inputs = cnp.ndarray((num_inputs,), - dtype=[('neuron_idx', 'i'), - ('weight_idx', 'i')]) - - self.num_inputs = num_inputs - - def add_ode_input(self, int idx, neuron, neural_container, **kwargs): - """ Add relevant external inputs to the ode.""" - # Create a struct to store the inputs and weights to the neuron - cdef HopfOscillatorNeuronInput n - # Get the neuron parameter - neuron_idx = neural_container.outputs.get_parameter_index( - 'nout_'+neuron.n_id) - - # Add the weight parameter - weight = neural_container.weights.add_parameter( - 'w_' + neuron.n_id + '_to_' + self.n_id, kwargs.get('weight', 0.0)) - weight_idx = neural_container.weights.get_parameter_index( - 'w_' + neuron.n_id + '_to_' + self.n_id) - n.neuron_idx = neuron_idx - n.weight_idx = weight_idx - - # Append the struct to the list - self.neuron_inputs[idx] = n - - def output(self): - """Neuron activation function. - Parameters - ---------- - m_potential: float - Neuron membrane potential - """ - return self.c_output() - - def ode_rhs(self, y, w, p): - """ Python interface to the ode_rhs computation.""" - self.c_ode_rhs(y, w, p) - - #################### C-FUNCTIONS #################### - cdef void c_ode_rhs(self, double[:] _y, double[:] _w, double[:] _p): - """ Compute the ODE. Internal Setup Function.""" - # Neuron inputs - cdef double _sum = 0.0 - cdef unsigned int j - cdef double _neuron_out - cdef double _weight - - for j in range(self.num_inputs): - _neuron_out = _y[self.neuron_inputs[j].neuron_idx] - _weight = _w[self.neuron_inputs[j].weight_idx] - _sum += self.c_neuron_inputs_eval(_neuron_out, _weight) - - # sates - cdef double x = self.x.c_get_value() - cdef double y = self.y.c_get_value() - cdef double mu = self.mu - cdef double omega = self.omega - self.xdot.c_set_value( - self.alpha*(self.mu - (x**2 + y**2))*x - self.omega*y - ) - self.ydot.c_set_value( - self.beta*(self.mu - (x**2 + y**2))*y + self.omega*x + ( - self.ext_in.c_get_value() + _sum - ) - ) - - cdef void c_output(self): - """ Neuron output. """ - self.nout.c_set_value(self.y.c_get_value()) - - cdef double c_neuron_inputs_eval(self, double _neuron_out, double _weight): - """ Evaluate neuron inputs.""" - return _neuron_out*_weight diff --git a/farms_network/integrators.pxd b/farms_network/integrators.pxd deleted file mode 100644 index bde90f0..0000000 --- a/farms_network/integrators.pxd +++ /dev/null @@ -1,4 +0,0 @@ -cimport numpy as cnp - - -cpdef cnp.ndarray c_rk4(double time, cnp.ndarray[double] state, func, double step_size) diff --git a/farms_network/integrators.pyx b/farms_network/integrators.pyx deleted file mode 100644 index 975a3e5..0000000 --- a/farms_network/integrators.pyx +++ /dev/null @@ -1,11 +0,0 @@ -import numpy as np - - -cpdef cnp.ndarray c_rk4(double time, cnp.ndarray[double, ndim=1] state, func, double step_size): - """ Runge-kutta order 4 integrator """ - cdef cnp.ndarray[double, ndim=1] K1 = np.asarray(func(time, state)) - cdef cnp.ndarray[double, ndim=1] K2 = np.asarray(func(time + step_size/2, state + (step_size/2 * K1))) - cdef cnp.ndarray[double, ndim=1] K3 = np.asarray(func(time + step_size/2, state + (step_size/2 * K2))) - cdef cnp.ndarray[double, ndim=1] K4 = np.asarray(func(time + step_size, state + (step_size * K3))) - cdef cnp.ndarray[double, ndim=1] new_state = state + (K1 + 2*K2 + 2*K3 + K4)*(step_size/6) - return new_state diff --git a/farms_network/leaky_integrator.pxd b/farms_network/leaky_integrator.pxd deleted file mode 100644 index 7837b49..0000000 --- a/farms_network/leaky_integrator.pxd +++ /dev/null @@ -1,59 +0,0 @@ -""" ------------------------------------------------------------------------ -Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty -Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. ------------------------------------------------------------------------ - -Leaky Integrator Neuron. -""" - -from farms_container.parameter cimport Parameter -from farms_network.neuron cimport Neuron - -cdef struct LeakyIntegratorNeuronInput: - int neuron_idx - int weight_idx - -cdef class LeakyIntegrator(Neuron): - cdef: - readonly str n_id - - unsigned int num_inputs - - # parameters - # constants - double tau - double bias - double D - - # states - Parameter m - - # inputs - Parameter ext_in - - # ode - Parameter mdot - - # Ouputs - Parameter nout - - # neuron connenctions - LeakyIntegratorNeuronInput[:] neuron_inputs - - cdef: - void c_ode_rhs(self, double[:] _y, double[:] _w, double[:] _p) - void c_output(self) - double c_neuron_inputs_eval(self, double _neuron_out, double _weight) diff --git a/farms_network/leaky_integrator.pyx b/farms_network/leaky_integrator.pyx deleted file mode 100644 index 78ade81..0000000 --- a/farms_network/leaky_integrator.pyx +++ /dev/null @@ -1,126 +0,0 @@ -""" ------------------------------------------------------------------------ -Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty -Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. ------------------------------------------------------------------------ - -Leaky Integrator Neuron. -""" -from libc.math cimport exp -import numpy as np -cimport numpy as cnp - -cdef class LeakyIntegrator(Neuron): - - def __init__(self, n_id, num_inputs, neural_container, **kwargs): - """Initialize. - - Parameters - ---------- - n_id: str - Unique ID for the neuron in the network. - """ - super(LeakyIntegrator, self).__init__('leaky') - # Neuron ID - self.n_id = n_id - # Initialize parameters - (_, self.tau) = neural_container.constants.add_parameter( - 'tau_' + self.n_id, kwargs.get('tau', 0.1)) - (_, self.bias) = neural_container.constants.add_parameter( - 'bias_' + self.n_id, kwargs.get('bias', -2.75)) - # pylint: disable=invalid-name - (_, self.D) = neural_container.constants.add_parameter( - 'D_' + self.n_id, kwargs.get('D', 1.0)) - - # Initialize states - self.m = neural_container.states.add_parameter( - 'm_' + self.n_id, kwargs.get('x0', 0.0))[0] - # External inputs - self.ext_in = neural_container.inputs.add_parameter( - 'ext_in_' + self.n_id)[0] - - # ODE RHS - self.mdot = neural_container.dstates.add_parameter( - 'mdot_' + self.n_id, 0.0)[0] - - # Output - self.nout = neural_container.outputs.add_parameter( - 'nout_' + self.n_id, 0.0)[0] - - # Neuron inputs - self.neuron_inputs = cnp.ndarray((num_inputs,), - dtype=[('neuron_idx', 'i'), - ('weight_idx', 'i')]) - - self.num_inputs = num_inputs - - def add_ode_input(self, int idx, neuron, neural_container, **kwargs): - """ Add relevant external inputs to the ode.""" - # Create a struct to store the inputs and weights to the neuron - cdef LeakyIntegratorNeuronInput n - # Get the neuron parameter - neuron_idx = neural_container.outputs.get_parameter_index( - 'nout_'+neuron.n_id) - - # Add the weight parameter - weight = neural_container.weights.add_parameter( - 'w_' + neuron.n_id + '_to_' + self.n_id, kwargs.get('weight', 0.0)) - weight_idx = neural_container.weights.get_parameter_index( - 'w_' + neuron.n_id + '_to_' + self.n_id) - n.neuron_idx = neuron_idx - n.weight_idx = weight_idx - - # Append the struct to the list - self.neuron_inputs[idx] = n - - def output(self): - """Neuron activation function. - Parameters - ---------- - m_potential: float - Neuron membrane potential - """ - return self.c_output() - - def ode_rhs(self, y, w, p): - """ Python interface to the ode_rhs computation.""" - self.c_ode_rhs(y, w, p) - - #################### C-FUNCTIONS #################### - cdef void c_ode_rhs(self, double[:] _y, double[:] _w, double[:] _p): - """ Compute the ODE. Internal Setup Function.""" - # Neuron inputs - cdef double _sum = 0.0 - cdef unsigned int j - cdef double _neuron_out - cdef double _weight - - for j in range(self.num_inputs): - _neuron_out = _y[self.neuron_inputs[j].neuron_idx] - _weight = _w[self.neuron_inputs[j].weight_idx] - _sum += self.c_neuron_inputs_eval(_neuron_out, _weight) - - self.mdot.c_set_value(( - (-self.m.c_get_value() + _sum + self.ext_in.c_get_value())/self.tau) - ) - - cdef void c_output(self): - """ Neuron output. """ - self.nout.c_set_value(1. / (1. + exp(-self.D * ( - self.m.c_get_value() + self.bias)))) - - cdef double c_neuron_inputs_eval(self, double _neuron_out, double _weight): - """ Evaluate neuron inputs.""" - return _neuron_out*_weight diff --git a/farms_network/lif_danner.pxd b/farms_network/lif_danner.pxd deleted file mode 100644 index b5e446d..0000000 --- a/farms_network/lif_danner.pxd +++ /dev/null @@ -1,85 +0,0 @@ -""" ------------------------------------------------------------------------ -Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty -Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. ------------------------------------------------------------------------ - -Leaky Integrate and Fire Neuron Based on Danner et.al. -""" - -from farms_container.parameter cimport Parameter -from libcpp.random cimport mt19937, normal_distribution - -from farms_network.neuron cimport Neuron -from farms_network.utils.ornstein_uhlenbeck cimport OrnsteinUhlenbeckParameters - -cdef struct DannerNeuronInput: - int neuron_idx - int weight_idx - -cdef class LIFDanner(Neuron): - cdef: - readonly str n_id - - unsigned int num_inputs - - # parameters - # constants - double c_m - double g_leak - double e_leak - double v_max - double v_thr - double g_syn_e - double g_syn_i - double e_syn_e - double e_syn_i - double m_e - double m_i - double b_e - double b_i - - double tau_noise - double mu_noise - double sigma_noise - double time_step_noise - unsigned long int seed_noise - - # states - Parameter v - - Parameter state_noise - - # inputs - Parameter alpha - - # ode - Parameter vdot - - # Ouputs - Parameter nout - - # neuron connenctions - DannerNeuronInput[:] neuron_inputs - - # current noise - OrnsteinUhlenbeckParameters noise_params - mt19937 random_mt19937 - normal_distribution[double] distribution - - cdef: - void c_ode_rhs(self, double[:] _y, double[:] _w, double[:] _p) - void c_output(self) - inline double c_neuron_inputs_eval(self, double _neuron_out, double _weight) diff --git a/farms_network/lif_danner.pyx b/farms_network/lif_danner.pyx deleted file mode 100644 index 201a81e..0000000 --- a/farms_network/lif_danner.pyx +++ /dev/null @@ -1,226 +0,0 @@ -""" ------------------------------------------------------------------------ -Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty -Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. ------------------------------------------------------------------------ - -Leaky Integrate and Fire Neuron Based on Danner et.al. -""" - -import time -import numpy as np - -cimport numpy as cnp -from libc.math cimport cosh as ccosh -from libc.math cimport exp as cexp -from libc.math cimport fabs as cfabs -from libc.stdio cimport printf - -from farms_network.utils.ornstein_uhlenbeck cimport c_noise_current_update - - -cdef class LIFDanner(Neuron): - """Leaky Integrate and Fire Neuron Based on Danner et.al. - """ - - def __init__(self, n_id, num_inputs, neural_container, **kwargs): - super( - LIFDanner, self).__init__('lif_danner') - - self.n_id = n_id # Unique neuron identifier - # Constants - (_, self.c_m) = neural_container.constants.add_parameter( - 'c_m_' + self.n_id, kwargs.get('c_m', 10.0)) # pF - - (_, self.g_leak) = neural_container.constants.add_parameter( - 'g_leak_' + self.n_id, kwargs.get('g_leak', 2.8)) # : nS - (_, self.e_leak) = neural_container.constants.add_parameter( - 'e_leak_' + self.n_id, kwargs.get('e_leak', -60.0)) # : mV - - (_, self.tau_noise) = neural_container.constants.add_parameter( - 'tau_noise_' + self.n_id, kwargs.get('tau_noise', 10.0)) # ms - (_, self.mu_noise) = neural_container.constants.add_parameter( - 'mu_noise_' + self.n_id, kwargs.get('mu_noise', 0.0)) # - (_, self.sigma_noise) = neural_container.constants.add_parameter( - 'sigma_noise_' + self.n_id, kwargs.get('sigma_noise', 0.005)) # - (_, self.seed_noise) = neural_container.constants.add_parameter( - 'seed_noise_' + self.n_id, kwargs.get('seed_noise', time.thread_time_ns())) # - (_, self.time_step_noise) = neural_container.constants.add_parameter( - 'time_step_noise_' + self.n_id, kwargs.get('time_step_noise', 1e-3/2.0)) # - - self.state_noise = neural_container.parameters.add_parameter( - 'state_noise_' + self.n_id, kwargs.get('state_noise', 0.0))[0] # - - (_, self.v_max) = neural_container.constants.add_parameter( - 'v_max_' + self.n_id, kwargs.get('v_max', 0.0)) # : mV - (_, self.v_thr) = neural_container.constants.add_parameter( - 'v_thr_' + self.n_id, kwargs.get('v_thr', -50.0)) # : mV - - (_, self.g_syn_e) = neural_container.constants.add_parameter( - 'g_syn_e_' + self.n_id, kwargs.get('g_syn_e', 10.0)) # : nS - (_, self.g_syn_i) = neural_container.constants.add_parameter( - 'g_syn_i_' + self.n_id, kwargs.get('g_syn_i', 10.0)) # : nS - (_, self.e_syn_e) = neural_container.constants.add_parameter( - 'e_syn_e_' + self.n_id, kwargs.get('e_syn_e', -10.0)) # : mV - (_, self.e_syn_i) = neural_container.constants.add_parameter( - 'e_syn_i_' + self.n_id, kwargs.get('e_syn_i', -75.0)) # : mV - - (_, self.m_e) = neural_container.constants.add_parameter( - 'm_e_' + self.n_id, kwargs.pop('m_e', 0.0)) # m_E,i - (_, self.m_i) = neural_container.constants.add_parameter( - 'm_i_' + self.n_id, kwargs.pop('m_i', 0.0)) # m_I,i - (_, self.b_e) = neural_container.constants.add_parameter( - 'b_e_' + self.n_id, kwargs.pop('b_e', 0.0)) # m_E,i - (_, self.b_i) = neural_container.constants.add_parameter( - 'b_i_' + self.n_id, kwargs.pop('b_i', 0.0)) # m_I,i - - # State Variables - # pylint: disable=invalid-name - self.v = neural_container.states.add_parameter( - 'V_' + self.n_id, kwargs.get('v0', -60.0))[0] # Membrane potential - - # ODE - self.vdot = neural_container.dstates.add_parameter( - 'vdot_' + self.n_id, 0.0)[0] - - # Output - self.nout = neural_container.outputs.add_parameter( - 'nout_' + self.n_id, 0.0)[0] - - # External Input (BrainStem Drive) - self.alpha = neural_container.inputs.add_parameter( - 'alpha_' + self.n_id, 0.22)[0] - - # Neuron inputs - self.num_inputs = num_inputs - self.neuron_inputs = cnp.ndarray((num_inputs,), - dtype=[('neuron_idx', 'i'), - ('weight_idx', 'i')]) - - # Initialize noisy current - self.random_mt19937 = mt19937(self.seed_noise) - self.distribution = normal_distribution[double](0.0, 1.0) - - self.noise_params = OrnsteinUhlenbeckParameters( - mu=self.mu_noise, - sigma=self.sigma_noise, - tau=self.tau_noise, - dt=self.time_step_noise, - random_generator=self.random_mt19937, - distribution=self.distribution - ) - - def add_ode_input(self, int idx, neuron, neural_container, **kwargs): - """ Add relevant external inputs to the ode. - Parameters - ---------- - """ - - # Create a struct to store the inputs and weights to the neuron - cdef DannerNeuronInput n - # Get the neuron parameter - neuron_idx = neural_container.outputs.get_parameter_index( - 'nout_'+neuron.n_id) - - # Add the weight parameter - weight = neural_container.weights.add_parameter( - 'w_' + neuron.n_id + '_to_' + self.n_id, kwargs.get('weight', 0.0))[0] - weight_idx = neural_container.weights.get_parameter_index( - 'w_' + neuron.n_id + '_to_' + self.n_id) - n.neuron_idx = neuron_idx - n.weight_idx = weight_idx - - # Append the struct to the list - self.neuron_inputs[idx] = n - - def output(self): - """Neuron activation function. - Parameters - ---------- - m_potential: float - Neuron membrane potential - """ - return self.c_output() - - def ode_rhs(self, y, w, p): - """ Python interface to the ode_rhs computation.""" - self.c_ode_rhs(y, w, p) - - #################### C-FUNCTIONS #################### - - cdef void c_ode_rhs(self, double[:] _y, double[:] _w, double[:] _p): - """ Compute the ODE. Internal Setup Function.""" - - # States - cdef double _v = self.v.c_get_value() - - # Drive inputs - cdef double d_e = self.m_e * self.alpha.c_get_value() + self.b_e - cdef double d_i = self.m_i * self.alpha.c_get_value() + self.b_i - - # Ileak - cdef double i_leak = self.g_leak * (_v - self.e_leak) - - # ISyn_Excitatory - cdef double i_syn_e = self.g_syn_e * d_e * (_v - self.e_syn_e) - - # ISyn_Inhibitory - cdef double i_syn_i = self.g_syn_i * d_i * (_v - self.e_syn_i) - - # Neuron inputs - cdef double _sum = 0.0 - cdef unsigned int j - cdef double _neuron_out - cdef double _weight - - for j in range(self.num_inputs): - _neuron_out = _y[self.neuron_inputs[j].neuron_idx] - _weight = _w[self.neuron_inputs[j].weight_idx] - _sum += self.c_neuron_inputs_eval(_neuron_out, _weight) - - # noise current - cdef double i_noise = c_noise_current_update( - self.state_noise.c_get_value(), &(self.noise_params) - ) - self.state_noise.c_set_value(i_noise) - - # dV - self.vdot.c_set_value( - -(i_leak + i_syn_e + i_syn_i + +i_noise + _sum)/self.c_m) - - cdef void c_output(self): - """ Neuron output. """ - cdef double _v = self.v.c_get_value() - cdef double _n_out - - if _v >= self.v_max: - _n_out = 1. - elif (self.v_thr <= _v) and (_v < self.v_max): - _n_out = (_v - self.v_thr) / (self.v_max - self.v_thr) - elif _v < self.v_thr: - _n_out = 0.0 - # Set the neuron output - self.nout.c_set_value(_n_out) - - cdef inline double c_neuron_inputs_eval(self, double _neuron_out, double _weight): - """ Evaluate neuron inputs.""" - cdef double _v = self.v.c_get_value() - - if _weight >= 0.0: - # Excitatory Synapse - return self.g_syn_e*cfabs(_weight)*_neuron_out*(_v - self.e_syn_e) - elif _weight < 0.0: - # Inhibitory Synapse - return self.g_syn_i*cfabs(_weight)*_neuron_out*(_v - self.e_syn_i) diff --git a/farms_network/lif_danner_nap.pxd b/farms_network/lif_danner_nap.pxd deleted file mode 100644 index b32c2d3..0000000 --- a/farms_network/lif_danner_nap.pxd +++ /dev/null @@ -1,98 +0,0 @@ -""" ------------------------------------------------------------------------ -Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty -Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. ------------------------------------------------------------------------ - -Leaky Integrate and Fire Neuron Based on Danner et.al. -""" - -from farms_container.parameter cimport Parameter -from libcpp.random cimport mt19937, normal_distribution - -from farms_network.neuron cimport Neuron -from farms_network.utils.ornstein_uhlenbeck cimport OrnsteinUhlenbeckParameters - - -cdef struct DannerNapNeuronInput: - int neuron_idx - int weight_idx - -cdef class LIFDannerNap(Neuron): - cdef: - readonly str n_id - - unsigned int num_inputs - - # parameters - # constants - double c_m - double g_nap - double e_na - double v1_2_m - double k_m - double v1_2_h - double k_h - double v1_2_t - double k_t - double g_leak - double e_leak - double tau_0 - double tau_max - double v_max - double v_thr - double g_syn_e - double g_syn_i - double e_syn_e - double e_syn_i - double m_e - double m_i - double b_e - double b_i - - double tau_noise - double mu_noise - double sigma_noise - double time_step_noise - unsigned long int seed_noise - - # states - Parameter v - Parameter h - - Parameter state_noise - - # inputs - Parameter alpha - - # ode - Parameter vdot - Parameter hdot - - # Ouputs - Parameter nout - - # neuron connenctions - DannerNapNeuronInput[:] neuron_inputs - - # current noise - OrnsteinUhlenbeckParameters noise_params - mt19937 random_mt19937 - normal_distribution[double] distribution - - cdef: - void c_ode_rhs(self, double[:] _y, double[:] _w, double[:] _p) - void c_output(self) - double c_neuron_inputs_eval(self, double _neuron_out, double _weight) diff --git a/farms_network/lif_danner_nap.pyx b/farms_network/lif_danner_nap.pyx deleted file mode 100644 index dcc1100..0000000 --- a/farms_network/lif_danner_nap.pyx +++ /dev/null @@ -1,277 +0,0 @@ -""" ------------------------------------------------------------------------ -Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty -Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. ------------------------------------------------------------------------ - -Leaky Integrate and Fire Neuron Based on Danner et.al. -""" - -import time -import numpy as np - -cimport numpy as cnp -from libc.math cimport cosh as ccosh -from libc.math cimport exp as cexp -from libc.math cimport fabs as cfabs -from libc.stdio cimport printf - -from farms_network.utils.ornstein_uhlenbeck cimport c_noise_current_update - - -cdef class LIFDannerNap(Neuron): - """Leaky Integrate and Fire Neuron Based on Danner et.al. - """ - - def __init__(self, n_id, num_inputs, neural_container, **kwargs): - super( - LIFDannerNap, self).__init__('lif_danner_nap') - - self.n_id = n_id # Unique neuron identifier - - # Constants - (_, self.c_m) = neural_container.constants.add_parameter( - 'c_m_' + self.n_id, kwargs.get('c_m', 10.0)) # pF - - (_, self.g_nap) = neural_container.constants.add_parameter( - 'g_nap_'+self.n_id, kwargs.get('g_nap', 4.5)) # nS - (_, self.e_na) = neural_container.constants.add_parameter( - 'e_na_'+self.n_id, kwargs.get('e_na', 50.0)) # mV - - (_, self.v1_2_m) = neural_container.constants.add_parameter( - 'v1_2_m_' + self.n_id, kwargs.get('v1_2_m', -40.0)) # mV - (_, self.k_m) = neural_container.constants.add_parameter( - 'k_m_' + self.n_id, kwargs.get('k_m', -6.0)) # mV - - (_, self.v1_2_h) = neural_container.constants.add_parameter( - 'v1_2_h_' + self.n_id, kwargs.get('v1_2_h', -45.0)) # mV - (_, self.k_h) = neural_container.constants.add_parameter( - 'k_h_' + self.n_id, kwargs.get('k_h', 4.0)) # mV - - (_, self.v1_2_t) = neural_container.constants.add_parameter( - 'v1_2_t_' + self.n_id, kwargs.get('v1_2_t', -35.0)) # mV - (_, self.k_t) = neural_container.constants.add_parameter( - 'k_t_' + self.n_id, kwargs.get('k_t', 15.0)) # mV - - (_, self.g_leak) = neural_container.constants.add_parameter( - 'g_leak_' + self.n_id, kwargs.get('g_leak', 4.5)) # nS - (_, self.e_leak) = neural_container.constants.add_parameter( - 'e_leak_' + self.n_id, kwargs.get('e_leak', -62.5)) # mV - - (_, self.tau_0) = neural_container.constants.add_parameter( - 'tau_0_' + self.n_id, kwargs.get('tau_0', 80.0)) # ms - (_, self.tau_max) = neural_container.constants.add_parameter( - 'tau_max_' + self.n_id, kwargs.get('tau_max', 160.0)) # ms - - (_, self.tau_noise) = neural_container.constants.add_parameter( - 'tau_noise_' + self.n_id, kwargs.get('tau_noise', 10.0)) # ms - (_, self.mu_noise) = neural_container.constants.add_parameter( - 'mu_noise_' + self.n_id, kwargs.get('mu_noise', 0.0)) # - (_, self.sigma_noise) = neural_container.constants.add_parameter( - 'sigma_noise_' + self.n_id, kwargs.get('sigma_noise', 0.005)) # - (_, self.seed_noise) = neural_container.constants.add_parameter( - 'seed_noise_' + self.n_id, kwargs.get('seed_noise', time.thread_time_ns())) # - (_, self.time_step_noise) = neural_container.constants.add_parameter( - 'time_step_noise_' + self.n_id, kwargs.get('time_step_noise', 1e-3/2.0)) # - - self.state_noise = neural_container.parameters.add_parameter( - 'state_noise_' + self.n_id, kwargs.get('state_noise', 0.0))[0] # - - (_, self.v_max) = neural_container.constants.add_parameter( - 'v_max_' + self.n_id, kwargs.get('v_max', 0.0)) # mV - (_, self.v_thr) = neural_container.constants.add_parameter( - 'v_thr_' + self.n_id, kwargs.get('v_thr', -50.0)) # mV - - (_, self.g_syn_e) = neural_container.constants.add_parameter( - 'g_syn_e_' + self.n_id, kwargs.get('g_syn_e', 10.0)) # nS - (_, self.g_syn_i) = neural_container.constants.add_parameter( - 'g_syn_i_' + self.n_id, kwargs.get('g_syn_i', 10.0)) # nS - (_, self.e_syn_e) = neural_container.constants.add_parameter( - 'e_syn_e_' + self.n_id, kwargs.get('e_syn_e', -10.0)) # mV - (_, self.e_syn_i) = neural_container.constants.add_parameter( - 'e_syn_i_' + self.n_id, kwargs.get('e_syn_i', -75.0)) # mV - - (_, self.m_e) = neural_container.constants.add_parameter( - 'm_e_' + self.n_id, kwargs.pop('m_e', 0.0)) # m_E,i - (_, self.m_i) = neural_container.constants.add_parameter( - 'm_i_' + self.n_id, kwargs.pop('m_i', 0.0)) # m_I,i - (_, self.b_e) = neural_container.constants.add_parameter( - 'b_e_' + self.n_id, kwargs.pop('b_e', 0.0)) # m_E,i - (_, self.b_i) = neural_container.constants.add_parameter( - 'b_i_' + self.n_id, kwargs.pop('b_i', 0.0)) # m_I,i - - # State Variables - # pylint: disable=invalid-name - self.v = neural_container.states.add_parameter( - 'V_' + self.n_id, kwargs.get('v0', -60.0))[0] # Membrane potential - self.h = neural_container.states.add_parameter( - 'h_' + self.n_id, kwargs.get('h0', np.random.uniform(0, 1)))[0] - - # ODE - self.vdot = neural_container.dstates.add_parameter( - 'vdot_' + self.n_id, 0.0)[0] - self.hdot = neural_container.dstates.add_parameter( - 'hdot_' + self.n_id, 0.0)[0] - - # Ouput - self.nout = neural_container.outputs.add_parameter( - 'nout_' + self.n_id, 0.0)[0] - - # External Input (BrainStem Drive) - self.alpha = neural_container.inputs.add_parameter( - 'alpha_' + self.n_id, 0.22)[0] - - # Neuron inputs - self.num_inputs = num_inputs - self.neuron_inputs = cnp.ndarray((num_inputs,), - dtype=[('neuron_idx', 'i'), - ('weight_idx', 'i')]) - - # Initialize noisy current - self.random_mt19937 = mt19937(self.seed_noise) - self.distribution = normal_distribution[double](0.0, 1.0) - - self.noise_params = OrnsteinUhlenbeckParameters( - mu=self.mu_noise, - sigma=self.sigma_noise, - tau=self.tau_noise, - dt=self.time_step_noise, - random_generator=self.random_mt19937, - distribution=self.distribution - ) - - - def add_ode_input(self, int idx, neuron, neural_container, **kwargs): - """ Add relevant external inputs to the ode. - Parameters - ---------- - """ - - # Create a struct to store the inputs and weights to the neuron - cdef DannerNapNeuronInput n - # Get the neuron parameter - neuron_idx = neural_container.outputs.get_parameter_index( - 'nout_'+neuron.n_id) - - # Add the weight parameter - weight = neural_container.weights.add_parameter( - 'w_' + neuron.n_id + '_to_' + self.n_id, kwargs.get('weight', 0.0))[0] - weight_idx = neural_container.weights.get_parameter_index( - 'w_' + neuron.n_id + '_to_' + self.n_id) - n.neuron_idx = neuron_idx - n.weight_idx = weight_idx - - # Append the struct to the list - self.neuron_inputs[idx] = n - - def output(self): - """Neuron activation function. - Parameters - ---------- - m_potential: float - Neuron membrane potential - """ - return self.c_output() - - def ode_rhs(self, y, w, p): - """ Python interface to the ode_rhs computation.""" - self.c_ode_rhs(y, w, p) - - #################### C-FUNCTIONS #################### - - cdef void c_ode_rhs(self, double[:] _y, double[:] _w, double[:] _p): - """ Compute the ODE. Internal Setup Function.""" - - # States - cdef double _v = self.v.c_get_value() - cdef double _h = self.h.c_get_value() - - # Drive inputs - cdef double d_e = self.m_e * self.alpha.c_get_value() + self.b_e - cdef double d_i = self.m_i * self.alpha.c_get_value() + self.b_i - - # tau_h(V) - cdef double tau_h = self.tau_0 + (self.tau_max - self.tau_0) / \ - ccosh((_v - self.v1_2_t) / self.k_t) - - # h_inf(V) - cdef double h_inf = 1./(1.0 + cexp((_v - self.v1_2_h) / self.k_h)) - - # m(V) - cdef double m = 1./(1.0 + cexp((_v - self.v1_2_m) / self.k_m)) - - # Inap - # pylint: disable=no-member - cdef double i_nap = self.g_nap * m * _h * (_v - self.e_na) - - # Ileak - cdef double i_leak = self.g_leak * (_v - self.e_leak) - - # ISyn_Excitatory - cdef double i_syn_e = self.g_syn_e * d_e * (_v - self.e_syn_e) - - # ISyn_Inhibitory - cdef double i_syn_i = self.g_syn_i * d_i * (_v - self.e_syn_i) - - # Neuron inputs - cdef double _sum = 0.0 - cdef unsigned int j - cdef double _neuron_out - cdef double _weight - - for j in range(self.num_inputs): - _neuron_out = _y[self.neuron_inputs[j].neuron_idx] - _weight = _w[self.neuron_inputs[j].weight_idx] - _sum += self.c_neuron_inputs_eval(_neuron_out, _weight) - - # Slow inactivation - self.hdot.c_set_value((h_inf - _h) / tau_h) - - # noise current - cdef double i_noise = c_noise_current_update( - self.state_noise.c_get_value(), &(self.noise_params) - ) - self.state_noise.c_set_value(i_noise) - - # dV - self.vdot.c_set_value( - -(i_nap + i_leak + i_syn_e + i_syn_i + i_noise + _sum)/self.c_m) - - cdef void c_output(self): - """ Neuron output. """ - cdef double _v = self.v.c_get_value() - cdef double _n_out - - if _v >= self.v_max: - _n_out = 1. - elif self.v_thr <= _v < self.v_max: - _n_out = (_v - self.v_thr) / (self.v_max - self.v_thr) - else: - _n_out = 0.0 - # Set the neuron output - self.nout.c_set_value(_n_out) - - cdef double c_neuron_inputs_eval(self, double _neuron_out, double _weight): - """ Evaluate neuron inputs.""" - cdef double _v = self.v.c_get_value() - - if _weight >= 0.0: - # Excitatory Synapse - return ( - self.g_syn_e*cfabs(_weight)*_neuron_out*(_v - self.e_syn_e)) - elif _weight < 0.0: - # Inhibitory Synapse - return ( - self.g_syn_i*cfabs(_weight)*_neuron_out*(_v - self.e_syn_i)) diff --git a/farms_network/lif_daun_interneuron.pxd b/farms_network/lif_daun_interneuron.pxd deleted file mode 100644 index 261cca0..0000000 --- a/farms_network/lif_daun_interneuron.pxd +++ /dev/null @@ -1,75 +0,0 @@ -""" ------------------------------------------------------------------------ -Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty -Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. ------------------------------------------------------------------------ - -Leaky Integrate and Fire InterNeuron Based on Daun et.al. -""" - -from farms_container.parameter cimport Parameter -from farms_network.neuron cimport Neuron - -cdef struct DaunInterNeuronInput: - int neuron_idx - int g_syn_idx - int e_syn_idx - int gamma_s_idx - int v_h_s_idx - -cdef class LIFDaunInterneuron(Neuron): - cdef: - readonly str n_id - - unsigned int num_inputs - - # parameters - # constants - double c_m - double g_nap - double e_nap - double v_h_h - double gamma_h - double v_t_h - double eps - double gamma_t - double v_h_m - double gamma_m - double g_leak - double e_leak - - # states - Parameter v - Parameter h - - # inputs - Parameter g_app - Parameter e_app - - # ode - Parameter vdot - Parameter hdot - - # Ouputs - Parameter nout - - # neuron connenctions - DaunInterNeuronInput[:] neuron_inputs - - cdef: - void c_ode_rhs(self, double[:] _y, double[:] _w, double[:] _p) - void c_output(self) - double c_neuron_inputs_eval(self, double _neuron_out, double _g_syn, double _e_syn, - double _gamma_s, double _v_h_s) diff --git a/farms_network/lif_daun_interneuron.pyx b/farms_network/lif_daun_interneuron.pyx deleted file mode 100644 index 7b27e5b..0000000 --- a/farms_network/lif_daun_interneuron.pyx +++ /dev/null @@ -1,231 +0,0 @@ -""" ------------------------------------------------------------------------ -Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty -Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. ------------------------------------------------------------------------ - -Leaky Integrate and Fire Interneuron. Daun et -""" -from libc.stdio cimport printf -import numpy as np -from libc.math cimport exp as cexp -from libc.math cimport cosh as ccosh -from libc.math cimport fabs as cfabs -cimport numpy as cnp - - -cdef class LIFDaunInterneuron(Neuron): - """Leaky Integrate and Fire Interneuron. - Based on Silvia Daun and Tbor's model. - """ - - def __init__(self, n_id, num_inputs, neural_container, **kwargs): - super(LIFDaunInterneuron, self).__init__('lif_daun_interneuron') - - self.n_id = n_id # Unique neuron identifier - # Constants - (_, self.g_nap) = neural_container.constants.add_parameter( - 'g_nap_' + self.n_id, kwargs.get('g_nap', 10.0)) - (_, self.e_nap) = neural_container.constants.add_parameter( - 'e_nap_' + self.n_id, kwargs.get('e_nap', 50.0)) - - # Parameters of h - (_, self.v_h_h) = neural_container.constants.add_parameter( - 'v_h_h_' + self.n_id, kwargs.get('v_h_h', -30.0)) - (_, self.gamma_h) = neural_container.constants.add_parameter( - 'gamma_h_' + self.n_id, kwargs.get('gamma_h', 0.1667)) - - # Parameters of tau - (_, self.v_t_h) = neural_container.constants.add_parameter( - 'v_t_h_' + self.n_id, kwargs.get('v_t_h', -30.0)) - (_, self.eps) = neural_container.constants.add_parameter( - 'eps_' + self.n_id, kwargs.get('eps', 0.0023)) - (_, self.gamma_t) = neural_container.constants.add_parameter( - 'gamma_t_' + self.n_id, kwargs.get('gamma_t', 0.0833)) - - # Parameters of m - (_, self.v_h_m) = neural_container.constants.add_parameter( - 'v_h_m_' + self.n_id, kwargs.get('v_h_m', -37.0)) - (_, self.gamma_m) = neural_container.constants.add_parameter( - 'gamma_m_' + self.n_id, kwargs.get('gamma_m', -0.1667)) - - # Parameters of Ileak - (_, self.g_leak) = neural_container.constants.add_parameter( - 'g_leak_' + self.n_id, kwargs.get('g_leak', 2.8)) - (_, self.e_leak) = neural_container.constants.add_parameter( - 'e_leak_' + self.n_id, kwargs.get('e_leak', -65.0)) - - # Other constants - (_, self.c_m) = neural_container.constants.add_parameter( - 'c_m_' + self.n_id, kwargs.get('c_m', 0.9154)) - - # State Variables - # pylint: disable=invalid-name - # Membrane potential - self.v = neural_container.states.add_parameter( - 'V_' + self.n_id, kwargs.get('v0', -60.0))[0] - self.h = neural_container.states.add_parameter( - 'h_' + self.n_id, kwargs.get('h0', 0.0))[0] - - # ODE - self.vdot = neural_container.dstates.add_parameter( - 'vdot_' + self.n_id, 0.0)[0] - self.hdot = neural_container.dstates.add_parameter( - 'hdot_' + self.n_id, 0.0)[0] - - # External Input - self.g_app = neural_container.inputs.add_parameter( - 'g_app_' + self.n_id, kwargs.get('g_app', 0.2))[0] - self.e_app = neural_container.inputs.add_parameter( - 'e_app_' + self.n_id, kwargs.get('e_app', 0.0))[0] - - # Output - self.nout = neural_container.outputs.add_parameter( - 'nout_' + self.n_id, 0.0)[0] - - # Neuron inputs - self.num_inputs = num_inputs - self.neuron_inputs = cnp.ndarray((num_inputs,), - dtype=[('neuron_idx', 'i'), - ('g_syn_idx', 'i'), - ('e_syn_idx', 'i'), - ('gamma_s_idx', 'i'), - ('v_h_s_idx', 'i')]) - - def add_ode_input(self, int idx, neuron, neural_container, **kwargs): - """ Add relevant external inputs to the ode. - Parameters - ---------- - neuron : - Neuron model from which the input is received. - weight : - Strength of the synapse between the two neurons""" - - # Create a struct to store the inputs and weights to the neuron - cdef DaunInterNeuronInput n - # Get the neuron parameter - neuron_idx = neural_container.outputs.get_parameter_index( - 'nout_'+neuron.n_id) - - g_syn = neural_container.parameters.add_parameter( - 'g_syn_' + self.n_id, kwargs.pop('g_syn', 0.0))[0] - e_syn = neural_container.parameters.add_parameter( - 'e_syn_' + self.n_id, kwargs.pop('e_syn', 0.0))[0] - gamma_s = neural_container.parameters.add_parameter( - 'gamma_s_' + self.n_id, kwargs.pop('gamma_s', 0.0))[0] - v_h_s = neural_container.parameters.add_parameter( - 'v_h_s_' + self.n_id, kwargs.pop('v_h_s', 0.0))[0] - - # Get neuron parameter indices - g_syn_idx = neural_container.parameters.get_parameter_index( - 'g_syn_' + self.n_id) - e_syn_idx = neural_container.parameters.get_parameter_index( - 'e_syn_' + self.n_id) - gamma_s_idx = neural_container.parameters.get_parameter_index( - 'gamma_s_' + self.n_id) - v_h_s_idx = neural_container.parameters.get_parameter_index( - 'v_h_s_' + self.n_id) - - # Add the indices to the struct - n.neuron_idx = neuron_idx - n.g_syn_idx = g_syn_idx - n.e_syn_idx = e_syn_idx - n.gamma_s_idx = gamma_s_idx - n.v_h_s_idx = v_h_s_idx - - # Append the struct to the list - self.neuron_inputs[idx] = n - - def output(self): - """Neuron activation function. - Parameters - ---------- - m_potential: float - Neuron membrane potential - """ - return self.c_output() - - def ode_rhs(self, y, w, p): - """ Python interface to the ode_rhs computation.""" - self.c_ode_rhs(y, w, p) - - #################### C-FUNCTIONS #################### - cdef void c_ode_rhs(self, double[:] _y, double[:] _w, double[:] _p): - """ Compute the ODE. Internal Setup Function.""" - - # States - cdef double _v = self.v.c_get_value() - cdef double _h = self.h.c_get_value() - - # tau_h(V) - cdef double tau_h = 1./(self.eps*ccosh(self.gamma_t*(_v - self.v_t_h))) - - # h_inf(V) - cdef double h_inf = 1./(1. + cexp(self.gamma_h*(_v - self.v_h_h))) - - # m_inf(V) - cdef double m_inf = 1./(1. + cexp(self.gamma_m*(_v - self.v_h_m))) - - # Inap - # pylint: disable=no-member - cdef double i_nap = self.g_nap * m_inf * _h * (_v - self.e_nap) - - # Ileak - cdef double i_leak = self.g_leak * (_v - self.e_leak) - - # Iapp - cdef double i_app = self.g_app.c_get_value() * ( - _v - self.e_app.c_get_value()) - - # Neuron inputs - cdef double _sum = 0.0 - cdef unsigned int j - cdef double _neuron_out - cdef double _g_syn - cdef double _e_syn - cdef double _gamma_s - cdef double _v_h_s - cdef DaunInterNeuronInput _neuron - - for j in range(self.num_inputs): - _neuron = self.neuron_inputs[j] - _neuron_out = _y[_neuron.neuron_idx] - _g_syn = _p[_neuron.g_syn_idx] - _e_syn = _p[_neuron.e_syn_idx] - _gamma_s = _p[_neuron.gamma_s_idx] - _v_h_s = _p[_neuron.v_h_s_idx] - _sum += self.c_neuron_inputs_eval( - _neuron_out, _g_syn, _e_syn, _gamma_s, _v_h_s) - - # Slow inactivation - self.hdot.c_set_value((h_inf - _h)/tau_h) - - # dV - self.vdot.c_set_value((-i_nap - i_leak - i_app - _sum)/self.c_m) - - cdef void c_output(self): - """ Neuron output. """ - # Set the neuron output - self.nout.c_set_value(self.v.c_get_value()) - - cdef double c_neuron_inputs_eval( - self, double _neuron_out, double _g_syn, double _e_syn, - double _gamma_s, double _v_h_s): - """ Evaluate neuron inputs.""" - cdef double _v = self.v.c_get_value() - - cdef double _s_inf = 1./(1. + cexp(_gamma_s*(_neuron_out - _v_h_s))) - - return _g_syn*_s_inf*(_v - _e_syn) diff --git a/farms_network/matsuoka_neuron.pxd b/farms_network/matsuoka_neuron.pxd deleted file mode 100644 index af59c22..0000000 --- a/farms_network/matsuoka_neuron.pxd +++ /dev/null @@ -1,67 +0,0 @@ -""" ------------------------------------------------------------------------ -Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty -Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. ------------------------------------------------------------------------ - -Matsuoka Neuron model. -""" - -from farms_container.parameter cimport Parameter -from farms_network.neuron cimport Neuron - -cdef struct MatsuokaNeuronInput: - int neuron_idx - int weight_idx - int phi_idx - -cdef class MatsuokaNeuron(Neuron): - cdef: - readonly str n_id - - unsigned int num_inputs - - # parameters - # constants - double c - double b - double tau - double T - double theta - double nu - - # states - Parameter V - Parameter w - - # inputs - Parameter ext_in - - # ode - Parameter V_dot - Parameter w_dot - - # Ouputs - Parameter nout - - # neuron connenctions - MatsuokaNeuronInput[:] neuron_inputs - - cdef: - void c_ode_rhs(self, double[:] _y, double[:] _w, double[:] _p) - void c_output(self) - cdef double c_neuron_inputs_eval( - self, double _neuron_out, double _weight, double _phi, - double _V, double _w) diff --git a/farms_network/matsuoka_neuron.pyx b/farms_network/matsuoka_neuron.pyx deleted file mode 100644 index cb9e78d..0000000 --- a/farms_network/matsuoka_neuron.pyx +++ /dev/null @@ -1,168 +0,0 @@ -""" ------------------------------------------------------------------------ -Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty -Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. ------------------------------------------------------------------------ -Matsuoka Neuron model -""" -from libc.stdio cimport printf -import farms_pylog as pylog -import numpy as np -cimport numpy as cnp - - -cdef class MatsuokaNeuron(Neuron): - - def __init__(self, n_id, num_inputs, neural_container, **kwargs): - """Initialize. - - Parameters - ---------- - n_id: str - Unique ID for the neuron in the network. - """ - super(MatsuokaNeuron, self).__init__('matsuoka_neuron') - - # Neuron ID - self.n_id = n_id - - # Initialize parameters - - (_, self.c) = neural_container.constants.add_parameter( - 'c_' + self.n_id, kwargs.get('c', 1)) - - (_, self.b) = neural_container.constants.add_parameter( - 'b_' + self.n_id, kwargs.get('b', 1)) - - (_, self.tau) = neural_container.constants.add_parameter( - 'tau_' + self.n_id, kwargs.get('tau', 1)) - - (_, self.T) = neural_container.constants.add_parameter( - 'T_' + self.n_id, kwargs.get('T', 12)) - - (_, self.theta) = neural_container.constants.add_parameter( - 'theta_' + self.n_id, kwargs.get('theta', 0.0)) - - (_, self.nu) = neural_container.constants.add_parameter( - 'nu' + self.n_id, kwargs.get('nu', 0.5)) - - # Initialize states - self.V = neural_container.states.add_parameter( - 'V_' + self.n_id, kwargs.get('V0', 0.0))[0] - self.w = neural_container.states.add_parameter( - 'w_' + self.n_id, kwargs.get('w0', 0.5))[0] - - # External inputs - self.ext_in = neural_container.inputs.add_parameter( - 'ext_in_' + self.n_id)[0] - - # ODE RHS - self.V_dot = neural_container.dstates.add_parameter( - 'V_dot_' + self.n_id, 0.0)[0] - self.w_dot = neural_container.dstates.add_parameter( - 'w_dot_' + self.n_id, 0.0)[0] - - # Output - self.nout = neural_container.outputs.add_parameter( - 'nout_' + self.n_id, 0.0)[0] - - # Neuron inputs - self.neuron_inputs = cnp.ndarray((num_inputs,), - dtype=[('neuron_idx', 'i'), - ('weight_idx', 'i'), - ('phi_idx', 'i')]) - - self.num_inputs = num_inputs - - def add_ode_input(self, int idx, neuron, neural_container, **kwargs): - """ Add relevant external inputs to the ode.""" - # Create a struct to store the inputs and weights to the neuron - cdef MatsuokaNeuronInput n - # Get the neuron parameter - neuron_idx = neural_container.outputs.get_parameter_index( - 'nout_'+neuron.n_id) - - # Add the weight parameter - weight = neural_container.weights.add_parameter( - 'w_' + neuron.n_id + '_to_' + self.n_id, - kwargs.get('weight', 2.5))[0] - phi = neural_container.parameters.add_parameter( - 'phi_' + neuron.n_id + '_to_' + self.n_id, - kwargs.get('phi', 0.0))[0] - - weight_idx = neural_container.weights.get_parameter_index( - 'w_' + neuron.n_id + '_to_' + self.n_id) - phi_idx = neural_container.parameters.get_parameter_index( - 'phi_' + neuron.n_id + '_to_' + self.n_id) - - n.neuron_idx = neuron_idx - n.weight_idx = weight_idx - n.phi_idx = phi_idx - # Append the struct to the list - self.neuron_inputs[idx] = n - - def output(self): - """Neuron activation function. - Parameters - ---------- - m_potential: float - Neuron membrane potential - """ - return self.c_output() - - def ode_rhs(self, y, w, p): - """ Python interface to the ode_rhs computation.""" - self.c_ode_rhs(y, w, p) - #################### C-FUNCTIONS #################### - cdef void c_ode_rhs(self, double[:] _y, double[:] _w, double[:] _p): - """ Compute the ODE. Internal Setup Function.""" - - # Current state - cdef double _V = self.V.c_get_value() - cdef double _W = self.w.c_get_value() - - # Neuron inputs - cdef double _sum = 0.0 - cdef unsigned int j - cdef double _neuron_out - cdef double _weight - cdef double _phi - - for j in range(self.num_inputs): - _neuron_out = _y[self.neuron_inputs[j].neuron_idx] - _weight = _w[self.neuron_inputs[j].weight_idx] - _phi = _p[self.neuron_inputs[j].phi_idx] - _sum += self.c_neuron_inputs_eval(_neuron_out, - _weight, _phi, _V, _W) - - # phidot : V_dot - self.V_dot.c_set_value((1/self.tau)*(self.c - _V - _sum - self.b*_W)) - - # wdot - self.w_dot.c_set_value((1/self.T)*(-_W + self.nu*_V)) - - cdef void c_output(self): - """ Neuron output. """ - _V = self.V.c_get_value() - if _V < 0: - self.nout.c_set_value(max(-1, _V)) - else: - self.nout.c_set_value(min(1, _V)) - - cdef double c_neuron_inputs_eval( - self, double _neuron_out, double _weight, double _phi, - double _V, double _w): - """ Evaluate neuron inputs.""" - return _weight*_neuron_out diff --git a/farms_network/morphed_oscillator.pxd b/farms_network/morphed_oscillator.pxd deleted file mode 100644 index f2f611c..0000000 --- a/farms_network/morphed_oscillator.pxd +++ /dev/null @@ -1,69 +0,0 @@ -""" ------------------------------------------------------------------------ -Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty -Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. ------------------------------------------------------------------------ - -Oscillator model. -""" - -from farms_container.parameter cimport Parameter -from farms_network.neuron cimport Neuron - -cdef struct MorphedOscillatorNeuronInput: - int neuron_idx - int weight_idx - int phi_idx - -cdef class MorphedOscillator(Neuron): - cdef: - readonly str n_id - - unsigned int num_inputs - - # parameters - # constants - double f - double gamma # : Gamma - double mu # : Mu - double zeta # : Zeta - - # Morphing function - Parameter f_theta - Parameter fd_theta - - # states - Parameter theta - Parameter r - - # inputs - Parameter ext_in - - # ode - Parameter theta_dot - Parameter r_dot - - # Ouputs - Parameter nout - - # neuron connenctions - MorphedOscillatorNeuronInput[:] neuron_inputs - - cdef: - void c_ode_rhs(self, double[:] _y, double[:] _w, double[:] _p) - void c_output(self) - double c_neuron_inputs_eval( - self, double _neuron_out, double _weight, double _theta, - double _phi) diff --git a/farms_network/morphed_oscillator.pyx b/farms_network/morphed_oscillator.pyx deleted file mode 100644 index ea6e3c4..0000000 --- a/farms_network/morphed_oscillator.pyx +++ /dev/null @@ -1,177 +0,0 @@ -""" ------------------------------------------------------------------------ -Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty -Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. ------------------------------------------------------------------------ - -Morphed Oscillator model -""" -from libc.stdio cimport printf -import farms_pylog as pylog -from libc.math cimport exp -from libc.math cimport M_PI -from libc.math cimport sin as csin -import numpy as np -cimport numpy as cnp - - -cdef class MorphedOscillator(Neuron): - - def __init__(self, n_id, num_inputs, neural_container, **kwargs): - """Initialize. - - Parameters - ---------- - n_id: str - Unique ID for the neuron in the network. - """ - super(MorphedOscillator, self).__init__('leaky') - - # Neuron ID - self.n_id = n_id - - # Initialize parameters - (_, self.f) = neural_container.constants.add_parameter( - 'f_' + self.n_id, kwargs.get('f', 0.5)) - - (_, self.gamma) = neural_container.constants.add_parameter( - 'g_' + self.n_id, kwargs.get('gamma', 100)) - - (_, self.mu) = neural_container.constants.add_parameter( - 'mu_' + self.n_id, kwargs.get('mu', 1.0)) - - (_, self.zeta) = neural_container.constants.add_parameter( - 'z_' + self.n_id, kwargs.get('zeta', 0.0)) - print(self.zeta) - # Initialize states - self.theta = neural_container.states.add_parameter( - 'theta_' + self.n_id, kwargs.get('theta0', 0.0))[0] - self.r = neural_container.states.add_parameter( - 'r_' + self.n_id, kwargs.get('r0', 0.0))[0] - - # External inputs - self.ext_in = neural_container.inputs.add_parameter( - 'ext_in_' + self.n_id)[0] - - # Morphing function - self.f_theta = neural_container.parameters.add_parameter( - 'f_theta_' + self.n_id, kwargs.get('f_theta0', 0.0))[0] - self.fd_theta = neural_container.parameters.add_parameter( - 'fd_theta_' + self.n_id, kwargs.get('fd_theta0', 0.0))[0] - - # ODE RHS - self.theta_dot = neural_container.dstates.add_parameter( - 'theta_dot_' + self.n_id, 0.0)[0] - self.r_dot = neural_container.dstates.add_parameter( - 'r_dot_' + self.n_id, 0.0)[0] - - # Output - self.nout = neural_container.outputs.add_parameter( - 'nout_' + self.n_id, 0.0)[0] - - # Neuron inputs - self.neuron_inputs = cnp.ndarray((num_inputs,), - dtype=[('neuron_idx', 'i'), - ('weight_idx', 'i'), - ('theta_idx', 'i')]) - - self.num_inputs = num_inputs - - def add_ode_input(self, int idx, neuron, neural_container, **kwargs): - """ Add relevant external inputs to the ode.""" - # Create a struct to store the inputs and weights to the neuron - cdef MorphedOscillatorNeuronInput n - # Get the neuron parameter - neuron_idx = neural_container.outputs.get_parameter_index( - 'nout_'+neuron.n_id) - - # Add the weight parameter - weight = neural_container.weights.add_parameter( - 'w_' + neuron.n_id + '_to_' + self.n_id, - kwargs.get('weight', 0.0))[0] - phi = neural_container.parameters.add_parameter( - 'phi_' + neuron.n_id + '_to_' + self.n_id, - kwargs.get('phi', 0.0))[0] - - weight_idx = neural_container.weights.get_parameter_index( - 'w_' + neuron.n_id + '_to_' + self.n_id) - phi_idx = neural_container.parameters.get_parameter_index( - 'phi_' + neuron.n_id + '_to_' + self.n_id) - - n.neuron_idx = neuron_idx - n.weight_idx = weight_idx - n.phi_idx = phi_idx - - # Append the struct to the list - self.neuron_inputs[idx] = n - - def output(self): - """Neuron activation function. - Parameters - ---------- - m_potential: float - Neuron membrane potential - """ - return self.c_output() - - def ode_rhs(self, y, w, p): - """ Python interface to the ode_rhs computation.""" - self.c_ode_rhs(y, w, p) - - #################### C-FUNCTIONS #################### - cdef void c_ode_rhs(self, double[:] _y, double[:] _w, double[:] _p): - """ Compute the ODE. Internal Setup Function.""" - - # Current state - cdef double _theta = self.theta.c_get_value() - cdef double _r = self.r.c_get_value() - cdef double f_theta = self.f_theta.c_get_value() - cdef double fd_theta = self.fd_theta.c_get_value() - - # Neuron inputs - cdef double _sum = 0.0 - cdef unsigned int j - cdef double _neuron_out - cdef double _weight - cdef double _phi - - for j in range(self.num_inputs): - _neuron_out = _y[self.neuron_inputs[j].neuron_idx] - _weight = _w[self.neuron_inputs[j].weight_idx] - _phi = _p[self.neuron_inputs[j].phi_idx] - _sum += self.c_neuron_inputs_eval( - _neuron_out, _weight, _theta, _phi) - - # thetadot : theta_dot - self.theta_dot.c_set_value(2*M_PI*self.f + _sum) - - # rdot - # cdef double r_dot_1 = 2*M_PI*self.f*_r*(fd_theta/f_theta) - # cdef double r_dot_2 = _r*self.gamma*(self.mu - ((_r*_r)/(f_theta*f_theta))) - # self.r_dot.c_set_value(r_dot_1 + r_dot_2 + self.zeta) - - cdef double r_dot_1 = fd_theta*self.theta_dot.c_get_value() - cdef double r_dot_2 = self.gamma*(f_theta - _r) - self.r_dot.c_set_value(r_dot_1 + r_dot_2 + self.zeta) - - cdef void c_output(self): - """ Neuron output. """ - self.nout.c_set_value(self.theta.c_get_value()) - - cdef double c_neuron_inputs_eval( - self, double _neuron_out, double _weight, double _theta, - double _phi): - """ Evaluate neuron inputs.""" - return _weight*csin(_neuron_out - _theta - _phi) diff --git a/farms_network/morris_lecar.pxd b/farms_network/morris_lecar.pxd deleted file mode 100644 index 164bc2e..0000000 --- a/farms_network/morris_lecar.pxd +++ /dev/null @@ -1,75 +0,0 @@ -""" ------------------------------------------------------------------------ -Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty -Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. ------------------------------------------------------------------------ - -Morris Lecar Neuron model. -""" - -from farms_container.parameter cimport Parameter -from farms_network.neuron cimport Neuron - -cdef struct MLNeuronInput: - int neuron_idx - int weight_idx - int phi_idx - -cdef class MorrisLecarNeuron(Neuron): - cdef: - readonly str n_id - - unsigned int num_inputs - - # parameters - # constants - double internal_curr - double C - double g_fast - double g_slow - double g_leak - double E_fast - double E_slow - double E_leak - double phi_w - double beta_m - double beta_w - double gamma_m - double gamma_w - - # states - Parameter V - Parameter w - - # inputs - Parameter ext_in - - # ode - Parameter V_dot - Parameter w_dot - - # Ouputs - Parameter nout - - # neuron connenctions - MLNeuronInput[:] neuron_inputs - - cdef: - void c_ode_rhs(self, double[:] _y, double[:] _w, double[:] _p) - void c_output(self) - cdef double c_neuron_inputs_eval( - self, double _neuron_out, double _weight, double _phi, - double _V, double _w - ) diff --git a/farms_network/morris_lecar.pyx b/farms_network/morris_lecar.pyx deleted file mode 100644 index d2877c2..0000000 --- a/farms_network/morris_lecar.pyx +++ /dev/null @@ -1,185 +0,0 @@ -""" ------------------------------------------------------------------------ -Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty -Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. ------------------------------------------------------------------------ - -Morris Lecar Neuron model -""" -from libc.stdio cimport printf -import farms_pylog as pylog -from libc.math cimport tanh as ctanh -from libc.math cimport cosh as ccosh -import numpy as np -cimport numpy as cnp - - -cdef class MorrisLecarNeuron(Neuron): - - def __init__(self, n_id, num_inputs, neural_container, **kwargs): - """Initialize. - - Parameters - ---------- - n_id: str - Unique ID for the neuron in the network. - """ - super(MorrisLecarNeuron, self).__init__('morris_lecar') - - # Neuron ID - self.n_id = n_id - - # Initialize parameters - (_, self.internal_curr) = neural_container.constants.add_parameter( - 'I_' + self.n_id, kwargs.get('I', 100.0)) - (_, self.C) = neural_container.constants.add_parameter( - 'C_' + self.n_id, kwargs.get('C', 2.0)) - (_, self.g_fast) = neural_container.constants.add_parameter( - 'g_fast_' + self.n_id, kwargs.get('g_fast', 20.0)) - (_, self.g_slow) = neural_container.constants.add_parameter( - 'g_slow_' + self.n_id, kwargs.get('g_slow', 20.0)) - (_, self.g_leak) = neural_container.constants.add_parameter( - 'g_leak_' + self.n_id, kwargs.get('g_leak', 2.0)) - (_, self.E_fast) = neural_container.constants.add_parameter( - 'E_fast_' + self.n_id, kwargs.get('E_fast', 50.0)) - (_, self.E_slow) = neural_container.constants.add_parameter( - 'E_slow_' + self.n_id, kwargs.get('E_slow', -100.0)) - (_, self.E_leak) = neural_container.constants.add_parameter( - 'E_leak_' + self.n_id, kwargs.get('E_leak', -70.0)) - (_, self.phi_w) = neural_container.constants.add_parameter( - 'phi_w_' + self.n_id, kwargs.get('phi_w', 0.15)) - (_, self.beta_m) = neural_container.constants.add_parameter( - 'beta_m_' + self.n_id, kwargs.get('beta_m', 0.0)) - (_, self.gamma_m) = neural_container.constants.add_parameter( - 'gamma_m_' + self.n_id, kwargs.get('gamma_m', 18.0)) - (_, self.beta_w) = neural_container.constants.add_parameter( - 'beta_w_' + self.n_id, kwargs.get('beta_w', -10.0)) - (_, self.gamma_w) = neural_container.constants.add_parameter( - 'gamma_w_' + self.n_id, kwargs.get('gamma_w', 13.0)) - - # Initialize states - self.V = neural_container.states.add_parameter( - 'V_' + self.n_id, kwargs.get('V0', 0.0))[0] - self.w = neural_container.states.add_parameter( - 'w_' + self.n_id, kwargs.get('w0', 0.0))[0] - - # External inputs - self.ext_in = neural_container.inputs.add_parameter( - 'ext_in_' + self.n_id)[0] - - # ODE RHS - self.V_dot = neural_container.dstates.add_parameter( - 'V_dot_' + self.n_id, 0.0)[0] - self.w_dot = neural_container.dstates.add_parameter( - 'w_dot_' + self.n_id, 0.0)[0] - - # Output - self.nout = neural_container.outputs.add_parameter( - 'nout_' + self.n_id, 0.0)[0] - - # Neuron inputs - self.neuron_inputs = cnp.ndarray((num_inputs,), - dtype=[('neuron_idx', 'i'), - ('weight_idx', 'i'), - ('phi_idx', 'i')]) - - self.num_inputs = num_inputs - - def add_ode_input(self, int idx, neuron, neural_container, **kwargs): - """ Add relevant external inputs to the ode.""" - # Create a struct to store the inputs and weights to the neuron - cdef MLNeuronInput n - # Get the neuron parameter - neuron_idx = neural_container.outputs.get_parameter_index( - 'nout_'+neuron.n_id) - - # Add the weight parameter - weight = neural_container.weights.add_parameter( - 'w_' + neuron.n_id + '_to_' + self.n_id, - kwargs.get('weight', 0.0))[0] - phi = neural_container.parameters.add_parameter( - 'phi_' + neuron.n_id + '_to_' + self.n_id, - kwargs.get('phi', 0.0))[0] - - weight_idx = neural_container.weights.get_parameter_index( - 'w_' + neuron.n_id + '_to_' + self.n_id) - phi_idx = neural_container.parameters.get_parameter_index( - 'phi_' + neuron.n_id + '_to_' + self.n_id) - - n.neuron_idx = neuron_idx - n.weight_idx = weight_idx - n.phi_idx = phi_idx - - # Append the struct to the list - self.neuron_inputs[idx] = n - - def output(self): - """Neuron activation function. - Parameters - ---------- - m_potential: float - Neuron membrane potential - """ - return self.c_output() - - def ode_rhs(self, y, w, p): - """ Python interface to the ode_rhs computation.""" - self.c_ode_rhs(y, w, p) - - #################### C-FUNCTIONS #################### - cdef void c_ode_rhs(self, double[:] _y, double[:] _w, double[:] _p): - """ Compute the ODE. Internal Setup Function.""" - - # Current state - cdef double _V = self.V.c_get_value() - cdef double _W = self.w.c_get_value() - - # Neuron inputs - cdef double _sum = 0.0 - cdef unsigned int j - cdef double _neuron_out - cdef double _weight - cdef double _phi - - for j in range(self.num_inputs): - _neuron_out = _y[self.neuron_inputs[j].neuron_idx] - _weight = _w[self.neuron_inputs[j].weight_idx] - _phi = _p[self.neuron_inputs[j].phi_idx] - _sum += self.c_neuron_inputs_eval(_neuron_out, - _weight, _phi, _V, _W) - - cdef double m_inf_V = 0.5*(1.0+ctanh((_V-self.beta_m)/self.gamma_m)) - - cdef double w_inf_V = 0.5*(1.0+ctanh((_V-self.beta_w)/self.gamma_w)) - - cdef double tau_w_V = (1./ccosh((_V-self.beta_w)/(2*self.gamma_w))) - - # V_dot - self.V_dot.c_set_value((1.0/self.C)*(self.internal_curr - self.g_fast*m_inf_V*(_V-self.E_fast) - - self.g_slow*_W*(_V - self.E_slow) - self.g_leak*(_V - self.E_leak))) - - # wdot - self.w_dot.c_set_value(self.phi_w*(w_inf_V - _W)/tau_w_V) - - cdef void c_output(self): - """ Neuron output. """ - self.nout.c_set_value(self.V.c_get_value()) - - cdef double c_neuron_inputs_eval( - self, double _neuron_out, double _weight, double _phi, - double _V, double _w): - """ Evaluate neuron inputs.""" - # Linear coupling term in potential - return _weight*(_neuron_out - _V) diff --git a/farms_network/network_generator.pxd b/farms_network/network_generator.pxd deleted file mode 100644 index 4d2a043..0000000 --- a/farms_network/network_generator.pxd +++ /dev/null @@ -1,40 +0,0 @@ -""" ------------------------------------------------------------------------ -Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty -Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. ------------------------------------------------------------------------ -""" -from farms_network.leaky_integrator cimport LeakyIntegrator -from farms_container.table cimport Table -from farms_network.neuron cimport Neuron - -cimport numpy - - -cdef class NetworkGenerator(object): - cdef: - dict __dict__ - Neuron[:] c_neurons - Table states - Table dstates - Table constants - Table inputs - Table weights - Table parameters - Table outputs - - unsigned int num_neurons - - cpdef double[:] ode(self, double t, double[:] state) diff --git a/farms_network/network_generator.pyx b/farms_network/network_generator.pyx deleted file mode 100644 index 18c0e71..0000000 --- a/farms_network/network_generator.pyx +++ /dev/null @@ -1,136 +0,0 @@ -""" ------------------------------------------------------------------------ -Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty -Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. ------------------------------------------------------------------------ - -Generate neural network. -""" -from farms_container.table cimport Table -from libc.stdio cimport printf - -from farms_network.neuron cimport Neuron - -from collections import OrderedDict - -import farms_pylog as pylog -import numpy as np -from cython.parallel import prange - -from farms_network.neuron_factory import NeuronFactory - -cimport cython -cimport numpy as cnp - - -cdef class NetworkGenerator: - """ Generate Neural Network. - """ - - def __init__(self, graph, neural_container): - """Initialize. - - Parameters - ---------- - graph_file_path: - File path to the graphml structure. - """ - super(NetworkGenerator, self).__init__() - - # Attributes - self.neurons = OrderedDict() # Neurons in the network - self.states = neural_container.add_table('states') - self.dstates =
neural_container.add_table('dstates') - self.constants =
neural_container.add_table('constants', table_type='CONSTANT') - self.inputs =
neural_container.add_table('inputs') - self.weights =
neural_container.add_table('weights', table_type='CONSTANT') - self.parameters =
neural_container.add_table('parameters', table_type='CONSTANT') - self.outputs =
neural_container.add_table('outputs') - - self.odes = [] - - self.fin = {} - self.integrator = {} - - # Read the graph - self.graph = graph - - # Get the number of neurons in the model - self.num_neurons = len(self.graph) - - self.c_neurons = np.ndarray((self.num_neurons,), dtype=Neuron) - self.generate_neurons(neural_container) - self.generate_network(neural_container) - - def generate_neurons(self, neural_container): - """Generate the complete neural network. - Instatiate a neuron model for each node in the graph - - Returns - ------- - out : - Return true if successfully created the neurons - """ - cdef int j - for j, (name, neuron) in enumerate(sorted(self.graph.nodes.items())): - # Add neuron to list - pylog.debug( - 'Generating neuron model : {} of type {}'.format( - name, neuron['model'])) - # Generate Neuron Models - _neuron = NeuronFactory.gen_neuron(neuron['model']) - self.neurons[name] = _neuron( - name, self.graph.in_degree(name), - neural_container, - **neuron - ) - self.c_neurons[j] = self.neurons[name] - - def generate_network(self, neural_container): - """ - Generate the network. - """ - for name, neuron in list(self.neurons.items()): - pylog.debug( - 'Establishing neuron {} network connections'.format( - name)) - for j, pred in enumerate(self.graph.predecessors(name)): - pylog.debug(('{} -> {}'.format(pred, name))) - # Set the weight of the parameter - neuron.add_ode_input( - j, - self.neurons[pred], - neural_container, - **self.graph[pred][name]) - - #################### C-FUNCTIONS #################### - cpdef double[:] ode(self, double t, double[:] state): - self.states.c_set_values(state) - cdef unsigned int j - cdef Neuron neuron - - cdef double[:] outputs = self.outputs.c_get_values() - cdef double[:] weights = self.weights.c_get_values() - cdef double[:] parameters = self.parameters.c_get_values() - - for j in range(self.num_neurons): - neuron = self.c_neurons[j] - neuron.c_output() - - for j in range(self.num_neurons): - neuron = self.c_neurons[j] - neuron.c_ode_rhs(outputs, weights, parameters) - - return self.dstates.c_get_values() diff --git a/farms_network/networkx_model.py b/farms_network/networkx_model.py deleted file mode 100644 index e0e87bb..0000000 --- a/farms_network/networkx_model.py +++ /dev/null @@ -1,240 +0,0 @@ -""" ------------------------------------------------------------------------ -Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty -Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. ------------------------------------------------------------------------ - -This class implements the network of different neurons. -""" - -import os - -import networkx as nx -import numpy as np - -import farms_pylog as pylog - - -class NetworkXModel(object): - """Generate Network based on graphml format. - """ - - def __init__(self): - """ Initialize. """ - super(NetworkXModel, self).__init__() - self.graph = None # NetworkX graph - self.pos = {} # Neuron positions - self.edge_pos = {} - self.color_map = [] # Neuron color map - self.color_map_arr = [] # Neuron color map - self.color_map_edge = [] # Neuron edge color map - self.alpha_edge = [] # Neuron edge alpha - self.edge_style = [] # Arrow edge style - self.net_matrix = None - - def read_graph(self, path): - """Read graph from the file path. - Parameters - ---------- - path : - File path of the graph - - Returns - ------- - out : - Graph object created by Networkx - """ - self.graph = nx.read_graphml(path) - return self.graph - - def network_sparse_matrix(self): - """Show network connectivity matrix.""" - self.net_matrix = nx.to_scipy_sparse_matrix(self.graph) - self.net_matrix = self.net_matrix.todense() - return self.net_matrix - - def show_network_sparse_matrix(self): - """Show network connectivity matrix.""" - pylog.info('Showing network connectivity matrix') - pylog.info(self.net_matrix) - - def read_neuron_position_in_graph(self, from_layout=False): - """ Read the positions of neurons. - Only if positions are defined. """ - for _neuron, data in list(self.graph.nodes.items()): - self.pos[_neuron] = (data.get('x', None), - data.get('y', None)) - self.edge_pos[_neuron] = (data.get('x', None), - data.get('y', None)) - check_pos_is_none = None in [ - val for x in list(self.pos.values()) for val in x] - if check_pos_is_none: - pylog.warning('Missing neuron position information.') - # self.pos = nx.kamada_kawai_layout(self.graph) - # self.pos = nx.spring_layout(self.graph) - self.pos = nx.shell_layout(self.graph) - self.edge_pos = self.pos - - def read_neuron_colors_in_graph(self): - """ Read the neuron display colors.""" - import matplotlib.colors as mcolors - for data in list(self.graph.nodes.values()): - self.color_map.extend(data.get('color', 'r')) - self.color_map_arr.append(mcolors.colorConverter.to_rgb( - self.color_map[-1])) - - def read_edge_colors_in_graph(self, edge_attribute='weight'): - """ Read the neuron display colors.""" - max_weight = max(list(dict(self.graph.edges).items()), - key=lambda x: abs(x[1][edge_attribute]), - default=[{edge_attribute: 0.0}])[-1][edge_attribute] - - max_weight = abs(max_weight) - for _, _, attr in self.graph.edges(data=True): - _weight = attr.get(edge_attribute, 0.0) - # pylint: disable=no-member - try: - _weight_ratio = _weight/max_weight - except ZeroDivisionError: - _weight_ratio = 0.0 - - if np.sign(_weight_ratio) == 1: - self.color_map_edge.extend('g') - # pylint: disable=no-member - elif np.sign(_weight_ratio) == -1: - self.color_map_edge.extend('r') - else: - self.color_map_edge.extend('k') - self.alpha_edge.append( - max(np.abs(_weight_ratio), 0.1)) - - def visualize_network(self, - node_size=1500, - node_labels=False, - edge_labels=False, - edge_attribute='weight', - edge_alpha=True, - plt_out=None, - **kwargs - ): - """ Visualize the neural network.""" - self.read_neuron_position_in_graph() - self.read_neuron_colors_in_graph() - if color_map_edge := kwargs.get('color_map_edge'): - self.color_map_edge = color_map_edge - else: - self.read_edge_colors_in_graph(edge_attribute=edge_attribute) - - if plt_out is not None: - fig = plt_out.figure('Network') - plt_out.autoscale(True) - ax = plt_out.gca() - else: - import matplotlib.pyplot as plt - fig = plt.figure('Network') - plt.autoscale(True) - ax = plt.gca() - - # Draw Nodes - _ = nx.draw_networkx_nodes(self.graph, pos=self.pos, - node_color=self.color_map, - node_size=node_size, - alpha=kwargs.pop('alpha', 0.25), - edgecolors='k', - linewidths=2.0, - ax=ax - ) - if node_labels: - nx.draw_networkx_labels( - self.graph, - pos=self.pos, - labels={n: val["label"] for n, val in self.graph.nodes.items()}, - font_size=kwargs.pop('font_size', 11.0), - font_weight=kwargs.pop('font_weight', 'bold'), - font_family=kwargs.pop('font_family', 'sans-serif'), - alpha=kwargs.pop('alpha', 1.0), - ax=ax - ) - if edge_labels: - labels = { - ed: round(val, 3) - for ed, val in nx.get_edge_attributes( - self.graph, edge_attribute - ).items() - } - nx.draw_networkx_edge_labels(self.graph, - pos=self.pos, - rotate=False, - edge_labels=labels, - font_size=kwargs.pop( - 'font_size', 6.5), - clip_on=True, - ax=ax) - edges = nx.draw_networkx_edges(self.graph, - pos=self.pos, - node_size=node_size, - edge_color=self.color_map_edge, - width=kwargs.pop('edge_width', 1.), - arrowsize=kwargs.pop('arrow_size', 10), - style=kwargs.pop( - 'edge_style', 'dashed'), - arrows=kwargs.pop('arrows', True), - connectionstyle=kwargs.pop( - 'connection_style', "arc3,rad=-0.0"), - min_source_margin=kwargs.pop( - 'min_source_margin', 5), - min_target_margin=kwargs.pop( - 'min_target_margin', 5), - ax=ax) - if edge_alpha: - for edge in range(self.graph.number_of_edges()): - edges[edge].set_alpha(self.alpha_edge[edge]) - - if plt_out is not None: - plt_out.draw() - plt_out.subplots_adjust( - left=0, right=1, top=1, bottom=0) - plt_out.grid() - ax.invert_yaxis() - plt_out.tight_layout() - else: - # fig.draw() - ax.invert_yaxis() - fig.subplots_adjust( - left=0, right=1, top=1, bottom=0) - ax.grid() - return fig - - def save_network_to_dot(self, name='graph'): - """ Save network file to dot format.""" - from networkx.drawing.nx_pydot import write_dot - write_dot(self.graph, name + '.dot') - try: - os.system('dot -Tpng {0}.dot > {0}.png'.format(name)) - except BaseException: - pylog.error('Command not found') - - -def main(): - """Main. - Test NetworkXModel Reading and Visualization.""" - net_ = NetworkXModel() - net_.read_graph( - './conf/stick_insect_cpg_v1.graphml') - net_.visualize_network() - - -if __name__ == '__main__': - main() diff --git a/farms_network/neural_system.py b/farms_network/neural_system.py deleted file mode 100644 index 5dc51c9..0000000 --- a/farms_network/neural_system.py +++ /dev/null @@ -1,123 +0,0 @@ -""" ------------------------------------------------------------------------ -Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty -Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. ------------------------------------------------------------------------ - -""" - -import sys - -import numpy as np -from networkx import DiGraph -from scipy.integrate import ode - -from farms_network.network_generator import NetworkGenerator -from farms_network.integrators import c_rk4 - -from .networkx_model import NetworkXModel - -if not sys.warnoptions: - import warnings - warnings.simplefilter("ignore", UserWarning) - - -class NeuralSystem(NetworkXModel): - """Neural System. - """ - - def __init__(self, network_graph, container): - """ Initialize neural system. """ - super(NeuralSystem, self).__init__() - self.container = container - # Add name-space for neural system data - neural_table = self.container.add_namespace('neural') - # self.config_path = config_path - self.integrator = None - if isinstance(network_graph, str): - self.read_graph(network_graph) - elif isinstance(network_graph, DiGraph): - self.graph = network_graph - # Create network - self.network = NetworkGenerator(self.graph, neural_table) - self.time = None - self.state = None - - def setup_integrator( - self, x0=None, integrator=u'dopri5', atol=1e-12, rtol=1e-6, - max_step=0.0, method=u'adams' - ): - """Setup system.""" - self.integrator = ode(self.network.ode).set_integrator( - integrator, - method=method, - atol=atol, - rtol=rtol, - max_step=max_step, - # nsteps=nsteps - ) - - if x0 is None: - # initial_values = np.random.rand( - # self.container.neural.states.values - # ) - self.integrator.set_initial_value( - self.container.neural.states.values, 0.0 - ) - else: - self.integrator.set_initial_value(x0, 0.0) - self.state = self.integrator.y - self.time = 0.0 - - def euler(self, time, state, func, step_size=1e-3): - """ Euler integrator """ - new_state = state + step_size*np.array(func(time, state)) - return new_state - - def rk4(self, time, state, func, step_size=1e-3, n_substeps=1): - """ Runge-kutta order 4 integrator """ - step_size = step_size/float(n_substeps) - for j in range(n_substeps): - K1 = np.array(func(time, state)) - K2 = np.array(func(time + step_size/2, state + (step_size/2 * K1))) - K3 = np.array(func(time + step_size/2, state + (step_size/2 * K2))) - K4 = np.array(func(time + step_size, state + (step_size * K3))) - state = state + (K1 + 2*K2 + 2*K3 + K4)*(step_size/6) - time += step_size - return state - - def rk5(self, time, state, func, step_size=1e-3): - """ Runge-kutta order 5 integrator """ - K1 = np.array(func(time, state)) - K2 = np.array(func(time + step_size/4.0, state + (step_size/4.0 * K1))) - K3 = np.array(func(time + step_size/4.0, state + (step_size/8.0)*(K1 + K2))) - K4 = np.array(func(time + step_size/2.0, state - (step_size/2.0 * K2) + (step_size * K3))) - K5 = np.array(func(time + 3*step_size/4.0, state + (step_size/16.0)*(3*K1 + 9*K4))) - K6 = np.array(func(time + step_size, state + (step_size/7.0)*(-3*K1 + 2*K2 + 12*K3 + -12*K4 + 8*K5))) - new_state = np.array(state) + (7/90*K1 + 32/90*K3 + 12/90*K4 + 32/90*K5 + 7/90*K6)*(step_size) - return new_state - - def step(self, dt=1, n_substeps=2, update=True): - """Step ode system. """ - self.time += dt - self.state = self.rk4( - self.time, self.state, self.network.ode, step_size=dt, n_substeps=n_substeps - ) - # self.state = c_rk4( - # self.time, self.state, self.network.ode, step_size=dt - # ) - # self.integrator.set_initial_value(self.integrator.y, - # self.integrator.t) - # self.integrator.integrate(self.integrator.t+dt) diff --git a/farms_network/neuron.pxd b/farms_network/neuron.pxd deleted file mode 100644 index 6462daa..0000000 --- a/farms_network/neuron.pxd +++ /dev/null @@ -1,31 +0,0 @@ -""" ------------------------------------------------------------------------ -Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty -Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. ------------------------------------------------------------------------ - -Header for Neuron Base Class. -""" - -cdef class Neuron: - """Base neuron class. - """ - - cdef: - str _model_type - - cdef: - void c_ode_rhs(self, double[:] _y, double[:] _w, double[:] _p) - void c_output(self) diff --git a/farms_network/neuron.pyx b/farms_network/neuron.pyx deleted file mode 100644 index acb4a4f..0000000 --- a/farms_network/neuron.pyx +++ /dev/null @@ -1,85 +0,0 @@ -""" ------------------------------------------------------------------------ -Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty -Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. ------------------------------------------------------------------------ -""" -import farms_pylog as pylog -pylog.set_level('error') - - -cdef class Neuron: - """Base neuron class. - """ - - def __init__(self, model_type): - super(Neuron, self).__init__() - self._model_type = model_type # : Type of neuron @property - - def add_ode_input(self, neuron, neural_container, **kwargs): - """Add relevant external inputs to the ode. - Parameters - ---------- - neuron : - Neuron model from which the input is received. - kwargs : - Contains the weight/synaptic information from the receiving neuron. - """ - pylog.error( - 'add_ode_input : Method not implemented in Neuron child class') - raise NotImplementedError() - - def ode_rhs(self, y, w, p): - """ ODE RHS. - Returns - ---------- - ode_rhs: - List containing the rhs equations of the ode states in the system - """ - pylog.error('ode_rhs : Method not implemented in Neuron child class') - raise NotImplementedError() - - def output(self): - """ Output of the neuron model. - Returns - ---------- - out: - Output of the neuron model - """ - pylog.error('output : Method not implemented in Neuron child class') - raise NotImplementedError() - - #################### PROPERTIES #################### - @property - def model_type(self): - """Neuron type. """ - return self._model_type - - @model_type.setter - def model_type(self, value): - """ - Parameters - ---------- - value : - Type of neuron model - """ - self._model_type = value - - #################### C-FUNCTIONS #################### - cdef void c_ode_rhs(self, double[:] _y, double[:] _w, double[:] _p): - pass - - cdef void c_output(self): - pass diff --git a/farms_network/neuron_factory.py b/farms_network/neuron_factory.py deleted file mode 100644 index 9ec08a5..0000000 --- a/farms_network/neuron_factory.py +++ /dev/null @@ -1,97 +0,0 @@ -""" ------------------------------------------------------------------------ -Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty -Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. ------------------------------------------------------------------------ - -Factory class for generating the neuron model. -""" - -from farms_network.fitzhugh_nagumo import FitzhughNagumo -from farms_network.hh_daun_motorneuron import HHDaunMotorneuron -from farms_network.hopf_oscillator import HopfOscillator -from farms_network.leaky_integrator import LeakyIntegrator -from farms_network.lif_danner import LIFDanner -from farms_network.lif_danner_nap import LIFDannerNap -from farms_network.lif_daun_interneuron import LIFDaunInterneuron -from farms_network.matsuoka_neuron import MatsuokaNeuron -from farms_network.morphed_oscillator import MorphedOscillator -from farms_network.morris_lecar import MorrisLecarNeuron -from farms_network.oscillator import Oscillator -from farms_network.sensory_neuron import SensoryNeuron -from farms_network.relu import ReLUNeuron - - -class NeuronFactory(object): - """Implementation of Factory Neuron class. - """ - neurons = { # 'if': IntegrateAndFire, - 'oscillator': Oscillator, - 'hopf_oscillator': HopfOscillator, - 'morphed_oscillator': MorphedOscillator, - 'leaky': LeakyIntegrator, - 'sensory': SensoryNeuron, - 'lif_danner_nap': LIFDannerNap, - 'lif_danner': LIFDanner, - 'lif_daun_interneuron': LIFDaunInterneuron, - 'hh_daun_motorneuron': HHDaunMotorneuron, - 'fitzhugh_nagumo': FitzhughNagumo, - 'matsuoka_neuron': MatsuokaNeuron, - 'morris_lecar': MorrisLecarNeuron, - 'relu': ReLUNeuron, - } - - def __init__(self): - """Factory initialization.""" - super(NeuronFactory, self).__init__() - - @staticmethod - def register_neuron(neuron_type, neuron_instance): - """ - Register a new type of neuron that is a child class of Neuron. - Parameters - ---------- - self: type - description - neuron_type: - String to identifier for the neuron. - neuron_instance: - Class of the neuron to register. - """ - NeuronFactory.neurons[neuron_type] = neuron_instance - - @staticmethod - def gen_neuron(neuron_type): - """Generate the necessary type of neuron. - Parameters - ---------- - self: type - description - neuron_type: - One of the following list of available neurons. - 1. if - Integrate and Fire - 2. lif_danner_nap - LIF Danner Nap - 3. lif_danner - LIF Danner - 4. lif_daun_interneuron - LIF Daun Interneuron - 5. hh_daun_motorneuron - HH_Daun_Motorneuron - Returns - ------- - neuron: - Appropriate neuron class. - """ - neuron = NeuronFactory.neurons.get(neuron_type) - if not neuron: - raise ValueError(neuron_type) - return neuron diff --git a/farms_network/oscillator.pxd b/farms_network/oscillator.pxd deleted file mode 100644 index 9729036..0000000 --- a/farms_network/oscillator.pxd +++ /dev/null @@ -1,64 +0,0 @@ -""" ------------------------------------------------------------------------ -Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty -Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. ------------------------------------------------------------------------ - -Oscillator model. -""" - -from farms_container.parameter cimport Parameter -from farms_network.neuron cimport Neuron - -cdef struct OscillatorNeuronInput: - int neuron_idx - int weight_idx - int phi_idx - -cdef class Oscillator(Neuron): - cdef: - readonly str n_id - - unsigned int num_inputs - - # parameters - # constants - Parameter f - Parameter R - Parameter a - - # states - Parameter phase - Parameter amp - - # inputs - Parameter ext_in - - # ode - Parameter phase_dot - Parameter amp_dot - - # Ouputs - Parameter nout - - # neuron connenctions - OscillatorNeuronInput[:] neuron_inputs - - cdef: - void c_ode_rhs(self, double[:] _y, double[:] _w, double[:] _p) - void c_output(self) - cdef double c_neuron_inputs_eval( - self, double _neuron_out, double _weight, double _phi, - double _phase, double _amp) diff --git a/farms_network/oscillator.pyx b/farms_network/oscillator.pyx deleted file mode 100644 index 93402fe..0000000 --- a/farms_network/oscillator.pyx +++ /dev/null @@ -1,161 +0,0 @@ -""" ------------------------------------------------------------------------ -Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty -Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. ------------------------------------------------------------------------ - -Oscillator model -""" -from libc.stdio cimport printf -import farms_pylog as pylog -from libc.math cimport exp -from libc.math cimport M_PI -from libc.math cimport sin as csin -import numpy as np -cimport numpy as cnp - - -cdef class Oscillator(Neuron): - - def __init__(self, n_id, num_inputs, neural_container, **kwargs): - """Initialize. - - Parameters - ---------- - n_id: str - Unique ID for the neuron in the network. - """ - super(Oscillator, self).__init__('leaky') - - # Neuron ID - self.n_id = n_id - # Initialize parameters - self.f = neural_container.parameters.add_parameter( - 'freq_' + self.n_id, kwargs.get('f', 0.1))[0] - - self.R = neural_container.parameters.add_parameter( - 'R_' + self.n_id, kwargs.get('R', 0.1))[0] - - self.a = neural_container.parameters.add_parameter( - 'a_' + self.n_id, kwargs.get('a', 0.1))[0] - - # Initialize states - self.phase = neural_container.states.add_parameter( - 'phase_' + self.n_id, kwargs.get('phase0', 0.0))[0] - self.amp = neural_container.states.add_parameter( - 'amp_' + self.n_id, kwargs.get('amp0', 0.0))[0] - - # External inputs - self.ext_in = neural_container.inputs.add_parameter( - 'ext_in_' + self.n_id)[0] - - # ODE RHS - self.phase_dot = neural_container.dstates.add_parameter( - 'phase_dot_' + self.n_id, 0.0)[0] - self.amp_dot = neural_container.dstates.add_parameter( - 'amp_dot_' + self.n_id, 0.0)[0] - - # Output - self.nout = neural_container.outputs.add_parameter( - 'nout_' + self.n_id, 0.0)[0] - - # Neuron inputs - self.neuron_inputs = cnp.ndarray((num_inputs,), - dtype=[('neuron_idx', 'i'), - ('weight_idx', 'i'), - ('phi_idx', 'i')]) - - self.num_inputs = num_inputs - - def add_ode_input(self, int idx, neuron, neural_container, **kwargs): - """ Add relevant external inputs to the ode.""" - # Create a struct to store the inputs and weights to the neuron - cdef OscillatorNeuronInput n - # Get the neuron parameter - neuron_idx = neural_container.outputs.get_parameter_index( - 'nout_'+neuron.n_id) - - # Add the weight parameter - weight = neural_container.weights.add_parameter( - 'w_' + neuron.n_id + '_to_' + self.n_id, - kwargs.get('weight', 0.0))[0] - phi = neural_container.parameters.add_parameter( - 'phi_' + neuron.n_id + '_to_' + self.n_id, - kwargs.get('phi', 0.0))[0] - - weight_idx = neural_container.weights.get_parameter_index( - 'w_' + neuron.n_id + '_to_' + self.n_id) - phi_idx = neural_container.parameters.get_parameter_index( - 'phi_' + neuron.n_id + '_to_' + self.n_id) - - n.neuron_idx = neuron_idx - n.weight_idx = weight_idx - n.phi_idx = phi_idx - - # Append the struct to the list - self.neuron_inputs[idx] = n - - def output(self): - """Neuron activation function. - Parameters - ---------- - m_potential: float - Neuron membrane potential - """ - return self.c_output() - - def ode_rhs(self, y, w, p): - """ Python interface to the ode_rhs computation.""" - self.c_ode_rhs(y, w, p) - - #################### C-FUNCTIONS #################### - cdef void c_ode_rhs(self, double[:] _y, double[:] _w, double[:] _p): - """ Compute the ODE. Internal Setup Function.""" - - # Current state - cdef double _phase = self.phase.c_get_value() - cdef double _amp = self.amp.c_get_value() - - # Neuron inputs - cdef double _sum = 0.0 - cdef unsigned int j - cdef double _neuron_out - cdef double _weight - cdef double _phi - - for j in range(self.num_inputs): - _neuron_out = _y[self.neuron_inputs[j].neuron_idx] - _weight = _w[self.neuron_inputs[j].weight_idx] - _phi = _p[self.neuron_inputs[j].phi_idx] - _sum += self.c_neuron_inputs_eval(_neuron_out, - _weight, _phi, _phase, _amp) - - # phidot : phase_dot - self.phase_dot.c_set_value(2*M_PI*self.f.c_get_value() + _sum) - - # ampdot - self.amp_dot.c_set_value( - self.a.c_get_value()*(self.R.c_get_value() - _amp) - ) - - cdef void c_output(self): - """ Neuron output. """ - self.nout.c_set_value(self.phase.c_get_value()) - - cdef double c_neuron_inputs_eval( - self, double _neuron_out, double _weight, double _phi, - double _phase, double _amp): - """ Evaluate neuron inputs.""" - return _weight*_amp*csin(_neuron_out - _phase - _phi) diff --git a/farms_network/relu.pxd b/farms_network/relu.pxd deleted file mode 100644 index 027101c..0000000 --- a/farms_network/relu.pxd +++ /dev/null @@ -1,53 +0,0 @@ -""" ------------------------------------------------------------------------ -Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty -Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. ------------------------------------------------------------------------ - -Rectified Linear Unit (ReLU) neurons. -""" - -from farms_container.parameter cimport Parameter -from farms_network.neuron cimport Neuron - - -cdef struct ReLUNeuronInput: - int neuron_idx - int weight_idx - - -cdef class ReLUNeuron(Neuron): - cdef: - readonly str n_id - - unsigned int num_inputs - - # Parameters - Parameter gain - Parameter sign - Parameter offset - - # Input from external system - Parameter ext_inp - - # neuron connenctions - ReLUNeuronInput[:] neuron_inputs - - # Ouputs - Parameter nout - - cdef: - void c_ode_rhs(self, double[:] _y, double[:] _w, double[:] _p) - void c_output(self) diff --git a/farms_network/relu.pyx b/farms_network/relu.pyx deleted file mode 100644 index 0f67c5d..0000000 --- a/farms_network/relu.pyx +++ /dev/null @@ -1,132 +0,0 @@ -""" ------------------------------------------------------------------------ -Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty -Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. ------------------------------------------------------------------------ - -Sensory afferent neurons. -""" - -cimport numpy as cnp - -from farms_network.neuron import Neuron -from libc.stdio cimport printf - -cdef class ReLUNeuron(Neuron): - """ Rectified Linear Unit neurons connecting """ - - def __init__(self, n_id, num_inputs, neural_container, **kwargs): - """Initialize. - Parameters - ---------- - n_id: str - Unique ID for the neuron in the network. - """ - super().__init__('relu') - - # Neuron ID - self.n_id = n_id - - # Initialize parameters - self.gain = neural_container.parameters.add_parameter( - 'gain_' + self.n_id, kwargs.get('gain', 1.0))[0] - - self.sign = neural_container.parameters.add_parameter( - 'sign_' + self.n_id, kwargs.get('sign', 1.0))[0] - - # assert abs(self.sign.value) != 1.0, "ReLU sign parameter should be 1.0" - - self.offset = neural_container.parameters.add_parameter( - 'offset_' + self.n_id, kwargs.get('offset', 0.0))[0] - - self.ext_inp = neural_container.inputs.add_parameter( - 'ext_' + self.n_id, kwargs.get('init', 0.0))[0] - - # Output - self.nout = neural_container.outputs.add_parameter( - 'nout_' + self.n_id, 0.0)[0] - - # Neuron inputs - self.num_inputs = num_inputs - self.neuron_inputs = cnp.ndarray((num_inputs,), - dtype=[('neuron_idx', 'i'), - ('weight_idx', 'i')]) - - def reset_sensory_param(self, param): - """ Add the sensory input. """ - self.aff_inp = param - - def add_ode_input(self, int idx, neuron, neural_container, **kwargs): - """ Add relevant external inputs to the output. - Parameters - ---------- - """ - - # Create a struct to store the inputs and weights to the neuron - cdef ReLUNeuronInput n - # Get the neuron parameter - neuron_idx = neural_container.outputs.get_parameter_index( - 'nout_'+neuron.n_id) - - # Add the weight parameter - weight = neural_container.weights.add_parameter( - 'w_' + neuron.n_id + '_to_' + self.n_id, kwargs.get('weight', 0.0))[0] - weight_idx = neural_container.weights.get_parameter_index( - 'w_' + neuron.n_id + '_to_' + self.n_id) - n.neuron_idx = neuron_idx - n.weight_idx = weight_idx - - # Append the struct to the list - self.neuron_inputs[idx] = n - - def ode_rhs(self, y, w, p): - """Abstract method""" - self.c_ode_rhs(y, w, p) - - def output(self): - """ Output of the neuron model. - Returns - ---------- - out: - Output of the neuron model - """ - return self.c_output() - - #################### C-FUNCTIONS #################### - - cdef void c_ode_rhs(self, double[:] _y, double[:] _w, double[:] _p): - """ Compute the ODE. Internal Setup Function.""" - - # Neuron inputs - cdef double _sum = 0.0 - cdef unsigned int j - cdef double _neuron_out - cdef double _weight - - for j in range(self.num_inputs): - _neuron_out = _y[self.neuron_inputs[j].neuron_idx] - _weight = _w[self.neuron_inputs[j].weight_idx] - _sum += (_neuron_out*_weight) - self.ext_inp.c_set_value(_sum) - - cdef void c_output(self): - """ Neuron output. """ - # Set the neuron output - cdef double gain = self.gain.c_get_value() - cdef double sign = self.sign.c_get_value() - cdef double offset = self.offset.c_get_value() - cdef double ext_in = self.ext_inp.c_get_value() - cdef double res = gain*(sign*ext_in + offset) - self.nout.c_set_value(max(0.0, res)) diff --git a/farms_network/sensory_neuron.pxd b/farms_network/sensory_neuron.pxd deleted file mode 100644 index b68af13..0000000 --- a/farms_network/sensory_neuron.pxd +++ /dev/null @@ -1,37 +0,0 @@ -""" ------------------------------------------------------------------------ -Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty -Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. ------------------------------------------------------------------------ - -Sensory afferent neurons. -""" - -from farms_container.parameter cimport Parameter -from farms_network.neuron cimport Neuron - -cdef class SensoryNeuron(Neuron): - cdef: - readonly str n_id - - # Input from external system - Parameter aff_inp - - # Ouputs - Parameter nout - - cdef: - void c_ode_rhs(self, double[:] _y, double[:] _w, double[:] _p) - void c_output(self) diff --git a/farms_network/sensory_neuron.pyx b/farms_network/sensory_neuron.pyx deleted file mode 100644 index 315d970..0000000 --- a/farms_network/sensory_neuron.pyx +++ /dev/null @@ -1,78 +0,0 @@ -""" ------------------------------------------------------------------------ -Copyright 2018-2020 Jonathan Arreguit, Shravan Tata Ramalingasetty -Copyright 2018 BioRobotics Laboratory, École polytechnique fédérale de Lausanne - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. ------------------------------------------------------------------------ - -Sensory afferent neurons. -""" - -from farms_network.neuron import Neuron -from libc.stdio cimport printf - -cdef class SensoryNeuron(Neuron): - """Sensory afferent neurons connecting muscle model with the network. - """ - - def __init__(self, n_id, num_inputs, neural_container, **kwargs): - """Initialize. - Parameters - ---------- - n_id: str - Unique ID for the neuron in the network. - """ - super(SensoryNeuron, self).__init__('sensory') - - # Neuron ID - self.n_id = n_id - - self.aff_inp = neural_container.inputs.add_parameter( - 'aff_' + self.n_id, kwargs.get('init', 0.0))[0] - - # Output - self.nout = neural_container.outputs.add_parameter( - 'nout_' + self.n_id, 0.0)[0] - - def reset_sensory_param(self, param): - """ Add the sensory input. """ - self.aff_inp = param - - def add_ode_input(self, int idx, neuron, neural_container, **kwargs): - """Abstract method""" - pass - - def ode_rhs(self, y, w, p): - """Abstract method""" - self.c_ode_rhs(y, w, p) - - def output(self): - """ Output of the neuron model. - Returns - ---------- - out: - Output of the neuron model - """ - return self.c_output() - - #################### C-FUNCTIONS #################### - - cdef void c_ode_rhs(self, double[:] _y, double[:] _w, double[:] _p): - """ Compute the ODE. Internal Setup Function.""" - pass - - cdef void c_output(self): - """ Neuron output. """ - # Set the neuron output - self.nout.c_set_value(self.aff_inp.c_get_value()) From 49d7ba552d8d3bc13cabd1b59b93c16e6498aed4 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Thu, 25 Sep 2025 22:34:12 -0400 Subject: [PATCH 290/316] [MODELS] Fixed matsuoka model function signatures --- farms_network/models/matsuoka_cy.pxd | 3 ++- farms_network/models/matsuoka_cy.pyx | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/farms_network/models/matsuoka_cy.pxd b/farms_network/models/matsuoka_cy.pxd index dab2aea..74fcba1 100644 --- a/farms_network/models/matsuoka_cy.pxd +++ b/farms_network/models/matsuoka_cy.pxd @@ -22,12 +22,13 @@ cdef packed struct matsuoka_params_t: double nu # -cdef processed_inputs_t matsuoka_input_tf( +cdef void matsuoka_input_tf( double time, const double* states, const node_inputs_t inputs, const node_t* node, const edge_t** edges, + processed_inputs_t* out ) noexcept diff --git a/farms_network/models/matsuoka_cy.pyx b/farms_network/models/matsuoka_cy.pyx index 3f85fa1..412bcb2 100644 --- a/farms_network/models/matsuoka_cy.pyx +++ b/farms_network/models/matsuoka_cy.pyx @@ -10,12 +10,13 @@ cpdef enum STATE: w = STATE_W -cdef processed_inputs_t matsuoka_input_tf( +cdef void matsuoka_input_tf( double time, const double* states, const node_inputs_t inputs, const node_t* node, const edge_t** edges, + processed_inputs_t* out ) noexcept: # Parameters cdef matsuoka_params_t params = ( node[0].params)[0] From af278053ac661f3c2561b89186b38c2f88b8217b Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Fri, 26 Sep 2025 12:47:32 -0400 Subject: [PATCH 291/316] [DUCKS] Fixed node class import --- ducks/source/core/node.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ducks/source/core/node.rst b/ducks/source/core/node.rst index 532d7ed..67d9ffb 100644 --- a/ducks/source/core/node.rst +++ b/ducks/source/core/node.rst @@ -24,7 +24,7 @@ A stateless node will have zero states and is useful in using the node as a tran Node ==== -.. autoclass:: farms_network.node.Node +.. autoclass:: farms_network.core.node.Node :members: The Node class provides a high-level interface for neural network nodes. From 598e2b0cf3947dd62a708b112e48614218928246 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Fri, 26 Sep 2025 12:47:53 -0400 Subject: [PATCH 292/316] [SCRATCH][WIP] Updates to test functions for changes in network refactor --- scratch/test_gui.py | 14 +++---- scratch/test_network.py | 82 +++++++++++++++++++++++++---------------- scratch/test_neuron.py | 18 +++++---- 3 files changed, 69 insertions(+), 45 deletions(-) diff --git a/scratch/test_gui.py b/scratch/test_gui.py index 6b812b4..a6ad2e7 100644 --- a/scratch/test_gui.py +++ b/scratch/test_gui.py @@ -540,7 +540,7 @@ def draw_network(network_options, data, iteration, edges_x, edges_y): ) implot.push_style_var( implot.StyleVar_.fill_alpha, - 0.05+data.nodes[index].output.array[iteration]*3.0 + 0.05+data.nodes[index].output.array[iteration] ) implot.plot_scatter( "##", @@ -727,7 +727,7 @@ def main(): alpha_input_indices = [ index for index, node in enumerate(network_options.nodes) - if "input" in node.name and node.model == "external_relay" + if "input" in node.name and node.model == "relay" ] drive_input_indices = [ index @@ -752,12 +752,12 @@ def main(): Vn_input_indices = [ index for index, node in enumerate(network_options.nodes) - if "Vn" == node.name[-2:] and node.model == "external_relay" + if "Vn" == node.name[-2:] and node.model == "relay" ] Cut_input_indices = [ index for index, node in enumerate(network_options.nodes) - if "cut" == node.name[-3:] and node.model == "external_relay" + if "cut" == node.name[-3:] and node.model == "relay" ] slider_values = np.zeros((7,)) slider_values[0] = 1.0 @@ -766,7 +766,7 @@ def main(): button_state = False # input_array[drive_input_indices[0]] *= 1.05 for index, node in enumerate(network_options.nodes): - if "BS_DR" in node.name and node.model == "linear": + if "BS_DR" in node.name: # and node.model == "linear" bs_dr = index for iteration in tqdm(range(0, N_ITERATIONS), colour="green", ascii=" >="): @@ -793,8 +793,8 @@ def main(): # button_state = draw_play_pause_button(button_state) draw_table(network_options, network.data) draw_network(network_options, network.data, buffer_iteration, edges_x, edges_y) - plot_hind_motor_activity(buffer_iteration, network.data) - plot_fore_motor_activity(buffer_iteration, network.data) + # plot_hind_motor_activity(buffer_iteration, network.data) + # plot_fore_motor_activity(buffer_iteration, network.data) # draw_vn_activity(buffer_iteration, network.data) gui.render_frame() implot.pop_style_var() diff --git a/scratch/test_network.py b/scratch/test_network.py index 2f9b061..69c8ffb 100644 --- a/scratch/test_network.py +++ b/scratch/test_network.py @@ -11,7 +11,8 @@ from farms_network.core import options from farms_network.core.data import (NetworkConnectivity, NetworkData, NetworkStates) -from farms_network.core.network import PyNetwork, rk4 +from farms_network.core.network import Network +from farms_network.numeric.integrators_cy import RK4Solver from farms_network.core.options import NetworkOptions from scipy.integrate import ode from tqdm import tqdm @@ -40,28 +41,29 @@ def linear_network(): def quadruped_network(): """ Quadruped network """ - param_opts = options.LIDannerParameterOptions.defaults() - state_opts = options.LINaPDannerStateOptions.from_kwargs(v0=0.0, h0=-70.0) + param_opts = options.LIDannerNodeParameterOptions.defaults() + state_opts = options.LINaPDannerStateOptions.from_kwargs(v=0.0, h=-70.0) vis_opts = options.NodeVisualOptions() - danner_network = nx.read_graphml( - "/Users/tatarama/projects/work/research/neuromechanics/quadruped/mice/mouse-locomotion/data/config/network/siggraph_network.graphml" + danner_network = nx.read_graphml( "/Users/tatarama/projects/work/research/neuromechanics/quadruped/mice/mouse-locomotion/data/config/network/siggraph_network.graphml" ) network_options = options.NetworkOptions( directed=True, multigraph=False, graph={"name": "network"}, + logs=options.NetworkLogOptions(n_iterations=5000) ) for node, data in danner_network.nodes.items(): if data["model"] == "li_nap_danner": network_options.add_node( - options.LIDannerNodeOptions( + options.LINaPDannerNodeOptions( name=node, parameters=param_opts, visual=vis_opts, state=state_opts, + noise=None, ) ) else: @@ -71,6 +73,7 @@ def quadruped_network(): parameters=param_opts, visual=vis_opts, state=state_opts, + noise=None, ) ) @@ -90,14 +93,17 @@ def quadruped_network(): def oscillator_network(): """ Oscillator network """ - param_opts = options.OscillatorNodeParameterOptions.defaults() - state_opts = options.OscillatorStateOptions.from_kwargs(phase=0.0, amplitude=0.0) + param_opts = options.OscillatorNodeParameterOptions.defaults(amplitude_rate=10.0, intrinsic_frequency=1) + state_opts = options.OscillatorStateOptions.from_kwargs( + phase=0.0, amplitude_0=0.0, amplitude=0.0 + ) vis_opts = options.NodeVisualOptions() network_options = options.NetworkOptions( directed=True, multigraph=False, graph={"name": "network"}, + logs=options.NetworkLogOptions(n_iterations=5000) ) network_options.add_node( @@ -106,6 +112,7 @@ def oscillator_network(): parameters=param_opts, visual=vis_opts, state=state_opts, + noise=None, ) ) @@ -115,14 +122,15 @@ def oscillator_network(): parameters=param_opts, visual=vis_opts, state=state_opts, + noise=None, ) ) network_options.add_edge( - options.EdgeOptions( + options.OscillatorEdgeOptions( source="O1", target="O2", - weight=1.0, + weight=0.0, type="excitatory", visual=options.EdgeVisualOptions(), parameters=options.OscillatorEdgeParameterOptions( @@ -132,10 +140,10 @@ def oscillator_network(): ) network_options.add_edge( - options.EdgeOptions( + options.OscillatorEdgeOptions( source="O2", target="O1", - weight=1.0, + weight=10.0, type="excitatory", visual=options.EdgeVisualOptions(), parameters=options.OscillatorEdgeParameterOptions( @@ -146,47 +154,59 @@ def oscillator_network(): return network_options -network_options = linear_network() +# network_options = linear_network() network_options = oscillator_network() + +# pprint(network_options) + # network_options = quadruped_network() data = NetworkData.from_options(network_options) -network = PyNetwork.from_options(network_options) +network = Network.from_options(network_options) + +print(network.nnodes, network.nedges) -integrator = ode(network.ode).set_integrator( +rk4solver = RK4Solver(network.nstates, 1e-3) + +integrator = ode(network.evaluate).set_integrator( u'dopri5', method=u'adams', max_step=0.0, - nsteps=0 + # nsteps=0 ) -# nnodes = len(network_options.nodes) -# integrator.set_initial_value(np.zeros(len(data.states.array),), 0.0) + +nnodes = len(network_options.nodes) +integrator.set_initial_value(np.zeros(len(data.states.array[0, :]),), 0.0) # print("Data ------------", np.array(network.data.states.array)) # data.to_file("/tmp/sim.hdf5") -# integrator.integrate(integrator.t + 1e-3) +integrator.integrate(integrator.t + 1e-3) # # Integrate -iterations = 10000 -states = np.ones((iterations+1, len(data.states.array)))*1.0 -outputs = np.ones((iterations, len(data.outputs.array)))*1.0 +iterations = network_options.logs.buffer_size +states = np.zeros(np.shape(data.states.array))*1.0 +outputs = np.zeros(np.shape(data.outputs.array))*1.0 # states[0, 2] = -1.0 for iteration in tqdm(range(0, iterations), colour='green', ascii=' >='): - # integrator.set_initial_value(integrator.y, integrator.t) + time = iteration*1e-3 + integrator.set_initial_value(integrator.y, integrator.t) + # integrator.integrate(integrator.t+1e-3) + # states[iteration+1, :] = states[iteration, :] + np.array(network.ode(iteration*1e-3, states[iteration, :]))*1e-3 - # network.ode(0.0, states) - # network.ode(0.0, states) - # network.ode(0.0, states) - network.data.external_inputs.array[:] = np.ones((1,))*np.sin(iteration*1e-3) - states[iteration+1, :] = rk4(iteration*1e-3, states[iteration, :], network.ode, step_size=1) - outputs[iteration, :] = network.data.outputs.array + # network.data.external_inputs.array[:] = np.ones((1,))*np.sin(iteration*1e-3) + # states[iteration+1, :] = rk4(iteration*1e-3, states[iteration, :], network.ode, step_size=1) + # network.evaluate(integrator.t+(iteration*1e-3), states[iteration, :]) + + rk4solver.step(network._network_cy, time, network.data.states.array[0, :]) - # integrator.integrate(integrator.t+(iteration*1e-3)) + outputs[iteration, :] = network.data.outputs.array[0, :] + states[iteration, :] = network.data.states.array[0, :] -plt.plot(np.linspace(0.0, iterations*1e-3, iterations), outputs[:, :]) +# plt.plot(np.linspace(0.0, iterations*1e-3, iterations), np.sin(outputs[:, :])) +plt.plot(np.linspace(0.0, iterations*1e-3, iterations), states[:, :]) plt.show() diff --git a/scratch/test_neuron.py b/scratch/test_neuron.py index eed8f87..7a12af2 100644 --- a/scratch/test_neuron.py +++ b/scratch/test_neuron.py @@ -1,17 +1,21 @@ import numpy as np -from farms_network.core import li_danner, network, node, options -from farms_network.data.data import NetworkData, StatesArray +from farms_network.core import network, node, options +from farms_network.core.data import NetworkData nstates = 100 niterations = 1000 -states = StatesArray(np.empty((niterations, nstates))) -data = NetworkData(nstates=100, states=states) +net_opts = options.NetworkOptions( + logs=options.NetworkLogOptions( + n_iterations=niterations, + ) +) + +data = NetworkData.from_options(net_opts) -net = network.PyNetwork(nnodes=10) -net.test(data) +net = network.Network(nnodes=10) n1_opts = options.NodeOptions( name="n1", @@ -19,7 +23,7 @@ visual=options.NodeVisualOptions(), state=options.NodeStateOptions(initial=[0, 0]), ) -n1 = node.PyNode.from_options(n1_opts) +n1 = node.Node.from_options(n1_opts) n1_opts.save("/tmp/opts.yaml") From 19f3c0d43bbb218f24b2a1600af2cb6d4ea96a57 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Mon, 29 Sep 2025 14:25:10 -0400 Subject: [PATCH 293/316] [MAIN] Addressed few cython import warnings --- farms_network/core/data_cy.pyx | 2 -- farms_network/core/edge_cy.pyx | 7 ++----- farms_network/core/network.py | 3 +++ farms_network/core/network_cy.pyx | 13 ++++++++----- farms_network/models/li_danner_cy.pyx | 2 +- farms_network/models/oscillator_cy.pyx | 3 ++- 6 files changed, 16 insertions(+), 14 deletions(-) diff --git a/farms_network/core/data_cy.pyx b/farms_network/core/data_cy.pyx index 4fa337e..4b6b88c 100644 --- a/farms_network/core/data_cy.pyx +++ b/farms_network/core/data_cy.pyx @@ -1,7 +1,5 @@ """ Core Data """ -from typing import Dict, Iterable, List - cimport numpy as cnp import numpy as np diff --git a/farms_network/core/edge_cy.pyx b/farms_network/core/edge_cy.pyx index 5ea74a0..2955f9f 100644 --- a/farms_network/core/edge_cy.pyx +++ b/farms_network/core/edge_cy.pyx @@ -4,9 +4,6 @@ from libc.stdio cimport printf from libc.stdlib cimport free, malloc from libc.string cimport strdup -from farms_network.core.options import EdgeOptions -from farms_network.models import EdgeTypes - cpdef enum types: @@ -21,7 +18,7 @@ cpdef enum types: cdef class EdgeCy: """ Python interface to Edge C-Structure""" - def __cinit__(self, edge_type: str, **kwargs): + def __cinit__(self, edge_type: str): self._edge = malloc(sizeof(edge_t)) if self._edge is NULL: raise MemoryError("Failed to allocate memory for edge_t") @@ -33,7 +30,7 @@ cdef class EdgeCy: if self._edge is not NULL: free(self._edge) - def __init__(self, edge_type: str, **kwargs): + def __init__(self, edge_type: str): ... @property diff --git a/farms_network/core/network.py b/farms_network/core/network.py index f206c52..3a3292d 100644 --- a/farms_network/core/network.py +++ b/farms_network/core/network.py @@ -3,6 +3,7 @@ from typing import List, Optional import numpy as np +from farms_core import pylog from ..models.factory import EdgeFactory, NodeFactory from .data import NetworkData, NetworkLog @@ -50,6 +51,8 @@ def __init__(self, network_options: NetworkOptions): def _setup_network(self): """ Setup network nodes and edges """ + pylog.info(f"Number of nodes in network: {len(self.options.nodes)}") + pylog.info(f"Number of edges in network: {len(self.options.edges)}") # Create Python nodes nstates = 0 for index, node_options in enumerate(self.options.nodes): diff --git a/farms_network/core/network_cy.pyx b/farms_network/core/network_cy.pyx index cf4cd85..fbcbbcd 100644 --- a/farms_network/core/network_cy.pyx +++ b/farms_network/core/network_cy.pyx @@ -18,8 +18,7 @@ from .data_cy cimport (NetworkConnectivityCy, NetworkDataCy, NetworkNoiseCy, from typing import List -from .options import (EdgeOptions, IntegrationOptions, NetworkOptions, - NodeOptions) +from .options import NetworkOptions cdef inline void ode( @@ -95,8 +94,6 @@ cdef inline void ode( 0.0, c_nodes[j], ) - # # Swap pointers for the outputs (Maybe!) - # c_network.outputs, c_network.tmp_outputs = c_network.tmp_outputs, c_network.outputs cdef inline void _noise_states_to_output( @@ -215,7 +212,13 @@ cdef class NetworkCy(ODESystem): free(self._network) self._network = NULL - def setup_network(self, options: NetworkOptions, data: NetworkData, nodes: List[NodeCy], edges: List[EdgeCy]): + def setup_network( + self, + options: NetworkOptions, + data: NetworkData, + nodes: List[NodeCy], + edges: List[EdgeCy] + ): """ Setup network """ for index, node in enumerate(nodes): diff --git a/farms_network/models/li_danner_cy.pyx b/farms_network/models/li_danner_cy.pyx index 01884eb..cdb4e2c 100644 --- a/farms_network/models/li_danner_cy.pyx +++ b/farms_network/models/li_danner_cy.pyx @@ -35,7 +35,7 @@ cdef void li_danner_input_tf( double _cholinergic_sum = 0.0 unsigned int j double _node_out, res, _input, _weight - edge_t* _edge + const edge_t* _edge cdef unsigned int ninputs = inputs.ninputs for j in range(ninputs): diff --git a/farms_network/models/oscillator_cy.pyx b/farms_network/models/oscillator_cy.pyx index 1d16a8c..1e89a13 100644 --- a/farms_network/models/oscillator_cy.pyx +++ b/farms_network/models/oscillator_cy.pyx @@ -38,8 +38,9 @@ cdef void oscillator_input_tf( double _sum = 0.0 unsigned int j double _input, _weight + unsigned int ninputs = inputs.ninputs - for j in range(inputs.ninputs): + for j in range(ninputs): _input = inputs.network_outputs[inputs.node_indices[j]] _weight = inputs.weights[j] edge_params = ( edges[inputs.edge_indices[j]].params)[0] From 94ac89da763053617f75d3c67b44b06b07f731c8 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Mon, 29 Sep 2025 14:29:54 -0400 Subject: [PATCH 294/316] [CORE] Removed network_options arg to network_cy setup_network --- farms_network/core/network.py | 2 +- farms_network/core/network_cy.pyx | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/farms_network/core/network.py b/farms_network/core/network.py index 3a3292d..d193c75 100644 --- a/farms_network/core/network.py +++ b/farms_network/core/network.py @@ -73,7 +73,7 @@ def _setup_network(self): self._network_cy.nstates = nstates # Pass Python nodes/edges to Cython layer for C struct setup - self._network_cy.setup_network(self.options, self.data, self.nodes, self.edges) + self._network_cy.setup_network(self.data, self.nodes, self.edges) # Initialize states self._initialize_states() diff --git a/farms_network/core/network_cy.pyx b/farms_network/core/network_cy.pyx index fbcbbcd..b7fd291 100644 --- a/farms_network/core/network_cy.pyx +++ b/farms_network/core/network_cy.pyx @@ -214,7 +214,6 @@ cdef class NetworkCy(ODESystem): def setup_network( self, - options: NetworkOptions, data: NetworkData, nodes: List[NodeCy], edges: List[EdgeCy] From 9e1bafb6c52c7225697f2f9570df8ca66becc4e5 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Mon, 29 Sep 2025 14:30:49 -0400 Subject: [PATCH 295/316] [EX] Changed default iterations to 4e3 in mouse/run --- examples/mouse/run.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/mouse/run.py b/examples/mouse/run.py index 91dfba9..9fc0e93 100644 --- a/examples/mouse/run.py +++ b/examples/mouse/run.py @@ -603,7 +603,7 @@ def main(): # Generate the network # network_options = generate_network(int(1e4)) # network_options = generate_limb_circuit(int(5e4)) - network_options = generate_quadruped_circuit((1e4)) + network_options = generate_quadruped_circuit((4e3)) # plot_network(network_options) network = run_network(network_options) @@ -646,5 +646,5 @@ def main(): if __name__ == "__main__": - profile.profile(main) + profile.profile(main, profile_filename="/tmp/network.prof") # main() From f1f9bf287bfaf71680cea8ce87c834ca618bb1d8 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Mon, 29 Sep 2025 14:31:11 -0400 Subject: [PATCH 296/316] [CORE] Network sorted nodes by model during initialization --- farms_network/core/network.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/farms_network/core/network.py b/farms_network/core/network.py index d193c75..b184619 100644 --- a/farms_network/core/network.py +++ b/farms_network/core/network.py @@ -21,6 +21,11 @@ def __init__(self, network_options: NetworkOptions): """ Initialize network with composition approach """ self.options = network_options + # Sort nodes based on node-type + self.options.nodes = sorted( + self.options.nodes, key=lambda node: node["model"] + ) + # Core network data and Cython implementation self.data = NetworkData.from_options(network_options) self.log = NetworkLog.from_options(network_options) From 1471b66fcd4ee1d70ed34a0d012324e840488453 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Mon, 6 Oct 2025 15:15:02 -0400 Subject: [PATCH 297/316] [EX] Fixed rhythm_generator run state_options initialization --- examples/rhythm_generator/run.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/rhythm_generator/run.py b/examples/rhythm_generator/run.py index 2d568fe..503c41d 100644 --- a/examples/rhythm_generator/run.py +++ b/examples/rhythm_generator/run.py @@ -167,12 +167,12 @@ def create_node( color=color, ) if node_type == "LINaPDanner": - state_options = options.LINaPDannerStateOptions.from_kwargs(**states) + state_options = options.LINaPDannerStateOptions(list(states.values())) parameters = options.LINaPDannerNodeParameterOptions.defaults(**parameters) noise = options.OrnsteinUhlenbeckOptions.defaults() node_options_class = options.LINaPDannerNodeOptions elif node_type == "LIDanner": - state_options = options.LIDannerStateOptions.from_kwargs(**states) + state_options = options.LIDannerStateOptions(list(states.values())) parameters = options.LIDannerNodeParameterOptions.defaults(**parameters) noise = options.OrnsteinUhlenbeckOptions.defaults() node_options_class = options.LIDannerNodeOptions @@ -485,7 +485,7 @@ def edges(self): # return edges -def generate_network(iterations=1000): +def generate_network(iterations=5000): """ Generate network """ # Main network From e2d8c6e4e5d13d0ee1f3166c5221371008f51bb3 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Tue, 28 Oct 2025 10:31:25 -0400 Subject: [PATCH 298/316] [NOISE] Renamed ornstein noise model files --- farms_network/core/network_cy.pyx | 2 +- farms_network/noise/ornstein_uhlenbeck.py | 0 .../{ornstein_uhlenbeck.pxd => ornstein_uhlenbeck_cy.pxd} | 0 .../{ornstein_uhlenbeck.pyx => ornstein_uhlenbeck_cy.pyx} | 7 +++++-- 4 files changed, 6 insertions(+), 3 deletions(-) create mode 100644 farms_network/noise/ornstein_uhlenbeck.py rename farms_network/noise/{ornstein_uhlenbeck.pxd => ornstein_uhlenbeck_cy.pxd} (100%) rename farms_network/noise/{ornstein_uhlenbeck.pyx => ornstein_uhlenbeck_cy.pyx} (96%) diff --git a/farms_network/core/network_cy.pyx b/farms_network/core/network_cy.pyx index b7fd291..a4d1a2d 100644 --- a/farms_network/core/network_cy.pyx +++ b/farms_network/core/network_cy.pyx @@ -9,7 +9,7 @@ from libc.stdlib cimport free, malloc from libc.string cimport strdup from ..models.factory import NodeFactory -from ..noise.ornstein_uhlenbeck import OrnsteinUhlenbeck +# from ..noise.ornstein_uhlenbeck import OrnsteinUhlenbeck from .data import NetworkData, NetworkStates from .node_cy cimport processed_inputs_t diff --git a/farms_network/noise/ornstein_uhlenbeck.py b/farms_network/noise/ornstein_uhlenbeck.py new file mode 100644 index 0000000..e69de29 diff --git a/farms_network/noise/ornstein_uhlenbeck.pxd b/farms_network/noise/ornstein_uhlenbeck_cy.pxd similarity index 100% rename from farms_network/noise/ornstein_uhlenbeck.pxd rename to farms_network/noise/ornstein_uhlenbeck_cy.pxd diff --git a/farms_network/noise/ornstein_uhlenbeck.pyx b/farms_network/noise/ornstein_uhlenbeck_cy.pyx similarity index 96% rename from farms_network/noise/ornstein_uhlenbeck.pyx rename to farms_network/noise/ornstein_uhlenbeck_cy.pyx index 0acdbc4..f3e4dca 100644 --- a/farms_network/noise/ornstein_uhlenbeck.pyx +++ b/farms_network/noise/ornstein_uhlenbeck_cy.pyx @@ -23,7 +23,7 @@ limitations under the License. from libc.math cimport sqrt as csqrt from libc.stdlib cimport free, malloc -from .ornstein_uhlenbeck cimport mt19937, mt19937_64, normal_distribution +from .ornstein_uhlenbeck_cy cimport mt19937, mt19937_64, normal_distribution from typing import List @@ -123,7 +123,10 @@ cdef class OrnsteinUhlenbeck(SDESystem): return diffusion def initialize_parameters_from_options(self, noise_options, random_seed=123124): - """ Initialize the parameters from noise options """ + """ Initialize the parameters from noise options + + # TODO: Remove default random seed in code + """ for index in range(self.n_dim): noise_option = noise_options[index] self.parameters.mu[index] = noise_option.mu From 13ce595f80c1a32134c278297b6215b9b03c12ee Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Thu, 30 Oct 2025 11:07:58 -0400 Subject: [PATCH 299/316] [DUCKS] Updated copyright attribute --- ducks/source/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ducks/source/conf.py b/ducks/source/conf.py index 39c57d0..191e901 100644 --- a/ducks/source/conf.py +++ b/ducks/source/conf.py @@ -18,7 +18,7 @@ # -- Project information ----------------------------------------------------- project = 'FARMS-NETWORK' -copyright = '2019, BioRob' +copyright = '2025, Jonathan Arreguit & Shravan Tata Ramalingasetty' author = 'FARMSIM' master_doc = 'index' From d7c9fd5084b4d11b80479323399e8dbcb5578201 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Tue, 4 Nov 2025 17:21:23 -0500 Subject: [PATCH 300/316] [CORE][WIP] Added basic structure for integrating stochastic noise --- farms_network/core/data.py | 22 ++-- farms_network/core/network_cy.pxd | 23 +++- farms_network/core/network_cy.pyx | 50 ++++++-- farms_network/core/node_cy.pxd | 3 + farms_network/noise/ornstein_uhlenbeck.py | 16 +++ farms_network/noise/ornstein_uhlenbeck_cy.pxd | 20 ++- farms_network/noise/ornstein_uhlenbeck_cy.pyx | 119 +++++++++--------- 7 files changed, 161 insertions(+), 92 deletions(-) diff --git a/farms_network/core/data.py b/farms_network/core/data.py index 66f9200..9213178 100644 --- a/farms_network/core/data.py +++ b/farms_network/core/data.py @@ -78,7 +78,6 @@ def from_options(cls, network_options: NetworkOptions): states = NetworkStates.from_options(network_options) derivatives = NetworkStates.from_options(network_options) connectivity = NetworkConnectivity.from_options(network_options) - noise = NetworkNoise.from_options(network_options) outputs = DoubleArray1D( array=np.full( @@ -112,6 +111,9 @@ def from_options(cls, network_options: NetworkOptions): ) for node_index, node_options in enumerate(network_options.nodes) ] + + noise = NetworkNoise.from_options(network_options) + # nodes = np.array( # [ # NodeData.from_options( @@ -257,7 +259,7 @@ def to_dict(self, iteration: int = None) -> Dict: class NetworkNoise(NetworkNoiseCy): """ Data for network noise modeling """ - def __init__(self, states, indices,drift, diffusion, outputs): + def __init__(self, states, indices, drift, diffusion, outputs): super().__init__(states, indices, drift, diffusion, outputs) @classmethod @@ -268,10 +270,10 @@ def from_options(cls, network_options: NetworkOptions): n_nodes = len(nodes) indices = [] - # for index, node in enumerate(nodes): - # if node.noise and node.noise.is_stochastic: - # n_noise_states += 1 - # indices.append(index) + for index, node in enumerate(nodes): + if node.noise and node.noise.is_stochastic: + n_noise_states += 1 + indices.append(index) return cls( states=np.full( @@ -279,10 +281,6 @@ def from_options(cls, network_options: NetworkOptions): fill_value=0.0, dtype=NPDTYPE, ), - indices=np.array( - indices, - dtype=NPUITYPE, - ), drift=np.full( shape=n_noise_states, fill_value=0.0, @@ -293,6 +291,10 @@ def from_options(cls, network_options: NetworkOptions): fill_value=0.0, dtype=NPDTYPE, ), + indices=np.array( + indices, + dtype=NPUITYPE, + ), outputs=np.full( shape=n_nodes, fill_value=0.0, diff --git a/farms_network/core/network_cy.pxd b/farms_network/core/network_cy.pxd index 8899aff..ad12dbc 100644 --- a/farms_network/core/network_cy.pxd +++ b/farms_network/core/network_cy.pxd @@ -7,6 +7,17 @@ from .edge_cy cimport EdgeCy, edge_t from .node_cy cimport NodeCy, node_t, node_inputs_t +cdef struct noise_t: + # States + int nstates + double* states + double* drift + double* diffusion + const unsigned int* indices + # Outputs + double* outputs + + cdef struct network_t: # info unsigned int nnodes @@ -30,13 +41,14 @@ cdef struct network_t: const double* external_inputs - double* noise - const unsigned int* node_indices const unsigned int* edge_indices const double* weights const unsigned int* index_offsets + # Noise + noise_t noise + cdef class NetworkCy(ODESystem): """ Python interface to Network ODE """ @@ -55,3 +67,10 @@ cdef class NetworkCy(ODESystem): cdef void evaluate(self, double time, double[:] states, double[:] derivatives) noexcept # cpdef void update_iteration(self) + + +cdef class NetworkNoiseCy(SDESystem): + """ Interface to stochastic noise in the network """ + + cdef void evaluate_a(self, double time, double[:] states, double[:] drift) noexcept + cdef void evaluate_b(self, double time, double[:] states, double[:] diffusion) noexcept diff --git a/farms_network/core/network_cy.pyx b/farms_network/core/network_cy.pyx index a4d1a2d..acafa81 100644 --- a/farms_network/core/network_cy.pyx +++ b/farms_network/core/network_cy.pyx @@ -48,6 +48,9 @@ cdef inline void ode( cdef double* states_ptr = &states[0] cdef double* derivatives_ptr = &derivatives[0] + # Noise + cdef double* noise = c_network.noise.outputs + for j in range(nnodes): __node = c_nodes[j] @@ -83,7 +86,7 @@ cdef inline void ode( states_ptr + c_network.states_indices[j], derivatives_ptr + c_network.states_indices[j], processed_inputs, - 0.0, + noise[j], c_nodes[j] ) # Check for writing to proper outputs array @@ -91,7 +94,7 @@ cdef inline void ode( time, states_ptr + c_network.states_indices[j], processed_inputs, - 0.0, + noise[j], c_nodes[j], ) @@ -168,11 +171,6 @@ cdef class NetworkCy(ODESystem): else: raise ValueError("Temp Outputs array cannot be of size 0") - if self.data.noise.outputs.size > 0: - self._network.noise = &self.data.noise.outputs[0] - else: - self._network.noise = NULL - if self.data.connectivity.node_indices.size > 0: self._network.node_indices = &self.data.connectivity.node_indices[0] else: @@ -193,6 +191,32 @@ cdef class NetworkCy(ODESystem): else: raise ValueError("Connectivity array cannot be of size 0") + # Noise + if self.data.noise.states.size > 0: + self._network.noise.states = &self.data.noise.states[0] + else: + self._network.noise.states = NULL + + if self.data.noise.drift.size > 0: + self._network.noise.drift = &self.data.noise.drift[0] + else: + self._network.noise.drift = NULL + + if self.data.noise.diffusion.size > 0: + self._network.noise.diffusion = &self.data.noise.diffusion[0] + else: + self._network.noise.diffusion = NULL + + if self.data.noise.indices.size > 0: + self._network.noise.indices = &self.data.noise.indices[0] + else: + self._network.noise.indices = NULL + + if self.data.noise.outputs.size > 0: + self._network.noise.outputs = &self.data.noise.outputs[0] + else: + self._network.noise.outputs = NULL + def __init__(self, nnodes, nedges, data: NetworkDataCy, log: NetworkLogCy): """ Initialize """ @@ -228,6 +252,8 @@ cdef class NetworkCy(ODESystem): cdef void evaluate(self, double time, double[:] states, double[:] derivatives) noexcept: """ Evaluate the ODE """ + # Update noise states + # Update network ODE ode(time, states, derivatives, self._network) # Swap the temporary outputs self.data.outputs.array[:] = self.data.tmp_outputs.array[:] @@ -262,3 +288,13 @@ cdef class NetworkCy(ODESystem): def nstates(self, value: int): """ Number of network states """ self._network.nstates = value + + @property + def noise_nstates(self): + """ Number of noise states in the network """ + return self._network.noise.nstates + + @noise_nstates.setter + def noise_nstates(self, value: int): + """ Number of network noise states """ + self._network.noise.nstates = value diff --git a/farms_network/core/node_cy.pxd b/farms_network/core/node_cy.pxd index da37a95..40f8f33 100644 --- a/farms_network/core/node_cy.pxd +++ b/farms_network/core/node_cy.pxd @@ -68,6 +68,9 @@ cdef struct node_t: # Parameters void* params # Pointer to the parameters of the node. + # Noise parameters + void* noise_params # Pointer to noise parameters for the node. + # Functions void input_tf( double time, diff --git a/farms_network/noise/ornstein_uhlenbeck.py b/farms_network/noise/ornstein_uhlenbeck.py index e69de29..f81e0fc 100644 --- a/farms_network/noise/ornstein_uhlenbeck.py +++ b/farms_network/noise/ornstein_uhlenbeck.py @@ -0,0 +1,16 @@ +from ornstein_uhlenbeck_cy import OrnsteinUhlenbeckCy + + +class OrnsteinUhlenbeck: + """ OrnsteinUhlenbeck Noise Model """ + + def __init__( + self, timestep: float, seed: int, noise_options: List[OrnsteinUhlenbeckOptions] + ): + """ Init """ + assert all([opt.is_stochastic for opt in noise_options]), f"Invalid noise options{noise_options} " + self.noise_options = noise_options + self.n_dim = len(self.noise_options) + self.timestep = timestep + self.seed = seed + self._ou_cy = O diff --git a/farms_network/noise/ornstein_uhlenbeck_cy.pxd b/farms_network/noise/ornstein_uhlenbeck_cy.pxd index c92630d..b51d98c 100644 --- a/farms_network/noise/ornstein_uhlenbeck_cy.pxd +++ b/farms_network/noise/ornstein_uhlenbeck_cy.pxd @@ -42,23 +42,19 @@ cdef extern from "" namespace "std" nogil: result_type max() -cdef struct OrnsteinUhlenbeckParameters: - double* mu - double* sigma - double* tau - mt19937_64 random_generator - normal_distribution[double] distribution +cdef struct ornstein_uhlenbeck_params_t: + double mu + double sigma + double tau -cdef class OrnsteinUhlenbeck(SDESystem): +cdef class OrnsteinUhlenbeckCy(SDESystem): cdef: - double timestep - unsigned int n_dim - OrnsteinUhlenbeckParameters* parameters - normal_distribution[double] distribution + int n_dim + ornstein_uhlenbeck_params_t** params mt19937_64 random_generator - double* random_samples + normal_distribution[double] distribution cdef void evaluate_a(self, double time, double[:] states, double[:] drift) noexcept cdef void evaluate_b(self, double time, double[:] states, double[:] diffusion) noexcept diff --git a/farms_network/noise/ornstein_uhlenbeck_cy.pyx b/farms_network/noise/ornstein_uhlenbeck_cy.pyx index f3e4dca..ef4fcd2 100644 --- a/farms_network/noise/ornstein_uhlenbeck_cy.pyx +++ b/farms_network/noise/ornstein_uhlenbeck_cy.pyx @@ -1,4 +1,4 @@ -# distutils: language = c++ + # distutils: language = c++ """ ----------------------------------------------------------------------- @@ -32,86 +32,82 @@ import numpy as np from ..core.options import OrnsteinUhlenbeckOptions -cdef class OrnsteinUhlenbeck(SDESystem): - """ Ornstein Uhlenheck parameters """ +cdef void evaluate_a( + double time, + double[:] states, + double[:] drift, + ornstein_uhlenbeck_params_t params +) noexcept: + cdef unsigned int j + for j in range(params.n_dim): + drift[j] = (params.mu-states[j])/params.tau - def __cinit__(self, noise_options: List[OrnsteinUhlenbeckOptions], random_seed: int=None): - """ C initialization for manual memory allocation """ - self.n_dim = len(noise_options) +cdef void evaluate_b( + double time, + double[:] states, + double[:] diffusion, + ornstein_uhlenbeck_params_t params +) noexcept: + cdef unsigned int j + for j in range(params.n_dim): + diffusion[j] = params.sigma*csqrt(2.0/params.tau)*params.distribution(params.random_generator) - self.parameters = malloc( - sizeof(OrnsteinUhlenbeckParameters) - ) - if self.parameters is NULL: - raise MemoryError( - "Failed to allocate memory for OrnsteinUhlenbeck Parameters" - ) - self.parameters.mu = malloc(self.n_dim*sizeof(double)) - if self.parameters.mu is NULL: - raise MemoryError( - "Failed to allocate memory for OrnsteinUhlenbeck parameter MU" - ) +cdef class OrnsteinUhlenbeckCy(SDESystem): + """ Ornstein Uhlenheck parameters """ - self.parameters.sigma = malloc(self.n_dim*sizeof(double)) - if self.parameters.sigma is NULL: - raise MemoryError( - "Failed to allocate memory for OrnsteinUhlenbeck parameter SIGMA" - ) + def __cinit__( + self, + n_dim: int, + noise_options: List[OrnsteinUhlenbeckOptions], + random_seed: int = None + ): + """ C initialization for manual memory allocation """ - self.parameters.tau = malloc(self.n_dim*sizeof(double)) - if self.parameters.tau is NULL: - raise MemoryError( - "Failed to allocate memory for OrnsteinUhlenbeck parameter TAU" - ) + self.n_dim = n_dim + + self.params = malloc( + self.n_dim * sizeof(ornstein_uhlenbeck_params_t*) + ) - self.random_samples = malloc(self.n_dim*sizeof(double)) - if self.random_samples is NULL: + if self.params is NULL: raise MemoryError( - "Failed to allocate memory for OrnsteinUhlenbeck random samples" + "Failed to allocate memory for OrnsteinUhlenbeck Parameters" ) def __dealloc__(self): """ Deallocate any manual memory as part of clean up """ - if self.parameters.mu is not NULL: - free(self.parameters.mu) - if self.parameters.sigma is not NULL: - free(self.parameters.sigma) - if self.parameters.tau is not NULL: - free(self.parameters.tau) - if self.parameters is not NULL: - free(self.parameters) - if self.random_samples is not NULL: - free(self.random_samples) - - def __init__(self, noise_options: List[OrnsteinUhlenbeckOptions], random_seed: int=None): + if self.params is not NULL: + free(self.params) + + def __init__( + self, + n_dim: int, + noise_options: List[OrnsteinUhlenbeckOptions], + random_seed: int = None + ): super().__init__() - # if random_seed is None: - # random_seed = np.random.rand + self.initialize_parameters_from_options(noise_options) cdef void evaluate_a(self, double time, double[:] states, double[:] drift) noexcept: cdef unsigned int j - cdef OrnsteinUhlenbeckParameters params = ( - self.parameters[0] - ) + cdef ornstein_uhlenbeck_params_t* param + for j in range(self.n_dim): - drift[j] = (params.mu[j]-states[j])/params.tau[j] + param = self.params[j] + drift[j] = (param.mu - states[j])/param.tau cdef void evaluate_b(self, double time, double[:] states, double[:] diffusion) noexcept: cdef unsigned int j - cdef OrnsteinUhlenbeckParameters params = ( - self.parameters[0] - ) - cdef normal_distribution[double] distribution = self.distribution - cdef mt19937_64 random_generator = self.random_generator - for j in range(self.n_dim): - self.random_samples[j] = distribution(random_generator) + cdef ornstein_uhlenbeck_params_t* param + for j in range(self.n_dim): - diffusion[j] = params.sigma[j]*csqrt(2.0/params.tau[j])*( - self.random_samples[j] + param = self.params[j] + diffusion[j] = param.sigma*( + csqrt(2.0/param.tau)*(self.distribution(self.random_generator)) ) def py_evaluate_a(self, time, states, drift): @@ -129,9 +125,10 @@ cdef class OrnsteinUhlenbeck(SDESystem): """ for index in range(self.n_dim): noise_option = noise_options[index] - self.parameters.mu[index] = noise_option.mu - self.parameters.sigma[index] = noise_option.sigma - self.parameters.tau[index] = noise_option.tau + self.params[index].mu = noise_option.mu + self.params[index].sigma = noise_option.sigma + self.params[index].tau = noise_option.tau + self.random_generator = mt19937_64(random_seed) # The distribution should always be mean=0.0 and std=1.0 self.distribution = normal_distribution[double](0.0, 1.0) From bb16da17b4f9000f156f6720b9ba14c8c22f0666 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Wed, 5 Nov 2025 09:32:43 -0500 Subject: [PATCH 301/316] [NOISE] Updated ornstein model to newer farms update --- farms_network/noise/ornstein_uhlenbeck.py | 23 +++++---- farms_network/noise/ornstein_uhlenbeck_cy.pxd | 2 +- farms_network/noise/ornstein_uhlenbeck_cy.pyx | 50 +++++-------------- 3 files changed, 27 insertions(+), 48 deletions(-) diff --git a/farms_network/noise/ornstein_uhlenbeck.py b/farms_network/noise/ornstein_uhlenbeck.py index f81e0fc..278e88d 100644 --- a/farms_network/noise/ornstein_uhlenbeck.py +++ b/farms_network/noise/ornstein_uhlenbeck.py @@ -1,16 +1,21 @@ -from ornstein_uhlenbeck_cy import OrnsteinUhlenbeckCy +from .ornstein_uhlenbeck_cy import OrnsteinUhlenbeckCy + +from ..core.options import NetworkOptions class OrnsteinUhlenbeck: """ OrnsteinUhlenbeck Noise Model """ - def __init__( - self, timestep: float, seed: int, noise_options: List[OrnsteinUhlenbeckOptions] - ): + def __init__(self, network_options: NetworkOptions): """ Init """ - assert all([opt.is_stochastic for opt in noise_options]), f"Invalid noise options{noise_options} " - self.noise_options = noise_options + self.noise_options = [ + node.noise + for node in network_options.nodes + if node.noise + if node.noise.is_stochastic + ] + self.n_dim = len(self.noise_options) - self.timestep = timestep - self.seed = seed - self._ou_cy = O + self.timestep = network_options.integration.timestep + self.seed = network_options.random_seed + self._ou_cy = OrnsteinUhlenbeckCy(self.noise_options, self.seed) diff --git a/farms_network/noise/ornstein_uhlenbeck_cy.pxd b/farms_network/noise/ornstein_uhlenbeck_cy.pxd index b51d98c..56d8a73 100644 --- a/farms_network/noise/ornstein_uhlenbeck_cy.pxd +++ b/farms_network/noise/ornstein_uhlenbeck_cy.pxd @@ -52,7 +52,7 @@ cdef class OrnsteinUhlenbeckCy(SDESystem): cdef: int n_dim - ornstein_uhlenbeck_params_t** params + ornstein_uhlenbeck_params_t* params mt19937_64 random_generator normal_distribution[double] distribution diff --git a/farms_network/noise/ornstein_uhlenbeck_cy.pyx b/farms_network/noise/ornstein_uhlenbeck_cy.pyx index ef4fcd2..71cd5ed 100644 --- a/farms_network/noise/ornstein_uhlenbeck_cy.pyx +++ b/farms_network/noise/ornstein_uhlenbeck_cy.pyx @@ -21,6 +21,7 @@ limitations under the License. from libc.math cimport sqrt as csqrt +from libc.stdio cimport printf from libc.stdlib cimport free, malloc from .ornstein_uhlenbeck_cy cimport mt19937, mt19937_64, normal_distribution @@ -32,43 +33,18 @@ import numpy as np from ..core.options import OrnsteinUhlenbeckOptions -cdef void evaluate_a( - double time, - double[:] states, - double[:] drift, - ornstein_uhlenbeck_params_t params -) noexcept: - cdef unsigned int j - for j in range(params.n_dim): - drift[j] = (params.mu-states[j])/params.tau - - -cdef void evaluate_b( - double time, - double[:] states, - double[:] diffusion, - ornstein_uhlenbeck_params_t params -) noexcept: - cdef unsigned int j - for j in range(params.n_dim): - diffusion[j] = params.sigma*csqrt(2.0/params.tau)*params.distribution(params.random_generator) - - cdef class OrnsteinUhlenbeckCy(SDESystem): """ Ornstein Uhlenheck parameters """ def __cinit__( self, - n_dim: int, noise_options: List[OrnsteinUhlenbeckOptions], - random_seed: int = None + random_seed: int ): """ C initialization for manual memory allocation """ - self.n_dim = n_dim - - self.params = malloc( - self.n_dim * sizeof(ornstein_uhlenbeck_params_t*) + self.params = malloc( + len(noise_options) * sizeof(ornstein_uhlenbeck_params_t) ) if self.params is NULL: @@ -78,23 +54,21 @@ cdef class OrnsteinUhlenbeckCy(SDESystem): def __dealloc__(self): """ Deallocate any manual memory as part of clean up """ - if self.params is not NULL: free(self.params) def __init__( - self, - n_dim: int, - noise_options: List[OrnsteinUhlenbeckOptions], - random_seed: int = None + self, + noise_options: List[OrnsteinUhlenbeckOptions], + random_seed: int ): super().__init__() - - self.initialize_parameters_from_options(noise_options) + self.n_dim = len(noise_options) + self.initialize_parameters_from_options(noise_options, random_seed) cdef void evaluate_a(self, double time, double[:] states, double[:] drift) noexcept: cdef unsigned int j - cdef ornstein_uhlenbeck_params_t* param + cdef ornstein_uhlenbeck_params_t param for j in range(self.n_dim): param = self.params[j] @@ -102,7 +76,7 @@ cdef class OrnsteinUhlenbeckCy(SDESystem): cdef void evaluate_b(self, double time, double[:] states, double[:] diffusion) noexcept: cdef unsigned int j - cdef ornstein_uhlenbeck_params_t* param + cdef ornstein_uhlenbeck_params_t param for j in range(self.n_dim): param = self.params[j] @@ -118,7 +92,7 @@ cdef class OrnsteinUhlenbeckCy(SDESystem): self.evaluate_b(time, states, diffusion) return diffusion - def initialize_parameters_from_options(self, noise_options, random_seed=123124): + def initialize_parameters_from_options(self, noise_options, random_seed): """ Initialize the parameters from noise options # TODO: Remove default random seed in code From 8dfaf2183efbdda262d760184c441fa4d92b7c53 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Wed, 5 Nov 2025 09:35:09 -0500 Subject: [PATCH 302/316] [CORE] Updated nodes interface for data logs --- farms_network/core/data.py | 47 ++++++++++++++++++++++++++++++-------- 1 file changed, 37 insertions(+), 10 deletions(-) diff --git a/farms_network/core/data.py b/farms_network/core/data.py index 9213178..5e0fc97 100644 --- a/farms_network/core/data.py +++ b/farms_network/core/data.py @@ -367,7 +367,7 @@ def __init__( self.external_inputs = external_inputs self.noise = noise - self.nodes: List[NodeData] = nodes + self.nodes: Nodes = nodes # assert that the data created is c-contiguous assert self.states.array.is_c_contig() @@ -405,15 +405,8 @@ def from_options(cls, network_options: NetworkOptions): dtype=NPDTYPE, ) ) - nodes = [ - NodeData( - node_options.name, - NodeStates(states, node_index,), - NodeOutput(outputs, node_index,), - NodeExternalInput(external_inputs, node_index,), - ) - for node_index, node_options in enumerate(network_options.nodes) - ] + + nodes = Nodes(network_options, states, outputs, external_inputs) return cls( times=times, @@ -446,6 +439,40 @@ def to_file(self, filename: str, iteration: int = None): pylog.info('Saved data to %s', filename) +class Nodes: + """ Nodes """ + + def __init__(self, network_options: NetworkOptions, states, outputs, external_inputs): + self._nodes = [] + self._name_to_index = {} + + for idx, node_opt in enumerate(network_options.nodes): + node = NodeData( + node_opt.name, + NodeStates(states, idx), + NodeOutput(outputs, idx), + NodeExternalInput(external_inputs, idx), + ) + self._nodes.append(node) + self._name_to_index[node_opt.name] = idx + + def __getitem__(self, key): + # Access by index + if isinstance(key, int): + return self._nodes[key] + # Access by name + return self._nodes[self._name_to_index[key]] + + def __len__(self): + return len(self._nodes) + + def __iter__(self): + return iter(self._nodes) + + def names(self): + return list(self._name_to_index.keys()) + + class NodeStates: def __init__(self, network_states, node_index): self._network_states = network_states From 0196e4d1044f6c7ced31dad013acc1ce7f6abf3c Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Wed, 5 Nov 2025 09:35:48 -0500 Subject: [PATCH 303/316] [CORE] Added kwargs to edge_cy params for cinit inheritence --- farms_network/core/edge_cy.pyx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/farms_network/core/edge_cy.pyx b/farms_network/core/edge_cy.pyx index 2955f9f..8d9bf33 100644 --- a/farms_network/core/edge_cy.pyx +++ b/farms_network/core/edge_cy.pyx @@ -18,7 +18,7 @@ cpdef enum types: cdef class EdgeCy: """ Python interface to Edge C-Structure""" - def __cinit__(self, edge_type: str): + def __cinit__(self, edge_type: str, **kwargs): self._edge = malloc(sizeof(edge_t)) if self._edge is NULL: raise MemoryError("Failed to allocate memory for edge_t") @@ -30,7 +30,7 @@ cdef class EdgeCy: if self._edge is not NULL: free(self._edge) - def __init__(self, edge_type: str): + def __init__(self, edge_type: str, **kwargs): ... @property From a79d5c256ce84e1748ef34ac42720ccb4fd2e297 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Wed, 5 Nov 2025 09:36:31 -0500 Subject: [PATCH 304/316] [CORE] Improved check for stochastic noise state initialization --- farms_network/core/data.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/farms_network/core/data.py b/farms_network/core/data.py index 5e0fc97..5d3fd51 100644 --- a/farms_network/core/data.py +++ b/farms_network/core/data.py @@ -271,9 +271,10 @@ def from_options(cls, network_options: NetworkOptions): indices = [] for index, node in enumerate(nodes): - if node.noise and node.noise.is_stochastic: - n_noise_states += 1 - indices.append(index) + if node.noise: + if node.noise.is_stochastic: + n_noise_states += 1 + indices.append(index) return cls( states=np.full( From e930d96a25314e9bc3ca3ab3c39187960650caf3 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Wed, 5 Nov 2025 09:38:39 -0500 Subject: [PATCH 305/316] [CORE] Removed unused noise_params from node struct --- farms_network/core/node_cy.pxd | 3 --- 1 file changed, 3 deletions(-) diff --git a/farms_network/core/node_cy.pxd b/farms_network/core/node_cy.pxd index 40f8f33..da37a95 100644 --- a/farms_network/core/node_cy.pxd +++ b/farms_network/core/node_cy.pxd @@ -68,9 +68,6 @@ cdef struct node_t: # Parameters void* params # Pointer to the parameters of the node. - # Noise parameters - void* noise_params # Pointer to noise parameters for the node. - # Functions void input_tf( double time, From 8c501e44ee08adf1210c09a5c6e66b5fa2764d90 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Wed, 5 Nov 2025 09:44:06 -0500 Subject: [PATCH 306/316] [CORE] Added preliminary support for Ornstein Uhlenbeck noise --- farms_network/core/network.py | 71 ++++++++++++++++++++----------- farms_network/core/network_cy.pxd | 18 +++++--- farms_network/core/network_cy.pyx | 38 +++++++++++++---- 3 files changed, 87 insertions(+), 40 deletions(-) diff --git a/farms_network/core/network.py b/farms_network/core/network.py index b184619..c9e3c72 100644 --- a/farms_network/core/network.py +++ b/farms_network/core/network.py @@ -4,8 +4,10 @@ import numpy as np from farms_core import pylog +from farms_network.numeric.integrators_cy import RK4Solver from ..models.factory import EdgeFactory, NodeFactory +from ..noise.ornstein_uhlenbeck import OrnsteinUhlenbeck from .data import NetworkData, NetworkLog from .edge import Edge from .network_cy import NetworkCy @@ -27,12 +29,12 @@ def __init__(self, network_options: NetworkOptions): ) # Core network data and Cython implementation - self.data = NetworkData.from_options(network_options) - self.log = NetworkLog.from_options(network_options) + self.data = NetworkData.from_options(self.options) + self.log = NetworkLog.from_options(self.options) self._network_cy = NetworkCy( - nnodes=len(network_options.nodes), - nedges=len(network_options.edges), + nnodes=len(self.options.nodes), + nedges=len(self.options.edges), data=self.data, log=self.log ) @@ -43,16 +45,43 @@ def __init__(self, network_options: NetworkOptions): # Setup the network self._setup_network() - # self._setup_integrator() + + # Internal default solver + self.solver: RK4Solver = None # Logs - self.buffer_size: int = network_options.logs.buffer_size + self.buffer_size: int = self.options.logs.buffer_size # Iteration - if network_options.integration: - self.timestep: float = network_options.integration.timestep + if self.options.integration: + self.timestep: float = self.options.integration.timestep self.iteration: int = 0 - self.n_iterations: int = network_options.integration.n_iterations + self.n_iterations: int = self.options.integration.n_iterations + else: + raise ValueError("Integration options missing!") + + def step(self, time): + """ Step the network integration """ + self.solver.step( + self._network_cy, + time, + self.data.states.array + ) + # Update noise + self._network_cy.update_noise(time, self.timestep) + + # Update logs + def update_logs(self, time): + self._network_cy.update_logs(time) + + def run(self, n_iterations: Optional[int] = None): + """ Run the network for n_iterations """ + if n_iterations is None: + n_iterations = self.n_iterations + + for iteration in range(n_iterations): + self.step(iteration*self.timestep) + self.update_logs(iteration*self.timestep) def _setup_network(self): """ Setup network nodes and edges """ @@ -77,15 +106,20 @@ def _setup_network(self): self._network_cy.nstates = nstates + # Noise + self.sde_noise = OrnsteinUhlenbeck(self.options) + # Pass Python nodes/edges to Cython layer for C struct setup - self._network_cy.setup_network(self.data, self.nodes, self.edges) + self._network_cy.setup_network( + self.data, self.nodes, self.edges, self.sde_noise._ou_cy + ) # Initialize states self._initialize_states() - def _setup_integrator(self): + def setup_integrator(self): """ Setup numerical integrators """ - self._network_cy.setup_integrator(self.options) + self.solver = RK4Solver(self._network_cy.nstates, self.options.integration.timestep) def _initialize_states(self): """ Initialize node states from options """ @@ -125,19 +159,6 @@ def nedges(self) -> int: def nstates(self) -> int: return self._network_cy.nstates - def step(self): - """ Step the network simulation """ - self._network_cy.step() - self.iteration += 1 - - def run(self, n_iterations: Optional[int] = None): - """ Run the network for n_iterations """ - if n_iterations is None: - n_iterations = self.n_iterations - - for _ in range(n_iterations): - self.step() - # Factory methods @classmethod def from_options(cls, options: NetworkOptions): diff --git a/farms_network/core/network_cy.pxd b/farms_network/core/network_cy.pxd index ad12dbc..13639a8 100644 --- a/farms_network/core/network_cy.pxd +++ b/farms_network/core/network_cy.pxd @@ -2,6 +2,7 @@ cimport numpy as cnp from ..numeric.integrators_cy cimport EulerMaruyamaSolver, RK4Solver from ..numeric.system_cy cimport ODESystem, SDESystem +from ..noise.ornstein_uhlenbeck_cy cimport OrnsteinUhlenbeckCy from .data_cy cimport NetworkDataCy, NetworkLogCy from .edge_cy cimport EdgeCy, edge_t from .node_cy cimport NodeCy, node_t, node_inputs_t @@ -31,20 +32,20 @@ cdef struct network_t: # ODE double* states - const unsigned int* states_indices + unsigned int* states_indices double* derivatives - const unsigned int* derivatives_indices + unsigned int* derivatives_indices double* outputs double* tmp_outputs - const double* external_inputs + double* external_inputs - const unsigned int* node_indices - const unsigned int* edge_indices - const double* weights - const unsigned int* index_offsets + unsigned int* node_indices + unsigned int* edge_indices + double* weights + unsigned int* index_offsets # Noise noise_t noise @@ -65,7 +66,10 @@ cdef class NetworkCy(ODESystem): const unsigned int buffer_size const double timestep + OrnsteinUhlenbeckCy sde_noise + cdef void evaluate(self, double time, double[:] states, double[:] derivatives) noexcept + cdef void c_update_noise(self, double time, double timestep) noexcept # cpdef void update_iteration(self) diff --git a/farms_network/core/network_cy.pyx b/farms_network/core/network_cy.pyx index acafa81..7c230b9 100644 --- a/farms_network/core/network_cy.pyx +++ b/farms_network/core/network_cy.pyx @@ -9,12 +9,16 @@ from libc.stdlib cimport free, malloc from libc.string cimport strdup from ..models.factory import NodeFactory -# from ..noise.ornstein_uhlenbeck import OrnsteinUhlenbeck + +from libc.math cimport sqrt as csqrt + +from ..noise.ornstein_uhlenbeck_cy cimport OrnsteinUhlenbeckCy + from .data import NetworkData, NetworkStates -from .node_cy cimport processed_inputs_t from .data_cy cimport (NetworkConnectivityCy, NetworkDataCy, NetworkNoiseCy, NetworkStatesCy) +from .node_cy cimport processed_inputs_t from typing import List @@ -240,7 +244,8 @@ cdef class NetworkCy(ODESystem): self, data: NetworkData, nodes: List[NodeCy], - edges: List[EdgeCy] + edges: List[EdgeCy], + sde_noise: SDESystem=None, ): """ Setup network """ @@ -250,24 +255,41 @@ cdef class NetworkCy(ODESystem): for index, edge in enumerate(edges): self._network.edges[index] = ((edge._edge_cy)._edge) + self.sde_noise = sde_noise + cdef void evaluate(self, double time, double[:] states, double[:] derivatives) noexcept: """ Evaluate the ODE """ - # Update noise states # Update network ODE ode(time, states, derivatives, self._network) # Swap the temporary outputs self.data.outputs.array[:] = self.data.tmp_outputs.array[:] + cdef void c_update_noise(self, double time, double timestep) noexcept: + """ Update """ + if self.sde_noise is not None: + self.sde_noise.evaluate_a(time, self.data.noise.states, self.data.noise.drift) + self.sde_noise.evaluate_b(time, self.data.noise.states, self.data.noise.diffusion) + for j in range(self.sde_noise.n_dim): + self.data.noise.states[j] += ( + self.data.noise.drift[j]*timestep + csqrt(timestep)*self.data.noise.diffusion[j] + ) + self.data.noise.outputs[self.data.noise.indices[j]] = self.data.noise.states[j] + + def update_noise(self, double time, double timestep): + self.c_update_noise(time, timestep) + def ode_func(self, double time, double[:] states): """ Evaluate the ODE """ self.evaluate(time, states, self.data.derivatives.array) return self.data.derivatives.array - def update_logs(self, iteration: int): + def update_logs(self, time: float): """ Updated logs to copy current iteration data into logs """ - self.log.states.array[iteration, :] = self.data.states.array[:] - self.log.external_inputs.array[iteration, :] = self.data.external_inputs.array[:] - self.log.outputs.array[iteration, :] = self.data.outputs.array[:] + self.iteration += 1 + self.log.times.array[self.iteration] = time + self.log.states.array[self.iteration, :] = self.data.states.array[:] + self.log.external_inputs.array[self.iteration, :] = self.data.external_inputs.array[:] + self.log.outputs.array[self.iteration, :] = self.data.outputs.array[:] @property def nnodes(self): From 52e62e1b3efcd5d5e952739140a09d33eefca414 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Wed, 5 Nov 2025 09:45:01 -0500 Subject: [PATCH 307/316] [EX] Updated ijspeert07 to new changes to network interface --- examples/ijspeert07/run.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/examples/ijspeert07/run.py b/examples/ijspeert07/run.py index b539870..6786a78 100644 --- a/examples/ijspeert07/run.py +++ b/examples/ijspeert07/run.py @@ -173,6 +173,7 @@ def run_network(network_options: options.NetworkOptions): network = Network.from_options(network_options) iterations = network_options.integration.n_iterations timestep = network_options.integration.timestep + network.setup_integrator() # Setup integrators rk4solver = RK4Solver(network.nstates, timestep) @@ -201,17 +202,19 @@ def run_network(network_options: options.NetworkOptions): states = np.ones((iterations+1, network.nstates))*1.0 outputs = np.ones((iterations, network.nnodes))*1.0 for iteration in tqdm(range(0, iterations), colour='green', ascii=' >='): - network.data.times.array[iteration] = iteration*timestep + # network.data.times.array[iteration] = iteration*timestep - integrator.set_initial_value(integrator.y, integrator.t) - integrator.integrate(integrator.t+timestep) + # integrator.set_initial_value(integrator.y, integrator.t) + # integrator.integrate(integrator.t+timestep) - # sc_integrator.step() + # # sc_integrator.step() - # rk4solver.step(network._network_cy, iteration*timestep, network.data.states.array) + # # rk4solver.step(network._network_cy, iteration*timestep, network.data.states.array) - network._network_cy.update_logs(network._network_cy.iteration) - network._network_cy.iteration += 1 + # network._network_cy.update_logs(network._network_cy.iteration) + # network._network_cy.iteration += 1 + network.step(iteration*timestep) + network.update_logs(iteration*timestep) plt.figure() for j in range(int(network.nnodes/2)): @@ -223,7 +226,7 @@ def run_network(network_options: options.NetworkOptions): lw=1.0, ) plt.plot( - np.array(network.data.times.array), + np.array(network.log.times.array), 2*j + (1 + np.sin(network.log.outputs.array[:, j])), label=f"{j}" ) From e975a6da9f1f862a4ef0ed74063440f4e10943e6 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Wed, 5 Nov 2025 09:45:33 -0500 Subject: [PATCH 308/316] [EX] Added analysis for mouse network --- examples/mouse/components.py | 3 +- examples/mouse/run.py | 173 ++++++++++++++++++++++++++++++++--- 2 files changed, 162 insertions(+), 14 deletions(-) diff --git a/examples/mouse/components.py b/examples/mouse/components.py index b278ae0..ac3829e 100644 --- a/examples/mouse/components.py +++ b/examples/mouse/components.py @@ -202,7 +202,7 @@ def create_node( return node_options_class( name=full_name, parameters=parameters, - visual=None, + visual=visual_options, state=state_options, noise=noise, ) @@ -1839,7 +1839,6 @@ def brain_stem_circuit( "excitatory", ) ) - print(edge_specs) edges = create_edges(edge_specs, base_name="") return edges diff --git a/examples/mouse/run.py b/examples/mouse/run.py index 9fc0e93..676b40a 100644 --- a/examples/mouse/run.py +++ b/examples/mouse/run.py @@ -1,18 +1,19 @@ """ Generate and reproduce Zhang, Shevtsova, et al. eLife 2022;11:e73424. DOI: https://doi.org/10.7554/eLife.73424 paper network """ +import numpy.matlib as npml import seaborn as sns from farms_core.io.yaml import read_yaml from farms_core.utils import profile from farms_network.core import options - -from components import * -from components import limb_circuit from farms_network.core.network import Network from farms_network.numeric.integrators_cy import RK4Solver from scipy.integrate import ode from tqdm import tqdm +from components import * +from components import limb_circuit + def generate_network(n_iterations: int): """Generate network""" @@ -394,7 +395,8 @@ def run_network(*args): network = Network.from_options(network_options) iterations = network_options.integration.n_iterations - rk4solver = RK4Solver(network.nstates, network_options.integration.timestep) + timestep = network_options.integration.timestep + network.setup_integrator() integrator = ode(network.get_ode_func()).set_integrator( u'dopri5', @@ -424,7 +426,15 @@ def run_network(*args): if "BS_input" in node.name and node.model == "relay" ] inputs = np.zeros(np.shape(network.data.external_inputs.array[:])) - # print(np.array(network.data.connectivity.weights), np.array(network.data.connectivity.edge_indices), np.array(network.data.connectivity.node_indices), np.array(network.data.connectivity.index_offsets)) + + # Network drive : Alpha + time_vec = np.arange(0, iterations)*timestep + drive = 1.0 + drive_vec = np.hstack( + (np.linspace(0, 1.05, len(time_vec[::2])), + np.linspace(1.05, 0, len(time_vec[::2]))) + ) + for iteration in tqdm(range(0, iterations), colour='green', ascii=' >='): time = iteration # network.step(network.ode, iteration*1e-3, network.data.states.array) @@ -433,19 +443,18 @@ def run_network(*args): # network.step() # network.evaluate(iteration*1e-3, states[iteration, :]) - _iter = network._network_cy.iteration - network.log.times.array[_iter] = time - inputs[drive_input_indices] = 0.5 + inputs[drive_input_indices] = drive_vec[iteration]*drive network.data.external_inputs.array[:] = inputs + + network.step(time) + network.update_logs(time) # integrator.set_initial_value(integrator.y, integrator.t) # integrator.integrate(integrator.t+1.0) # network.data.states.array[:] = integrator.y - rk4solver.step(network._network_cy, time, network.data.states.array) # outputs[iteration, :] = network.data.outputs.array # states[iteration, :] = integrator.y# network.data.states.array # network._network_cy.update_iteration() - network._network_cy.update_logs(network._network_cy.iteration) - network._network_cy.iteration += 1 + # # Integrate # N_ITERATIONS = network_options.integration.n_iterations @@ -550,6 +559,145 @@ def plot_network(network_options): plt.show() +def get_gait_plot_from_neuron_act(act): + """ Get start and end times of neurons for gait plot. """ + act = np.reshape(act, (np.shape(act)[0], 1)) + act_binary = (np.array(act) > 0.1).astype(int) + act_binary = np.logical_not(act_binary).astype(int) + act_binary[0] = 0 + gait_cycle = [] + start = (np.where(np.diff(act_binary[:, 0]) == 1.))[0] + end = (np.where(np.diff(act_binary[:, 0]) == -1.))[0] + for id, val in enumerate(start[:len(end)]): + # HARD CODED TIME SCALING HERE!! + gait_cycle.append((val*0.001, end[id]*0.001 - val*0.001)) + return gait_cycle + + +def calc_on_offsets(time_vec, out): + os_=((np.diff((out>0.1).astype(np.int64),axis=0)==1).T) + of_=((np.diff((out>0.1).astype(np.int64),axis=0)==-1).T) + onsets=npml.repmat(time_vec[:-1],out.shape[1],1)[os_] + offsets=npml.repmat(time_vec[:-1],out.shape[1],1)[of_] + leg_os=(npml.repmat(np.arange(out.shape[1]),len(time_vec)-1,1).T)[os_] + leg_of=(npml.repmat(np.arange(out.shape[1]),len(time_vec)-1,1).T)[of_] + + times_os=np.stack((onsets,leg_os,np.arange(len(leg_os))),1) + times_os=times_os[times_os[:,0].argsort()] + times_of=np.stack((offsets,leg_of,np.arange(len(leg_of))),1) + times_of=times_of[times_of[:,0].argsort()] + + times = np.concatenate(( + np.concatenate((times_os,np.ones((len(times_os),1))*0.0),1), + np.concatenate((times_of,np.ones((len(times_of),1))*1.0),1))) + times=times[times[:,0].argsort()] + return times + + +def calc_phase(time_vec, out, phase_diffs): + times = calc_on_offsets(time_vec,out) + ref_onsets = times[np.logical_and(times[:,1]==0,times[:,3]==0)][:,0] + phase_dur=np.append(ref_onsets[1:]-ref_onsets[:-1],np.nan) + + p = times[times[:,1]==0] + indices = np.where(np.diff(p[:,3])==1) + fl_phase_dur = np.zeros((len(ref_onsets))) + fl_phase_dur[:] = np.nan + fl_phase_dur[p[indices,2].astype(int)] = p[[ind+1 for ind in indices],0] - p[indices,0] + ex_phase_dur = phase_dur-fl_phase_dur + + M = np.zeros((len(ref_onsets),out.shape[1])) + M[:] = np.nan + M[:,0]=ref_onsets + + + for i in range(1,out.shape[1]): + p = times[np.logical_and((times[:,1]==0) | (times[:,1]==i),times[:,3]==0)] + indices = np.where(np.diff(p[:,1])==i) + M[p[indices,2].astype(int),i] = p[[ind+1 for ind in indices],0] + + + phases=np.zeros((len(ref_onsets),len(phase_diffs))) + for i,(x,y) in enumerate(phase_diffs): + phases[:,i] = ((M[:,y]-M[:,x])/phase_dur) % 1.0 + + if phases.shape[0]!=0: + no_nan = ~np.isnan(np.concatenate( + (np.stack((phase_dur,fl_phase_dur,ex_phase_dur),1),phases),1 + )).any(axis=1) + return (phase_dur[no_nan],fl_phase_dur[no_nan],ex_phase_dur[no_nan],phases[no_nan],ref_onsets[no_nan]) + else: + return (phase_dur,fl_phase_dur,ex_phase_dur,phases,ref_onsets[:-1]) + + +def plot_analysis(network: Network, network_options): + """ Plot analysis """ + plot_names = [ + 'right_fore_RG_F', + 'left_fore_RG_F', + 'right_hind_RG_F', + 'left_hind_RG_F', + ] + + plot_traces = [ + network.log.nodes[name].output.array for name in plot_names + ] + + _split_ramp = int(len(network.log.times.array)/2) + phases_up = calc_phase( + network.log.times.array[:_split_ramp], + (np.asarray(plot_traces[:4]).T)[:_split_ramp], + ((3, 2), (1, 0), (3, 1), (3, 0)) + ) + phases_down = calc_phase( + network.log.times.array[_split_ramp:], + (np.asarray(plot_traces[:4]).T)[_split_ramp:], + ((3, 2), (1, 0), (3, 1), (3, 0)) + ) + + alpha_vec = np.array(network.log.nodes["BS_input"].output.array) + + fig, ax = plt.subplots(4, 1, sharex='all') + for j in range(4): + ax[j].plot(alpha_vec[np.int32(phases_up[4])], phases_up[3][:, j], 'b*') + ax[j].plot(alpha_vec[np.int32(phases_down[4])], phases_down[3][:, j], 'r*') + + fig, ax = plt.subplots(len(plot_names)+2, 1, sharex='all') + #fig.canvas.set_window_title('Model Performance') + fig.suptitle('Model Performance', fontsize=12) + time_vec = np.array(network.log.times.array) + for i, tr in enumerate(plot_traces): + ax[i].plot(time_vec*0.001, np.array(tr), 'b', linewidth=1) + ax[i].grid('on', axis='x') + ax[i].set_ylabel(plot_names[i], fontsize=10) + ax[i].set_yticks([0, 1]) + + _width = 0.2 + colors = ['blue', 'green', 'red', 'black'] + for i, tr in enumerate(plot_traces): + if i > 3: + break + ax[len(plot_names)].broken_barh(get_gait_plot_from_neuron_act(tr), + (1.6-i*0.2, _width), facecolors=colors[i]) + + ax[len(plot_names)].broken_barh(get_gait_plot_from_neuron_act(plot_traces[3]), + (1.0, _width*4), facecolors=(0.2, 0.2, 0.2), alpha=0.5) + ax[len(plot_names)].set_ylim(1.0, 1.8) + ax[len(plot_names)].set_xlim(0) + ax[len(plot_names)].set_xlabel('Time') + ax[len(plot_names)].set_yticks([1.1, 1.3, 1.5, 1.7]) + ax[len(plot_names)].set_yticklabels(['RF', 'LF', 'RH', 'LH']) + ax[len(plot_names)].grid(True) + + ax[len(plot_names)+1].fill_between(time_vec*0.001, 0, alpha_vec, + color=(0.2, 0.2, 0.2), alpha=0.5) + ax[len(plot_names)+1].grid('on', axis='x') + ax[len(plot_names)+1].set_ylabel('ALPHA') + ax[len(plot_names)+1].set_xlabel('Time [s]') + + plt.show() + + def plot_data(network, network_options): plot_nodes = [ index @@ -603,11 +751,12 @@ def main(): # Generate the network # network_options = generate_network(int(1e4)) # network_options = generate_limb_circuit(int(5e4)) - network_options = generate_quadruped_circuit((4e3)) + network_options = generate_quadruped_circuit((1e3)) # plot_network(network_options) network = run_network(network_options) plot_data(network, network_options) + plot_analysis(network, network_options) # from abstract_control.control.generate import quadruped_siggraph_network From 991b45189ca8d49e7cd22c0746877a021261bd9c Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Thu, 6 Nov 2025 14:37:39 -0500 Subject: [PATCH 309/316] [CORE] Added option to fully disable logging --- examples/ijspeert07/run.py | 1 - examples/mouse/run.py | 1 - farms_network/core/data.py | 24 +++--------------------- farms_network/core/network.py | 6 ++---- farms_network/core/options.py | 14 ++++++-------- 5 files changed, 11 insertions(+), 35 deletions(-) diff --git a/examples/ijspeert07/run.py b/examples/ijspeert07/run.py index 6786a78..0b2252e 100644 --- a/examples/ijspeert07/run.py +++ b/examples/ijspeert07/run.py @@ -144,7 +144,6 @@ def generate_network(iterations=10000): timestep=float(1e-3), ), logs=options.NetworkLogOptions( - n_iterations=iterations, buffer_size=iterations, ) ) diff --git a/examples/mouse/run.py b/examples/mouse/run.py index 676b40a..115310a 100644 --- a/examples/mouse/run.py +++ b/examples/mouse/run.py @@ -382,7 +382,6 @@ def generate_quadruped_circuit( timestep=1.0, ), logs=options.NetworkLogOptions( - n_iterations=int(n_iterations), buffer_size=int(n_iterations), ), ) diff --git a/farms_network/core/data.py b/farms_network/core/data.py index 5d3fd51..f58084e 100644 --- a/farms_network/core/data.py +++ b/farms_network/core/data.py @@ -30,7 +30,6 @@ class NetworkData(NetworkDataCy): def __init__( self, - times, states, derivatives, connectivity, @@ -45,7 +44,6 @@ def __init__( super().__init__() - self.times = times self.states = states self.derivatives = derivatives self.connectivity = connectivity @@ -67,14 +65,6 @@ def __init__( def from_options(cls, network_options: NetworkOptions): """ From options """ - buffer_size = network_options.logs.buffer_size - times = DoubleArray1D( - array=np.full( - shape=buffer_size, - fill_value=0, - dtype=NPDTYPE, - ) - ) states = NetworkStates.from_options(network_options) derivatives = NetworkStates.from_options(network_options) connectivity = NetworkConnectivity.from_options(network_options) @@ -114,18 +104,7 @@ def from_options(cls, network_options: NetworkOptions): noise = NetworkNoise.from_options(network_options) - # nodes = np.array( - # [ - # NodeData.from_options( - # node_options, - # buffer_size=network_options.logs.buffer_size - # ) - # for node_options in network_options.nodes - # ], - # dtype=NodeDataCy - # ) return cls( - times=times, states=states, derivatives=derivatives, connectivity=connectivity, @@ -380,6 +359,7 @@ def from_options(cls, network_options: NetworkOptions): """ From options """ buffer_size = network_options.logs.buffer_size + times = DoubleArray1D( array=np.full( shape=buffer_size, @@ -388,7 +368,9 @@ def from_options(cls, network_options: NetworkOptions): ) ) states = NetworkLogStates.from_options(network_options) + connectivity = NetworkConnectivity.from_options(network_options) + noise = NetworkNoise.from_options(network_options) outputs = DoubleArray2D( diff --git a/farms_network/core/network.py b/farms_network/core/network.py index c9e3c72..1c34904 100644 --- a/farms_network/core/network.py +++ b/farms_network/core/network.py @@ -49,9 +49,6 @@ def __init__(self, network_options: NetworkOptions): # Internal default solver self.solver: RK4Solver = None - # Logs - self.buffer_size: int = self.options.logs.buffer_size - # Iteration if self.options.integration: self.timestep: float = self.options.integration.timestep @@ -72,7 +69,8 @@ def step(self, time): # Update logs def update_logs(self, time): - self._network_cy.update_logs(time) + if self.options.logs.enable: + self._network_cy.update_logs(time) def run(self, n_iterations: Optional[int] = None): """ Run the network for n_iterations """ diff --git a/farms_network/core/options.py b/farms_network/core/options.py index 4ec9e40..001b555 100644 --- a/farms_network/core/options.py +++ b/farms_network/core/options.py @@ -1202,14 +1202,13 @@ class NetworkLogOptions(Options): nodes_all (bool): Whether to log all nodes or only selected ones. Defaults to False. """ - def __init__(self, n_iterations: int, **kwargs): + def __init__(self, **kwargs): super().__init__(**kwargs) - self.n_iterations: int = n_iterations - assert isinstance(self.n_iterations, int), "iterations shoulde be an integer" - self.buffer_size: int = kwargs.pop('buffer_size', self.n_iterations) - if self.buffer_size == 0: - self.buffer_size = self.n_iterations + self.enable: bool = kwargs.pop('enable', True) + self.buffer_size: int = kwargs.pop('buffer_size') + if self.buffer_size < 0: + pylog.debug("Logging is disabled because buffer size is -1") assert isinstance(self.buffer_size, int), "buffer_size shoulde be an integer" self.nodes_all: bool = kwargs.pop("nodes_all", False) @@ -1219,5 +1218,4 @@ def __init__(self, n_iterations: int, **kwargs): @classmethod def from_options(cls, kwargs: Dict): """ From options """ - n_iterations = kwargs.pop("n_iterations") - return cls(n_iterations, **kwargs) + return cls(**kwargs) From e4049b5a1ca87cc8110bfae684f3d7a308f01094 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Tue, 11 Nov 2025 11:43:38 -0500 Subject: [PATCH 310/316] [OPTIONS] Added load method for loading from file path --- farms_network/core/options.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/farms_network/core/options.py b/farms_network/core/options.py index 001b555..cfcdcc4 100644 --- a/farms_network/core/options.py +++ b/farms_network/core/options.py @@ -1083,6 +1083,12 @@ def __init__(self, **kwargs): if kwargs: raise Exception(f'Unknown kwargs: {kwargs}') + @classmethod + def load(cls, file_path: str): + """ Load from file """ + opts = Options.load(file_path) + return NetworkOptions.from_options(opts) + @classmethod def from_options(cls, kwargs): """ From options """ From b0ceec5c69848810e134e3c4ecef13ec067b6eeb Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Mon, 17 Nov 2025 11:34:03 -0500 Subject: [PATCH 311/316] [CORE] Added get_node and get_edge helper methods to network options --- farms_network/core/options.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/farms_network/core/options.py b/farms_network/core/options.py index cfcdcc4..5f978fd 100644 --- a/farms_network/core/options.py +++ b/farms_network/core/options.py @@ -1151,6 +1151,20 @@ def __add__(self, other: Self): self.add_edge(edge) return self + def get_node(self, name: str): + """ Get node options from name """ + for node in self.nodes: + if name == node.name: + return node + raise KeyError(f"Node {name} not found!") + + def get_edge(self, source: str, target: str): + """ Get edge options for target and source node names """ + for edge in self.edges: + if (source == edge.source) and (target == edge.target): + return edge + raise KeyError(f"No edge between source node {source} and target noode {target} found!") + ################################# # Numerical Integration Options # From c555c80c78f4b6d50c4ba7008fcdf3bc2fc3bf0a Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Tue, 18 Nov 2025 22:32:20 -0500 Subject: [PATCH 312/316] [CORE] Removed unused derivatives pointer in network_cy --- farms_network/core/network_cy.pyx | 6 ------ 1 file changed, 6 deletions(-) diff --git a/farms_network/core/network_cy.pyx b/farms_network/core/network_cy.pyx index 7c230b9..68b8442 100644 --- a/farms_network/core/network_cy.pyx +++ b/farms_network/core/network_cy.pyx @@ -154,12 +154,6 @@ cdef class NetworkCy(ODESystem): # assert self._network.states == NULL self._network.derivatives = NULL - # if self.data.derivatives.indices.size > 0: - # self._network.derivatives_indices = &self.data.derivatives.indices[0] - # else: - # assert self._network.derivatives == NULL - self._network.derivatives_indices = NULL - if self.data.external_inputs.array.size > 0: self._network.external_inputs = &self.data.external_inputs.array[0] else: From 16c2e47cc829c857679a2ce15fb2dcc5c7a64ec1 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Wed, 19 Nov 2025 15:02:02 -0500 Subject: [PATCH 313/316] [DATA] Fixed nodes initialization for core data --- farms_network/core/data.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/farms_network/core/data.py b/farms_network/core/data.py index f58084e..d8f6c06 100644 --- a/farms_network/core/data.py +++ b/farms_network/core/data.py @@ -92,15 +92,7 @@ def from_options(cls, network_options: NetworkOptions): dtype=NPDTYPE, ) ) - nodes = [ - NodeData( - node_options.name, - NodeStates(states, node_index,), - NodeOutput(outputs, node_index,), - NodeExternalInput(external_inputs, node_index,), - ) - for node_index, node_options in enumerate(network_options.nodes) - ] + nodes = Nodes(network_options, states, outputs, external_inputs) noise = NetworkNoise.from_options(network_options) From 411feb5c63e514f49f4c9ef02c2f03bd392043b3 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Wed, 19 Nov 2025 15:45:56 -0500 Subject: [PATCH 314/316] [DATA] Refactored for better node level interface --- farms_network/core/data.py | 190 ++++++++++++------------------------- 1 file changed, 60 insertions(+), 130 deletions(-) diff --git a/farms_network/core/data.py b/farms_network/core/data.py index d8f6c06..a2c7cff 100644 --- a/farms_network/core/data.py +++ b/farms_network/core/data.py @@ -424,14 +424,14 @@ def __init__(self, network_options: NetworkOptions, states, outputs, external_in for idx, node_opt in enumerate(network_options.nodes): node = NodeData( node_opt.name, - NodeStates(states, idx), - NodeOutput(outputs, idx), - NodeExternalInput(external_inputs, idx), + NodeStates(states, idx, node_opt.name), + NodeOutput(outputs, idx, node_opt.name), + NodeExternalInput(external_inputs, idx, node_opt.name), ) self._nodes.append(node) self._name_to_index[node_opt.name] = idx - def __getitem__(self, key): + def __getitem__(self, key: str): # Access by index if isinstance(key, int): return self._nodes[key] @@ -449,38 +449,84 @@ def names(self): class NodeStates: - def __init__(self, network_states, node_index): + def __init__(self, network_states, node_index: int, node_name: str): + self.node_name = node_name self._network_states = network_states self._node_index = node_index - - @property - def array(self): + self.ndim = self._network_states.array.ndim start = self._network_states.indices[self._node_index] end = self._network_states.indices[self._node_index + 1] if start == end: - return None - return self._network_states.array[:, start:end] + self._has_states = False + else: + self._start_idx = start + self._end_idx = end + self._has_states = True + + @property + def values(self): + if not self._has_states: + raise ValueError(f"Node {self.node_name} has no states") + + if self.ndim == 1: + return self._network_states.array[self._start_idx:self._end_idx] + return self._network_states.array[:, self._start_idx:self._end_idx] + + @values.setter + def values(self, v: np.ndarray): + if not self._has_states: + raise ValueError(f"Node {self.node_name} has no states to be set") + assert v.dtype == np.float_, "Values must be of type double/float" + + if self.ndim == 1: + self._network_states.array[self._start_idx:self._end_idx] = v[:] + return + + raise AttributeError("Cannot assign to values in logging mode.") class NodeOutput: - def __init__(self, network_outputs, node_index): + def __init__(self, network_outputs, node_index: str, node_name: str): + self.node_name = node_name self._network_outputs = network_outputs + self.ndim = self._network_outputs.array.ndim self._node_index = node_index @property - def array(self): + def values(self): + if self.ndim == 1: + return self._network_outputs.array[self._node_index] return self._network_outputs.array[:, self._node_index] + @values.setter + def values(self, v: float): + + if self.ndim == 1: + self._network_outputs.array[self._node_index] = v + return + raise AttributeError("Cannot assign to values in logging mode.") + class NodeExternalInput: - def __init__(self, network_external_inputs, node_index): + def __init__(self, network_external_inputs, node_index: int, node_name: str): + self.node_name = node_name self._network_external_inputs = network_external_inputs + self.ndim = self._network_external_inputs.array.ndim self._node_index = node_index @property - def array(self): + def values(self): + if self.ndim == 1: + return self._network_external_inputs.array[self._node_index] return self._network_external_inputs.array[:, self._node_index] + @values.setter + def values(self, v: float): + if self.ndim == 1: + self._network_external_inputs.array[self._node_index] = v + return + raise AttributeError("Cannot assign to values in logging mode.") + class NodeData: """ Accesssor for Node Data """ @@ -496,119 +542,3 @@ def __init__( self.states = states self.output = output self.external_input = external_input - - -# class NodeData(NodeDataCy): -# """ Base class for representing an arbitrary node data """ - -# def __init__( -# self, -# name: str, -# states: "NodeStatesArray", -# output: "NodeOutputArray", -# external_input: "NodeExternalInputArray", -# ): -# """ Node data initialization """ - -# super().__init__() -# self.name = name -# self.states = states -# self.output = output -# self.external_input = external_input - -# @classmethod -# def from_options(cls, options: NodeOptions, buffer_size: int): -# """ Node data from class """ -# return cls( -# name=options.name, -# states=NodeStatesArray.from_options(options, buffer_size), -# output=NodeOutputArray.from_options(options, buffer_size), -# external_input=NodeExternalInputArray.from_options(options, buffer_size), -# ) - -# def to_dict(self, iteration: int = None) -> Dict: -# """ Concert data to dictionary """ -# return { -# 'states': self.states.to_dict(iteration), -# 'output': to_array(self.output.array), -# 'external_input': to_array(self.output.array), -# } - - -# class NodeStatesArray(DoubleArray2D): -# """ State array data """ - -# def __init__(self, array: NDARRAY_V2_D, names: List): -# super().__init__(array) -# self.names = names - -# @classmethod -# def from_options(cls, options: NodeOptions, buffer_size: int): -# """ State options """ -# nstates = options._nstates -# if nstates > 0: -# names = options.state.names -# array = np.full( -# shape=[buffer_size, nstates], -# fill_value=0, -# dtype=NPDTYPE, -# ) -# else: -# names = [] -# array = np.full( -# shape=[buffer_size, 0], -# fill_value=0, -# dtype=NPDTYPE, -# ) -# return cls(array=array, names=names) - -# def to_dict(self, iteration: int = None) -> Dict: -# """ Concert data to dictionary """ -# return { -# 'names': self.names, -# 'array': to_array(self.array) -# } - - -# class NodeOutputArray(DoubleArray1D): -# """ Output array data """ - -# def __init__(self, array: NDARRAY_V1_D): -# super().__init__(array) - -# @classmethod -# def from_options(cls, options: NodeOptions, buffer_size: int): -# """ State options """ -# array = np.full( -# shape=buffer_size, -# fill_value=0, -# dtype=NPDTYPE, -# ) -# return cls(array=array) - - -# class NodeExternalInputArray(DoubleArray1D): -# """ ExternalInput array data """ - -# def __init__(self, array: NDARRAY_V1_D): -# super().__init__(array) - -# @classmethod -# def from_options(cls, options: NodeOptions, buffer_size: int): -# """ State options """ -# array = np.full( -# shape=buffer_size, -# fill_value=0, -# dtype=NPDTYPE, -# ) -# return cls(array=array) - - -def main(): - - data = NetworkData(100) - print(data.nodes[0].states.names) - - -if __name__ == '__main__': - main() From 23ba7155981c4f467606bc9df85bbe437f6596d5 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Wed, 19 Nov 2025 15:54:41 -0500 Subject: [PATCH 315/316] [EX] Adapted examples for changes in data interface --- examples/ijspeert07/run.py | 5 +++-- examples/mouse/run.py | 15 +++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/examples/ijspeert07/run.py b/examples/ijspeert07/run.py index 0b2252e..84049e0 100644 --- a/examples/ijspeert07/run.py +++ b/examples/ijspeert07/run.py @@ -60,7 +60,7 @@ def oscillator_chain(network_options, n_oscillators, name_prefix, **kwargs): ) # Connect phase_diff = kwargs.get('axial_phi', -np.pi/2) - weight = kwargs.get('axial_w', 1e4) + weight = kwargs.get('axial_w', 1e1) connections = np.vstack( (np.arange(n_oscillators), np.roll(np.arange(n_oscillators), -1)))[:, :-1] @@ -102,7 +102,7 @@ def oscillator_double_chain(network_options, n_oscillators, **kwargs): # Connect double chain phase_diff = kwargs.get('anti_phi', np.pi) - weight = kwargs.get('anti_w', 1e4) + weight = kwargs.get('anti_w', 1e1) for n in range(n_oscillators): network_options.add_edge( options.OscillatorEdgeOptions( @@ -200,6 +200,7 @@ def run_network(network_options: options.NetworkOptions): # Integrate states = np.ones((iterations+1, network.nstates))*1.0 outputs = np.ones((iterations, network.nnodes))*1.0 + for iteration in tqdm(range(0, iterations), colour='green', ascii=' >='): # network.data.times.array[iteration] = iteration*timestep diff --git a/examples/mouse/run.py b/examples/mouse/run.py index 115310a..63747e0 100644 --- a/examples/mouse/run.py +++ b/examples/mouse/run.py @@ -442,8 +442,7 @@ def run_network(*args): # network.step() # network.evaluate(iteration*1e-3, states[iteration, :]) - inputs[drive_input_indices] = drive_vec[iteration]*drive - network.data.external_inputs.array[:] = inputs + network.data.nodes['BS_input'].external_input.values = drive_vec[iteration]*drive network.step(time) network.update_logs(time) @@ -639,7 +638,7 @@ def plot_analysis(network: Network, network_options): ] plot_traces = [ - network.log.nodes[name].output.array for name in plot_names + network.log.nodes[name].output.values for name in plot_names ] _split_ramp = int(len(network.log.times.array)/2) @@ -654,7 +653,7 @@ def plot_analysis(network: Network, network_options): ((3, 2), (1, 0), (3, 1), (3, 0)) ) - alpha_vec = np.array(network.log.nodes["BS_input"].output.array) + alpha_vec = np.array(network.log.nodes["BS_input"].output.values) fig, ax = plt.subplots(4, 1, sharex='all') for j in range(4): @@ -709,14 +708,14 @@ def plot_data(network, network_options): for index, node_index in enumerate(plot_nodes): plt.fill_between( np.array(network.log.times.array)*1e-3, - index + np.array(network.log.nodes[node_index].output.array), + index + np.array(network.log.nodes[node_index].output.values), index, alpha=0.2, lw=1.0, ) plt.plot( np.array(network.log.times.array)*1e-3, - index + np.array(network.log.nodes[node_index].output.array), + index + np.array(network.log.nodes[node_index].output.values), label=network.log.nodes[node_index].name, ) plt.legend() @@ -730,14 +729,14 @@ def plot_data(network, network_options): for index, node_index in enumerate(plot_nodes): plt.fill_between( np.array(network.log.times.array)*1e-3, - index + np.array(network.log.nodes[node_index].output.array), + index + np.array(network.log.nodes[node_index].output.values), index, alpha=0.2, lw=1.0, ) plt.plot( np.array(network.log.times.array)*1e-3, - index + np.array(network.log.nodes[node_index].output.array), + index + np.array(network.log.nodes[node_index].output.values), label=network.log.nodes[node_index].name, ) plt.legend() From 7661c2b9cb6ba928dfc1ec02f73d1a5afa60c0c7 Mon Sep 17 00:00:00 2001 From: Shravan Tata Date: Tue, 30 Dec 2025 17:28:26 -0500 Subject: [PATCH 316/316] [EX] Fixed phase difference weight sign in ijspeert --- examples/ijspeert07/run.py | 36 ++++++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/examples/ijspeert07/run.py b/examples/ijspeert07/run.py index 84049e0..699e636 100644 --- a/examples/ijspeert07/run.py +++ b/examples/ijspeert07/run.py @@ -60,7 +60,7 @@ def oscillator_chain(network_options, n_oscillators, name_prefix, **kwargs): ) # Connect phase_diff = kwargs.get('axial_phi', -np.pi/2) - weight = kwargs.get('axial_w', 1e1) + weight = kwargs.get('axial_w', 1e2) connections = np.vstack( (np.arange(n_oscillators), np.roll(np.arange(n_oscillators), -1)))[:, :-1] @@ -101,8 +101,8 @@ def oscillator_double_chain(network_options, n_oscillators, **kwargs): network_options = oscillator_chain(network_options, n_oscillators, 'right', **kwargs) # Connect double chain - phase_diff = kwargs.get('anti_phi', np.pi) - weight = kwargs.get('anti_w', 1e1) + phase_diff = kwargs.get('anti_phi', 2*np.pi/3) + weight = kwargs.get('anti_w', 1e2) for n in range(n_oscillators): network_options.add_edge( options.OscillatorEdgeOptions( @@ -123,7 +123,7 @@ def oscillator_double_chain(network_options, n_oscillators, **kwargs): weight=weight, type="excitatory", parameters=options.OscillatorEdgeParameterOptions( - phase_difference=phase_diff + phase_difference=-1*phase_diff ), visual=options.EdgeVisualOptions(), ) @@ -149,13 +149,13 @@ def generate_network(iterations=10000): ) # Generate rhythm centers - n_oscillators = 10 + n_oscillators = 9 network_options = oscillator_double_chain(network_options, n_oscillators) graph = nx.node_link_graph( network_options, directed=True, multigraph=False, - link="edges", + edges="edges", name="name", source="source", target="target" @@ -216,6 +216,7 @@ def run_network(network_options: options.NetworkOptions): network.step(iteration*timestep) network.update_logs(iteration*timestep) + names = network.data.nodes.names() plt.figure() for j in range(int(network.nnodes/2)): plt.fill_between( @@ -224,11 +225,29 @@ def run_network(network_options: options.NetworkOptions): 2*j, alpha=0.2, lw=1.0, + label=names[j] ) plt.plot( np.array(network.log.times.array), 2*j + (1 + np.sin(network.log.outputs.array[:, j])), - label=f"{j}" + # label=f"{j}" + label="_nolegend_" + ) + for j in range(int(network.nnodes/2), int(network.nnodes)): + k = j - int(network.nnodes/2) + plt.fill_between( + np.array(network.log.times.array), + 2*k + (1 + np.sin(np.array(network.log.outputs.array[:, j]))), + 2*k, + alpha=0.2, + lw=1.0, + label=names[j] + ) + plt.plot( + np.array(network.log.times.array), + 2*k + (1 + np.sin(network.log.outputs.array[:, j])), + # label=f"{j}" + label="_nolegend_" ) plt.legend() @@ -236,7 +255,7 @@ def run_network(network_options: options.NetworkOptions): network_options, directed=True, multigraph=False, - link="edges", + edges="edges", name="name", source="source", target="target" @@ -274,6 +293,7 @@ def run_network(network_options: options.NetworkOptions): for edge, data in graph.edges.items() ], width=1., + alpha=np.array(network.data.connectivity.weights)/1e2, arrowsize=10, style='dashed', arrows=True,