Skip to content

Commit 006d4eb

Browse files
committed
Fix edge case in LaminarCurrentSourceDensity
Fixes #196 Fixes #189 Fixes #195
1 parent 0c43f11 commit 006d4eb

10 files changed

Lines changed: 117 additions & 78 deletions

File tree

.github/workflows/coveralls.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ jobs:
1616
strategy:
1717
matrix:
1818
os: [ubuntu-latest]
19-
python-version: [3.8]
19+
python-version: [3.12]
2020

2121
steps:
2222
- uses: actions/checkout@v3

.github/workflows/flake8.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ jobs:
1212
- name: Set up Python environment
1313
uses: actions/setup-python@v3
1414
with:
15-
python-version: "3.8"
15+
python-version: "3.12"
1616
- name: flake8 Lint
1717
uses: reviewdog/action-flake8@v3
1818
with:

.github/workflows/python-app.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ jobs:
1616
strategy:
1717
matrix:
1818
os: [ubuntu-latest]
19-
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]
19+
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
2020

2121
steps:
2222
- uses: actions/checkout@v3

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ dmypy.json
130130

131131
# MacOS crap
132132
.DS_Store
133+
__MACOSX/
133134

134135
# NEURON
135136
arm64/
@@ -140,3 +141,4 @@ doc/source/_build
140141

141142
# examples
142143
examples/src
144+
L5bPCmodelsEH/

lfpykit/models.py

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2063,8 +2063,34 @@ def get_transformation_matrix(self):
20632063

20642064
z2 = np.array([z_i[0], z_i[1], z_i[0]])
20652065
z3 = np.array([z_i[0], z_i[1], z_i[1]])
2066+
2067+
def _get_fraction_from_z_crossing(k, z_cross, endpoint):
2068+
z0 = self.cell.z[k, 0]
2069+
z1 = self.cell.z[k, 1]
2070+
dz = z1 - z0
2071+
if np.isclose(dz, 0.):
2072+
return None
2073+
2074+
t = (z_cross - z0) / dz
2075+
if not (0. <= t <= 1.):
2076+
return None
2077+
2078+
if endpoint == 0:
2079+
return abs(t)
2080+
return abs(1. - t)
2081+
20662082
# iterate over lower, right, upper boundary
20672083
for k in np.where(inds)[0]:
2084+
# If both endpoints are inside the cylindrical radius, the
2085+
# in-volume fraction follows directly from z-intersection.
2086+
if kk1[k]:
2087+
z_cross = z_i[1] if self.cell.z[k, 1] >= z_i[1] else z_i[0]
2088+
frac = _get_fraction_from_z_crossing(
2089+
k=k, z_cross=z_cross, endpoint=0)
2090+
if frac is not None:
2091+
M[i, k] = frac
2092+
continue
2093+
20682094
for ll in range(3):
20692095
Pr, Pz, hit = _PrPz(r0=R[k, 0], z0=self.cell.z[k, 0],
20702096
r1=R[k, 1], z1=self.cell.z[k, 1],
@@ -2080,6 +2106,16 @@ def get_transformation_matrix(self):
20802106
inds = (~ll0) & ll1
20812107

20822108
for k in np.where(inds)[0]:
2109+
# If both endpoints are inside the cylindrical radius, the
2110+
# in-volume fraction follows directly from z-intersection.
2111+
if kk0[k]:
2112+
z_cross = z_i[1] if self.cell.z[k, 0] >= z_i[1] else z_i[0]
2113+
frac = _get_fraction_from_z_crossing(
2114+
k=k, z_cross=z_cross, endpoint=1)
2115+
if frac is not None:
2116+
M[i, k] = frac
2117+
continue
2118+
20832119
for ll in range(3):
20842120
Pr, Pz, hit = _PrPz(r0=R[k, 0], z0=self.cell.z[k, 0],
20852121
r1=R[k, 1], z1=self.cell.z[k, 1],
@@ -2106,15 +2142,7 @@ def _PrPz(r0, z0, r1, z1, r2, z2, r3, z3):
21062142
- (r0 - r1) * (r2 * z3 - r3 * z2)) / denom)
21072143
Pz = (((r0 * z1 - z0 * r1) * (z2 - z3)
21082144
- (z0 - z1) * (r2 * z3 - r3 * z2)) / denom)
2109-
# check if intersection point lies on lines
2110-
if (Pr >= r0) & (Pr <= r1) & (Pz >= z0) & (Pz <= z1):
2111-
hit = True
2112-
elif (Pr <= r0) & (Pr >= r1) & (Pz >= z0) & (Pz <= z1):
2113-
hit = True
2114-
elif (Pr >= r0) & (Pr <= r1) & (Pz <= z0) & (Pz >= z1):
2115-
hit = True
2116-
elif (Pr <= r0) & (Pr >= r1) & (Pz <= z0) & (Pz >= z1):
2117-
hit = True
2118-
else:
2119-
hit = False
2145+
# check if intersection point lies on line segment (r0, z0) -> (r1, z1)
2146+
hit = (min(r0, r1) <= Pr <= max(r0, r1)
2147+
and min(z0, z1) <= Pz <= max(z0, z1))
21202148
return Pr, Pz, hit

lfpykit/tests/test_module.py

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -732,7 +732,7 @@ def test_LaminarCurrentSourceDensity_02(self):
732732

733733
np.testing.assert_allclose(M_gt, M)
734734

735-
def test_LaminarCurrentSourceDensity_3(self):
735+
def test_LaminarCurrentSourceDensity_03(self):
736736
'''test LaminarCurrentSourceDensity implementation'''
737737
cell = get_cell(n_seg=3)
738738
cell.z = cell.z * 10
@@ -754,7 +754,7 @@ def test_LaminarCurrentSourceDensity_3(self):
754754

755755
np.testing.assert_allclose(M_gt, M)
756756

757-
def test_LaminarCurrentSourceDensity_4(self):
757+
def test_LaminarCurrentSourceDensity_04(self):
758758
'''test LaminarCurrentSourceDensity implementation'''
759759
cell = get_cell(n_seg=4)
760760
cell.z = cell.z * 10
@@ -826,6 +826,55 @@ def test_LaminarCurrentSourceDensity_06(self):
826826

827827
np.testing.assert_allclose(M_gt, M)
828828

829+
830+
def test_LaminarCurrentSourceDensity_07(self):
831+
'''test LaminarCurrentSourceDensity implementation
832+
833+
Issue #196 (https://github.com/LFPy/LFPykit/issues/196)
834+
'''
835+
cell = lfp.CellGeometry(x=np.array([[50, 50], [50, -50]]),
836+
y=np.array([[0, 0], [0, 0]]),
837+
z=np.array([[-100, -50], [-50, 50]]),
838+
d=np.array([1, 1]))
839+
840+
h = 150.
841+
r = 200.
842+
M_gt = np.array([[1., 0.5],
843+
[0., 0.5]]) / (np.pi * r**2 * h)
844+
845+
z = np.array([[-h, 0.], [0., h]])
846+
r = np.array([r, r])
847+
848+
# instantiate electrode, get linear response matrix
849+
csd_lam = lfp.LaminarCurrentSourceDensity(cell=cell, z=z, r=r)
850+
M = csd_lam.get_transformation_matrix()
851+
852+
np.testing.assert_allclose(M_gt, M)
853+
854+
def test_LaminarCurrentSourceDensity_08(self):
855+
'''test LaminarCurrentSourceDensity implementation
856+
857+
Issue #196 (https://github.com/LFPy/LFPykit/issues/196)
858+
'''
859+
cell = lfp.CellGeometry(x=np.array([[0, 0], [0, 100]]),
860+
y=np.array([[0, 0], [0, 0]]),
861+
z=np.array([[-100, -50], [-50, 50]]),
862+
d=np.array([1, 1]))
863+
864+
h = 150.
865+
r = 200.
866+
M_gt = np.array([[1., 0.5],
867+
[0., 0.5]]) / (np.pi * r**2 * h)
868+
869+
z = np.array([[-h, 0.], [0., h]])
870+
r = np.array([r, r])
871+
872+
# instantiate electrode, get linear response matrix
873+
csd_lam = lfp.LaminarCurrentSourceDensity(cell=cell, z=z, r=r)
874+
M = csd_lam.get_transformation_matrix()
875+
876+
np.testing.assert_allclose(M_gt, M)
877+
829878
def test_VolumetricCurrentSourceDensity_00(self):
830879
cell = get_cell(n_seg=1)
831880
cell.z = cell.z * 10.

lfpykit/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
version = "0.5.1"
1+
version = "0.6.0"

pyproject.toml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
[build-system]
2+
requires = ["poetry-core>=1.0.0"]
3+
build-backend = "poetry.core.masonry.api"
4+
5+
[tool.poetry]
6+
name = "LFPykit"
7+
version = "0.6.0"
8+
description = "Electrostatic models for multicompartment neuron models"
9+
authors = ["LFPy-team <lfpy@users.noreply.github.com>"]
10+
11+
[tool.poetry.dependencies]
12+
python = ">=3.10"
13+
numpy = ">=1.15.2"
14+
scipy = ">=1.5.2"
15+
meautility = ">=1.5.1"
16+
17+
[tool.poetry.extras]
18+
tests = ["pytest", "sympy"]
19+
docs = ["sphinx", "numpydoc", "sphinx_rtd_theme", "recommonmark"]
20+
21+
[tool.poetry.dev-dependencies]

setup.cfg

Lines changed: 0 additions & 2 deletions
This file was deleted.

setup.py

Lines changed: 0 additions & 59 deletions
This file was deleted.

0 commit comments

Comments
 (0)