I am using NEURON (8.2.7) + Python (3.13) with the Bulletin-board style parallelization to simulate populations of (multicompartment) neurons. NEURON so far works wonderfully for this however I have now run into a bit of memory trouble. The issue is that I often do parameter explorations or simulate stochastic behavior, both requiring running my population of cells multiple times, saving the results in between (for example voltages, or i_membrane) to pickle files. I noticed that if use "pc.submit(...) and while(pc.working())" multiple times, memory accumulates until I run out of memory. I thought I could "reset" the simulation and bulletin board in between rounds if I delete the generated python objects, therefore removing any (python side) references that NEURON still has on the individual cells and vectors, however this does not seem to free up memory.
I created a very simplified version of my simulation setup to illustrate, where I have a cell class that is used to create cells, and set up its recording vectors, a simulator class that handles the submission of jobs to the bulletin board, creation of cells using the cell class, running the individual simulations and then saving of results. And finally a run script with which I create a simulator object and run the simulation (multiple times).
If I print found PythonObjects using h.allobjects('PythonObject') after the first round of simulations and then again after the second round of simulations, I see after the first run:
...
PythonObject[11094] with 1 refs
PythonObject[11095] with 1 refs
PythonObject[11096] with 1 refs
PythonObject[11097] with 1 refs
and after second run:
...
PythonObject[41832] with 1 refs
PythonObject[41833] with 1 refs
PythonObject[41834] with 1 refs
PythonObject[41835] with 1 refs
so the number of PythonObjects increases with each round of simulations, and if i track the memory consumption of the main python process it steadily accumulates over the two rounds. This happens even if I delete my cell objects after each simulation and also if i delete the whole simulator object and recreate it.
Is there something I am missing to cleanup the "hoc side" of NEURON between rounds or is this style of running multiple simulations not really a good idea when using NEURON in general?
To execute the run script i am running:
Code: Select all
mpiexec -n 9 python run_simple.py > output.txt
Lukas
run_simple.py
Code: Select all
from anf_simple import ANF as model
from simulator_simple import Simulator as sim
import gc
from neuron import h
my_sim = sim(model=model, nof_runs=100, neuron_range=range(100))
my_sim.setup_mpi()
my_sim.run_fiber_population()
h.allobjects('PythonObject')
del my_sim.sim_results
del my_sim
gc.collect() # deletion does not have any effect on number of PythonObjects still found with 1 reference
h.allobjects('PythonObject')
my_sim = sim(model=model, nof_runs=100, neuron_range=range(100))
my_sim.setup_mpi()
my_sim.run_fiber_population()
h.allobjects('PythonObject')
my_sim.close_mpi()
h.quit()
Code: Select all
from neuron import h
from neuron.units import mV, ms
import gc
h.load_file("stdrun.hoc")
h.nrnmpi_init()
pc = h.ParallelContext()
def _run_single_fiber(model, run_no, fiber_no):
print(f"Running fiber {fiber_no} in run {run_no}")
fiber = model(run_no, fiber_no)
h.finitialize(-65 * mV)
h.fcurrent()
# Here recalculate e.g. leakage reversal potential
h.fcurrent()
h.frecord_init()
h.continuerun(100 * ms)
ret_vals ={}
ret_vals["v"] = fiber.v_vec
ret_vals["t"] = fiber.t_vec
fiber.cleanup_fiber()
del fiber
gc.collect()
return ret_vals
class Simulator:
def __init__(self, model, nof_runs, neuron_range):
self.model = model
self.nof_runs = nof_runs
self.neuron_range = neuron_range
def setup_mpi(self):
pc.runworker()
def close_mpi(self):
pc.done()
def run_fiber_population(self):
self.ret_list = []
self.sim_results = None
for run_no in range(self.nof_runs):
for fiber_no in self.neuron_range:
pc.submit(_run_single_fiber, self.model, run_no, fiber_no)
while pc.working():
self.ret_list.append(pc.pyret())
self.sim_results = self.ret_list
print(self.sim_results)
del self.ret_list
Code: Select all
from neuron import h
from neuron.units import um, mV
import gc
class ANF:
def __init__(self, run_no, fiber_no):
self.run_no = run_no
self.fiber_no = fiber_no
self._setup_morphology()
self._setup_biophysics()
self._setup_recording_vecs()
def _setup_morphology(self):
self.sections = []
for i in range(10):
sec = h.Section(name=f"fiber_{self.fiber_no}_sec_{i}", cell=self)
if i % 2 == 0: # Node
self.L = 2 * um
self.diam = 2 * um
else: # internode
self.L = 100 * um
self.diam = 2 * um
self.sections.append(sec)
def _setup_biophysics(self):
for i, sec in enumerate(self.sections):
if i % 2 == 0: # Node
sec.insert("hh")
sec.gnabar_hh = 0.12 * 10
sec.gkbar_hh = 0.036 * 10
sec.gl_hh = 0.0003 * 10
sec.Ra = 50
sec.cm = 1.0
else: # internode
sec.insert("pas")
sec.e_pas = -65 * mV
sec.g_pas = 1 * 1e-3 # mS
sec.cm = 1.0 / 30
def _setup_recording_vecs(self):
self.v_vec = []
self.t_vec = []
self.t_vec.append(h.Vector())
self.t_vec[-1].record(h._ref_t)
for sec in self.sections:
self.v_vec.append(h.Vector())
self.v_vec[-1].record(sec(0.5)._ref_v)
def cleanup_fiber(self):
del self.v_vec
del self.t_vec
for sec in self.sections:
del sec
gc.collect()