├── MANIFEST.in ├── demo ├── osc.png └── oscillator.py ├── CTRNN ├── __init__.py └── ctrnn.py ├── CITATION.cff ├── setup.py ├── LICENSE ├── README.md └── tests └── test_basic.py /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE 2 | -------------------------------------------------------------------------------- /demo/osc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madvn/CTRNN/HEAD/demo/osc.png -------------------------------------------------------------------------------- /CTRNN/__init__.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Packagifying CTRNN 3 | Created Dec 18, 2017 4 | Madhavun Candadai 5 | ''' 6 | 7 | from CTRNN.ctrnn import CTRNN 8 | -------------------------------------------------------------------------------- /CITATION.cff: -------------------------------------------------------------------------------- 1 | cff-version: 1.2.0 2 | message: "If you use this software, please cite it as below." 3 | authors: 4 | - family-names: "Candadai" 5 | given-names: "Madhavun" 6 | orcid: "https://orcid.org/0000-0002-9254-8641" 7 | title: "CTRNN" 8 | version: 2.0 9 | date-released: 2020-03-10 10 | url: "https://github.com/madvn/CTRNN" 11 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | 4 | #with open('README.md') as f: 5 | readme = None 6 | 7 | with open('LICENSE') as f: 8 | license = f.read() 9 | 10 | setup( 11 | name='CTRNN', 12 | version='2.0', 13 | description='A package that implements Continuous Time Recurrent Neural Networks', 14 | long_description=readme, 15 | author='Madhavun Candadai', 16 | author_email='madvncv@gmail.com', 17 | url='https://github.com/madvn/CTRNN', 18 | license=license, 19 | packages=['CTRNN'], 20 | install_requires=['numpy','scipy'] 21 | ) 22 | -------------------------------------------------------------------------------- /demo/oscillator.py: -------------------------------------------------------------------------------- 1 | # imports 2 | import numpy as np 3 | import matplotlib.pyplot as plt 4 | # importing the CTRNN class 5 | from CTRNN import CTRNN 6 | 7 | # params 8 | run_duration = 250 9 | net_size = 2 10 | step_size = 0.01 11 | 12 | # set up network 13 | network = CTRNN(size=net_size,step_size=step_size) 14 | network.taus = [1.,1.] 15 | network.biases = [-2.75,-1.75] 16 | network.weights[0,0] = 4.5 17 | network.weights[0,1] = 1 18 | network.weights[1,0] = -1 19 | network.weights[1,1] = 4.5 20 | 21 | # initialize network 22 | network.randomize_outputs(0.1,0.2) 23 | 24 | # simulate network 25 | outputs = [] 26 | for _ in range(int(run_duration/step_size)): 27 | network.euler_step([0]*net_size) # zero external_inputs 28 | outputs.append([network.outputs[i] for i in range(net_size)]) 29 | outputs = np.asarray(outputs) 30 | 31 | # plot oscillator output 32 | plt.plot(np.arange(0,run_duration,step_size),outputs[:,0]) 33 | plt.plot(np.arange(0,run_duration,step_size),outputs[:,1]) 34 | plt.xlabel('Time') 35 | plt.ylabel('Neuron outputs') 36 | plt.show() 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Madhavun Candadai Vasu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | CTRNN 2 | ========================= 3 | Python package that implements Continuous Time Recurrent Neural Networks (CTRNNs) 4 | 5 | See Beer, R.D. (1995). On the dynamics of small continuous-time recurrent neural networks. Adaptive Behavior 3:469-509. for a study of CTRNNs. 6 | 7 | Using this repo in conjunction with [StochSearch](https://github.com/madvn/stochsearch) allows you to optimize CTRNNs to perform different tasks. Stochsearch uses Python's multiprocessing framework to parallelize population based stochastic search optimization methods. 8 | 9 | You can also find a Tensorflow version of building and implementing a population of CTRNNs [here](https://github.com/madvn/TFSearch/blob/master/examples/CTRNN_oscillator/CTRNN.py). Refer to this same repo for optimizing CTRNNs using Tensorflow on a GPU. 10 | 11 | Installation instructions 12 | ------------------------- 13 | $ pip install CTRNN 14 | 15 | Requirements: numpy, scipy 16 | 17 | Citing 18 | ------------------ 19 | @misc{Candadai_CTRNN_2020, 20 | author = {Candadai, Madhavun}, 21 | month = {3}, 22 | title = {CTRNN}, 23 | url = {https://github.com/madvn/CTRNN}, 24 | year = {2020} 25 | } 26 | 27 | Using the package 28 | ------------------ 29 | #### Importing the CTRNN package: 30 | from CTRNN import CTRNN 31 | #### Creating a CTRNN object: 32 | cns = CTRNN(network_size,step_size=0.1) 33 | weights are initialized randomly; gains, time-constants and biases are set to 1 34 | #### Setting gain for neuron i: 35 | cns.gains[i] = 1 36 | where i is in range [0,network_size) 37 | #### Setting gain for all neurons: 38 | cns.gains = [1,2,3,..] 39 | with list of size=network_size 40 | #### Setting biases and time-constants (taus) is similar 41 | cns.biases 42 | cns.taus 43 | #### Setting weights to neuron i from neuron j: 44 | cns.weights[i,j] = 3 45 | where i,j in range [0,network_size) 46 | #### Setting weights as a matrix: 47 | from scipy.sparse import csr_matrix 48 | cns.weights = csr_matrix(weights_matrix) 49 | where weights_matrix is of size=network_sizeXnetwork_size 50 | #### Euler stepping the network: 51 | cns.euler_step(external_inputs) 52 | where external_inputs is a list of size=network_size 53 | #### Accessing/Setting output of neuron i: 54 | print(cns.outputs[i]) 55 | cns.outputs[i] = 0.5 56 | where i in range [0,network_size) and output in range [0,1] 57 | #### Accessing/Setting output of all neurons: 58 | print(cns.outputs) 59 | cns.outputs = [0.5,0.75,0.4] 60 | where list is of size=network_size 61 | #### Same as above for states 62 | cns.states 63 | where state values can range in (-inf,inf) 64 | #### Randomizing states/outputs 65 | cns.randomize_states(ub,lb) 66 | upper bound and lower bound in range (-inf,inf) 67 | 68 | cns.randomize_outputs(ub,lb) 69 | upper bound and lower bound in [0,1] 70 | 71 | Example 72 | ------- 73 | 74 | The following code creates a 2-neuron CTRNN sinusoidal oscillator, See demo folder:: 75 | 76 | # imports 77 | import numpy as np 78 | import matplotlib.pyplot as plt 79 | # importing the CTRNN class 80 | from CTRNN import CTRNN 81 | 82 | # params 83 | run_duration = 250 84 | net_size = 2 85 | step_size = 0.01 86 | 87 | # set up network 88 | network = CTRNN(size=net_size,step_size=step_size) 89 | network.taus = [1.,1.] 90 | network.biases = [-2.75,-1.75] 91 | network.weights[0,0] = 4.5 92 | network.weights[0,1] = 1 93 | network.weights[1,0] = -1 94 | network.weights[1,1] = 4.5 95 | 96 | # initialize network 97 | network.randomize_outputs(0.1,0.2) 98 | 99 | # simulate network 100 | outputs = [] 101 | for _ in range(int(run_duration/step_size)): 102 | network.euler_step([0]*net_size) # zero external_inputs 103 | outputs.append([network.outputs[i] for i in range(net_size)]) 104 | outputs = np.asarray(outputs) 105 | 106 | # plot oscillator output 107 | plt.plot(np.arange(0,run_duration,step_size),outputs[:,0]) 108 | plt.plot(np.arange(0,run_duration,step_size),outputs[:,1]) 109 | plt.xlabel('Time') 110 | plt.ylabel('Neuron outputs') 111 | plt.show() 112 | 113 | Output: 114 | 115 | ![demo/osc.png][osc_output] 116 | 117 | [osc_output]: https://raw.githubusercontent.com/madvn/CTRNN/master/demo/osc.png 118 | -------------------------------------------------------------------------------- /CTRNN/ctrnn.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from scipy.sparse import csr_matrix 3 | 4 | class CTRNN: 5 | 6 | def __init__(self,size=2,step_size=0.1): 7 | ''' 8 | Constructer that initializes a random network 9 | with unit time-constants and biases 10 | ARGS: 11 | size: integer = network size 12 | step_size:float = euler integration step size 13 | ''' 14 | self.size = size 15 | self.step_size = step_size 16 | self.taus = np.ones(size) 17 | self.biases = np.ones(size) 18 | self.gains = np.ones(size) 19 | self.weights = csr_matrix(np.random.rand(size,size)) 20 | self.states = np.random.rand(size) 21 | self.outputs = self.sigmoid(self.states) 22 | 23 | @property 24 | def taus(self): return self.__taus 25 | 26 | @property 27 | def biases(self): return self.__biases 28 | 29 | @property 30 | def gains(self): return self.__gains 31 | 32 | @property 33 | def states(self): return self.__states 34 | 35 | @property 36 | def outputs(self): return self.__outputs 37 | 38 | @taus.setter 39 | def taus(self,ts): 40 | ''' 41 | Set time-constants 42 | args = ts:array[size,] = time-constant for each neuron 43 | ''' 44 | if len(ts) != self.size: 45 | raise Exception("Size mismatch error - len(taus) != network_size") 46 | self.__taus = np.asarray(ts) 47 | 48 | @biases.setter 49 | def biases(self,bis): 50 | ''' 51 | Set biases 52 | args = bis:array[size,] = bias for each neuron 53 | ''' 54 | if len(bis) != self.size: 55 | raise Exception("Size mismatch - len(biases) != network_size") 56 | self.__biases = np.asarray(bis) 57 | 58 | @gains.setter 59 | def gains(self,gs): 60 | ''' 61 | Set gains 62 | args = gs:array[size,] = gain for each neuron 63 | ''' 64 | if len(gs) != self.size: 65 | raise Exception("Size mismatch - len(gains) != network_size") 66 | self.__gains = np.asarray(gs) 67 | 68 | @states.setter 69 | def states(self,s): 70 | ''' 71 | Set states 72 | args = s:array[size,] = state for each neuron 73 | ''' 74 | if len(s) != self.size: 75 | raise Exception("Size mismatch - len(states) != network_size") 76 | self.__states = np.asarray(s) 77 | self.__outputs = self.sigmoid(s) 78 | 79 | @outputs.setter 80 | def outputs(self,o): 81 | ''' 82 | Set outputs 83 | args = o:array[size,] = output for each neuron 84 | ''' 85 | if len(o) != self.size: 86 | raise Exception("Size mismatch - len(outputs) != network_size") 87 | self.__outputs = np.asarray(o) 88 | self.__states = self.inverse_sigmoid(o)/self.gains - self.biases 89 | 90 | def randomize_states(self,lb,ub): 91 | ''' 92 | Randomize states in range [lb,ub] 93 | args = lb:float = lower bound for random range 94 | ub:float = upper bound for random range 95 | ''' 96 | self.states = np.random.uniform(lb,ub,size=(self.size)) 97 | 98 | def randomize_outputs(self,lb,ub): 99 | ''' 100 | Randomize outputs in range [lb,ub] 101 | args = lb:float = lower bound for random range 102 | ub:float = upper bound for random range 103 | ''' 104 | self.outputs = np.random.uniform(lb,ub,size=(self.size)) 105 | 106 | def euler_step(self,external_inputs): 107 | ''' 108 | Euler stepping the network by self.step_size with provided inputs 109 | args = external_inputs:array[size,] = one float input per neuron 110 | ''' 111 | if len(external_inputs) != self.size: 112 | raise Exception("Size mismatch - len(external_inputs) != network_size") 113 | external_inputs = np.asarray(external_inputs) 114 | total_inputs = external_inputs + self.weights.dot(self.outputs) 115 | self.states += self.step_size*(1/self.taus)* (total_inputs - self.states) 116 | self.outputs = self.sigmoid(self.gains*(self.states+self.biases)) 117 | 118 | def sigmoid(self,s): 119 | ''' 120 | Computes the sigmoid function on input array 121 | args = s:array of any Size 122 | output = sigmoid(s):array of same size as input 123 | ''' 124 | return 1/(1+np.exp(-s)) 125 | 126 | def inverse_sigmoid(self,o): 127 | ''' 128 | Computes the inverse of the sigmoid function 129 | args = o:array of any size 130 | returns = inverse_sigmoid(o):array same size as o 131 | ''' 132 | inverse_sig = np.log(o/(1-o)) 133 | #inverse_sig[np.isinf(inverse_sig)] = 0. 134 | return inverse_sig 135 | 136 | #CTRNN = CTRNN() 137 | if __name__ == "__main__": 138 | print('CTRNN python package.') 139 | -------------------------------------------------------------------------------- /tests/test_basic.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from CTRNN import CTRNN 3 | 4 | test_net_size = 3 5 | 6 | def show_exception(message,e): 7 | print(message) 8 | print(e) 9 | 10 | def test_initialization(): 11 | ''' 12 | Inits a CTRNN and returns True if succesful 13 | ''' 14 | try: 15 | print("Initing network of size 3...") 16 | test_net_size = 3 17 | ns = CTRNN(test_net_size) 18 | print("Done!") 19 | return True 20 | except Exception as e: 21 | show_exception("Raised exception with ns = CTRNN(test_net_size)",e) 22 | return False 23 | 24 | def test_modification(): 25 | ''' 26 | Tests different kinds of modification and returns any(results) 27 | ''' 28 | results = [] 29 | # init first 30 | test_net_size = 3 31 | ns = CTRNN(test_net_size) 32 | 33 | print("**Testing taus and biases") 34 | print("Before",ns.taus,ns.biases) 35 | try: 36 | ns.taus = np.random.rand(test_net_size) 37 | results.append(True) 38 | except Exception as e: 39 | show_exception("Raised exception with ns.taus = np.random.rand(test_net_size)",e) 40 | results.append(False) 41 | pass 42 | try: 43 | ns.biases = np.random.rand(test_net_size) 44 | results.append(True) 45 | except Exception as e: 46 | show_exception("Raised exception with ns.biases = np.random.rand(test_net_size)",e) 47 | results.append(False) 48 | pass 49 | print("After",ns.taus,ns.biases) 50 | 51 | try: 52 | ns.taus = np.random.rand(test_net_size-1) 53 | results.append(False) 54 | except Exception as e: 55 | show_exception("Raised exception for ns.taus = np.random.rand(test_net_size-1)",e) 56 | results.append(True) 57 | 58 | try: 59 | ns.biases = np.random.rand(test_net_size-1) 60 | results.append(False) 61 | except Exception as e: 62 | show_exception("Raised exception for ns.biases = np.random.rand(test_net_size-1)",e) 63 | results.append(True) 64 | 65 | print("**Testing weights") 66 | #ns.weights = np.random.rand([test_net_size,test_net_size]) 67 | print("Before ",ns.weights[1,2],type(ns.weights)) 68 | try: 69 | ns.weights[test_net_size-1,test_net_size-1] = 3.45 70 | results.append(True) 71 | except Exception as e: 72 | show_exception("Raised exception with ns.weights[test_net_size-1,test_net_size-1] = 3.45",e) 73 | results.append(False) 74 | pass 75 | print("After ",ns.weights[1,2],type(ns.weights)) 76 | try: 77 | ns.weights[test_net_size+1,test_net_size-1] = 3.45 78 | results.append(False) 79 | except Exception as e: 80 | show_exception("Raised exception for ns.weights[test_net_size+1,test_net_size-1] = 3.45",e) 81 | results.append(True) 82 | 83 | print("**Testing states") 84 | print("Before ",ns.states,ns.outputs) 85 | try: 86 | ns.states = np.ones(test_net_size)*0.5 87 | results.append(True) 88 | except Exception as e: 89 | show_exception("Raised exception with ns.states = np.ones(test_net_size)*0.5",e) 90 | results.append(False) 91 | pass 92 | print("After ",ns.states,ns.outputs) 93 | try: 94 | ns.states = np.random.rand(test_net_size-1) 95 | results.append(False) 96 | except Exception as e: 97 | show_exception("Raised exception with ns.states = np.random.rand(test_net_size-1)",e) 98 | results.append(True) 99 | 100 | print("Before ",ns.states,ns.outputs) 101 | try: 102 | ns.randomize_states(0.5,0.6) 103 | results.append(True) 104 | except Exception as e: 105 | show_exception("Raised exception with ns.randomize_states(0.5,0.6)",e) 106 | results.append(False) 107 | pass 108 | print("After ",ns.states,ns.outputs) 109 | 110 | print("**Testing outputs") 111 | print("Before ",ns.states,ns.outputs) 112 | try: 113 | ns.outputs = np.ones(test_net_size)*0.5 114 | results.append(True) 115 | except Exception as e: 116 | show_exception("Raised exception with ns.outputs = np.ones(test_net_size)*0.5",e) 117 | results.append(False) 118 | pass 119 | print("After ",ns.states,ns.outputs) 120 | try: 121 | ns.outputs = np.random.rand(test_net_size-1) 122 | results.append(False) 123 | except Exception as e: 124 | show_exception("Raised exception with ns.outputs = np.random.rand(test_net_size-1)",e) 125 | results.append(True) 126 | 127 | print("Before ",ns.states,ns.outputs) 128 | try: 129 | ns.randomize_outputs(0.5,0.6) 130 | results.append(True) 131 | except Exception as e: 132 | show_exception("Raised exception with ns.randomize_outputs(0.5,0.6)",e) 133 | results.append(False) 134 | pass 135 | print("After ",ns.states,ns.outputs) 136 | 137 | return any(results) 138 | 139 | def test_simulation(): 140 | ''' 141 | Euler steps a CTRNN and returns True if no exceptions were raised 142 | ''' 143 | ns = CTRNN(test_net_size) 144 | print("**Stepping network for 50 time steps") 145 | print("Before ",ns.states,ns.outputs) 146 | try: 147 | for _ in range(50): 148 | ns.euler_step(np.random.rand(test_net_size)) 149 | result = True 150 | except Exception as e: 151 | show_exception("Simulation error:",e) 152 | result = False 153 | 154 | print("After ",ns.states,ns.outputs,result) 155 | return result 156 | 157 | def run_basic_test(): 158 | ''' 159 | Main test function - runs each sub-test 160 | ''' 161 | print("************************************** Basic test of CTRNN.py **************************************") 162 | print("Contact madcanda@indiana.edu if test fails or other bugs are discovered") 163 | 164 | print("\n## Testing Initialization") 165 | assert test_initialization(),"CTRNN Initialization Test Failed" 166 | print("**Initialization Test Passed**") 167 | 168 | print("\n## Testing Modification") 169 | assert test_modification(),"CTRNN Modification Test Failed" 170 | print("**CTRNN Modification Test Passed**") 171 | 172 | print("\n## Testing Simulation") 173 | assert test_simulation(),"CTRNN Simulation Test Failed" 174 | print("**CTRNN Simulation Test Passed**") 175 | 176 | print("\nALL TESTS PASSED!!") 177 | 178 | if __name__ == "__main__": 179 | run_basic_test() 180 | --------------------------------------------------------------------------------