This page explains the steps needed to connect The Virtual Brain models with external simulators using NRPCore. Contrary to other simulators with Python API, like OpenSim, TVB doesn't have a dedicated engine. You should use Python JSON Engine directly. To do this, you need to define a class called Script
, which inherits from EngineScript
. The base class takes care of all tasks related to simulation control and data exchange. In particular, it provides the following methods, in which calls to TVB API should be embedded:
initialize()
: executed when the engine is initialized
runLoop(timestep_ns)
: executed when the engine is requested to advance its simulation (from EngineClient::runLoopStep)
shutdown()
: executed when the engine is requested to shutdown
A fully working example is available under examples/opensim_tvb/
, with the code related to TVB located in examples/opensim_tvb/tvb_engine.py
.
Defining proxy nodes in the brain model
Proxy nodes allow to inject external signals into the brain. The following code creates a single proxy node labeled "Arm":
56 connectivity.region_labels = np.concatenate([connectivity.region_labels,
58 number_of_regions = connectivity.region_labels.shape[0]
59 arm_proxy_index = number_of_regions - np.array([1]).astype(
'i')
61 maxCentres = np.max(connectivity.centres, axis=0)
62 connectivity.centres = np.concatenate([connectivity.centres,
63 -maxCentres[np.newaxis],
64 maxCentres[np.newaxis]])
65 connectivity.areas = np.concatenate([connectivity.areas,
66 np.array([connectivity.areas.min()])])
67 connectivity.hemispheres = np.concatenate([connectivity.hemispheres,
69 connectivity.cortical = np.concatenate([connectivity.cortical,
You may want to connect the newly created region into other regions:
74 for attr, val
in zip([
"weights",
"tract_lengths"], [1.0, MIN_TRACT_LENGTH]):
75 prop = getattr(connectivity, attr).copy()
76 prop = np.concatenate([prop, np.zeros((number_of_regions - len(motor_regions), len(motor_regions)))], axis=1)
77 prop = np.concatenate([prop, np.zeros((len(motor_regions), number_of_regions))], axis=0)
78 prop[arm_proxy_index[0], motor_index[0]] = val
79 prop[motor_index[0], arm_proxy_index[0]] = val
80 setattr(connectivity, attr, prop)
Extending the Simulator class
An extended version of the Simulator class, which is able to handle data coming from the proxy regions, is available in the TVB cosimulation package:
from tvb.contrib.cosimulation.cosimulator import CoSimulator as Simulator
After creating the CoSimulator object, some of its variables need to be set:
voi
: variables of interest. These are the state variables that will be exchanged with the proxy region.
proxy_inds
: indexes of the proxy nodes
exclusive
: must be set to True to indicate that data from proxy regions are simulated externaly
121 simulator.voi = np.array([0, 1])
122 simulator.proxy_inds = self.iF
126 simulator.cosim_monitors = (CosimCoupling(), )
127 simulator.cosim_monitors[0].coupling.a = np.array([1.0])
128 simulator.exclusive =
True
Injecting data into the proxy region
The following piece of code shows how to inject position of an external agent into the proxy region:
165 def simulate_fun(self, simulator, elbow_position, elbow_velocity):
166 motor_commands_data = deepcopy(simulator.loop_cosim_monitor_output()[0])
167 motor_commands_data[1] = motor_commands_data[1][:, :, self.iF, :]
169 input = deepcopy(self.prev_state)
174 input[1][0, 0, 0, 0] = elbow_position * 5
180 input[0][0] -= simulator.integrator.dt
184 dtres = list(simulator(cosim_updates=input))[0]
186 sim_res = list(dtres)
188 sim_res[0][0] = np.array([sim_res[0][0]])
189 sim_res[0][1] = sim_res[0][1][np.newaxis]
191 self.prev_state[0] = deepcopy(motor_commands_data[0])
192 self.prev_state[1] = deepcopy(motor_commands_data[1])
194 return motor_commands_data, sim_res