GENESIS was designed to be extensible. If you find that the objects provided by GENESIS do not serve your modeling needs, you can modify the functionality of existing objects, or create entirely new types. Likewise, you can extend the set of commands recognized by the SLI to include your own commands. You may do either of these within the GENESIS scripting language, by creating extended objects (see Extended Objects) and by defining script language functions.
However, there are occasions when you will need the computational efficiency and speed gained by compiling new object and command definitions into your own customized version of GENESIS. The speed advantage of using a compiled object instead of a script-defined extended object is most noticeable when it is necesssary to define a new PROCESS action, as this action will be performed at each simulation step. If the simulation requires many elements made from the object, then the setup time for the simulation may also be longer when using script-defined objects.
A GENESIS object is simply a particular C structure definition that is associated with a particular C function definition. Compiling these for use by GENESIS is equivalent to compiling a new library. When compiling new libraries into GENESIS, it is best to leave the ``official'' version of the genesis/src directory and its subdirectories unmodified. That way, you and other users will be able to create private customized versions of GENESIS, without changing the standard version. Typically, you will create a directory to contain your customized version of GENESIS, and one or more subdirectories to contain libraries for your new GENESIS commands and objects.
There are two main steps involved in creating a customized version of GENESIS which incorporates new commands or objects. First, one must create a library containing the the files which define the new commands or objects. Then, one or more libraries must be compiled into the new genesis executable. These two steps are described in Defining New Objects and Commands and Compiling a New Version of GENESIS.
If you have existing libraries, you may skip this section and go on to Compiling a New Version of GENESIS.
The files in the Scripts/newlib directory illustrate the steps to follow in constructing a new GENESIS library:
mkdir newlibwhere 'newlib' will be the location of the new library. As described under Compiling a New Version of GENESIS, this directory should be created as a subdirectory of the one in which the new version of GENESIS will be compiled. The following steps refer to files which are created within this library directory.
#include "sim_ext.h" #include "example_struct.h"The file sim_ext.h is a specific GENESIS header file which must be included. It resides in the genesis/include directory, and includes several other files which make a number of necessary definitions. example_struct.h is a user-defined structure definition file which must be created.
#include "struct_defs.h" struct mystruct { TYPE fields ... }; struct another_struct { TYPE other_fields ... }; etc ...The structure names (e.g. mystruct) are arbitrary but must not conflict with any existing structure names. ``TYPE'' specifies a list of basic object fields. It should be selected from one those defined in genesis/include/struct_defs.h:
ELEMENT_TYPE BUFFER_TYPE SEGMENT_TYPE CONNECTION_TYPE PROJECTION_TYPE CHAN_TYPE LINK_TYPE OUTPUT_TYPEThese are macros which define a set of fields depending on the class of the object. The ``fields'' of the structure are additional fields that are completely user-defined. When writing a new object definition, you will pick one of the above types which most closely matches your needs and supplement it with any needed additional fields. For example, a new segment class object might have a structure definition like:
struct mystruct { SEGMENT_TYPE float state; int count; };The newlib/example_struct.h structure file contains:
#include "struct_defs.h" struct example_type { ELEMENT_TYPE float input; float output; };You can find other examples in the genesis/include directory. The documentation for each GENESIS object gives the name of the file which defines its data structure. If no new structure definitions are to be added, then simply create an empty structure file. This can be accomplished using the UNIX shell command
touch "my_struct.h"
do_myfunc(argc,argv) int argc; char **argv; { }or object functions of the form
MyObject(element,action) struct my_struct *element; Action *action; { }The names of these functions are arbitrary as long as they do not conflict with existing function names. If there is a conflict it will be reported during the link phase of compilation as a multiply defined function. In this case the function should be renamed. As a suggested convention, all shell functions should be prefixed with ``do_''. In the newlib directory, we have a separate file (command.c) for the new shell function (to become a new GENESIS routine) and another (example.c) for the new object function. These could have been combined into a single file, however. Note that the shell functions look like any normal C function, except that they must have two arguments, argc and argv, which will be used to get the actual arguments passed by the Script Language Interpreter. The file newlib/command.c illustrates a simple function which will be bound to a new GENESIS command which returns the number of arguments:
#include "sim_ext.h" int do_example(argc,argv) int argc; char **argv; { printf("%d arguments passed to %s\n",argc,argv[0]); /* ** functions can return values which can be used in the interpreter */ return(argc); }Note that it begins with a required inclusion of the definitions in sim_ext.h. Source files which define object functions must also include a header file giving structure declarations. example.c accomplishes this in a somewhat indirect way with the initial statement
#include "example_ext.h"Thus, sim_ext.h is included, as was done with command.c, and an additional structure declaration file is also included, as described in step 3. The format for object functions is somewhat more complicated than that for shell functions. In the skeleton object function given above, the example function ``MyObject'' takes two arguments: (1) the pointer to the instantiation of the object structure (element) and (2) the pointer to the instantiation of the structure that specifies the action to be performed (action). Code which selects and implements the various actions which can be performed is given within the two curly brackets. The code for the example object ``ExampleObject'' is listed below in ``An Example Object Definition''. It has detailed comments explaining how to specify the actions performed and the way that messages are processed during the PROCESS action. The section on actions in Elements describes the actions which are common to many GENESIS objects. In general, the best way to write an object function is to begin by examining the source code for an existing object which is similar. The documentation for each object gives the name of the file and the name of the function which implements the object.
Command | Description |
addfunc | Binds a compiled C function to the name of a GENESIS command. |
newclass | Adds a new class identifier to the list of object classes. |
object | Defines attributes of a GENESIS object and gives it a name. |
addfunc example do_example int newclass exampleclass addaction NEWACTION 20 object example_object example_type ExampleObject exampleclass device \ -author "M.Wilson Caltech 2/89" \ -actions RESET PROCESS NEWACTION \ -messages ADD 0 1 input \ SUBTRACT 1 1 input \ TWOARGS 2 2 arg1 arg2 \ -readwrite input "Input variable, altered by ADD and SUBTRACT" \ -readonly output "Running total of input at each step" \ -description "exercise in creating new objects" \ "keeps a running sum of its inputs"The first line associates the command name ``example'' with the function do_example, defined in command.c. The optional argument for the data type (int) is needed here, because this routine returns an integer value. Next, a new class name (exampleclass) is defined, as well as a new action name and associated number. The final object command is continued over several lines. It is of the form:
object name data_type function class [class] ... [options]Here, the new object will be given the name ``example_object''. In its defining function (ExampleObject) it was given a data structure of type ``example_type'', defined in step 3. The object may belong to more than one class. In this case, it is assigned to the newly defined exampleclass and to the pre-existing device class. It is required that any actions which the object performs be listed following the ``
-actions
'' argument. The code in
example.c gives the statements to be executed for the actions RESET,
PROCESS, and NEWACTION.
If messages are used by the object then the ``-message
'' option must
be defined with the following arguments:
-readwrite
'',
``-readonly
'', or ``-hidden
''. These option names also
give the protection that is assigned to the fields. In this case, we
want to be able to set and inspect the input field. As the
output field will be calculated by the object, it should be
readable, but not writeable by the user. In other cases we may wish to use
fields for internal calculations, but keep them hidden from the user.
These are specified with the ``-hidden
'' option.
The remaining fields are added for the purpose of documenting the
object and are optional.
GENESIS = /usr/genesisThis is normally written into the Libmake file at the time GENESIS is installed. If GENESIS has been moved, or you are modifying libraries provided by someone else, you should check to be sure that the path is correct.
LIBRARY_NAME = exampleThe LIBRARY_NAME will also be entered in the 'liblist' file residing in the parent directory of the library directory, as decribed in Compiling a New Version of GENESIS.
STARTUP = examplelib.g
STRUCTURES = example_struct.h
EXT_HEADER = example_ext.h
TARGET_OBJ = examplelib.oThis name (with the path to the library) is also used for the USERLIB variable in the Makefile (derived from Usermake) in the parent directory, as decribed in Compiling a New Version of GENESIS.
OBJECTS = command.o example.o
The file Scripts/newlib/example.c illustrates features which are common to the source code for many GENESIS object functions:
#include "example_ext.h" #define ADD 0 #define SUBTRACT 1 #define TWOARGS 2 #define NEWACTION 20 /* ** example of how to define a new object function */ /* M.Wilson Caltech 1/89 */ /* ** The user can give the object function any unique name. ** Similarly, the arguments to the function can have arbitrary names. */ ExampleObject(element,action) struct example_type *element; Action *action; { /* If the element is to receive messages, this pointer (MsgIn *msg) must be ** declared. */ MsgIn *msg; double value; /* ** The debugging level can be assigned at runtime within the ** interpreter using the 'debug' command. The function ActionHeader ** will cause GENESIS to print a standard message consisting ** of the name of the function called, the name of the element, ** and the name of the action being executed. */ if(debug > 1){ /* just prints out information which helps see what is happening */ ActionHeader("ExampleObjectt",element,action); } SELECT_ACTION(action){ /* ** SELECT_ACTION is a macro for a switch-case statement switching on the ** action requested. ** There are a number of predefined actions (see sim_defs.h) ** which are typically used by elements. PROCESS is one of them ** New actions can be added in any element. Use the 'addaction' ** command in the object definition script to inform the simulator ** of the new action. The case number asssigned to new actions ** is relatively arbitrary as long as it does not conflict with ** the case numbers of other actions defined in the element. ** (you should get a compiler error if there is a conflict). */ case NEWACTION: printf("code for the new action\n"); break; case PROCESS: element->input = 0; /* ** This is the way in which messages are processed ** MSGLOOP is a macro which scans all incoming messages and ** executes the code in the appropriate case statement ** depending on the message type ** */ MSGLOOP(element,msg) { /* ** The case number assigned here must be defined in the ** in the message section of the object definition ** (see examplelib.g) */ case ADD: /* ** The function MSGVALUE allows you to access the contents ** of the message arguments passed into the element. ** The first argument is just the msg pointer, ** the second argument is the argument number ** Thus to get the first argument of a message use ** MSGVALUE(msg,0). To get the second (assuming there are ** more than one argument in the message) use ** MSGVALUE(msg,1). ** Note that the return type from ** MSGVALUE is always type double ** ** You are free to place whatever code you would like in here */ value = MSGVALUE(msg,0); element->input += value; printf("adding a message value of %f\n",value); break; case SUBTRACT: value = MSGVALUE(msg,0); element->input -= value; printf("subtracting a message value of %f\n",value); break; case TWOARGS: printf("processing arguments %f and %f\n", MSGVALUE(msg,0),MSGVALUE(msg,1)); break; default: printf("Unknown message\n"); break; } /* ** In this case we add the element field 'input' to 'output', ** using output to maintain a running sum. ** You are free to place whatever code you would like in here. */ element->output += element->input; printf("element has been processed\n"); break; /* ** The RESET action is used to return the element to a known ** initial state */ case RESET: element->input = 0; element->output = 0; printf("element has been reset\n"); break; } }
We anticipate that many GENESIS users will want to write variants of the synchan and hebbsynchan objects to handle different kinds of synaptically-mediated behavior. Currently, this means that one has to write a C function defining the object, usually in a user library. See Customizing GENESIS and Defining New Objects and Commands for more information on this.
In general, one should start by copying an existing object that is as close to the desired object as possible and then modifying it. These modifications may involve adding new fields or deleting old ones. In order to guarantee that the existing genesis commands for setting up synaptic connections work properly with the new objects (e.g. planarconnect, volumeconnect, planar/volumeweight, planar/volumedelay, etc.) we ask that aspiring synchan hackers obey the following guidelines:
struct MyWeirdSynchan_type { SYNCHAN_TYPE /* use Synapse_type if using normal synapses */ struct MyWeirdSynapse_type *synapse; /* ... put extra fields here if needed ... */ };
struct MyWeirdSynapse_type { SYNAPSE_TYPE /* ... put extra fields here if needed ... */ }; typedef struct MyWeirdSynapse_type MyWeirdSynapse;An example of both (1) and (2) is in src/newconn/newconn_struct.h, with the definition of HebbSynchan_type and HebbSynapse_type. If your synapses are the same as previously-defined ones, then this step isn't necessary.
... SELECT_ACTION(action) { case CREATE: channel->synapse_size = (unsigned short) (sizeof(MyWeirdSynapse)); channel->synapse = NULL; /* no synapses to start with */ break; ...Note that all you have to do is change the word ``Synapse'' in synchan.c to ``MyWeirdSynapse''.
#include "newconn_struct.h" #include "synaptic_event.h" extern void RemovePendingSynapticEvents(); /* and for hebbsynchan derivatives: */ #include "seg_struct.h" /* for compartment definition */
If all this is done, and your code is correct, the new objects should work with the existing connection and weight/delay-setting functions in the same way as synchan and hebbsynchan do.
This documentation describes the implementation of the 'stdp_rules' object, and how it may be used as a model for implementing synaptic plasticity in a manner that is compatible with hsolve.
The object-oriented paradigm for scripting simulations in GENESIS 2 is one of its most attractive features. By isolating cell variables and parameters into separate objects that communicate with 'messages' it is easy to modify, share, and reuse pieces of simulation scripts without needing to understand most details of the simulation from which they were taken. This modularity has also made it possible for users with a little programming expertise to easily add new commands or objects to GENESIS for different types of synaptic plasticity.
In order to avoid the inefficiency of this message-based approach at the implementation level. The GENESIS 2 'hsolve' object was developed to bypass the object-oriented implementation and allow fast cable model solutions, as well as much more efficient delivery of spike events to synaptically activated channels. The computational speed increases when highly connected network models are simulated with hsolve are impressive, typically a factor of 10 to 20. However, the price for this efficiency is that only a subset of the GENESIS objects can be used with hsolve, and it is not possible to modify these objects without major changes to the simulator code.
As a result, it is not possible to use hsolve with cells that contain a facsynchan, hebbsynchan, or a new variety of synchan that is created by following the procedure givem in Doc/NewSynapticObjects.txt. However, the synapse (which is a substructure of the synchan) is not directly used by hsolve, so it is possible to modify the weights of a synaptic connection, and even to add extra fields in addition to the weight and delay.
The concept behind the 'stdp_rules' object is very general, but this particular implementation is specific to the Song, Miller, and Abbott (2000) (SMA) phenomenological model of spike timing dependent plasticity (STDP). Rather than using a more biologically based model that depends on calcium influx to the cell, the weight modification rules are based on experimentally observed relationships between the timing of pre and postsynaptic spikes, e.g. Dan and Poo (2004). It has been adapted for compartmental models with continous conductance changes, Hodgkin-Huxley channels, and axonal delays, rather than for 'point neuron' integrate and fire (IF) cells.
The commented simulation code in src/newconn/stdp_rules.c can be used as a model for implementing other types of synaptic plasticity. For example, a reimplementation of the facsynchan to be hsolvable would use another algorithm and variables to modify the standard synchan, but will have much in common with the code used for 'stdp_rules'.
In order to create another plasticity 'rules' object to modify synchan weights, begin with by studying the notes below on 'stdp_rules', and the demonstration scripts in Scripts/stdp_rules. The README file in that directory describes a script function 'apply_stdp_rules' that can be called with a clocked 'script_out' object to perform the same synaptic weight modifications as 'stdp_rules'. It will be slower, of course, but it can be modfied as a prototype for a new variety of hsolvable plasticity rules. After testing and refinement, the prototyped plasticity model can be compiled into an object in a new GENESIS library, by following the instructions in Doc/NewObjects.txt.
You will want to study these files in src/newconn in order to understand the relationship between the SLI script and the C code implementation:
Makefile -- adds stdp_rules.o to OBJECTS newconnlib.g -- adds section for 'object stdp_rules stdp_rules_type StdpRules' newconn_struct.h -- added '#include "stdp_rules_struct.h"'; changed SYNAPSE_TYPE stdp_rules_struct.h -- defines struct stdp_rules_type for the object fields stdp_rules.c -- the source code for StdpRules() and functions synchan.c -- has additions to the 'EVENT' and 'RESET' cases
Modifications to synchan.c should be restricted to those that do not affect the hsolvability of the synchan. In particular, no new messages should be added, nor any changes to the PROCESS action. In this case, synchan.c was modified to add the 'last_spike_time' field to each synapse and set it to the time of the last presynaptic spike plus delay in order to get the arrival time. This was done within the 'EVENT' case, where spike events are processed outside of hsolve.
Aplus and Aminus fields were added for use by stdp_rules or another synapse modification object. The added field 'lastupdate' can be used to store the time that the synapse weight, Aplus, and Aminus fields were last updated. Other than initialization on RESET, these three fields are not directly used by the synchan.
The 'stdp_rules' object (see Doc/stdp_rules.txt) is a clocked object (usually with a step of about 1 msec) that examines pre and post synaptic spike times of a cell or list of cells, and changes the weight of each synapse accordingly.
At each clock step, for the cell (or in turn for each cell):
It gets the 'lastevent' field of the cell's spikegen to find when it last spiked. Comparing it with the current time tells if it has spiked since the last clocked call of the function, or when it last spiked. The same thing is done for each of the synaptic connections to this synapse, by examining the 'last_spike_time' field of the synapse, in order to find the most recent presynaptic spike arrival time. For example:
getfield /cell[5]/dend/Ex_channel synapse[0].last_spike_time
The relative timing of the pre- and post-synaptic spikes is used in the STDP algorithm to adjust the weights. In this implementation it involves an exponential time window with a small number of parameters. This gives higher negative or positive weight changes when the time difference is small. (The change is negative if the pre-synaptic cell fired later). For more details, see the Methods section of Song et al. (2000).
In particular, note that the SMA algorithm adds each weight change to an ongoing exponentially decaying change from previous spike events, rather than adding it directly to the current weight. This provides a type of 'momentum' term to keep weight changes moving in the same direction. Using this approach instead of using a simple increase of weight by a fixed increment required the addition of the 'Aplus' and 'Aminus' fields to the synapse. The differences between these approaches to weight modification is discussed in the paper by Morrison, et al. (2008) For a description of an implementation of the SMA rules in Python using the Brian simulator, see Stimberg et al. (2104)
See also: synchan, spikegen, NewObjects, NewSynapticObjects
Dan Y and Poo M (2004) Spike timing-dependent plasticity of neural circuits. Neuron, 44:232-330.
Morrison A, Diesmann M, Gerstner W (2008) Phenomenological models of synaptic plasticity based on spike timing. Biol. Cybern. 98: 459-478.
Song S, Miller KD, Abbott LF (2000) Competitive Hebbian learning through spike-timing-dependent synaptic plasticity. Nat. Neurosci. 3: 919–926.
Stimberg M, Goodman DFM, Benichoux V, Brette R (2014) Equation-oriented specification of neural models for simulations. Front. Neuroinf. 8: Article 6. doi: 10.3389/fninf.2014.00006
To compile a new version of GENESIS containing new user libraries:
mkdir
command
to create your own directory for containing the customized GENESIS.
cp -r
(recursive copy) or mv
command to put the library subdirectories
under the directory which you created in the first step.
new genesis --> contains Makefile (from Usermake) /\ / \ / \ / \ new library additional new subdirectory library subdirectoriesEach library subdirectory will contain its own Makefile constructed by editing genesis/Libmake, as described in the Libmake file and in Defining New Objects and Commands.
GENINST = /usr/genesisIf, for some reason, the GENESIS files have been moved from the original GENESIS installation directory, this line may need to be edited to reflect the change. Next, there are some instructions regarding assignment of the system-specific variables, MACHINE, OS, XLIB, CC, CFLAGS, CPP, LD, LINKFLAGS, and LEXLIB. These should be set to the same values as those used in the original genesis/src/Makefile when GENESIS was compiled. The following line,
SIMNAME = genesisgives the name of the GENESIS executable that will be produced. If you would like to give it another name like mygenesis to distinguish it from the standard version, you may change it here. Next, we come to the USERDIR variable. The instructions in the Makefile say that this should give the names of any library directories. If we were compiling the single library in the subdirectory newlib, we would say:
USERDIR = newlibThe variable USEROBJ should give the pathnames of any new library object modules. After compilation, each library will have a ``.o'' file which will be linked into GENESIS. The instructions in Usermake tell us that this name is specified by the TARGET_OBJ variable in the library Makefile. For the example given in Scripts/newlib, we would look in newlib/Makefile (the library Makefile) and see that it is set to examplelib.o. We need to give the path relative to the place where we are compiling the new GENESIS, so we would say:
USEROBJ = newlib/examplelib.oThe remaining variable to set is USERLIB, which should give the names of any new libraries. The library name is specified by the LIBRARY_NAME variable in the library Makefile. Following through with the newlib example, USERLIB would be set to
USERLIB =exampleThis completes the edits to the Makefile.
GENESIS =
''. This should give the full path to the
GENESIS distribution. If it is not where you keep your GENESIS
distribution, change it accordingly.
echo {example 1 2 3 4}
'' and see if the results
are consistent with the comments in newlib/command.c. The command
``showobject example_object
'' should produce some information
about the new object. You may execute the test script,
newlib/testexample.g to see if elements created from this
object behave as expected.If you have problems running or compiling the new version of GENESIS, you should make sure that the files in genesis/lib were compiled under the same environment (operating system and compiler) as your new library. If you suspect that this is not the case, you should recompile GENESIS, following the instructions in genesis/src/README.