Advanced Tutorial

Subclassing Front

Every NeuroDevSim model requires at least one Front subclass definition with its own manage_front method:

class MyFront(Front):

    def manage_front(self,constellation):

This is how specific models are defined, as explained in Getting started. In this subsection additional, optional aspects of subclassing are described in detail.

Additional attributes

It may be useful to store data that is needed to control the behavior of fronts or that introduces new dynamics to the model as an additional Front attribute. There are strict rules on using additional attributes for a Front subclass:

  1. They need to be declared in the subclass definition using a special syntax shown below.

  2. They are all typed using data types defined in the Python ctypes library or as NeuroDevSim classes. Trying to set an attribute to a value of a different type will throw an error.

  3. They are all fixed size: no dictionaries, lists, strings,…

  4. They will be present in every instance of the subclass, instance specific attributes are not supported (see the Important dos and don’ts). So even if the attribute is functionally needed in only one or a few instances of the subclass, it will be present in all of them. Consider carefully whether an extra attribute is really needed.

The syntax to specify additional attributes is unusual, it is specific to how Python supports memory shared arrays. They are defined as a list of tuples, with each tuple containing the attribute name and and a ctypes data type:

[('attribute1_name', ctypes_data_type), ('attribute2_name', ctypes_data_type)]

For example, to define an int attribute foo:

class MyFront(Front):
    _fields_ = Front._fields_ + [('foo', c_int)]

and a second float attribute bar:

class MyFront(Front):
    _fields_ = Front._fields_ + [('foo', c_int), ('bar', c_double)]

Different from standard Python behavior, it is important to respect the types of the additional attributes. The following code will cause a ‘TypeError: int expected instead of float’ because foo has been declared to be an integer:

class MyFront(Front):
    _fields_ = Front._fields_ + [('foo', c_int)]

    def manage_front(self,constellation):
        ...
        # this will cause an error because 1.5 is a float
        self.foo = 1.5
        # this is fine
        self.foo = int(1.5)

The following ctypes are imported in NeuroDevSim and can be used in attribute definitions:

ctypes type

Python type

c_bool

bool

c_char

1-character bytes object

c_short

int

c_int

int

c_long

int

c_double

float

The differences between c_short, c_int and c_long is in the number of bytes used (2, 4, 8 bytes on 64 bit operating systems) and the corresponding range of numbers encoded (−32,768 through 32,767; −2,147,483,648 through 2,147,483,647 and -9,223,372,036,854,775,808 through 9,223,372,036,854,775,807). Additional ctypes data types exist and can be imported by knowledgeable users.

In addition, one can also use a class type, which has been defined elsewhere. At present we recommend using only the predefined Point class or the ID class, which is used to identify Fronts and Substrate:

class MyFront(Front):
    _fields_ = Front._fields_ + [('friend', ID)]

    def manage_front(self,constellation):
        ...
        # get a list of fronts with neuron_name beginning with "friend"
        my_friends = self.get_fronts(constellation,what='name',name="friend",returnID=True)
        # store the first front returned as an ID
        self.friend = my_friends[0][0]

Using Fronts or Substrate as an additional attribute is not recommended because it makes a copy of the original instance and this copy will not be updated. Moreover, for Front the specific subclass would have to be specified.

Attribute initialization

Additional attributes are automatically initialized to a zero value, in the case of a Structure all its fields are set to zero. An additional attribute can be given a starting value immediately after its instantiation by add_child:

class MyFront(Front):
    _fields_ = Front._fields_ + [('foo', c_int)]

    def manage_front(self,constellation):
        ...
        new_front = self.add_child(constellation,new_pos)
        new_front.foo = 7
        ...

Front status flags

Front status flags are important in scheduling manage_front calls and controlling model behavior. Some flags report on front status and can only be read, others can be changed by the user and some can be freely used. All flag methods are documented in simulator module. NeuroDevSim tries to keep the control of scheduling simple with the use of the enable(constellation) and disable(constellation) methods that directly changes status flags, but in some instances finer control is needed.

Status flags scheduling manage_front

manage_front is called only for Front that are active, but the order in which fronts are called depends on the growing and migrating status flags and, if either is set to True, on the position of Front in the simulation volume. Correct setting of these flags is important to reduce the likelyhood of GridCompetitionError. The active, growing and migrating flags can be set by the user or changed through the enable and disable methods:

Flag query

Set to True

Set to False

Active if True

is_active()

enable(constellation)

disable(constellation)

yes

is_growing()

set_growing() enable(constellation,growing=True)

clear_growing() disable(constellation)

yes

is_migrating()

set_migrating() enable(constellation,migrating=True)

clear_migrating() disable(constellation)

yes

Note that active changes to True if either growing or migrating are set to True, but the reverse does not apply. All new Front created by Admin_agent.add_neurons or Front.add_child are active and growing. For fronts created by Front.add_branch only the last of the series made is active and growing unless the optional parameter enable_all is set to True. Somata created by Admin_agent.add_neurons can also be migrating if the optional parameter is set to True.

Warning

enable and disable can be called for any front. Only self or new_front created by add_child or add_branch can call set_growing(), clear_growing(), set_migrating() and clear_migrating(). Calling these methods on other fronts will change the status flags but may not affect their behavior.

As mentioned, using disable(constellation) to stop growth of self is usually sufficient to control scheduling, but sometimes more fine-grained control is required:

def manage_front(self,constellation):
    ...
    try:
        new_front = self.add_child(constellation,new_pos) # make a new front and store it
        # make front stop growing but keep it active
        self.clear_growing()
        return # completed this call
    ...

In the code example above, self is kept active but is not expected to call add_child again. This could be useful if self should be able to react to some future condition as it will keep calling manage_front. If for some reason it should grow again at a later cycle, it is safer to call self.set_growing() first and wait till the next cycle to call add_child.

In the following code example a parent front is not only enabled again but set to growing after retraction of a child:

def manage_front(self,constellation):
    ...
    parent = self.get_parent(constellation)
    parent.enable(constellation,growing=True)
    self.retract(constellation) # retract self
    return # do not do anything else with self
    ...

The same effect can be obtained using self.enable_parent(constellation,growing=True).

Finally, a reminder: only keep fronts active if needed. Fronts calling manage_front without it executing any code before return can slow down simulations significantly. It is possible to disable fronts transiently using the optional parameters till_cycle, till_cycle_g or till_cycle_m:

def manage_front(self,constellation):
    ...
    self.disable(constellation,till_cycle_g=100)
    ...

this will disable self till cycle 100. On cycle 100 it becomes active again with is_growing() True.

Read-only status flags

The following status flags are set by NeuroDevSim methods and inform on status of Front:

Flag query

Set by method

Meaning of True

is_cylinder()

add_neurons, add_child, add_branch

shape is cylindrical, False for a sphere

has_moved()

migrate_soma

soma migrated at previous or current cycle

has_migrated()

migrate_soma

soma migrated at some cycle

is_retracted()

retract, retract_branch

front has been retracted

has_child_retracted

retract, retract_branch

a child of this front was retracted at some cycle, is reset to False after new child is made

is_arc()

add_branch

front is part of an arc made by arc_around

These status flags can be read for any Front but notice that has_moved() and has_migrated() can change value during the present cycle and for fronts other than self the timing of this change cannot be predicted.

User availabe status flags

The following status flags can be used as boolean variables by the user instead of making a c_bool additional attribute:

Flag query

Set to True

Set to False

is_status1()

set_status1()

clear_status1()

is_status2()

set_status2()

clear_status2()

is_status3()

set_status3()

clear_status3()

These status flags can be read and set/cleared for any Front. However, if setting or clearing on a front other than self be sure that only one front can do this during a given cycle to avoid competition. See Changing attributes of Front or Substrate for more information.

Importing a simulation

Some developmental models may simulate consecutive stages in development and each stage may be optimized separately during model creation. If the simulations take a lot of time to run it may then be advantageous to use a previous simulation of early stages of development to start simulating a later stage.

This is possible with the Admin_agent.import_simulation method:

fname_old = "output/prev_simulation.db"
fname = "output/new_simulation.db"
sim_volume = [[0., 0., 0.], [100., 100., 100.]]
neuron_types = [MyFront1,MyFront2]
num_procs = 4
admin = Admin_agent(num_procs,fname,sim_volume,neuron_types,seed=1)
admin.import_simulation(fname_old)

There are strict limitations to the use of import_simulation:

  1. The database should be importable: this should be set in the previous simulation as admin.importable_db = True, best immediately after Admin_agent class initalization.

  2. The importing simulation shoul call import_simulation as the first method after initalization of Admin_agent class.

  3. Admin_agent class initalization should be almost identical to that used for the previous simulation: num_procs, sim_volume and neuron_types should be identical (num_procs can be 0 for Interactive mode). The code defining each neuron type should be compatible with the previous simulation. Optional changes to Admin_agent array size attributes should also be identical.

  4. The database name for the new simulation should be different from that of the previous one. By default the database contents are copied to the new simulation database, but this can be turned off with the optional copy_db parameter.

  5. Admin_agent seed and verbose can be different.

The new simulation will start at the next cycle after the last one stored in the previous simulation database. Because it uses an importable database it has all the information needed to continue the simulation as if it was never interrupted. If any Additional attributes were declared their values should be stored during the previous simulation, this can be done with Storing additional attributes and setting the optional parameter last_only=True.

Several use cases of import_simulation are shown in the Import notebook.

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)

Understanding the database

All NeuroDevSim simulations are saved in a SQLite database. Many SQLite database browser apps are available to view the contents of the database, we like the DB Browser for SQLite.

NeuroDevSim comes with a set of methods in the processing module that print or plot database contents, so most users may never have to read this section of the manual. But understanding of the database structure is necessary to write specific analysis routines and may also be required to use the Interactive mode effectively.

Database tables

Information is stored in different database tables. These tables are shown in order of relevance for the user below and important tables are discussed in more detail next. Optional tables are tables that are only made when relevant: either when the corresponding class is instantiated or when Storing additional attributes has been used.

Table name

Optional

Description

neuron_data

no

data for every Neuron in simulation, necessary for front_data

front_data

no

data for every Front in simulation, needed for synapse_data, migration_data1

synapse_data

yes

data for every Synapse in simulation

substrate_data

yes

data for all Substrate in simulation

migration_data1

yes

coordinates of migrating somata, additional migration_data2,… may be present

neurodevsim

no

basic information about simulation: volume, number cycles, software version,…

neuron_types

no

class names of each neuron_type in simulation

attributes

yes

list of all tables storing additional attributes (tables not listed here)

arc_data

yes

technical table: data for all Arcs, only needed for import_simulation

arc_points

yes

technical table: points for all Arcs, only needed for import_simulation

mig_fronts_data

yes

technical table: order of migrating fronts, only needed for import_simulation

sqlite_sequence

no

technical table: standard sqlite table listing all other tables and their length

The contents of the database are updated at the end of each cycle. If a simulation crashes or is stopped, the database will be readable and contain correct information up till the last complete cycle. However, changing content in several tables is only updated at the end of the simulation by Admin_agent.destruction.

Reading the database

This subsection will be familiar to anybody who has previously written code to read from a SQLite database. One connects to the database using its filename db_name and creates a cursor:

import sqlite3
...
conn = sqlite3.connect(db_name)
cursor = conn.cursor()
conn.row_factory = sqlite3.Row

The last statement is quite important because it allows to access content by the name of columns, which results in more readable and easier to manage code.

Next one usually loads an entire table this_table and analyzes it row by row:

cursor.execute("select * from this_table")
result = cursor.fetchall()
for row in result:
    item1 = row['name_of_column2']
    item2 = row['name_of_column3']
    ...

Each table has as first column an id created by SQLite itself, this id is not related to the simulation. The content of relevant tables is described briefly in the following subsections, but id will not be mentioned. To get started yourself, it maybe helpful to look at the code in processing module, for example the nds_neuron method is fairly easy to understand.

NeuroDevSim writes new data to the database at the end of every cycle, so its information is complete up to the last completed cycle in case of a crash of the simulation. Type refers to SQLite data types, not NeuroDevSim data types. Updated refers to whether the value may be updated from its initial value when the object was created.

neuron_data table:

This table contains a row for each Neuron created and (a) new row(s) will be written to the database at the end of each admin.add_neurons call, it has the following columns:

Column_name

Type

Description

Updated

neuron_id

int

unique to each neuron, is the neuron identifier in DataID

no

type_id

int

identifies neuron_type, is the neuron identifier in ID

no

name

text

name of the neuron

no

firing_rate

real

initial firing_rate of neuron

no

CV_ISI

real

initial CV_ISI of neuron

no

num_fronts

int

final number of fronts in neuron

at end

num_retracted

int

final number of retracted fronts in neuron

at end

num_synapses

int

final number of synapses in neuron

at end

‘at end’ means that the column value is updated by admin.destruction().

front_data table:

This table contains a row for each Front created, with the following columns:

Column_name

Type

Description

Updated

neuron_id

int

neuron the front belongs to, refers to similar column in neuron_data

no

front_id

int

unique to each front of a specific neuron_type, front identifier in DataID and ID

no

branch

text

optional branch_name of front

no

swc_type

int

swc_type of the front, see SWC types used in NeuroDevSim

no

shape

int

indicates spherical (shape == 1) or cylindrical (shape == 2) front

no

orig_x

real

x of the orig coordinate of the front, for a migrating soma this is its original position

no

orig_y

real

y of the orig coordinate of the front

no

orig_z

real

z of the orig coordinate of the front

no

end_x

real

x of the end coordinate of the front, for spherical fronts identical to their orig

no

end_y

real

y of the end coordinate of the front

no

end_z

real

z of the end coordinate of the front

no

radius

real

front radius

no

parent_id

int

the front_id of the parent front or -1 for the root of a neuron tree

maybe

b_order

int

branching order of the front, 0 at the root

no

path_len

real

cumulated path_length from soma till end of the front

no

birth

int

birth of the front, cycle when the front was created

no

death

int

death, -1 or if retracted cycle when the front was retracted

maybe

migration

int

column number in migration_data table for migrating soma, 0 if not migrating

no

flags

int

Front status flags

maybe

The death column value is updated at the end of the cycle of retraction, otherwise it stays -1. The parent_id (may have changed due to Modeling soma migration) and flags columns are updated at the end of simulation by admin.destruction() if admin.importable_db == True, otherwise they keep the original value.

Note that num_children and the parent to child relation are not stored in the database. This information is implicit in the child to parent relation that is stored in the parent_id column.

synapse_data table:

This table contains a row for each Synapse created, with the following columns:

Column_name

Type

Description

Updated

pre_neuron_id

int

identifies presynaptic neuron, refers to neuron_id in neuron_data

no

pre_front_id

int

identifies presynaptic front, refers front_id in front_data

no

post_neuron_id

int

identifies postsynaptic neuron, refers to neuron_id in neuron_data

no

post_front_id

int

identifies postsynaptic front, refers front_id in front_data

no

weight:

real

initial synaptic weight

no

birth

int

birth of the synapse, cycle when the synapse was created

no

death

int

death, -1 or if removed cycle when synapse was removed

maybe

The death column value is updated at the end of the cycle of removal, otherwise it stays -1.

substrate_data table:

This table contains a row for each Substrate created, with the following columns:

Column_name

Type

Description

Updated

name

text

name of the substrate

no

x

real

x of the orig coordinate of the substrate

no

y

real

y of the orig coordinate of the substrate

no

z

real

z of the orig coordinate of the substrate

no

amount

real

n_mol of the substrate

no

rate

real

rate of the substrate

no

diff_c

real

diff_c of the substrate

no

birth

int

birth of the substrate, cycle when the substrate was created

no

death

int

death -1

no

At present Substrate cannot be removed.

migration_data table:

The database can contain several migration tables, numbered consecutively as migration_data1, migration_data2, migration_data3,… This is because the number of columns in a SQLite database table is limited, so if more than 600 somata migrated an extra migration_data table will be created.

These tables contains a row for each cycle during which a migration event took place. Its first column is the cycle and then 3 columns for each migrating soma:

Column_name

Type

Description

Updated

cycle

int

the cycle at which each soma migrated to these positions

no

x_

real

for each front identified by the elements of ID, the x position of the coordinate it migrated to this cycle or NULL if it did not migrate during this cycle

no

y_

real

same for the y position of the coordinate

no

z_

real

same for the z position of the coordinate

no

If multiple migration_data table are present the cycle column of each table is unique.

neurodevsim table

This table contains only a single row with information about the simulation in the following columns:

Column_name

Type

Description

Updated

xmin

int

x of the left-front-bottom coordinate of the simulation volume

no

ymin

int

y of the left-front-bottom coordinate of the simulation volume

no

zmin

int

z of the left-front-bottom coordinate of the simulation volume

no

xmax

int

x of the right-back-top coordinate of the simulation volume

no

ymax

int

y of the right-back-top coordinate of the simulation volume

no

zmax

int

z of the right-back-top coordinate of the simulation volume

no

num_cycles

int

total number of cycles simulated

at end

num_procs

int

number of computing processes used to instantiate Admin_agent

no

version

real

value representing the NeuroDevSim version number multiplied by 100., used by many methods to check whether database can be read

no

run_time

real

run time of the simulation in seconds

at end

importable

int

can database be read by import_simulation (1) or not (0)

at end

substrate

int

number of substrate tables present in the database (0 or 1)

yes

migration

int

number of migration tables present in the database

yes

synapses

int

number of synapses tables present in the database (0 or 1)

yes

attributes

int

number of tables storing attributes present in the database

yes

arcs

int

number of arc related tables present in the database (0 or 2)

at end

‘at end’ means that the column value is updated by admin.destruction().

neuron_types table

This table contains a row for each Front subclass listed in neuron_types during instantiation of Admin_agent class.

Column_name

Type

Description

Updated

type_id

int

index into shared arrays, is the neuron identifier in ID

no

neuron_type

text

class name of Front subclass

no

attributes table

This table contains a row for each attribute that was stored using admin.attrib_to_db, it is updated by this method.

Column_name

Type

Description

Updated

name

text

name of the attribute table

no

type

text

SQLite type of the attribute

no

neuron_name

text

neuron_name optional parameter of admin.attrib_to_db

no

last_only

int

last_only optional parameter of admin.attrib_to_db

no

Attribute data tables

Several such tables may be present with names listed in the attributes table: the name of the attribute followed by _data. It contents depend on the SQLite type of the attribute.

Column_name

Type

Description

Updated

neuron_id

int

identifies neuron, refers to neuron_id column in neuron_data

no

front_id

int

identifies front if relevant (0 for Neuron), refers to front_id column in front_data

no

cycle

int

simulation cycle for which data is stored

no

1, 2 or 3 columns:

attribute name

int

integer value of a simple attribute

no

or

attribute name

real

real value of a simple attribute

no

or

attribute name

text

text value of a simple attribute

no

or

attribute_ID_0

int

type_id component of an ID

no

attribute_ID_1

int

front_id component of an ID

no

or

attribute_x

real

x value of a Point

no

attribute_y

real

y value of a Point

no

attribute_z

real

z value of a Point

no

Database from a crashed simulation

The database of a crashed simulation will be intact but incomplete and never importable.

All tables will have up to date information till the cycle before the crash, but none of the data marked as Updated at end in the table listings will have been updated. The easiest way to identify a database as being from a crashed simulation is to check the num_cycles column in the neurodevsim table: it will be 0 (except after import_simulation, then it will be final cycle of the imported database). The number of cycles stored can be determined by the birth of the last fronts stored in the front_data table.

Storing additional attributes

It may be useful or necessary to store attributes that are not automatically stored by NeuroDevSim either because they need to be analyzed or plotted, or to prepare for Importing a simulation. These may either be user defined Additional attributes or attributes like Neuron firing_rate for which only the initial value is stored by default.

The admin.attrib_to_db method supports such storing. attrib_to_db is called in the main part of the code, anytime after at least one instance of the Front subclass has been made, for example by add_neurons. This code sample causes Front.num_children to be stored for all instances of SimpleNeuronFront:

if __name__ == '__main__':
    ...
    neuron_types = [SimpleNeuronFront,AxonFront]
    admin = Admin_agent(...)

    admin.add_neurons(SimpleNeuronFront,"simple_neuron",1,[[...],[...]],30.)

    admin.attrib_to_db(SimpleNeuronFront,"num_children","int")

attrib_to_db requires minimally 3 parameters: the subclass of ``Front`` for which the attribute should be stored, the name of the attribute to be stored and a sql_type. The latter defines the format that should be used to store the parameter in the database and should be one of the following:

sql_type

ctypes or class

columns in the data table

int

c_bool, c_short, c_int, c_long

1 int column containing the value

real

c_double

1 real column containing the value

text

c_char

1 text column containing the text

id

id

2 int columns containing both parts

point

Point

3 real columns containing x, y, z

An example of storing an additional attribute:

class SimpleNeuronFront(SynFront):
    _fields_ = SynFront._fields_ + [('signal', c_double)]
...

if __name__ == '__main__':
    ...
    neuron_types = [SimpleNeuronFront,AxonFront]
    admin = Admin_agent(...)

    admin.add_neurons(SimpleNeuronFront,"integrate_neuron",1,[[...],[...]],30.)

    admin.attrib_to_db(SimpleNeuronFront,"signal","real")

attrib_to_db will only store attributes that are not stored yet or that are not being updated. Trying to store an unchanging attribute that is always stored, like for example Front.radius or Neuron.neuron_name will cause an error.

By default attrib_to_db assumes that the attribute to store belongs to Front, but for Neuron firing_rate and CV_ISI and for Synapse weight can be stored but the class needs to be specified in the object optional parameter:

admin.attrib_to_db(AxonFront,"firing_rate","real",object=Neuron)
admin.attrib_to_db(SimpleNeuronFront,"weight","real",object=Synapse)

If multiple attributes from the same Front subclass or object are to be stored they can be specified as a list:

admin.attrib_to_db(AxonFront,["firing_rate","CV_ISI"],["real","real"],object=Neuron)

Note that sql_type then also becomes a list, because not all attributes may have the same type. It is not possible to combine attributes from different object types in a list.

Another optional parameter controls how often the attribute is saved. By default it is saved for every cycle after the attrib_to_db call, but this may not be necessary. If the attribute is stored to prepare for Importing a simulation then only its final value is needed. This can be achieved by setting the last_only optional parameter to True:

if __name__ == '__main__':
    ...
    neuron_types = [SimpleNeuronFront,AxonFront]
    admin = Admin_agent(...)
    admin.importable_db = True

    admin.add_neurons(AxonFront,"axon",10,[[...],[...]],1.,axon=[...])

    admin.attrib_to_db(AxonFront,"goalID","id",last_only=True)

goaldID will now only be stored for the last cycle of the simulation.

The last optional parameter of attrib_to_db controls for which neurons data will be stored. The neuron_name optional parameter will limit storage to data belonging to neurons with this name (neuron_name is a wildcard).

Several use cases of attrib_to_db are shown in the Database notebook, this also includes analysis and plotting of stored data. An Importing a simulation use case is shown in the Import notebook.