Welcome to Patch’s documentation!

https://badge.fury.io/gh/Helveg%2Fpatch.svg https://github.com/Helveg/patch/actions/workflows/main.yml/badge.svghttps://codecov.io/gh/Helveg/patch/branch/master/graph/badge.svg https://img.shields.io/badge/code%20style-black-000000.svg Documentation Status

Usage

Inline replacement of NEURON by Patch

Be aware that the interface is currently incomplete, this means that most parts are still “just” NEURON. I’ve only patched holes I frequently encounter myself when using NEURON’s Section, NetStim and NetCon functions. Feel free to open an issue or fork this project and open a pull request for missing or broken parts of the interface.

Philosophy

Python interfaces should be Pythonic, this wrapper offers just that:

  • Full Python objects: each wonky C-like NEURON object is wrapped in a full fledged Python object, easily handled and extended through inheritance.

  • Duck typed interface: take a look at the magic methods I use and any object you create with those methods present will work just fine with Patch.

  • Correct garbage collection, objects connected to eachother don’t dissapear: Objects that rely on eachother store a reference to eachother. As is the basis for any sane object oriented interface.

Basic usage

Use it like you would use NEURON. The wrapper doesn’t make many changes to the interface, it just patches up some of the more frequent and ridiculous gotchas.

Patch supplies a new HOC interpreter p, the PythonHocInterpreter which wraps the standard HOC interpreter neuron.h provided by NEURON. Any objects returned will either be PythonHocObject’s wrapping their corresponding NEURON object, or whatever NEURON returns.

When using just Patch the difference between NEURON and Patch objects is handled transparently, but if you wish to mix interpreters you can transform all Patch objects back to NEURON objects with __neuron__() or the helper function transform().

from patch import p, transform
import glia as g

section = p.Section()
point_process = g.insert(section, "AMPA")
stim = p.NetStim()
stim.start = 10
stim.number = 5
stim.interval = 10

# And here comes the magic! This explicitly defined connection
# isn't immediatly garbage collected! What a crazy world we live in.
# Has science gone too far?
p.NetCon(stim, point_process)

# It's fully compatible using __neuron__
from neuron import h
nrn_section = h.Section()
nrn_section.connect(transform(section))
nrn_section.connect(section.__neuron__())

Sections

Sections are cilindrical representations of pieces of a cell. They have a length and a diameter. Sections are the main building block of a simulation in NEURON.

You can use the connect() method to connect Sections together.

Sections can be subdivided into Segments by specifying nseg, the simulator calculates the voltage for each segment, thereby affecting the spatial resolution of the simulation. The position of a segment is represented by its normalized position along the axis of the Segment. This means that a Segment at x=0.5 is in the middle of the Section. By default every section consists of 1 segment and the potential will be calculated for 3 points: At the start (0) and end (1) of the section, and in the middle of every segment (0.5). For 2 segments the simulator would calculate at 0, 0.333…, 0.666… and 1.

from patch import p
s = p.Section()
s.L = 40
s.diam = 0.4
s.nseg = 11

s2 = p.Section()
s.connect(s2)
from neuron import h
s = h.Section()
s.L = 40
s.diam = 0.4
s.nseg = 11

s2 = h.Section()
s.connect(s2)

Retrieving segments

Sections can be called with an x to retrieve the segment at that x. The segments of a Section can also be iterated over.

s.nseg = 5
seg05 = s(0.5)
print(seg05)
for seg in s:
    print(seg)
s.nseg = 5
seg05 = s(0.5)
print(seg05)
for seg in s:
    print(seg)

Recording

You can tell Patch to record the membrane potential of your Section at one or multiple locations by calling the .record function and giving it an x. If x is omitted 0.5 is used.

In NEURON you’d have to create a Vector and keep track of it somewhere and find a way to link it back to the Section it recorded, in Patch a section automatically stores its recording vectors in section.recordings.

    s.record(x=1.0)
      v = h.Vector()
      v.record(s(1.0))
      all_recorders.append(v)

Position in space

With Patch it’s very straightforward to define the 3D path of your Section through space. Call the .add_3d function with a 2D array containing the xyz data of your points. Optionally, you can pass another array of diameters.

s.add_3d([[0, 0, 0], [2, 2, 2]], diameters)
s.push()
points = [[0, 0, 0], [2, 2, 2]]
for point, diameter in zip(points, diameters):
  h.pt3dadd(*point, diameter)
h.pop_section()

Full reference

Here is a full list of methods that Patch patched or added to the interface of NEURON Sections:

class patch.objects.Section(*args, **kwargs)[source]
add_3d(points, diameters=None)[source]

Add new 3D points to this section xyz data.

Parameters
  • points – A 2D array of xyz points.

  • diameters (float or array) – A scalar or array of diameters corresponding to the points. Default value is the section diameter.

connect(target, *args, **kwargs)[source]

Connect this section to another one as child section.

connect_points(target, x=None, **kwargs)[source]

Connect a Segment of this Section to a target. Usually used to connect the membrane potential to a point process.

iclamp(x=0.5, delay=0, duration=100, amplitude=0)[source]

Create a current clamp on the section.

Parameters
  • x (float) – Location along the segment from 0 to 1.

  • delay (float) – Duration of the pre-step holding interval, from 0 to delay ms.

  • duration (float) – Duration of the step interval, from delay to delay + duration ms.

  • amplitude (Union[float, List[float]]) – Can be a single value to define the current during the step (delay to delay + duration ms), or a sequence to play after delay ms. This will play 1 value of the sequence into the clamp per timestep.

Returns

The current clamp placed in the section.

Return type

objects.SEClamp

insert(*args, **kwargs)[source]

Insert a mechanism into the Section.

property parent

Returns the parent of the Section, or None

property points

Return the 3d point information associated to this section.

pop()[source]

Pop this section off the section stack.

push()[source]

Return a context manager that pushes this Section onto the section stack and takes it off when the context is exited.

record(x=None)[source]

Record the Section at a certain point.

Parameters

x (float) – Arcpoint, defaults to __arc__ if omitted.

set_dimensions(length, diameter)[source]

Set the length and diameter of the piece of cable this Section will represent in the simulation.

set_segments(segments)[source]

Set the number of discrete points where equations are solved during simulation.

synapse(factory, *args, store=False, **kwargs)[source]

Insert a synapse into the Section.

Parameters
  • factory (callable) – Callable that creates a point process, is given the Section as first argument and passes on all other args.

  • store (bool) – Store the synapse on the Section in a synapses attribute.

vclamp(x=0.5, delay=0, duration=100, after=0, voltage=- 70, holding=- 70)[source]

Create a voltage clamp on the section.

Parameters
  • x (float) – Location along the segment from 0 to 1.

  • delay (float) – Duration of the pre-step holding interval, from 0 to delay ms.

  • duration (float) – Duration of the step interval, from delay to delay + duration ms.

  • after (float) – Duration of the post-step holding interval, from delay + duration to delay + duration + after ms.

  • voltage (Union[float, List[float]]) – Can be a single value to define the voltage during the step (delay to delay + duration ms), or 3 values to define the pre-step, step and post-step voltages altogether.

  • holding (float) – If voltage is a single value, holding is used for the pre-step and post-step voltages.

Returns

The single electrode voltage clamp placed in the section.

Return type

objects.SEClamp

wholetree()[source]

Return the whole tree of child Sections

Return type

List[patch.Section]

Connecting components

Connecting sections

To other sections

Connecting sections together is the basic way of constructing cells in NEURON. You can do so using the Section.connect() method.

from patch import p
s = p.Section()
s2 = p.Section()
s.connect(s2)
from neuron import h
s = h.Section()
s2 = h.Section()
s.connect(s2)

Network connections

TO DO

In parallel simulations

In Patch most of the parallel context is managed for you, and you can use the ParallelCon() method to either connect an output (cell soma, axons, …) to a GID or a GID to an input (synapse on postsynaptic cell, …).

The following code transmits the spikes of a Section on GID 1:

from patch import p
gid = 1
s = p.Section()
nc = p.ParallelCon(s, gid)
from neuron import h
gid = 1
h.nrnmpi_init()
pc = h.ParallelContext()
s = h.Section()
nc = h.NetCon(s(0.5)._ref_v, None)
pc.set_gid2node(gid, pc.id())
pc.cell(gid, nc)
pc.outputcell(gid)

You can then receive the spikes of GID 1 on a synapse:

from patch import p
gid = 1
syn = p.Section().synapse(p.SynExp)
nc = p.ParallelCon(gid, syn)
from neuron import h
gid = 1
h.nrnmpi_init()
pc = h.ParallelContext()
s = h.Section()
syn = h.SynExp(s)
pc.gid_connect(gid, syn)

Magic methods

__neuron__

Get the object’s NEURON pointer

Whenever an object with this method present is sent to the NEURON HOC interpreter, the result of this method is passed instead. This allows Python methods to encapsulate NEURON pointers transparently

__netcon__

Get the object’s NetCon pointer

Whenever an object with this method present is used in a NetCon call, the result of this method is passed instead. The connection is stored on the original object. This allows to simplify the calls to NetCon, or to add more elegant default behavior. For example inserting a connection on a section might connect it to a random segment and you’d be able to use p.NetCon(section, synapse).

patch package

patch.core module

patch.core.assert_connectable(obj, label=None)[source]

Assert whether an object could be used as a Connectable.

Parameters

label (str) – Optional label to display to describe the object if the assertion fails.

patch.core.is_point_process(name)[source]

Check if a PointProcess with name exists on the HocInterpreter.

Parameters

name (str) – Name of the PointProcess to look for. Needs to be a known attribute of neuron.h.

Returns

Whether an attribute with name exists on neuron.h and has functions matching those expected to be present on a PointProcess.

Return type

bool

patch.core.is_section(obj)[source]

Check if the class name of an object is Section.

patch.core.transform(obj)[source]

Transform an object to its NEURON representation, if the __neuron__ magic method is present.

patch.core.transform_arc(obj, *args, **kwargs)[source]

Get an arclength object on a NEURON object. Calls the __arc__ magic method on the callable object if present, otherwise returns the transformed object.

patch.core.transform_netcon(obj)[source]

Transform an object into the pointer that should be connected, if the __netcon__ magic method is present.

patch.core.transform_record(obj)[source]

Transform an object into the pointer that should be recorded, if the __record__ magic method is present.

patch.interpreter module

class patch.interpreter.ParallelContext(*args, **kwargs)[source]

Bases: patch.objects.PythonHocObject

broadcast(data, root=0)[source]

Broadcast either a Vector or arbitrary picklable data. If data is a Vector, the Vectors are resized and filled with the data from the Vector in the root node. If data is not a Vector, it is pickled, transmitted and returned from this function to all nodes.

Parameters
  • data (Vector or any picklable object.) – The data to broadcast to the nodes.

  • root (int) – The id of the node that is broadcasting the data.

Returns

None (Vectors filled) or the transmitted data

Raises

BroadcastError if neuron.hoc.HocObjects that aren’t Vectors are transmitted

cell(gid, nc)[source]
setup_transfer(*args, **kwargs)
source_var(*args, **kwargs)
target_var(*args, **kwargs)
class patch.interpreter.PythonHocInterpreter[source]

Bases: object

IClamp(x=0.5, sec=None)[source]
NetCon(source, target, *args, **kwargs)[source]
NetStim(*args, **kwargs)
ParallelCon(a, b, output=True, *args, **kwargs)[source]
ParallelContext()[source]
PointProcess(*args, **kwargs)
SEClamp(sec, x=0.5)[source]
Section(*args, **kwargs)
SectionRef(*args, sec=None)[source]
Segment(interpreter, ptr, section, **kwargs)
VecStim(pattern=None, *args, **kwargs)[source]
Vector(interpreter, ptr)
cas()[source]
continuerun(time_stop, add=False)[source]
finitialize(initial=None)[source]
load_extension(extension)[source]
nrn_load_dll(path)[source]
property parallel
record(target)[source]
classmethod register_hoc_object(hoc_object_class)[source]
run()[source]
property time

patch.objects module

class patch.objects.Connectable[source]

Bases: object

class patch.objects.IClamp(interpreter, ptr)[source]

Bases: patch.objects.PythonHocObject, patch.objects.WrapsPointers

class patch.objects.NetCon(interpreter, ptr)[source]

Bases: patch.objects.PythonHocObject

record(vector=None)[source]
class patch.objects.NetStim(*args, **kwargs)[source]

Bases: patch.objects.PythonHocObject, patch.objects.Connectable

class patch.objects.PointProcess(*args, **kwargs)[source]

Bases: patch.objects.PythonHocObject, patch.objects.Connectable, patch.objects.WrapsPointers

Wrapper for all point processes (membrane and synapse mechanisms).

stimulate(pattern=None, weight=0.04, delay=0.0, **kwargs)[source]

Stimulate a point process.

Parameters
  • pattern (list[float]) – Specific stimulus event times to play into the point process.

  • kwargs – All keyword arguments will be passed set on the NetStim

class patch.objects.PointerWrapper(attr)[source]

Bases: object

class patch.objects.PythonHocObject(interpreter, ptr)[source]

Bases: object

__deref__(obj)[source]

Magic method that is called when a strong reference needs to be removed from the object.

__neuron__()[source]

Magic method that is called when this object is passed to NEURON.

__ref__(obj)[source]

Magic method that is called when a strong reference needs to be stored on the object.

class patch.objects.SEClamp(interpreter, ptr)[source]

Bases: patch.objects.PythonHocObject, patch.objects.WrapsPointers

class patch.objects.Section(*args, **kwargs)[source]

Bases: patch.objects.PythonHocObject, patch.objects.Connectable, patch.objects.WrapsPointers

add_3d(points, diameters=None)[source]

Add new 3D points to this section xyz data.

Parameters
  • points – A 2D array of xyz points.

  • diameters (float or array) – A scalar or array of diameters corresponding to the points. Default value is the section diameter.

connect(target, *args, **kwargs)[source]

Connect this section to another one as child section.

connect_points(target, x=None, **kwargs)[source]

Connect a Segment of this Section to a target. Usually used to connect the membrane potential to a point process.

iclamp(x=0.5, delay=0, duration=100, amplitude=0)[source]

Create a current clamp on the section.

Parameters
  • x (float) – Location along the segment from 0 to 1.

  • delay (float) – Duration of the pre-step holding interval, from 0 to delay ms.

  • duration (float) – Duration of the step interval, from delay to delay + duration ms.

  • amplitude (Union[float, List[float]]) – Can be a single value to define the current during the step (delay to delay + duration ms), or a sequence to play after delay ms. This will play 1 value of the sequence into the clamp per timestep.

Returns

The current clamp placed in the section.

Return type

objects.SEClamp

insert(*args, **kwargs)[source]

Insert a mechanism into the Section.

property parent

Returns the parent of the Section, or None

property points

Return the 3d point information associated to this section.

pop()[source]

Pop this section off the section stack.

push()[source]

Return a context manager that pushes this Section onto the section stack and takes it off when the context is exited.

record(x=None)[source]

Record the Section at a certain point.

Parameters

x (float) – Arcpoint, defaults to __arc__ if omitted.

set_dimensions(length, diameter)[source]

Set the length and diameter of the piece of cable this Section will represent in the simulation.

set_segments(segments)[source]

Set the number of discrete points where equations are solved during simulation.

synapse(factory, *args, store=False, **kwargs)[source]

Insert a synapse into the Section.

Parameters
  • factory (callable) – Callable that creates a point process, is given the Section as first argument and passes on all other args.

  • store (bool) – Store the synapse on the Section in a synapses attribute.

vclamp(x=0.5, delay=0, duration=100, after=0, voltage=- 70, holding=- 70)[source]

Create a voltage clamp on the section.

Parameters
  • x (float) – Location along the segment from 0 to 1.

  • delay (float) – Duration of the pre-step holding interval, from 0 to delay ms.

  • duration (float) – Duration of the step interval, from delay to delay + duration ms.

  • after (float) – Duration of the post-step holding interval, from delay + duration to delay + duration + after ms.

  • voltage (Union[float, List[float]]) – Can be a single value to define the voltage during the step (delay to delay + duration ms), or 3 values to define the pre-step, step and post-step voltages altogether.

  • holding (float) – If voltage is a single value, holding is used for the pre-step and post-step voltages.

Returns

The single electrode voltage clamp placed in the section.

Return type

objects.SEClamp

wholetree()[source]

Return the whole tree of child Sections

Return type

List[patch.Section]

class patch.objects.SectionRef(interpreter, ptr)[source]

Bases: patch.objects.PythonHocObject

property child
class patch.objects.Segment(interpreter, ptr, section, **kwargs)[source]

Bases: patch.objects.PythonHocObject, patch.objects.Connectable, patch.objects.WrapsPointers

class patch.objects.VecStim(*args, **kwargs)[source]

Bases: patch.objects.PythonHocObject, patch.objects.Connectable

property pattern
property vector
class patch.objects.Vector(interpreter, ptr)[source]

Bases: patch.objects.PythonHocObject

record(target, *args, **kwargs)[source]
class patch.objects.WrapsPointers[source]

Bases: object

Module contents

Candy Guide

Read through this guide to get an idea of the sugar you’re getting.

Referencing

All objects in Patch that depend on each other reference each other, meaning that Python won’t garbage collect parts of your simulation that you explicitly defined. An example of this is that when you create a synapse and a NetStim and connect them together with a NetCon, you only have to hold on to 1 of the variables for Patch to know that if you’re holding on to a NetCon you are most likely interested in the parts it is connected to. There is no longer a need to store every single NEURON variable in a list or on an object somewhere. This drastically cleans up your code.

Sections & Segments

  • When connecting Sections together they all reference each other.

  • Whenever you’re using a Section where a Segment is expected, a default Segment will be used (Segment 0.5):

s = p.Section()
syn = p.ExpSyn(s)
# syn = h.ExpSyn(s(0.5))
  • Whenever a Section/Segment is recorded or connected and a NEURON scalar is expected _ref_v is used:

s = p.Section()
v = p.Vector()
v.record(s) # v.record(s(0.7)._ref_v)
  • Sections can record themselves at multiple points:

s = p.Section()
s.record() # Creates a vector, makes it record s(0.5)._ref_v and stores it as a recorder
s.record(0.7) # Records s(0.5)._ref_v

plt.plot(list(s.recordings[0.5]), list(p.time))
  • Sections can connect themselves to a PointProcess target with .connect_points, which handles NetCon stack access transparently. This allows for example for easy creation of synaptic contacts between a Section and a target synapse:

s = p.Section()
target_syn = p.Section().synapse(p.ExpSyn) # Creates an ExpSyn synapse
s.connect_points(target_syn) # Creates a NetCon between s(0.5)._ref_v and target_syn
  • Create a current clamp in a Section and configure it with keyword arguments:

clamp = p.Section().iclamp(amp=10, delay=0, duration=100)
# Pass an array to inject a varying current per timestep starting from the delay.
clamp2 = p.Section().iclamp(amp=[i for i in range(1000)], delay=100)
  • You can place Sections on the stack with .push(), .pop() or a context manager:

s = p.Section()
s.push()
s.pop()
with s.push():
  s_clamp = p.IClamp()
# `s` is automatically popped from the stack when the context is exited.
  • p.SectionRef can be called with either an arg or sec kwarg:

s = p.Section()
sr = p.SectionRef(sec=s)
sr = p.SectionRef(s)

Parallel networks

When you get to the level of the network the work becomes alot easier if you can describe your cells in a more structured way, so be sure to check out Arborize.

If you want to stay vanilla Patch still has you covered; it comes with out-of-the-box parallelization. Introducing the transmitter-receiver pattern:

if p.parallel.id() == 0:
  transmitter = ParallelCon(obj1, gid)
if p.parallel.id() == 1:
  receiver = ParallelCon(gid, obj2)

Just these 2 commands will create a transmitter on node 0 that broadcasts the spikes of obj1 (sections, segments) with the specified GID and a receiver on node 1 for obj2 (synapses, most likely?) that listens to spikes with that GID. Know that under the hood it needs to be something that can be connected to a NetCon.

That’s it. You are now spiking in parallel!

Arbitrary data broadcasting

MPI has a great feature, it allows broadcasting data to other nodes. In NEURON this is restricted to just Vectors. Patch gives you back the freedom to transmit arbitrary data. Anything that can be pickled can be transmitted:

data = None # It's important to declare your var on all nodes to avoid NameErrors
if p.parallel.id() == 12:
  data = np.random.randint((12,12,12))
received = p.parallel.broadcast(data, root=12)

Installation

Patch can be installed using:

pip install nrn-patch

Syntactic sugar & quality of life

This wrapper aims to make NEURON more robust and user-friendly, by patching common gotcha’s and introducing sugar and quality of life improvements. For a detailed overview of niceties that will keep you sane instead of hunting down obscure bugs, check out the Candy Guide.

Indices and tables