Interactive mode¶
It is quite easy to run NeuroDevSim in interactive mode, either in a notebook with plotting or from the terminal. However, the interactive mode comes with severe restrictions:
nothing is stored, all results are transient
there is no parallel computing so only simple models can be simulated
a complete simulation is either interactive or not, one cannot switch
Nevertheless the interactive mode can be quite useful to gain intuition, explore ideas and, especially, debug complex models. These use cases are introduced in the Interactive Mode notebook.
Basic interactive simulation¶
All that is needed to run an interactive simulation is to instantiate Admin_agent class with zero num_procs. A Front class needs to be defined because a neuron_type is required. A minimal set-up taken from the notebook example is:
from neurodevsim.simulator import *
class RandomFront(Front):
def manage_front(self,constellation):
pass
if __name__ == '__main__':
# initialize Admin_agent
sim_volume = [[-100., -100., -100.], [100.0,100.0,100.0]]
neuron_types = [RandomFront]
admin = Admin_agent(0,"",sim_volume,neuron_types,verbose=0,plot=True) # interactive mode
constellation = admin.constellation
Notice that the manage_front method is empty, it will not be used. If desired one can have the normal manage_front
present as reference, this may be useful while debugging. Because there is no database output no file name needs to be specified for Admin_agent
.
The Constellation class is obtained because this will be needed in many method calls.
Now any NeuroDevSim method can be called. Because most of these depend on object instantiation, relevant objects should be made first. For example, one could call admin.add_neurons
to make a soma:
fronts = admin.add_neurons(RandomFront,"rand_neuron",1,[[-30.,0.,0.],[-30,0.,0.]],5.)
soma = fronts[0]
Notice that in this case we capture the return of admin.add_neurons
so that the soma that was created can be accessed. Using soma.add_child
more fronts can now be created to simulate simple growth or any other Front
method can be called as desired. Remember that the fronts are not stored in the simulation database and if notebook plotting is enabled, new fronts need to plotted explicitly with admin.plot_item
:
line = admin.plot_item(soma,color='k')
There is no need to call admin.destruction
because no extra processes were spawned.
Interactive model debugging¶
Because NeuroDevSim simulations are not reproducible they can be very difficult to debug in a traditional way. Instead the output of a buggy simulation can be loaded with the import_simulation
method and interactive mode can be used to investigate what went wrong.
To use this approach effectively it is important to identify “problem” fronts. This information can be obtained either by printing out relevant front IDs during the simulation or by analyzing the database content as explained in Understanding the database.
Start an interactive session with the same sim_volume and neuron_types as used for the stored simulation, as shown above. Then import an existing simulation database:
...
admin = Admin_agent(0,"",sim_volume,neuron_types,verbose=0,plot=True) # interactive mode
constellation = admin.constellation
admin.import_simulation("simulation.db")
The complete simulation till the end will be loaded and because plot=True
plotted. For large simulations, plotting takes a lot of time. This can be prevented by plotting only a relevant region of the simulation, using the Admin_agent
box attribute:
admin = Admin_agent(0,"",sim_volume,neuron_types,verbose=0,plot=True,box=[[60.,60.,0.],[90.,90.,30.]])
To use box effectively one should know which region to focus on, usually centered around the “problem” front. It is best to use an isometric box, with idential ax lengths for each dimension. Only when a box is defined, the list of Front
plotted is available as admin.plot_items. If the number of fronts plotted is small, investigating this list is the fastest way to discover what is plotted.
After import_simulation
all fronts that existed at the end of the simulation are present in memory and can be accessed by their ID
.
If “problem” fronts were identified using print statements during the original simulation information like this will have been printed:
Front ID: neuron type 1, index 4005
the corresponding front can now be obtained with:
my_front = constellation.front_by_id(ID(1,4005))
If “problem” fronts were identified in the database, the procedure is a bit more complicated. Each front has two numerical identifiers in the database: a neuron_id and a front_id, see Understanding the database. Combined, these constitute a DataID
which is unfortunately different from ID
, but one can easily be converted into the other using constellation.data_to_id
:
my_ID = constellation.data_to_id(DataID(neuron_id,front_id))
my_front = constellation.front_by_id(my_ID)
Where is my_front in the simulation plot? As this is often not easy to recognize, one can make any plotted front flash during an interactive session:
admin.flash_front(my_front)
If the plot is crowded, it may have to be rotated before the flashes are visible. One can flash_front
as often as necessary.
The next steps depend on the type of problem to solve. Let’s, for example, look at a fatal collison during an add_child
call:
new_pos = ...
try:
new_front = my_front.add_child(constellation,new_pos)
print (new_front)
except CollisionError as error:
print (error)
colerror = error # error is only defined within the except scope
Either a new_front will be created and printed or a CollisionError
occurs and then this will be printed. If the error occurred, one could then try solve_collision
:
points = my_front.solve_collision(constellation,new_pos,colerror)
print (len(points),"points:",points)
If solve_collision
fails (it returns an empty list), maybe this is due to multiple colliding structures? By default only the first collision is detected as explained in Preventing and dealing with collisions, but this can be changed so that all collisions are detected:
constellation.only_first_collision = False # report all collisions
colerror = None
try:
new_front = my_front.add_child(constellation,new_pos)
print (new_front)
except CollisionError as error:
print (error)
colerror = error # error is only defined within the except scope
if colerror:
for f in colerror.collider: # print all the colliding structures
print (f)
The possibilities of the interactive mode are endless… One can test large sections of code, the behavior of specific methods, explore alternatives, etc. Just remember that nothing gets stored!
Finally, when investigating Modeling soma migration, the interactive mode has an additional useful feature: it can provide a history of the migration of any migrating soma that was loaded by import_simulation
with get_migration_history
:
my_somaID = constellation.data_to_id(DataID(neuron_id,front_id))
my_soma = constellation.front_by_id(my_somaID)
coordinates, cycles = my_soma.get_migration_history(constellation)
coordinates is a list of Point
representing my_soma.orig, from the location where it was created up to the last cycle, and cycles contains the corresponding cycle for each entry in coordinates. One can print this information or plot it:
lines = admin.plot_item(coordinates,color='k',line=True)