Best practice to run hundreds of simulations / remove all objects between runs

When Python is the interpreter, what is a good
design for the interface to the basic NEURON
concepts.

Moderator: hines

Post Reply
ziemek
Posts: 45
Joined: Thu May 23, 2019 8:02 am
Location: Warsaw, Poland
Contact:

Best practice to run hundreds of simulations / remove all objects between runs

Post by ziemek »

Hi there,

I'm using Python wrapper. Before each run I would like to setup a fresh environment, so I want to remove all objects (sections, iclamps, netstims, point processes). Is there any way to do that?

As indicated in the reference the function h.nrninit() is not helpful, so I see 3 ways:
1. Run each NEURON instance on separated process and then kill it after finish. But how can I do that from Python?
2. Create a h.reset() function which performs: h.quit() and hypothetical new function h.reload()
2. Return a list of all objects and delete them separately in a loop.

Regards the second idea:
  • If h.allobjects() return a list of all objects instead of printing them I could remove them.
  • Destruction should work with Section (since setting up Python reference to None, eg. sec = None is a recommended way to make deletion)
    • I don't know what about other object such as NetStim, IClamp or PointPorcesses?
However I assume that it is no so easy with the legacy of the NEURON code, so maybe the idea 1 could be the most appropriate one?
ted
Site Admin
Posts: 6287
Joined: Wed May 18, 2005 4:50 pm
Location: Yale University School of Medicine
Contact:

Re: Best practice to run hundreds of simulations / remove all objects between runs

Post by ted »

After you have debugged your model, put all necessary statements (including model setup, instrumentation, simulation flow control, analysis, and i/o) inside a Python class definition. Assuming you called this class
ModelInABox
you can get a new instance of this class any time you want:
new_instance = ModelInABox(whatever parameters it needs)

After you have done
new_instance.arun()
(I'm assuming that ModelInABox includes a definition of a procedure called arun() that launches a single run, does whatever postrun analysis is necessary, and puts output somewhere for future use)
then you can throw away the instance that you used, and get a new one, by simply executing
new_instance = ModelInABox(whatever parameters it needs now)
ziemek
Posts: 45
Joined: Thu May 23, 2019 8:02 am
Location: Warsaw, Poland
Contact:

Re: Best practice to run hundreds of simulations / remove all objects between runs

Post by ziemek »

It doesn't work for me. I created a custom object-oriented framework to manage NEURON's Python wrapper.

So I have objects such as Cell, Simulation or Record. In a simplistic view, what I done looks like this:

Code: Select all

class Environment:
    def run(self):
        cell = Cell(name="cell")
        cell.add_sec(name="soma", diam=10, l=10, nseg=1)

        sim = Simulation()
        sim.run(100)

        del sim
        del cell
Then I run such code 2x and checked how many sections NEURON has:

Code: Select all

e = Environment()
e.run()
l1 = len(list(h.allsec()))
del e

e = Environment()
e.run()
l2 = len(list(h.allsec()))
del e
As you can see I have no reference outside the run() method to any objects, I made even deletion inside run() method and also on the whole Environment object.

However the number of sections after the first run is: l1 = 1 and after the second: l2 = 2, so it seems that sections haven't been deleted at all.

I double checked and I think that any of my objects have no reference "leaked" outside the self (the object instance) boundary.
ted
Site Admin
Posts: 6287
Joined: Wed May 18, 2005 4:50 pm
Location: Yale University School of Medicine
Contact:

Re: Best practice to run hundreds of simulations / remove all objects between runs

Post by ted »

Ah, my mistake. What worked from hoc may need modification to work from Python.

For a very pertinent discussion on the forum, see this thread
Running multiple simulations in one process
viewtopic.php?f=2&t=3213

Sure enough, h.delete_section() is discussed in the Programmer's Reference, and

Code: Select all

for sec in h.allsec():
  h.delete_section(sec=sec)
does get rid of all sections.
ziemek
Posts: 45
Joined: Thu May 23, 2019 8:02 am
Location: Warsaw, Poland
Contact:

Re: Best practice to run hundreds of simulations / remove all objects between runs

Post by ziemek »

Thanks, I will check the thread you mentioned.

According to your own response from the last year (the first response to this thread): viewtopic.php?f=2&t=1484&p=17633&hilit= ... ion#p17633 delete_section doesn't work in regular Python usage:
You must have seen the message:
Cannot delete an unnamed section

Python Section objects are deleted when their reference count goes to 0. Use

Code: Select all

c = None
if sections are created using the "create" statement, then they are deletable. eg.

Code: Select all

from neuron import h
h('create a, b, c')
h.topology()
c = h.c
print c, c.name()
h.delete_section(sec=c)
h.topology()
I also tried:

Code: Select all

for sec in h.allsec():
  h.delete_section(sec=sec)
However I do see the error:

Code: Select all

NEURON: Cannot delete an unnamed section
 near line 0
 ^
        delete_section()
Traceback (most recent call last):
  File "/home/ziemek/.config/JetBrains/PyCharm2020.1/scratches/scratch_4.py", line 20, in <module>
    h.delete_section(sec=sec)
RuntimeError: hoc error
ted
Site Admin
Posts: 6287
Joined: Wed May 18, 2005 4:50 pm
Location: Yale University School of Medicine
Contact:

Re: Best practice to run hundreds of simulations / remove all objects between runs

Post by ted »

Some of the posts in those previous threads illustrate that it is always possible to write complex code that has unintended, undesirable side effects.

In the past two days I used Python to work with two different implementations of simulation experiments wrapped in class definitions. One implementation employed a model cell class and an "experiment class" that were both written in hoc, and the other employed a model cell class and an "experiment class" that were both written in Python. Neither implementation called delete_section, nor did any of the Python code that I wrote to work with either implementation. With each implementation
1. No sections existed until an instance of the simulation experiment class was created.
2. Creation of an instance of the simulation experiment class resulted in creation of the exact number of sections that were in the model cell.
3. These sections persisted until the single variable that referenced the simulation experiment class was made to reference something else (specifically, an integer). At that point, the sections vanished.

And I was able to create and destroy new instances of the simulation experiment class repeatedly, and get the same results each time.

Here is the Experiment class that I used with a Python-defined cell class:

Code: Select all

class Experiment:
  def __init__(self):
    self.cell = Cell()
    # plus other statements for instrumentation
    # e.g. an IClamp, Vector.record, etc.
  def runsim(self):
    h.run()
    self.analyze()
    self.saveresults()
  def analyze(self):
    print("in analyze()")
  def saveresults(self):
    print("in saveresults()")
ziemek
Posts: 45
Joined: Thu May 23, 2019 8:02 am
Location: Warsaw, Poland
Contact:

Re: Best practice to run hundreds of simulations / remove all objects between runs

Post by ziemek »

Thanks, that's an interesting comment. The problem arises with the Python object management and garbage collector.

With simple objects, the removal is almost on demand, that's why you may see this scenario works well:

Code: Select all

class Experiment:
    def __init__(self):
        self.cell = Cell(name="cell")
        self.cell.sec = h.Section(name="name", cell=self.cell)

e = Experiment()
e = None
But when you have many wrapper objects:

Code: Select all

class Sec
   def __init__(hoc):
        self.hoc = hoc

class Cell:
   def __init__():
       self.secs = []
       
   def add_sec(name, diam, l, nseg=1):
   	hoc_sec = h.Section(name="name", cell=self.cell)
   	sec = Sec(hoc_sec, cell=self)
You can't expect Python will remove all of them on demand:

Code: Select all

class Experiment:
    def __init__(self):
        self.cell = Cell(name="cell")
        self.cell.cell.add_sec(name="soma", diam=10, l=10, nseg=1)

e = Experiment()
e = None
You may ask why do I need wrappers, but they contains additional fields which are helpful. Also look at this answer on the Stackoverflow: https://stackoverflow.com/a/6104568/1731803, it clearly points out that:
You cannot assume that __del__ will ever be called - it is not a place to hope that resources are automagically deallocated.
So defererencing or deletion may or may not work, depends on the heap. That's why there should be a more reliable way to clear the NEURON form all objects created.

A workaround for an instant deletion may be an implementation of such function in the Cell object:

Code: Select all

def remove_from_neuron(self):
    for k, v in self.__dict__.items():
        del v
Then you can implement __del__() in the Experiment like this:

Code: Select all

class Experiment:
    [...]
    
    def __del__(self):
        self.cell.remove_from_neuron()
At least it works for me, for the stack of wrapper I presented.
Post Reply