Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Meaningful Python types #1858

Merged
merged 42 commits into from
Sep 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
eb13ae2
hoc.Vector distinct from hoc.HocObject
ramcdougal Jun 20, 2022
c0eee1e
hoc.Vector is a subclass of hoc.HocObject
ramcdougal Jun 21, 2022
944bcbc
generating specs dynamically allows for different names
ramcdougal Jun 21, 2022
381e6b1
check result checks type
ramcdougal Jun 21, 2022
146643d
h.Vector returns hoc.Vector; hclass not needed anymore
ramcdougal Jun 21, 2022
476e9c6
Changing type lookup by string to symbol pointer
ferdonline Jun 21, 2022
3958e58
generating all HOC classes
ramcdougal Jun 21, 2022
27481ab
finished generalization to built-in classes
ramcdougal Jun 21, 2022
72b225d
addressed review comments
Helveg Jun 21, 2022
8d4b20a
fixed typecheck
Helveg Jun 21, 2022
477130f
Avoid function pointers to register classes.
ferdonline Jun 22, 2022
bc7f049
Ignore egg info
Helveg Jun 22, 2022
75aa55f
Fix style
ferdonline Jun 22, 2022
0f89ca2
Use a static list of exposed classes to avoid point processes
ferdonline Jun 22, 2022
199ffdd
fix hoctype validity check in hclass
Helveg Jun 22, 2022
cbc54f3
tweaked hoctype checking
Helveg Jun 22, 2022
560591e
allow old style hocobjects as hclass base
Helveg Jun 22, 2022
8a54909
hoc_base should always be oldstyle `h.`
Helveg Jun 22, 2022
0feed68
test inheritance basics
Helveg Jun 22, 2022
3292dbd
Go back to dyn create the list of classes to expose
ferdonline Jun 22, 2022
5e622af
wip
Helveg Jun 22, 2022
5900f79
Errors during hoc object construction were overwritten
Helveg Jun 22, 2022
a93e938
fixed/tested all cases of `hclass`/`HocBaseObject`
Helveg Jun 22, 2022
c631d94
fixed py inheritance tests
Helveg Jun 22, 2022
c6839e1
Merge branch 'master' into meaningful-types
alexsavulescu Jun 22, 2022
b2717b7
Black Style
ferdonline Jun 23, 2022
6e10085
Merge branch 'master' of github.com:neuronsimulator/nrn into meaningf…
ferdonline Jun 23, 2022
d4aa58e
Merge branch 'master' into meaningful-types
ferdonline Jul 8, 2022
ac02b53
Keep exposed pytype names alive until end execution
ferdonline Jul 9, 2022
c3baa26
Revisiting Ref counts
ferdonline Jul 9, 2022
5851cad
Merge branch 'master' into meaningful-types
ferdonline Jul 12, 2022
290bc37
Merge branch 'master' into meaningful-types
ramcdougal Jul 14, 2022
6ae5410
Merge branch 'master' into meaningful-types
ferdonline Jul 19, 2022
7e47522
Merge branch 'master' into meaningful-types
ramcdougal Aug 4, 2022
f0a068c
Merge branch 'master' into meaningful-types
ferdonline Sep 25, 2023
bb8435b
Merge branch 'master' into meaningful-types
rgourdine Sep 25, 2023
97976fe
Fixes for PR
ferdonline Sep 26, 2023
3ee944b
Merge branch 'master' into meaningful-types
ferdonline Sep 27, 2023
c106dce
Adding docs
ferdonline Sep 27, 2023
f092099
Merge branch 'master' into meaningful-types
ferdonline Sep 27, 2023
5b059f0
Merge branch 'master' into meaningful-types
ramcdougal Sep 28, 2023
f4be23d
branding: Neuron -> NEURON
ramcdougal Sep 28, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@
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

Check warning on line 68 in share/lib/python/neuron/hclass3.py

View check run for this annotation

Codecov / codecov/patch

share/lib/python/neuron/hclass3.py#L68

Added line #L68 was not covered by tests
if module_name is None:
module_name = __name__
if name is None:
Expand Down Expand Up @@ -90,23 +96,29 @@
"""

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.")

Check warning on line 111 in share/lib/python/neuron/hclass3.py

View check run for this annotation

Codecov / codecov/patch

share/lib/python/neuron/hclass3.py#L111

Added line #L111 was not covered by tests
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 @@
# 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 @@
"""

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 @@
assert match
else:
print("{} added {}".format(self, key))
if type(value) == type(h.Vector): # actually hoc.HocObject
if isinstance(value, hoc.Vector):

Check warning on line 78 in share/lib/python/neuron/tests/utils/checkresult.py

View check run for this annotation

Codecov / codecov/patch

share/lib/python/neuron/tests/utils/checkresult.py#L78

Added line #L78 was not covered by tests
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
Loading