Skip to content

Commit

Permalink
bugfix: allow NodeList to access extracellular nodes. (#1214)
Browse files Browse the repository at this point in the history
If a species was defined on a Region and on Extracellular, NodeList could not access the extracellular nodes.
  • Loading branch information
adamjhn committed Apr 25, 2021
1 parent d2b0849 commit f120189
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 28 deletions.
73 changes: 45 additions & 28 deletions share/lib/python/neuron/rxd/node.py
Original file line number Diff line number Diff line change
Expand Up @@ -392,12 +392,11 @@ def satisfies(self, condition):
If a Region object is provided, returns True if the Node lies in the Region; else False.
If a number between 0 and 1 is provided, returns True if the normalized position lies within the Node; else False.
"""
if isinstance(condition, nrn.Section) or isinstance(condition, rxdsection.RxDSection):
return self._in_sec(condition)
elif isinstance(condition, region.Region):
return self.region == condition
elif isinstance(condition, nrn.Segment):
return self.segment == condition
try:
# test Node conditions
return super(Node1D, self).satisfies(condition)
except:
pass # ignore the error and try Node1D specific conditions
try:
if 0 <= condition <= 1:
dx = 1. / self._sec.nseg
Expand All @@ -408,10 +407,11 @@ def satisfies(self, condition):
# self_index should be unique (no repeats due to roundoff error) because the inside should always be 0.5 over an integer
self_index = int(self._location * self._sec.nseg)
return check_index == self_index

except:
raise RxDException('unrecognized node condition: %r' % condition)

pass
raise RxDException("unrecognized node condition: %r" % condition)

@property
def x3d(self):
"""x coordinate"""
Expand Down Expand Up @@ -570,35 +570,32 @@ def satisfies(self, condition):
Does not currently support numbers between 0 and 1.
"""
if isinstance(condition, nrn.Section) or isinstance(condition, rxdsection.RxDSection):
return self._in_sec(condition)
elif isinstance(condition, region.Region):
return self.region == condition
elif isinstance(condition, nrn.Segment):
return self.segment == condition
elif isinstance(condition, tuple) and len(condition) == 3:
try:
# test the Node conditions
return super(Node3D, self).satisfies(condition)
except:
pass # ignore the error and try Node3D specific conditions

if isinstance(condition, tuple) and len(condition) == 3:
x, y, z = condition
mesh = self._r._mesh_grid
return (
int((x - mesh["xlo"]) / mesh['dx']) == self._i and
int((y - mesh["ylo"]) / mesh['dy']) == self._j and
int((z - mesh["zlo"]) / mesh['dz']) == self._k
)
position_type = False
did_error = False
# check for a position condition so as to provide a more useful error
try:
if 0 <= condition <= 1:
position_type = True
# TODO: the trouble here is that you can't do this super-directly based on x
# the way to do this is to find the minimum and maximum x values contained in the grid
# the extra difficulty with that is to handle boundary cases correctly
# (to test, consider a section 1 node wide by 15 discretized pieces long, access at 1./15, 2./15, etc...)
raise RxDException("selecting nodes by normalized position not yet supported for 3D nodes; see comments in source about how to fix this")
except:
did_error = True
if did_error:
raise RxDException('unrecognized node condition: %r' % condition)
if position_type:
# TODO: the trouble here is that you can't do this super-directly based on x
# the way to do this is to find the minimum and maximum x values contained in the grid
# the extra difficulty with that is to handle boundary cases correctly
# (to test, consider a section 1 node wide by 15 discretized pieces long, access at 1./15, 2./15, etc...)
raise RxDException('selecting nodes by normalized position not yet supported for 3D nodes; see comments in source about how to fix this')
pass
raise RxDException("unrecognized node condition: %r" % condition)

@property
def x3d(self):
# TODO: need to modify this to work with 1d
Expand Down Expand Up @@ -780,5 +777,25 @@ def volume(self):
def _grid_id(self):
return self._speciesref()._extracellular_instances[self._r]._grid_id

def satisfies(self, condition):
"""Tests if a Node satisfies a given condition.
If a nrn.Section object or RxDSection is provided, returns True if the Node lies in the section; else False.
If a Region object is provided, returns True if the Node lies in the Region; else False.
If a tuple is provided of length 3, return True if the Node contains the (x, y, z) point; else False.
"""
try:
# test the Node conditions
return super(NodeExtracellular, self).satisfies(condition)
except:
pass # ignore the error and try NodeExtracellular specific conditions

if isinstance(condition, tuple) and len(condition) == 3:
x, y, z = condition
r = self._regionref()
return (
int((x - r._xlo) / r._dx[0]) == self._i
and int((y - r._ylo) / r._dx[1]) == self._j
and int((z - r._zlo) / r._dx[2]) == self._k
)
raise RxDException("unrecognized node condition: %r" % condition)
14 changes: 14 additions & 0 deletions test/rxd/ecs/test_ecs_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,3 +179,17 @@ def test_ecs_example_cvode_tort(ecs_example):
if not save_path:
max_err = compare_data(data)
assert max_err < tol


def test_ecs_nodelists(ecs_example):
"""Test accessing species nodes with both Node1D and NodeExtracellular"""

(h, rxd, data, save_path), make_model = ecs_example
model = make_model(0.2, 1.6)
ecs, x = model[5], model[6]
# test accessing NodeExtracellular from species with both 1D and ECS
assert len(x.nodes(ecs)) == 64
assert all([nd.region == ecs for nd in x.nodes(ecs)])
# test accessing specific node by location
nd = x[ecs].nodes((-38, 27, 27))[0]
assert (nd.x3d, nd.y3d, nd.z3d) == (-38.5, 27.5, 27.5)

0 comments on commit f120189

Please sign in to comment.