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)