Skip to content

Commit

Permalink
Merge branch 'master' into cornu/clean_mpispike
Browse files Browse the repository at this point in the history
  • Loading branch information
alexsavulescu authored Sep 28, 2023
2 parents 427ad30 + 7fdff3b commit 6c48766
Show file tree
Hide file tree
Showing 11 changed files with 321 additions and 105 deletions.
1 change: 1 addition & 0 deletions docs/python/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ Basic Programming
programming/dynamiccode.rst
programming/projectmanagement.rst
programming/internals.rst
programming/neuron_classes.rst
programming/hoc-from-python.rst

Model Specification
Expand Down
87 changes: 87 additions & 0 deletions docs/python/programming/neuron_classes.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
NEURON Python Classes and Objects
=================================

NEURON exposes its internal objects and hoc templates as Python objects via an automatic
conversion layer, effectively making all entities from the HOC stack available to a Python program.

There are basically two main objects which expose most Neuron entities. The first is `hoc` which
exposes a number of internal established classes and functions.

.. code-block::
python
>>> from neuron import hoc
>>> hoc.
hoc.List(
hoc.SectionList(
hoc.SectionRef(
hoc.Vector(
...
However, for *dynamic* entities NEURON provides the `h` gateway object. It gives access to internal
classes (templates) and objects, even if they were just created. E.g.:

.. code-block::
python
>>> from neuron import h
>>> # Create objects in the hoc stack
>>> h("objref vec")
>>> h("vec = new Vector(5, 1)")
>>> # Access to objects
>>> h.vec.as_numpy()
array([1., 1., 1., 1., 1.])
>>>
>>> # Access to exposed types
>>> vv = h.Vector(5, 2)
>>> vv.as_numpy()
array([1., 1., 1., 1., 1.])
This is particularly useful as NEURON can dynamically load libraries with more functions and classes.

Class Hierarchy
---------------

All NEURON's internal interpreter objects are instances of a global top-level type: `HocObject`.
Until very recently they were considered direct instances, without any intermediate hierarchy.

With #1858 Hoc classes are now associated with actual Python types, created dynamically. Such
change enables type instances to be properly recognized as such, respecting e.g. `isinstance()`
predicates and subclassing.

.. code-block::
python
>>> isinstance(hoc.Vector, type)
True
>>> v = h.Vector()
>>> isinstance(v, hoc.HocObject)
True
>>> isinstance(v, hoc.Vector)
True
>>> type(v) is hoc.Vector # direct subclass
True
>>>isinstance(v, hoc.Deck) # Not instance of other class
False
Subclasses are also recognized properly. For creating them please inherit from `HocBaseObject`
with `hoc_type` given as argument. E.g.:

.. code-block::
python
>>> class MyStim(neuron.HocBaseObject, hoc_type=h.NetStim):
pass
>>> issubclass(MyStim, hoc.HocObject)
True
>>> issubclass(MyStim, neuron.HocBaseObject)
True
>>> MyStim._hoc_type == h.NetStim
True
>>> stim = MyStim()
>>> isinstance(stim, MyStim)
True
>>> isinstance(stim, h.NetStim)
True
>>> isinstance(stim, h.HocObject)
True
44 changes: 28 additions & 16 deletions share/lib/python/neuron/hclass3.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@
import sys


def _is_hoc_pytype(hoc_type):
return hoc_type is nrn.Section or (
isinstance(hoc_type, type) and issubclass(hoc_type, hoc.HocObject)
)


def assert_not_hoc_composite(cls):
"""
Asserts that a class is not directly composed of multiple HOC types.
Expand Down Expand Up @@ -58,8 +64,8 @@ class MyVector(myClassTemplate):
omitted the name of the HOC type is used.
:deprecated: Inherit from :class:`~neuron.HocBaseObject` instead.
"""
if hoc_type == h.Section:
return nrn.Section
if _is_hoc_pytype(hoc_type):
return hoc_type
if module_name is None:
module_name = __name__
if name is None:
Expand Down Expand Up @@ -90,23 +96,29 @@ class MyVector(neuron.HocBaseObject, hoc_type=neuron.h.Vector):
"""

def __init_subclass__(cls, hoc_type=None, **kwargs):
if hoc_type is not None:
if not isinstance(hoc_type, hoc.HocObject):
if _is_hoc_pytype(hoc_type):
cls_name = cls.__name__
raise TypeError(
f"Using HocBaseObject with {cls_name} is deprecated."
f" Inherit directly from {cls_name} instead."
)
if hoc_type is None:
if not hasattr(cls, "_hoc_type"):
raise TypeError(
f"Class's `hoc_type` {hoc_type} is not a valid HOC type."
"Class keyword argument `hoc_type` is required for HocBaseObjects."
)
elif not isinstance(hoc_type, hoc.HocObject):
raise TypeError(f"Class's `hoc_type` {hoc_type} is not a valid HOC type.")
else:
cls._hoc_type = hoc_type
elif not hasattr(cls, "_hoc_type"):
raise TypeError(
"Class keyword argument `hoc_type` is required for HocBaseObjects."
)
# HOC type classes may not be composed of multiple hoc types
assert_not_hoc_composite(cls)
hobj = hoc.HocObject
hbase = HocBaseObject
if _overrides(cls, hobj, "__init__") and not _overrides(cls, hbase, "__new__"):
# Subclasses that override `__init__` must also implement `__new__` to deal
# with the arguments that have to be passed into `HocObject.__new__`.
# See https://github.com/neuronsimulator/nrn/issues/1129
# Subclasses that override `__init__` must also implement `__new__` to deal
# with the arguments that have to be passed into `HocObject.__new__`.
# See https://github.com/neuronsimulator/nrn/issues/1129
if _overrides(cls, hoc.HocObject, "__init__") and not _overrides(
cls, HocBaseObject, "__new__"
):
raise TypeError(
f"`{cls.__qualname__}` implements `__init__` but misses `__new__`. "
+ "Class must implement `__new__`"
Expand All @@ -119,7 +131,7 @@ def __new__(cls, *args, **kwds):
# To construct HOC objects within NEURON from the Python interface, we use the
# C-extension module `hoc`. `hoc.HocObject.__new__` both creates an internal
# representation of the object in NEURON, and hands us back a Python object that
# is linked to that internal representation. The `__new__` functions takes the
# is linked to that internal representation. The `__new__` function takes the
# arguments that HOC objects of that type would take, and uses the `hocbase`
# keyword argument to determine which type of HOC object to create. The `sec`
# keyword argument can be passed along in case the construction of a HOC object
Expand Down
6 changes: 3 additions & 3 deletions share/lib/python/neuron/tests/utils/checkresult.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import json
import math
from neuron import h
from neuron import h, hoc


class Chk:
Expand Down Expand Up @@ -39,7 +39,7 @@ def __call__(self, key, value, tol=0.0):
"""

if key in self.d:
if type(value) == type(h.Vector): # actually hoc.HocObject
if isinstance(value, hoc.Vector):
# Convert to list to keep the `equal` method below simple
value = list(value)
# Hand-rolled comparison that uses `tol` for arithmetic values
Expand Down Expand Up @@ -75,7 +75,7 @@ def equal(a, b):
assert match
else:
print("{} added {}".format(self, key))
if type(value) == type(h.Vector): # actually hoc.HocObject
if isinstance(value, hoc.Vector):
self.d[key] = value.to_python()
else:
self.d[key] = value
Expand Down
16 changes: 8 additions & 8 deletions src/nrnoc/init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -866,13 +866,13 @@ int point_reg_helper(Symbol* s2) {
return pointtype++;
}

extern void class2oc(const char*,
void* (*cons)(Object*),
void (*destruct)(void*),
Member_func*,
int (*checkpoint)(void**),
Member_ret_obj_func*,
Member_ret_str_func*);
extern void class2oc_base(const char*,
void* (*cons)(Object*),
void (*destruct)(void*),
Member_func*,
int (*checkpoint)(void**),
Member_ret_obj_func*,
Member_ret_str_func*);


int point_register_mech(const char** m,
Expand All @@ -890,7 +890,7 @@ int point_register_mech(const char** m,
Symlist* sl;
Symbol *s, *s2;
nrn_load_name_check(m[1]);
class2oc(m[1], constructor, destructor, fmember, nullptr, nullptr, nullptr);
class2oc_base(m[1], constructor, destructor, fmember, nullptr, nullptr, nullptr);
s = hoc_lookup(m[1]);
sl = hoc_symlist;
hoc_symlist = s->u.ctemplate->symtable;
Expand Down
Loading

0 comments on commit 6c48766

Please sign in to comment.