def test_2_4_vectorization(): """Testing vectorization functionality of ComputeGraph class. See Also -------- :method:`_vectorize`: Detailed documentation of vectorize method of `ComputeGraph` class. """ backends = ['tensorflow', 'numpy'] for b in backends: # test whether vectorized networks produce same output as non-vectorized backend ################################################################################ # define simulation params dt = 1e-2 sim_time = 10. sim_steps = int(sim_time / dt) inp = np.zeros((sim_steps, 2)) + 0.5 # set up networks net_config0 = CircuitTemplate.from_yaml( "model_templates.test_resources.test_backend.net12").apply() net_config1 = CircuitTemplate.from_yaml( "model_templates.test_resources.test_backend.net12").apply() net0 = ComputeGraph(net_config=net_config0, name='net0', vectorization='none', dt=dt, build_in_place=False, backend=b) net1 = ComputeGraph(net_config=net_config1, name='net1', vectorization='nodes', dt=dt, build_in_place=False, backend=b) # simulate network behaviors results0 = net0.run(sim_time, outputs={ 'a': 'pop0/op7/a', 'b': 'pop1/op7/a' }, inputs={'all/op7/inp': inp}) results1 = net1.run(sim_time, outputs={ 'a': 'pop0/op7/a', 'b': 'pop1/op7/a' }, inputs={'all/op7/inp': inp}, out_dir='/tmp/log') error1 = nmrse(results0.values, results1.values) assert np.sum(results1.values) > 0. assert np.mean(error1) == pytest.approx(0., rel=1e-6, abs=1e-6)
def test_2_5_solver(): """Testing different numerical solvers of pyrates. See Also -------- :method:`_solve`: Detailed documentation of how to numerical integration is performed by the `NumpyBackend`. :method:`run`: Detailed documentation of the method that needs to be called to solve differential equations in the `NumpyBackend`. """ backend = 'numpy' # define input dt = 1e-3 sim_time = 100. sim_steps = int(np.round(sim_time / dt, decimals=0)) inp = np.zeros((sim_steps, 1)) + 0.5 # standard euler solver (trusted) net_config = CircuitTemplate.from_yaml( "model_templates.test_resources.test_backend.net13").apply( label='net0') net = net_config.compile(vectorization=True, step_size=dt, backend=backend, solver='euler') results = net.run(sim_time, outputs={ 'a1': 'p1/op9/a', 'a2': 'p2/op9/a' }, inputs={'p1/op9/I_ext': inp}) net.clear() # scipy solver (tested) net_config2 = CircuitTemplate.from_yaml( "model_templates.test_resources.test_backend.net13").apply( label='net1') net2 = net_config2.compile(vectorization=True, step_size=dt, backend=backend, solver='scipy') results2 = net2.run(sim_time, outputs={ 'a1': 'p1/op9/a', 'a2': 'p2/op9/a' }, inputs={'p1/op9/I_ext': inp}, method='RK23') net2.clear() assert np.mean(results.loc[:, 'a2'].values - results2.loc[:, 'a2'].values) == pytest.approx(0., rel=1e-4, abs=1e-4)
def test_op_caching_nodes(): """Test the case that two operator templates are identical up to variable definitions, where one inherits from the other""" from pyrates.frontend import CircuitTemplate circuit = CircuitTemplate.from_yaml("model_templates.test_resources.test_operator_caching_templates.TestCircuit1").apply() # access nodes node1 = circuit["node1"] node2 = circuit["node2"] # verify that operator labels and contents are identical op1, op1_dict = next(iter(node1.op_graph.nodes(data=True))) op2, op2_dict = next(iter(node2.op_graph.nodes(data=True))) assert op1 == op2 assert op1_dict == op2_dict assert op1_dict["operator"] is op2_dict["operator"] # verify that values in nodes are indeed different (but refer to same operator) assert node1.values["TestOpLin0"]["c"] == 0 assert node2.values["TestOpLin0"]["c"] == 1 # now do the vectorization step circuit = circuit.optimize_graph_in_place()
def eval_fitness(self, genes: list, gene_map: list, loss_func: callable, loss_kwargs: Optional[dict] = None, run_func: Optional[callable] = None, template: Optional[str] = None, compile_kwargs: Optional[dict] = None, run_kwargs: Optional[dict] = None) -> float: """Performs simulation in PyRates of the model defined by `template` and calculates the fitness based on the resulting timeseries of that simulation. Parameters ---------- genes List of model parameters gene_map List of string-based variable pointers that indicate which gene refers to which variable in the model. loss_func loss_kwargs run_func User-specified run function that can be specified instead of a template, to run user-specified routines. template compile_kwargs run_kwargs Returns ------- """ if run_func is None: attempts = 1 while attempts < 100: try: # load model template model_id = self.get_unique_id(int(attempts*1e6)) if type(template) is str: template = CircuitTemplate.from_yaml(template) model = deepcopy(template).apply(label=f'model_{model_id}') # apply new parameters to model template params, param_map = dict(), dict() for i, (p, key) in enumerate(zip(genes, gene_map)): params[i] = p param_map[i] = key adapt_circuit(model, params=params, param_map=param_map) # compile model into backend model_compiled = model.compile(**compile_kwargs) # define run func run_func = model_compiled.run break except (FileExistsError, FileNotFoundError): attempts += 1 continue results = run_func(**run_kwargs) return loss_func(results, **loss_kwargs)
def test_2_1_operator(): """Testing operator functionality of compute graph class: See Also -------- :method:`add_operator`: Detailed documentation of method for adding operations to instance of `ComputeGraph`. """ backends = ["tensorflow", "numpy"] accuracy = 1e-4 for b in backends: # test correct numerical evaluation of operator with two coupled simple, linear equations ######################################################################################### # create net config from YAML file net_config = CircuitTemplate.from_yaml( "model_templates.test_resources.test_backend.net0").apply() # instantiate compute graph from net config dt = 1e-1 net = ComputeGraph(net_config=net_config, name='net0', vectorization=True, backend=b) # simulate operator behavior sim_time = 10.0 results = net.run(simulation_time=sim_time, step_size=dt, outputs={'a': 'pop0/op0/a'}) net.clear() # generate target values sim_steps = int(sim_time / dt) update0_1 = lambda x: x * 0.5 update0_0 = lambda x: x + 2.0 targets = np.zeros((sim_steps + 1, 2), dtype=np.float32) for i in range(sim_steps): targets[i + 1, 0] = targets[i, 0] + dt * update0_0(targets[i, 1]) targets[i + 1, 1] = targets[i, 1] + dt * update0_1(targets[i, 0]) # compare results with target values diff = results['a'].values[:, 0] - targets[1:, 1] assert np.mean(np.abs(diff)) == pytest.approx(0., rel=accuracy, abs=accuracy) # test correct numerical evaluation of operator with a single differential equation and external input ###################################################################################################### # set up operator in pyrates net_config = CircuitTemplate.from_yaml( "model_templates.test_resources.test_backend.net1").apply() net = ComputeGraph(net_config=net_config, name='net1', vectorization=True, backend=b) # define input inp = np.zeros((sim_steps, )) + 0.5 # simulate operator behavior results = net.run(sim_time, step_size=dt, inputs={'pop0/op1/u': inp}, outputs={'a': 'pop0/op1/a'}) net.clear() # calculate operator behavior from hand update1 = lambda x, y: x + dt * (y - x) targets = np.zeros((sim_steps + 1, 1), dtype=np.float32) for i in range(sim_steps): targets[i + 1] = update1(targets[i], inp[i]) diff = results['a'].values[:, 0] - targets[1:, 0] assert np.mean(np.abs(diff)) == pytest.approx(0., rel=accuracy, abs=accuracy) # test correct numerical evaluation of operator with two coupled equations (1 ODE, 1 non-DE eq.) ################################################################################################ net_config = CircuitTemplate.from_yaml( "model_templates.test_resources.test_backend.net2").apply() net = ComputeGraph(net_config=net_config, name='net2', vectorization=True, backend=b) results = net.run(sim_time, outputs={'a': 'pop0/op2/a'}, step_size=dt) net.clear() # calculate operator behavior from hand update2 = lambda x: 1. / (1. + np.exp(-x)) targets = np.zeros((sim_steps + 1, 2), dtype=np.float32) for i in range(sim_steps): targets[i + 1, 1] = update2(targets[i, 0]) targets[i + 1, 0] = update1(targets[i, 0], targets[i + 1, 1]) diff = results['a'].values[:, 0] - targets[1:, 0] assert np.mean(np.abs(diff)) == pytest.approx(0., rel=accuracy, abs=accuracy) # test correct numerical evaluation of operator with a two coupled DEs ###################################################################### net_config = CircuitTemplate.from_yaml( "model_templates.test_resources.test_backend.net3").apply() net = ComputeGraph(net_config=net_config, name='net3', vectorization=True, backend=b) results = net.run(sim_time, outputs={'b': 'pop0/op3/b'}, inputs={'pop0/op3/u': inp}, out_dir="/tmp/log", step_size=dt) net.clear() # calculate operator behavior from hand update3_0 = lambda a, b, u: a + dt * (-10. * a + b**2 + u) update3_1 = lambda b, a: b + dt * 0.1 * a targets = np.zeros((sim_steps + 1, 2), dtype=np.float32) for i in range(sim_steps): targets[i + 1, 0] = update3_0(targets[i, 0], targets[i, 1], inp[i]) targets[i + 1, 1] = update3_1(targets[i, 1], targets[i, 0]) diff = results['b'].values[:, 0] - targets[1:, 1] assert np.mean(np.abs(diff)) == pytest.approx(0., rel=accuracy, abs=accuracy)
def test_2_3_edge(): """Testing edge functionality of compute graph class. See Also -------- :method:`add_edge`: Detailed documentation of add_edge method of `ComputeGraph`class. """ backends = ['numpy', 'tensorflow'] accuracy = 1e-4 for b in backends: # test correct numerical evaluation of graph with 1 source projecting unidirectional to 2 target nodes ###################################################################################################### # set up edge in pyrates dt = 1e-1 sim_time = 10. sim_steps = int(sim_time / dt) net_config = CircuitTemplate.from_yaml( "model_templates.test_resources.test_backend.net8").apply() net = ComputeGraph(net_config=net_config, name='net0', vectorization=True, backend=b) # calculate edge behavior from hand update0 = lambda x, y: x + dt * y * 0.5 update1 = lambda x, y: x + dt * (y + 2.0) update2 = lambda x, y: x + dt * (y - x) targets = np.zeros((sim_steps + 1, 4), dtype=np.float32) for i in range(sim_steps): targets[i + 1, 0] = update0(targets[i, 0], targets[i, 1]) targets[i + 1, 1] = update1(targets[i, 1], targets[i, 0]) targets[i + 1, 2] = update2(targets[i, 2], targets[i, 0] * 2.0) targets[i + 1, 3] = update2(targets[i, 3], targets[i, 0] * 0.5) # simulate edge behavior results = net.run(sim_time, outputs={ 'a': 'pop1/op1/a', 'b': 'pop2/op1/a' }, step_size=dt) net.clear() diff = np.mean(np.abs(results['a'].values[:, 0] - targets[1:, 2])) + \ np.mean(np.abs(results['b'].values[:, 0] - targets[1:, 3])) assert diff == pytest.approx(0., rel=accuracy, abs=accuracy) # test correct numerical evaluation of graph with 2 bidirectionaly coupled nodes ################################################################################ # define input inp = np.zeros((sim_steps, 1)) + 0.5 net_config = CircuitTemplate.from_yaml( "model_templates.test_resources.test_backend.net9").apply() net = ComputeGraph(net_config=net_config, name='net1', vectorization=True, backend=b) results = net.run(sim_time, outputs={ 'a': 'pop0/op1/a', 'b': 'pop1/op7/a' }, inputs={'pop1/op7/inp': inp}, step_size=dt) net.clear() # calculate edge behavior from hand update3 = lambda x, y, z: x + dt * (y + z - x) targets = np.zeros((sim_steps + 1, 2), dtype=np.float32) for i in range(sim_steps): targets[i + 1, 0] = update2(targets[i, 0], targets[i, 1] * 0.5) targets[i + 1, 1] = update3(targets[i, 1], targets[i, 0] * 2.0, inp[i]) diff = np.mean(np.abs(results['a'].values[:, 0] - targets[1:, 0])) + \ np.mean(np.abs(results['b'].values[:, 0] - targets[1:, 1])) assert diff == pytest.approx(0., rel=accuracy, abs=accuracy) # test correct numerical evaluation of graph with 2 bidirectionally delay-coupled nodes ####################################################################################### net_config = CircuitTemplate.from_yaml( "model_templates.test_resources.test_backend.net10").apply() net = ComputeGraph(net_config=net_config, name='net2', vectorization=True, backend=b, step_size=dt) results = net.run(sim_time, outputs={ 'a': 'pop0/op8/a', 'b': 'pop1/op8/a' }, step_size=dt) net.clear() # calculate edge behavior from hand delay0 = int(0.5 / dt) delay1 = int(1. / dt) targets = np.zeros((sim_steps + 1, 2), dtype=np.float32) update4 = lambda x, y: x + dt * (2.0 + y) for i in range(sim_steps): inp0 = 0. if i < delay0 else targets[i - delay0, 1] inp1 = 0. if i < delay1 else targets[i - delay1, 0] targets[i + 1, 0] = update4(targets[i, 0], inp0 * 0.5) targets[i + 1, 1] = update4(targets[i, 1], inp1 * 2.0) diff = np.mean(np.abs(results['a'].values[:, 0] - targets[1:, 0])) + \ np.mean(np.abs(results['b'].values[:, 0] - targets[1:, 1])) assert diff == pytest.approx(0., rel=accuracy, abs=accuracy) # test correct numerical evaluation of graph with 2 unidirectionally, multi-delay-coupled nodes ############################################################################################### # define input inp = np.zeros((sim_steps, 1)) + 0.5 net_config = CircuitTemplate.from_yaml( "model_templates.test_resources.test_backend.net9").apply() net = ComputeGraph(net_config=net_config, name='net3', vectorization=True, step_size=dt, backend=b) results = net.run(sim_time, step_size=dt, outputs={ 'a': 'pop0/op1/a', 'b': 'pop1/op7/a' }, inputs={'pop1/op7/inp': inp}) net.clear() # calculate edge behavior from hand update3 = lambda x, y, z: x + dt * (y + z - x) targets = np.zeros((sim_steps + 1, 2), dtype=np.float32) for i in range(sim_steps): targets[i + 1, 0] = update2(targets[i, 0], targets[i, 1] * 0.5) targets[i + 1, 1] = update3(targets[i, 1], targets[i, 0] * 2.0, inp[i]) diff = np.mean(np.abs(results['a'].values[:, 0] - targets[1:, 0])) + \ np.mean(np.abs(results['b'].values[:, 0] - targets[1:, 1])) assert diff == pytest.approx(0., rel=accuracy, abs=accuracy) # test correct numerical evaluation of graph with delay distributions ##################################################################### # define input dt = 1e-2 sim_time = 100. sim_steps = int(np.round(sim_time / dt, decimals=0)) inp = np.zeros((sim_steps, 1)) + 0.5 net_config = CircuitTemplate.from_yaml( "model_templates.test_resources.test_backend.net13").apply( label='net4') net = net_config.compile(vectorization=True, step_size=dt, backend=b, solver='euler') results = net.run(sim_time, outputs={ 'a1': 'p1/op9/a', 'a2': 'p2/op9/a' }, inputs={'p1/op9/I_ext': inp}) # TODO: add evaluation of network behavior by hand and compare results against that net.clear()
def test_2_2_node(): """Testing node functionality of compute graph class. See Also -------- :method:`add_node`: Detailed documentation of method for adding nodes to instance of `ComputeGraph`. """ backends = ['numpy', 'tensorflow'] accuracy = 1e-4 for b in backends: # test correct numerical evaluation of node with 2 operators, where op1 projects to op2 ####################################################################################### # set up node in pyrates dt = 1e-1 sim_time = 10. sim_steps = int(sim_time / dt) net_config = CircuitTemplate.from_yaml( "model_templates.test_resources.test_backend.net4").apply() net = ComputeGraph(net_config=net_config, name='net0', vectorization=True, backend=b) # simulate node behavior results = net.run(sim_time, outputs={'a': 'pop0/op1/a'}, step_size=dt) net.clear() # calculate node behavior from hand update0 = lambda x: x + dt * 2. update1 = lambda x, y: x + dt * (y - x) targets = np.zeros((sim_steps + 1, 2), dtype=np.float32) for i in range(sim_steps): targets[i + 1, 0] = update0(targets[i, 0]) targets[i + 1, 1] = update1(targets[i, 1], targets[i + 1, 0]) diff = results['a'].values[:, 0] - targets[:-1, 1] assert np.mean(np.abs(diff)) == pytest.approx(0., rel=accuracy, abs=accuracy) # test correct numerical evaluation of node with 2 independent operators ######################################################################## net_config = CircuitTemplate.from_yaml( "model_templates.test_resources.test_backend.net5").apply() net = ComputeGraph(net_config=net_config, name='net1', vectorization=True, backend=b) # simulate node behavior results = net.run(sim_time, outputs={'a': 'pop0/op5/a'}, step_size=dt) net.clear() # calculate node behavior from hand targets = np.zeros((sim_steps + 1, 2), dtype=np.float32) for i in range(sim_steps): targets[i + 1, 0] = update0(targets[i, 0]) targets[i + 1, 1] = update1(targets[i, 1], 0.) diff = results['a'].values[:, 0] - targets[:-1, 1] assert np.mean(np.abs(diff)) == pytest.approx(0., rel=accuracy, abs=accuracy) # test correct numerical evaluation of node with 2 independent operators projecting to the same target operator ############################################################################################################### net_config = CircuitTemplate.from_yaml( "model_templates.test_resources.test_backend.net6").apply() net = ComputeGraph(net_config=net_config, name='net2', vectorization=True, backend=b) results = net.run(sim_time, outputs={'a': 'pop0/op1/a'}, step_size=dt) net.clear() # calculate node behavior from hand targets = np.zeros((sim_steps + 1, 3), dtype=np.float32) update2 = lambda x: x + dt * (4. + np.tanh(0.5)) for i in range(sim_steps): targets[i + 1, 0] = update0(targets[i, 0]) targets[i + 1, 1] = update2(targets[i, 1]) targets[i + 1, 2] = update1(targets[i, 2], targets[i + 1, 0] + targets[i + 1, 1]) diff = results['a'].values[:, 0] - targets[:-1, 2] assert np.mean(np.abs(diff)) == pytest.approx(0., rel=accuracy, abs=accuracy) # test correct numerical evaluation of node with 1 source operator projecting to 2 independent targets ###################################################################################################### net_config = CircuitTemplate.from_yaml( "model_templates.test_resources.test_backend.net7").apply() net = ComputeGraph(net_config=net_config, name='net3', vectorization=True, backend=b) results = net.run(sim_time, outputs={ 'a': 'pop0/op1/a', 'b': 'pop0/op3/b' }, step_size=dt) # calculate node behavior from hand targets = np.zeros((sim_steps + 1, 4), dtype=np.float32) update3 = lambda a, b, u: a + dt * (-10. * a + b**2 + u) update4 = lambda x, y: x + dt * 0.1 * y for i in range(sim_steps): targets[i + 1, 0] = update0(targets[i, 0]) targets[i + 1, 1] = update1(targets[i, 1], targets[i + 1, 0]) targets[i + 1, 2] = update3(targets[i, 2], targets[i, 3], targets[i + 1, 0]) targets[i + 1, 3] = update4(targets[i, 3], targets[i, 2]) diff = np.mean(np.abs(results['a'].values[:, 0] - targets[:-1, 1])) + \ np.mean(np.abs(results['b'].values[:, 0] - targets[:-1, 3])) assert diff == pytest.approx(0., rel=accuracy, abs=accuracy)
from pyrates.utility.visualization import Interactive2DParamPlot # parameter definition dt = 1e-3 dts = 1e-2 cutoff = 100.0 T = 150.0 + cutoff start = int((0 + cutoff)/dt) dur = int(5/(0.6*dt)) steps = int(T/dt) inp = np.zeros((steps, 1)) inp[start:start+dur] = 0.6 # model setup path = "../config/spinal_cord/sc" template = CircuitTemplate.from_yaml(path).apply() plot_network_graph(template) model = template.compile(backend='numpy', solver='scipy', step_size=dt) # simulation results = model.run(simulation_time=T, step_size=dt, sampling_step_size=dts, inputs={'m1/m1_dummy/m_in': inp}, outputs={ 'psp': 'muscle/muscle_op/I_acc', 'alpha_exc': 'alpha/alpha_op/I_ampa', 'alpha_inh': 'alpha/alpha_op/I_glycin', 'renshaw': 'renshaw/renshaw_op/I_acc' } )
from pyrates.frontend import CircuitTemplate # %% # Step 2: Loading a model template from the `model_templates` library # ------------------------------------------------------------------- # # In the second step, we load the model template for an excitatory QIF population that comes with PyRates via the # :code:`from_yaml()` method of the :code:`CircuitTemplate`. This method returns a :code:`CircuitTemplate` instance # which provides the method :code:`apply()` for turning it into a graph-based representation, i.e. a # :code:`pyrates.ir.CircuitIR` instance. Have a look at the yaml definition of the model that can be found at the path # used for the :code:`from_yaml()` method. You will see that all variables and parameters are already defined there. # These are the basic steps you perform, if you want to load a model that is # defined inside a yaml file. To check out the different model templates provided by PyRates, have a look at # the :code:`PyRates.model_templates` module. qif_circuit = CircuitTemplate.from_yaml( "model_templates.montbrio.simple_montbrio.QIF_exc").apply() # %% # Step 3: Loading the model into the backend # ------------------------------------------ # # In this example, we directly load the :code:`CircuitIR` instance into the backend via the :code:`compile()` method # without any further changes to the graph. This way, a :code:`pyrates.backend.NumpyBackend` instance is created. # After this step, structural modifications of the network are not possible anymore. qif_compiled = qif_circuit.compile(backend='numpy', step_size=1e-3) # %% # Step 4: Numerical simulation of a the model behavior in time # ------------------------------------------------------------ #
import matplotlib.pyplot as plt import os import numpy as np # parameters dt = 5e-4 T = 50.0 start = int(10.0 / dt) stop = int(12.0 / dt) dts = 1e-2 inp = np.zeros((int(T / dt), 1)) inp[start:stop] = 1.0 # target: delayed biexponential feedback biexp = CircuitTemplate.from_yaml("config/stn_gpe/biexp_gamma") r1 = biexp.run(simulation_time=T, sampling_step_size=dts, inputs={'n1/biexp_rate/I_ext': inp}, outputs={'r': 'n1/biexp_rate/r'}, backend='numpy', step_size=dt, solver='euler') # fig, ax = plt.subplots() # ax.plot(r1['r']) # plt.show() # approximation: gamma-distributed feedback param_grid = { 'd': np.asarray([5.0, 6.0, 7.0]), 's': np.asarray([1.0, 1.5, 2.0])
# additional imports import numpy as np import matplotlib.pyplot as plt dt = 1e-1 # integration step size in s dts = 5e-1 # variable storage sub-sampling step size in s sub = int(dts / dt) # sub-sampling rate T = 1000.0 # total simulation time in ms inp = np.zeros((int(T / dt), 1), dtype='float32') # external input to the population start = 200.0 stop = 400.0 inp[int(start / dt):int(stop / dt), :] = 10.0 circuit = CircuitTemplate.from_yaml( "model_templates.wilson_cowan.simple_wilsoncowan.RC").apply() compute_graph = ComputeGraph(circuit, vectorization=True, backend='numpy', name='wc_net', step_size=dt, solver='scipy') result, t = compute_graph.run( T, inputs={"P1/Op_rate/I_ext": inp}, outputs={ "r1": "P1/Op_rate/r", "r2": "P2/Op_rate/r" }, sampling_step_size=dts,
from pyrates.frontend import CircuitTemplate # %% # Step 2: Loading a model template from the `model_templates` library # ------------------------------------------------------------------- # # In the second step, we load a model template for the Jansen-Rit model that comes with PyRates via the # :code:`from_yaml()` method of the :code:`CircuitTemplate`. This method returns a :code:`CircuitTemplate` instance # which provides the method :code:`apply()` for turning it into a graph-based representation, i.e. a # :code:`pyrates.ir.CircuitIR` instance. Have a look at the yaml definition of the model that can be found at the path # used for the :code:`from_yaml()` method. You will see that all variables and parameters are already defined there. # These are the basic steps you perform, if you want to load a model that is # defined inside a yaml file. To check out the different model templates provided by PyRates, have a look at # the :code:`PyRates.model_templates` module. jrc = CircuitTemplate.from_yaml( "model_templates.jansen_rit.simple_jansenrit.JRC_simple").apply() # %% # Step 3: Loading the model into the backend # ------------------------------------------ # # In this example, we directly load the :code:`CircuitIR` instance into the backend via the :code:`compile()` method # without any further changes to the graph. This way, a :code:`pyrates.backend.NumpyBackend` instance is created. # After this step, structural modifications of the network are not possible anymore. Here, we choose scipy as a solver # for our differential equation system. The default is the forward Euler method that is implemented in PyRates itself. # Generally, the scipy solver is both more accurate and faster and thus the recommended solver in PyRates. jrc_compiled = jrc.compile(backend='numpy', step_size=1e-4, solver='scipy') # %% # Step 4: Numerical simulation of a the model behavior in time
# simulations ############# outputs = { 'stn': 'stn/stn_op/R', 'gpe-p': 'gpe_p/gpe_p_op/R', 'gpe-a': 'gpe_a/gpe_a_op/R', 'msn-d1': 'msn_d1/msn_d1_op/R', 'msn-d2': 'msn_d2/msn_d2_op/R', 'fsi': 'fsi/fsi_op/R', } for c_dict in deepcopy(conditions): model = CircuitTemplate.from_yaml("config/stn_gpe_str/stn_gpe_str") model = model.update_var(node_vars=node_updates, edge_vars=edge_updates) results = model.run(simulation_time=T, step_size=dt, sampling_step_size=dts, outputs=outputs.copy(), solver='scipy', verbose=True, method='RK23', atol=1e-5, rtol=1e-4, clear=True) clear_frontend_caches() fig, ax = plt.subplots(figsize=(6, 2.0), dpi=dpi) results = results * 1e3 for key in outputs: ax.plot(results.loc[:, key]) plt.legend(list(outputs.keys())) ax.set_ylabel('Firing rate') ax.set_xlabel('time (ms)') # ax.set_xlim([4000.0, 5000.0]) # ax.set_ylim([0.0, 50.0]) ax.tick_params(axis='both', which='major', labelsize=9)
from pyrates.utility.visualization import plot_timeseries, create_cmap # additional imports import numpy as np import matplotlib.pyplot as plt dt = 1e-3 # integration step size in s dts = 1e-3 # variable storage sub-sampling step size in s sub = int(dts/dt) # sub-sampling rate T = 800.0 # total simulation time in s #inp = np.zeros((int(T/dt), 1), dtype='float32') # external input to the population #inp[int(20./dt):int((T-20.)/dt)] = 5. circuit = CircuitTemplate.from_yaml("model_templates.montbrio.simple_montbrio.QIF_sfa_exp" ).apply(node_values={'p/Op_sfa_exp/eta': -3.96, 'p/Op_sfa_exp/J': 15.0*np.sqrt(2.0), 'p/Op_sfa_exp/alpha': 0.7} ) compute_graph = circuit.compile(vectorization=True, backend='numpy', name='montbrio', solver='scipy') result, t = compute_graph.run(T, step_size=dt, #inputs={"E/Op_e_adapt/inp": inp}, outputs={"r": "p/Op_sfa_exp/r", "v": "p/Op_sfa_exp/v", "A": "p/Op_sfa_exp/I_a"}, sampling_step_size=dts, profile='t', verbose=True, method='LSODA' )
dt = 1e-3 dts = 1e-2 cutoff = 100.0 T = 200.0 + cutoff start = int((0 + cutoff) / dt) dur = int(5 / (0.6 * dt)) steps = int(T / dt) inp = np.zeros((steps, 1)) inp[start:start + dur] = 0.6 # target: delayed biexponential response of the alpha or renshaw neuron path = "../config/spinal_cord/sc" neuron = 'alpha' target_var = 'I_ampa' model = CircuitTemplate.from_yaml(path).apply().compile(backend='numpy', step_size=dt, solver='euler') r1 = model.run(simulation_time=T, sampling_step_size=dts, inputs={'m1/m1_dummy/m_in': inp}, outputs={neuron: f'{neuron}/{neuron}_op/{target_var}'}) model.clear() r1.plot() plt.show() # approximation: gamma-distributed feedback source = 'm1' param_grid = { 'd': np.asarray([1.5, 2.0, 2.5]), 's': np.asarray([0.4, 0.6, 0.8, 1.0]) }
def test_2_6_inputs_outputs(): """Tests the input-output interface of the run method in circuits of different hierarchical depth. See Also ------- :method:`CircuitIR.run` detailed documentation of how to use the arguments `inputs` and `outputs`. """ backend = 'numpy' dt = 1e-3 sim_time = 100. sim_steps = int(np.round(sim_time / dt, decimals=0)) # define inputs and outputs for each population separately ########################################################## # define input inp1 = np.zeros((sim_steps, 1)) + 0.5 inp2 = np.zeros((sim_steps, 1)) + 0.2 # perform simulation net_config = CircuitTemplate.from_yaml( "model_templates.test_resources.test_backend.net13").apply( label='net1') net = net_config.compile(vectorization=True, step_size=dt, backend=backend, solver='scipy') r1 = net.run(sim_time, outputs={ 'a1': 'p1/op9/a', 'a2': 'p2/op9/a' }, inputs={ 'p1/op9/I_ext': inp1, 'p2/op9/I_ext': inp2 }) net.clear() # define input and output for both populations simultaneously ############################################################# backend = 'numpy' # define input inp = np.zeros((sim_steps, 2)) inp[:, 0] = 0.5 inp[:, 1] = 0.2 # perform simulation net_config = CircuitTemplate.from_yaml( "model_templates.test_resources.test_backend.net13").apply( label='net2') net = net_config.compile(vectorization=True, step_size=dt, backend=backend, solver='scipy') r2 = net.run(sim_time, outputs={'a': 'all/op9/a'}, inputs={'all/op9/I_ext': inp}) net.clear() assert np.mean(r1.values.flatten() - r2.values.flatten()) == pytest.approx( 0., rel=1e-4, abs=1e-4) # repeat in a network with 2 hierarchical levels of node organization ##################################################################### # define input inp3 = np.zeros((sim_steps, 1)) + 0.1 inp4 = np.zeros((sim_steps, 1)) # perform simulation nc1 = CircuitTemplate.from_yaml( "model_templates.test_resources.test_backend.net14").apply( label='net3') n1 = nc1.compile(vectorization=True, step_size=dt, backend=backend, solver='scipy') r1 = n1.run(sim_time, outputs={ 'a1': 'c1/p1/op9/a', 'a2': 'c1/p2/op9/a', 'a3': 'c2/p1/op9/a', 'a4': 'c2/p2/op9/a' }, inputs={ 'c1/p1/op9/I_ext': inp1, 'c1/p2/op9/I_ext': inp2, 'c2/p1/op9/I_ext': inp3, 'c2/p2/op9/I_ext': inp4 }) n1.clear() # define input inp = np.zeros((sim_steps, 4)) inp[:, 0] = 0.5 inp[:, 1] = 0.2 inp[:, 2] = 0.1 # perform simulation nc2 = CircuitTemplate.from_yaml( "model_templates.test_resources.test_backend.net14").apply( label='net4') n2 = nc2.compile(vectorization=True, step_size=dt, backend=backend, solver='scipy') r2 = n2.run(sim_time, outputs={'a': 'all/all/op9/a'}, inputs={'all/all/op9/I_ext': inp}) n2.clear() assert np.mean(r1.values.flatten() - r2.values.flatten()) == pytest.approx( 0., rel=1e-4, abs=1e-4)
# documentation can be found # `here <https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.differential_evolution.html>`_. All # arguments that :code:`scipy.optimize.differential_evolution` takes can also be provided as keyword arguments to the # :code:`run()` method. By providing a :code:`template`, :code:`compile_kwargs` and :code:`run_kwargs`, the # :code:`run()` method knows that it should use the provided model template, load it into the backend via # :code:`CircuitIR.compile(**compile_kwargs)` and then simulate its behavior via :code:`CircuitIR.run(**run_kwargs)`. # The resulting timeseries is then forwarded to the :code:`loss_func` together with the keyword arguments in # :code:`loss_kwargs`. # # The return value of the :code:`run()` method contains the winning parameter set and its loss function value. # Let's check out, whether this model parameter indeed produces the behavior we optimized for: from pyrates.frontend import CircuitTemplate from matplotlib.pyplot import show jr_temp = CircuitTemplate.from_yaml(model_template).apply() jr_temp.set_node_var('JRC/JRC_op/c', winner.at[0, 'C']) jr_comp = jr_temp.compile(solver='scipy', backend='numpy', step_size=1e-4) results = jr_comp.run(simulation_time=3.0, sampling_step_size=1e-2, outputs={ 'V_pce': 'JRC/JRC_op/PSP_pc_e', 'V_pci': 'JRC/JRC_op/PSP_pc_i' }) results = results['V_pce'] - results['V_pci'] results.plot() jr_comp.clear() show() # %%
# %% # Part 1: Creating a PyAuto Instance # ================================== # # In this first part, we will be concerned with how to create a model representation that is compatible with auto-07p, # which is the software that is used for parameter continuations and bifurcation analysis in PyRates [2]_. # %% # Step 1: Load the model into PyRates # ----------------------------------- # # As a first step, we have to load the model into PyRates. This is done the usual way. If you are not familiar with # this, check out the example galleries for model definitions. from pyrates.frontend import CircuitTemplate qif = CircuitTemplate.from_yaml("model_templates.montbrio.simple_montbrio.QIF_exc").apply(label='qif') # %% # Step 2: Load the model into the backend # --------------------------------------- # # Here, we will parse the model equations into the PyRates backend. If you are not familiar with this, check out the # example galleries for model compilation and simulation. This step, however, differs slightly from the usual way. # We need to use the :code:`FortranBackend`, since :code:`auto07-p` requires a fortran file with the model equations # and initial values [2]_. Furthermore, we need to set the keyword argument :code:`auto_compat` of the :code:`compile()` # method to :code:`True`, to indicate to the :code:`FortranBackend` that it should compile the model equations in a way # that is compatible with auto-07p [2]_. qif_compiled = qif.compile(backend='fortran', auto_compat=True) # %%
References ^^^^^^^^^^ .. [1] B.H. Jansen & V.G. Rit (1995) *Electroencephalogram and visual evoked potential generation in a mathematical model of coupled cortical columns.* Biological Cybernetics, 73(4): 357-366. """ # %% # Part 1: Translating a model definition into an intermediate representation # -------------------------------------------------------------------------- # # First, we will need to load a model template into PyRates. We choose to load a YAML-based model definition, but this # can be done just as well from a Python-based model definition. from pyrates.frontend import CircuitTemplate jr_template = CircuitTemplate.from_yaml( "model_templates.jansen_rit.simple_jansenrit.JRC") print(jr_template.nodes) print(jr_template.edges) # %% # As demonstrated by the :code:`print()` calls above, the :code:`CircuitTemplate` contains fields for nodes and edges, # which contain list of :code:`NodeTemplate` and :code:`EdgeTemplate` instances with :code:`OperatorTemplate` instances # defined on them that contain the underlying model equations. This template can be transformed into an intermediate # representation via the :code:`apply()` method defined for each template class: jr_ir = jr_template.apply() print(jr_ir.nodes) print(jr_ir.edges)
def grid_search(circuit_template: Union[CircuitTemplate, str], param_grid: Union[dict, pd.DataFrame], param_map: dict, dt: float, simulation_time: float, inputs: dict, outputs: dict, sampling_step_size: Optional[float] = None, permute_grid: bool = False, init_kwargs: dict = None, **kwargs) -> tuple: """Function that runs multiple parametrizations of the same circuit in parallel and returns a combined output. Parameters ---------- circuit_template Path to the circuit template. param_grid Key-value pairs for each circuit parameter that should be altered over different circuit parametrizations. param_map Key-value pairs that map the keys of param_grid to concrete circuit variables. dt Simulation step-size in s. simulation_time Simulation time in s. inputs Inputs as provided to the `run` method of `:class:ComputeGraph`. outputs Outputs as provided to the `run` method of `:class:ComputeGraph`. sampling_step_size Sampling step-size as provided to the `run` method of `:class:ComputeGraph`. permute_grid If true, all combinations of the provided param_grid values will be realized. If false, the param_grid values will be traversed pairwise. kwargs Additional keyword arguments passed to the `:class:ComputeGraph` initialization. Returns ------- tuple Simulation results stored in a multi-index data frame, the mapping between the data frame column names and the parameter grid, the simulation time, and the memory consumption. """ # argument pre-processing ######################### if not init_kwargs: init_kwargs = {} vectorization = init_kwargs.pop('vectorization', 'nodes') if type(circuit_template) is str: circuit_template = CircuitTemplate.from_yaml(circuit_template) # linearize parameter grid if necessary if type(param_grid) is dict: param_grid = linearize_grid(param_grid, permute_grid) # create grid-structure of network ################################## # get parameter names and grid length param_keys = list(param_grid.keys()) N = param_grid.shape[0] # assign parameter updates to each circuit, combine them to unconnected network and remember their parameters circuit = CircuitIR() circuit_names = [] for idx in param_grid.index: new_params = {} for key in param_keys: new_params[key] = param_grid[key][idx] circuit_tmp = circuit_template.apply() circuit_key = f'{circuit_tmp.label}_{idx}' circuit_tmp = adapt_circuit(circuit_tmp, new_params, param_map) circuit.add_circuit(circuit_key, circuit_tmp) circuit_names.append(circuit_key) param_grid.index = circuit_names # create backend graph net = circuit.compile(dt=dt, vectorization=vectorization, **init_kwargs) # adjust input of simulation to combined network for inp_key, inp in inputs.copy().items(): inputs[f"all/{inp_key}"] = np.tile(inp, (1, N)) inputs.pop(inp_key) # adjust output of simulation to combined network for out_key, out in outputs.items(): outputs[out_key] = f"all/{out}" # simulate the circuits behavior results = net.run(simulation_time=simulation_time, inputs=inputs, outputs=outputs, sampling_step_size=sampling_step_size, **kwargs) # type: pd.DataFrame if 'profile' in kwargs: results, duration = results if 'profile' in kwargs: return results, param_grid, duration return results, param_grid
def benchmark(Ns, Ps, T, dt, init_kwargs, run_kwargs, disable_gpu=False): """Function that will run a benchmark simulation for each combination of N and P. Each benchmark simulation simulates the behavior of a neural population network, where the Jansen-Rit model is used for each of the N nodes and connections are drawn randomly, such that on overall coupling density of P is established. Parameters ---------- Ns Vector with network sizes. Ps Vector with coupling densities. T Overall simulation time. dt Integration step-size. init_kwargs Additional key-word arguments for the model initialization. run_kwargs Additional key-word arguments for running the simulation. disable_gpu If true, GPU devices will be disabled, so the benchmark will be run on the CPU only. Returns ------- tuple Simulation times, peak memory consumptions """ if disable_gpu: os.environ['CUDA_VISIBLE_DEVICES'] = '-1' else: os.environ['CUDA_VISIBLE_DEVICES'] = '0' times = np.zeros((len(Ns), len(Ps))) for i, n in enumerate(Ns): for j, p in enumerate(Ps): print(f'Running benchmark for n = {n} and p = {p}.') print("Setting up the network in PyRates...") # define inter-JRC connectivity C = np.random.uniform(size=(n, n)) C[C > p] = 0. c_sum = np.sum(C, axis=1) for k in range(C.shape[0]): if c_sum[k] != 0.: C[k, :] /= c_sum[k] conns = DataFrame( np.round(C, 3), columns=[f'jrc_{idx}/PC/PRO/m_out' for idx in range(n)]) conns.index = [f'jrc_{idx}/PC/RPO_e_pc/m_in' for idx in range(n)] # define input inp = 220 + np.asarray(np.random.randn(int(T / dt), n), dtype=np.float32) * 22. # set up template template = CircuitTemplate.from_yaml( "model_templates.jansen_rit.simple_jansenrit.JRC") # set up intermediate representation circuits = {} for idx in range(n): circuits[f'jrc_{idx}'] = deepcopy(template) circuit = CircuitIR.from_circuits(label='net', circuits=circuits, connectivity=conns) # set up compute graph net = circuit.compile(dt=dt, **init_kwargs) print("Starting the benchmark simulation...") # run simulations _, t = net.run(T, inputs={'all/PC/RPO_e_pc/u': inp}, outputs={'V': 'all/PC/OBS/V'}, verbose=False, **run_kwargs) times[i, j] = t print("Finished!") print(f'simulation time: {t} s.') return times