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

[MRG] Improved LFP calculation #329

Merged
merged 24 commits into from Jun 17, 2021
Merged

Conversation

cjayb
Copy link
Collaborator

@cjayb cjayb commented May 10, 2021

This PR builds on and supersedes #150 (commits by @ntolley and @jasmainak retained in squash)

  • account for all transmembrane currents (not just that through the mid point sec(0.5); see this gist for details)
  • validate units and shift conductance units to S/m (default: 0.3)
  • produces expected pattern of near-field current sinks close to active synapses (see second figure in this gist)
  • produces far-field dipolar field patterns (see gist)
  • fix PSA and LSA implementations
  • illustrate laminar LFP and CSD calculation in (weak) gamma example
  • test that net transmembrane current is (very close to) zero (implemented by setting all transfer resistances to unity)
  • test that far-field LFPs is dipolar
  • test that transfer resistances are correct
  • consolidate NEURON and LFP electrode coordinates (NB y-coordinate)
  • create a suitable example (what do we want to demonstrate?)
  • update whats_new.rst & api.rst
  • create demo using single instance of Cell (move to explore response properties of individual HNN network cells #323)
  • add tests for validity of PSA and LSA values close to cells (how?) not needed as long as the above three tests pass
  • optimise for speed future PR should improve implementation, possibly getting rid of extra_gather_scatter

Closes #68

@codecov-commenter
Copy link

codecov-commenter commented May 10, 2021

Codecov Report

Merging #329 (e198e62) into master (7c372ed) will decrease coverage by 1.22%.
The diff coverage is 82.25%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #329      +/-   ##
==========================================
- Coverage   91.00%   89.77%   -1.23%     
==========================================
  Files          13       15       +2     
  Lines        2557     2886     +329     
==========================================
+ Hits         2327     2591     +264     
- Misses        230      295      +65     
Impacted Files Coverage Δ
hnn_core/params_default.py 100.00% <ø> (ø)
hnn_core/viz.py 83.48% <53.57%> (-7.45%) ⬇️
hnn_core/extracellular.py 86.95% <86.95%> (ø)
hnn_core/dipole.py 94.00% <100.00%> (-0.02%) ⬇️
hnn_core/network.py 93.33% <100.00%> (+0.15%) ⬆️
hnn_core/network_builder.py 92.63% <100.00%> (+0.58%) ⬆️
hnn_core/parallel_backends.py 82.85% <100.00%> (+0.14%) ⬆️
hnn_core/utils.py 100.00% <100.00%> (ø)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 7c372ed...e198e62. Read the comment docs.

hnn_core/lfp.py Outdated Show resolved Hide resolved
hnn_core/lfp.py Outdated Show resolved Hide resolved
hnn_core/lfp.py Outdated Show resolved Hide resolved
hnn_core/lfp.py Outdated Show resolved Hide resolved
hnn_core/viz.py Outdated Show resolved Hide resolved
@cjayb
Copy link
Collaborator Author

cjayb commented May 11, 2021

I created this gist to validate the LFP calculations are correct.

@cjayb

This comment has been minimized.

@jasmainak
Copy link
Collaborator

Looks tantalizing!

By the way, your script could use some print statements with CVode.event. When I tried to run it, it quit with segmentation fault. I have no idea how far it went ...

@cjayb
Copy link
Collaborator Author

cjayb commented May 11, 2021

it quit with segmentation fault. I have no idea how far it went ...

Really?! Can you run it cell-by-cell? All the laminar analysis should work for sure, I suspect the problem is with trying to reset NEURON (h("forall delete_section()")), but failing.

I've separated the laminar and grid analyses into separate gists (links here and here, respectively). The 20 x 20 grid is super-slow, indicating some work is needed to optimise the calculation.

I couldn't get h.CVode().event working?!

@cjayb

This comment has been minimized.

@cjayb

This comment has been minimized.

hnn_core/lfp.py Outdated Show resolved Hide resolved
hnn_core/lfp.py Outdated Show resolved Hide resolved
hnn_core/lfp.py Outdated Show resolved Hide resolved
hnn_core/lfp.py Outdated Show resolved Hide resolved
hnn_core/lfp.py Outdated Show resolved Hide resolved
hnn_core/lfp.py Outdated Show resolved Hide resolved
hnn_core/lfp.py Outdated Show resolved Hide resolved
hnn_core/lfp.py Outdated Show resolved Hide resolved
hnn_core/lfp.py Outdated Show resolved Hide resolved
hnn_core/lfp.py Outdated Show resolved Hide resolved
hnn_core/lfp.py Outdated Show resolved Hide resolved
@jonescompneurolab jonescompneurolab deleted a comment from cjayb May 14, 2021
hnn_core/lfp.py Outdated Show resolved Hide resolved
hnn_core/lfp.py Outdated Show resolved Hide resolved
hnn_core/lfp.py Outdated Show resolved Hide resolved
@@ -849,6 +860,11 @@ def _reset_drives(self):
for drive_name in self.external_drives.keys():
self.external_drives[drive_name]['events'] = list()

def _reset_rec_array(self):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have thought about these reset methods. I think this is an indication that these objects don't belong as attributes of the Network object. They should be instantiated in simulate_dipole and returned separately

hnn_core/network.py Outdated Show resolved Hide resolved
net = default_network(deepcopy(params), add_drives_from_params=True)

# Test LFP electrodes
electrode_pos = (2, 400, 2)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this still with the xy axis swap? Probably doesn't matter for test but might be worth switching

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

throw a warning/error if all electrodes are too far from any cell?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't see that! That test function just tests the API, but I still made some clarifications to emphasise what's being tested.

Not sure there's a heuristic for "too far", especially since we want to compare with "macroscopic" recordings.


for method in ['psa', 'lsa']:
res = _transfer_resistance(sec, elec_pos, conductivity, method)
assert_allclose(res, target_vals[method])
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why allclose and not an exact equal?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question. I guess I just avoid "exactness" when it comes to floating point calculations. Note that the test is an independent implementation of the maths in the calculating function.

I've reduced rtol to 1e-12, doesn't get much more 'exact' than that :)

return (Q * cosT) / (4 * np.pi * R ** 2)


# require MPI to speed up due to large number of extracellular electrodes
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but this will not speed up in the CIs ...

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

while I like this code, I think this is not really a "unit" test. Could we perhaps maintain this in your example repository?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I know the definition, but this is clearly the slowest test. I guess I'm OK with cutting it out, will push update soon.

Comment on lines +300 to +301
't_evprox_1': 7,
't_evdist_1': 17})
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not using your own API? :)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for brevity! I hope to address this in a later proposal. I think the API should be something like:

net = default_network(params)
net.add_drives_from_params(modify_params={'t_evprox_1': 7, 't_evdist_1': 17})

WDYT?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do you really want to be loading drives from files and modifying them on the fly? It feels very opaque ...

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A discussion for another thread ;)

@jasmainak
Copy link
Collaborator

Okay I did a full read through of the code ... looks reasonable to me. Let's iterate more after merging. I don't want to block other PRs for fear of rebase issues with this PR. Would you be able to look at the math/code soon @ntolley ?

@ntolley
Copy link
Contributor

ntolley commented Jun 16, 2021

Honestly I am ok with merging now and then raising an issue in the unlikely case that the math doesn't add up. It's more or less just my own desire to understand the calculations. Everything else I've been able to read carefully and am quite happy with what I've seen!

Also I'm less concerned since this is a completely new feature and doesn't risk breaking existing functionality.

@jasmainak jasmainak mentioned this pull request Jun 17, 2021
15 tasks
Comment on lines +128 to +130
# To avoid very large values when electrode is placed (anywhere) on
# the section axis, enforce minimal perpendicular distance
R2 = np.maximum(R2, min_distance ** 2)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe just think about what min_distance is doing before merging @jasmainak and @ntolley

I think this'll come up again when repositioning the cells, so I wouldn't call it a show-stopper for merging this PR. Just something to keep in mind.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

humm ... why change R without changing b etc? Won't the math screw up then?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well the math gets "screwed up" either way, but this is to avoid division by zero. The situation is clearer in PSA, where the euclidean distance is restricted to be > min_distance. I noticed that LFPy and NetPyNE use a limit on R, too. We can return to this: it will resurface when talking about physical distances. Right now it "works".

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the electrode is so close to section axis, wouldn't the voltage simply be the membrane potential? I guess accessible with seg.v ?

each segment junction as a point extracellular current source.
``'lsa'`` (line source approximation) treats each segment as a line
source of current, which extends from the previous to the next segment
center point: |---x---|, where x is the current segment flanked by |.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is | in this diagram?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The 'x' is an element of seg_ctr, and the | are start and end on L118-9.

@jasmainak
Copy link
Collaborator

@cjayb are you ready to merge? I think I won't be able to sleep at night with a 1500 line PR waiting to be merged and 300 comments ... :)

@cjayb
Copy link
Collaborator Author

cjayb commented Jun 17, 2021

I'm ready to let this go :) There'll be plenty of opportunities to scrutinise the details in conjunction with the 0.2 release. Merge away, I say! I'll take the heat if we find something idiotic in there ;)

I'm (also) very convinced that eventually this needs to be pulled out of Network, but the current implementation makes it pretty trivial to do so, I think.

@jasmainak jasmainak merged commit 30fb2a1 into jonescompneurolab:master Jun 17, 2021
@jasmainak
Copy link
Collaborator

alright, I did the brave thing

tremendous effort @cjayb ! 🍺 🍻 And thanks @ntolley for working on the early draft

@cjayb
Copy link
Collaborator Author

cjayb commented Jun 17, 2021

Indeed, I had plenty to work with from the get-go!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

add CSD plot for comparing against animal studies
5 participants