Subject: New Features in the coming version of NEURON Sekar asked me to list the features that will be coming in the new version of NEURON to be released in October(December?). They can be divided into four classes: 1) object oriented syntax for hoc 2) variables that are lists of sections 3) vectorized number crunching 4) miscellaneous improvements 5) graphical user interface At this time, 1-4 are substantially complete and 5 is pretty much vaporware. What does exist in terms of a user interface at this time is useable only for single segment simulations, ie investigating properties of membrane mechanisms. 1-4 will work on a PC. Eventually 5 will also work on a high end PC (I think). With regard to compatibility, every simulation written in hoc and every .mod file should produce exactly the same results with one major and one minor exception. The major exception is that dot notation in the new version has been turned around. Thus in the old version "v.soma" referred to the voltage at the midpoint of the soma section. Now it must be written "soma.v". I have a shell script that helps make the transition to the new syntax. Fortunately, at least in my examples, the dot notation was seldom used. The reason for the change was that sections may be a part of a larger object. If the object is a cell in a network then one descends to the individual variable with the notation network.cell.soma.v This is more compatible with c++ notation. The minor exception has to do with a change in the way logical comparisons are made. Every comparison is now done with respect to a variable called float_epsilon which has a default value of 1e-11. If this is set to 0 then comparisons are equivalent to the old version. The rationale is to minimize the impact of roundoff error in computations. For example for (x=0; x < 1; x = x + .1) in the old version could have surprising results because .1 does not have an exact representation in the computer since it is an infinitely repeating binary number. Thus adding ten of them gives a result which is not exactly 1 and in fact on my machine differs by 1.11e-16. Thus the loop body is executed when x is essentially equal to 1 whereas the form of the statement clearly implies that the last execution should be at x=.9. Now, equality x == y means abs(x - y) < float_epsilon x < y means x < y - float_epsilon x <= y means x <= y + float_epsilon etc. Other exceptions are that Ra is now a section variable instead of a global variable. Thus when you set it use forall Ra = ... and look at it with section.Ra ie. it is now the same kind of variable that L has always been. Point Processes now have an object syntax instead of the old indexed vector syntax. 1) object oriented syntax for interpreter. Actually, it is more akin to abstract data types and modules since there is no support for inheritance or polymorphism. One creates object variables (pointers to objects) with objectvar ob1, gr[5], ... One creates the actual objects using for i=0,4 gr[i] = new(Graph) Graph is a built-in class or template for constructing an actual object, or instance of the class. (I am trying to call the same thing by several names in hopes that the uninitiated will be less confused than I was when I first heard the word "class". The most helpful analogy I have heard is that a class is like a cookie cutter and objects are the actual cookies that are cut out using the cookie cutter. At any rate, if you execute the above statement, four graph windows will appear on the screen which can be independently plotted to. These windows can be resized and the plots on them will automatically resize as well instead of disappearing like they did in the old x window.) One uses the objects by setting variables and calling functions that exist inside the object. The key point is that since there are potentially many of the same kind of object one must say *WHICH* object you are setting the variable for. This is done with a dot notation. gr[2].size(0,10, -1,1) gr[2].addvar("cos(x)") gr[1].label(.5, .5, "hello") notice that the label won't appear in the same window as the cosine curve. Object pointer variables can be assigned to and passed as arguments. When an object is no longer pointed to by any object variable the object is deleted. Classes can also be created in hoc. The best way to see why this is useful in some circumstances is by an example. Suppose we have a standard fadvance loop while (t < tstop) { fadvance() } and we want to plot something at uniform intervals. One way is to create a trigger function so the loop looks like: init() /* initialize trigger */ while (t < tstop) { fadvance() if (val()) plot_var() /* val returns 1 at the trigger times */ } The implementation might be /*-----------------------------------------------*/ /* when val() returns 1 it will not do so again til t passes the next trigger time */ delay = 0 interval=1 n=100 proc init() { i = 0 next_t = t + delay } func val() { if (i >= n || t < next_t) { return 0 }else{ next_t = next_t + interval i = i + 1 return 1 } } /*-------------------------------------------------*/ This works fine and we'll get 100 plots each a msec apart. But there are several potential problem areas. First, the variable names may conflict with other names elsewhere in the program. It's especially dangerous to use names like "i", and, "n". Second, some of the names such as "next_t" and "i" are needed only locally to the trigger mechanism and they are polluting the global name space. Third, a trigger mechanism is generally useful and we may want to use many different triggers for different purposes. We could change all the variables to vectors in this case but that is often cumbersome and the index bears no relation to what the trigger is used for. A better way is to enclose the implementation of the trigger mechanism in a class definition. begintemplate Trigger public delay, interval, n, val, init endtemplate Trigger Now "i" and "next_t" are local to the object and don't affect other variables of the same name. Note, however that those variables get initialized to 0 for new objects because direct commands in a template are executed only when the template is constructed. The "public" keyword lists those variables and functions that can be used outside the object. (init is a special name which gets executed whenever a new object is created). Now, the trigger mechanism is available for use in a variety of contexts. For example, if in addition to plotting, we want to generate a pulse train looking like ||...||...||...||...||.................... Then we can say objectvar ptrigger, trial, pulse ptrigger = new(Trigger) trial = new(Trigger) pulse = new(Trigger) ptrigger.interval=1 ptrigger.n=100 trial.interval=5 trial.n=5 pulse.interval=1 pulse.n=2 func pulses() { /* series of pulse triggers occur every trial */ if (t == 0) { trial.init() } if (trial.val()) { pulse.init() } return pulse.val() } and the fadvance loop is: while ( t < tstop) { fadvance() if (ptrigger.val()) plot_var() if (pulses()) set_variables_on_pulse() } As another example consider: begin_template Cell1 public soma, dendrite, axon create soma, dendrite[3], axon proc init() { for i=0,2 soma connect dendrite[i](0), 0 soma connect axon(0), 1 } end_template Cell1 It is now a simple matter to create an array of cells objectvar cell[10][10] for i=0,9 for j=0,9 cell[i][j]=new(Cell1) One of the cells can be aliased to a new name objectvar grandmother grandmother = cell[4][3] and grandmother.soma is equivalent to cell[4][3].soma One of the cells can be destroyed objectvar nil cell[5][6] = nil Now the object that cell[5][6] pointed to is no longer pointed to by any objectvar so all it's memory is released. But you'd have to remember to make sure that all synapses on it from other cells were changed so they didn't point to the now released memory. This would be a bad idea in practice since an error message would result if you tried to say cell[5][6].soma.v However it may be useful to destroy and replace by another cell type as in cell[5][4] = new(Cell2) The keyword forall still loops over *ALL* sections when it is executed at the toplevel but only over a single object's sections when it appears in a template. Effectively, templates give one all the power of structures. It can do some things (in an admittedly baroque manner) that were impossible otherwise. eg. passing an array of strings to a hoc procedure. eg begintemplate String public s strdef s endtemplate String begintemplate StringArray public sa, size n = 10 objectvar sa[n] proc init() { for i=0,n-1 sa[i] = new(String) } func size() {return n} endtemplate StringArray proc printarray() {local n /* arg is a StringArray */ n = $1.size() - 1 for i=0,n print $1.sa[i].s } ------------------------------------------------------------------- 2) variables that are lists of sections. I am anticipating that regular expressions are going to be inefficient for selecting groups of sections when large numbers of sections exist. Also they are cumbersome to type out more than once. Finally one often needs a combination of regular expressions to select the subset of sections one is interested in. For these reasons there is now a new variable type called a section list to which one can append and remove sections. They are declared with seclistdef l1, l2[2], l3[3][3] one adds the currently accessed section to the list with append_seclist(l1) and removes the currently accessed section (if it is in the list) with remove_seclist(l1) Notice that the argument to these functions is the name of the list. One can loop over the sections in a list with forsec l1 { statement } and check whether the currently accessed section is in the list with ifsec l1 { statement } (the forsec "reg_exp" and ifsec "reg_exp" still work) Thus one can construct a list of soma's containing hh channels with seclistdef activesomas forsec "soma" if (ismembrane("hh") append_seclist(activesomas) From now on one can say things like forsec activesomas do_something() The nice thing is that the looping over section lists is very efficient. Also, the division of sections into intersecting subsets is conceptually very useful. ----------------------------- 3) vectorized number crunching Setup of the conductance matrix and calculation of states has been reorganized to take advantage of single instruction/multiple data stream cpu's. ie done in parallel or very great speedup on machines like the cray. Gausian elimination also now proceeds substantially in parallel by carefully ordering the elimination and back substitution steps so as much as possible is done at one time consistent with the requirement that the same address cannot be written to in the same machine cycle. This results in a speedup on a CRAY by a factor of more than 10. (The number crunching loops in question speed up by exactly 64, ie the number of things a cray can do at one time). A side effect of this vectorization is a speedup of about 30% on normal computers. Optimization is standard in the Imakefile now, so some people should see their simulations run in about half the time. ------------------------------- 4) miscellaneous improvements I already mentioned that logical comparisons will now produce meaningful results in the presence of roundoff errors. Gnu emacs command line editing. This includes an interpreter prompt. c++ objects can be interfaced to the interpreter. uninsert a mechanism --------------------------------- 5) Graphical user interface I am doing the interface in InterViews, a C++ class library. Graphics is open ended and there is way too much for one person to do. The promise of C++ and InterViews is that the support is there for many people to make additions and be ensured that they will all work together. ie the specification is separate from the implementation and as long as a person obeys the protocols expressed in the base classes their work will certainly fit into the system without getting broken everytime I change something. The goal: Maintain conceptual control (match between what you think the state of the simulation is and what is actually inside the computer) by being able to effortlessly create a view of some data. In general the user will be working with several graph windows, control menus, panels containing values of variables and pictures of the neuron. She can arrange these windows on the screen however she wants -- creating new ones to see information she's not certain about and dismissing irrelevant ones in order to maintain a feel for what is going on. What exists: Print window manager: -------------------- Its primary purpose is to organize the windows onto a page for printing. The manager contains two scenes representing the screen and a piece of paper. The location and relative size of each hoc window appears on the screen scene. To specify which subset of windows is to be printed you click on the relevant rectangles in the screen scene. A rectangle representing the relative location and size on the page will appear in the page scene. Windows selected for printing may be manipulated in the page scene. Place the mouse cursor over the desired window rectangle in the page scene and: Right button --- remove the window from the page. If one clicks again on that window in the screen scene then the window will return to the same location and relative size on the page as when it was removed. Middle button --- resize the window. This resizes not the window on the screen but how large the window will appear on the page. The window always maintains the same aspect ratio as the window on the console screen. To resize, drag the mouse with the button down til the desired size is reached. Left button --- move the window. Drag the mouse to the desired position and release the button. When the manager is iconified, all the windows disappear. When the manager is redisplayed all the windows come back where they left off. The manager has some other functionality as well. One can use it to organize several windows into a single window called a tray. You can print in either portrait or landscape mode. Panels, Buttons, FieldEditors, and Menus: ----------------- You can create these in hoc. NEURON creates quite a few of them automatically so you can select just those sections and mechanism variables you are interested in viewing. A button just executes a hoc procedure. A Field editor displays the value of a variable. The variable can be changed by typing an arbitrary expression in its field. Variables that depend on other variables are automatically updated. NEURON has a control menu for common simulation control such as init, run, stop, continue, single step, etc. It has a main menu by which one navigates through the mechanism variables. Graph windows ------------- Portions of a graph can be selected to create a new window. The plots resize as the window is resized. If you place the mouse over a curve and press the left button a cross hairs with coordinates is displayed. Dragging the mouse moves the cross hairs along the curve. Views of 3-d reconstructed neurons ---------------------------------- Right now it is only projected onto the x-y plane. It is drawn in either line mode (fast) or as filled trapezoids defined by position and diameter of adjacent 3-d points. If you click at a position, the name of the section is printed. As in a graph window you can select a portion of the view to create a new view. Things to do before it can be released: Select variables to plot without having to write hoc code. Mark a field editor if its value differs from the default. Save a session with all its arrangement of windows. Save graphs in a way which can be retrieved by idraw (the macdraw like drawing editor.) Drag mechanisms in and out of a section. Color a neuron view according to the value of some variable. Attach menus to a neuron view. After those there are a million other things to do. If some of you are interested, (and you want to make your own core dumps), I can arrange to put a snapshot of the current experimental version on my anonymous ftp server. All I can guarantee is that it will compile on a SUN4. I would be very interested in comments, suggestions, and contributions. If you want to do something with the interface you would have to get the 3.1 beta release of InterViews from interviews.stanford.edu. Warning: people have been haveing trouble getting it to compile with the GNU g++ compiler but patches do exist that will do the job. If you have a SUN4 I can put the binaries in with the snapshot. All in all it would not be for the faint of heart. As always, documentation is inadequate. ---Michael Hines