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] Refactor API for creating external network drives #221
[MRG] Refactor API for creating external network drives #221
Conversation
Sorry I have been slow with the tonic PR ... have been helping folks in other PRs. Can help you rebase later :) I propose that we aim for a release after this PR and the few small PRs needed for GUI integration. We can still add a warning that the version is semi-stable (alpha, beta whatever version) and there might be breaking API changes in the future but having |
552c922
to
80ac507
Compare
@jasmainak @rythorpe @ntolley ready for feedback (code review also welcome, though many things still in flux...). See also updated aims. |
@cjayb not too thrilled by the new methods :( Could we perhaps zoom so I can understand better what you're trying to achieve? I may be misunderstanding the goals. We need to simplify, not overengineer. What we need is: net.add_poisson_input(...)
net.add_evoked_input(...)
net.add_rhythmic_input(...) and these functions will create |
Happy to discuss :) Send me an email with a few time proposals in the coming days! Try writing out your add_*-methods though: they end up duplicating everything but the event time stats. The idea behind the 'attach'-concept is that all drives have similarly-defined stats and connectivity. Focusing on the actual 'instantiated' event times in the API is IMHO wrong, or at least misses the point. I'm sure there's a way to use your semantics too, let's talk. |
Update: close but no cigar... After much shuffling around, I thought I finally got @jasmainak @rythorpe would you take a look at ----------------------------- Captured stdout call -----------------------------
Loading custom mechanism files from /Users/cjb/src/git/hnn-core/hnn_core/mod/x86_64/.libs/libnrnmech.so
Building the NEURON model
oc_restore_code tobj_count=1 should be 0
Traceback (most recent call last):
File "/Users/cjb/src/git/hnn-core/hnn_core/network_builder.py", line 501, in _connect_celltypes
gid_src, nc_dict, target_cell.synapses[syn_key])
File "/Users/cjb/src/git/hnn-core/hnn_core/cell.py", line 346, in parconnect_from_src
nc = _PC.gid_connect(gid_presyn, postsyn)
RuntimeError: hoc error
----------------------------- Captured stderr call -----------------------------
NEURON: gid 271 owned by 0 but no associated cell
near line 0
objref hoc_obj_[2]
^
ParallelContext[0].gid_connect(271, ...) |
4390052
to
d05c026
Compare
Addressed:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Synaptic delay (in ms) at the column origin, dispersed laterally as | ||
a function of the space_constant | ||
cell_specific : bool | ||
Whether each cell has unique connection parameters (default: True) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's really the cell type though right? As in this doesn't support single gid targeting... yet!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct. I was on the fence on this one, opted for the short (but misleading) name. Changing to cell_type_specific
for clarity.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops, spoke too early... I actually do mean cell-specific here! This is the old p_unique
(vs. p_common
) distinction. The flag controls whether each (real) cell in the network gets 'specific' input from a single artificial drive cell, or whether there is a one 'global' drive targeting all cells. In the latter case, the spike dynamics are identical across cells, though the connection strength and synaptic delay may differ (at the level of cell types, not individual cells, as you point out). Make sense?
hnn_core/network.py
Outdated
if isinstance(synaptic_delays, float): | ||
drive_conn_by_cell[cellname][receptor][ | ||
'A_delay'] = synaptic_delays | ||
else: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe elif isinstance(synaptic delays, dict)
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Made the change, though there's a test to ensure it's either a float or a dict (so not including an else: raise ValueError
here.
f', got {cell_type}, {amplitude}') | ||
if 'tonic' not in self.external_biases: | ||
self.external_biases['tonic'] = dict() | ||
if cell_type in self.external_biases['tonic']: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm so does this still get triggered when you copy the network? Or does _reset_drives()
take care of that. If not, it may be nice to overwrite specific drives after copying the Network when writing code for parameter sweeps.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See my comment to @rythorpe below. The only thing _reset
are previous simulation results, specifically: vsoma/isoma
and the 'events'
values (spike times) of drives. It does not remove drives or biases.
I think this is what you want when doing a parameter sweep, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So if a user first creates a tonic input that targets L2/3 basket cells by calling net.add_tonic_bias()
, and then post-hoc decides to change the strength of the injected current for this input, how are they supposed to do this? If I understand things correctly, they can't call net.add_tonic_bias()
again because the L2/3 basket cell type already exists in net.external_biases['tonic']
right? Alternatively, I guess the user could directly modify net.external_biases['tonic']
, but if this is the case it should say so in the documentation explicitly (maybe it already does and I just missed it?).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this is a documentation issue rather than a bug, let's definitely save this for a later PR. I'm just unclear at the moment how tonic biases get modified (either in an original Network
instance or a copied instance).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the current implementation doesn't explicitly have methods for ad hoc modification, though it's certainly possible by modifying the dicts directly, as you point out (and as is done in the evoked
example). But both are easy to add, since both drives and biases don't really "materialise" until they're baked into the simulation in NetworkBuilder
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ow are they supposed to do this?
To answer your question: either modify the dict, or create a new network and add the correct bias.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the modification thing is a separate discussion to have :) You can always change one line in your script and re-run those lines ... so not even sure if it's a strong use case
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's weird though is that one of the examples shows how to modify the drives ... so it suggests that any other modifications should work. I'm almost inclined to remove the syncing example until we can properly think about how to modify the drives on an existing network. Or keep it with the intention of fixing it properly in a future PR ... along with the dozen other things :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, I'm cool with leaving this to be discussed in more detail later.
cell.add_tonic_input( | ||
**self.net.feed_times['tonic'][_short_name(src_type)]) | ||
if ('tonic' in self.net.external_biases and | ||
src_type in self.net.external_biases['tonic']): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Curious if there's any instance where src_type is not present? No need to change either way, just wondering if this is a real problem that would crop up.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh yeah. That's actually the case most often than not: you are only likely to insert a current clamp onto a single cell type, say, L5_pyramidal
, and leave the rest alone.
hnn_core_root = op.dirname(hnn_core.__file__) | ||
params_fname = op.join(hnn_core_root, 'param', 'default.json') | ||
params = read_params(params_fname) | ||
net = Network(params, legacy_mode=False) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should there be a test with legacy mode?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All tests are in 'legacy mode', except this one, as it's testing the new API explicitly.
The most insidious place this is used is in network.py:L591
:
if self._legacy_mode:
# XXX tests must match HNN GUI output
target_populations = self.cellname_list
That's needed to get all the tests against HNN GUI to pass. The code will work fine without, but then there'll be zero chance of prng
seeds to match those in GUI.
The net._legacy_mode
also skips parameter checking for values extracted from param
-files: these can contain all manner of rubbish (notably: drive tstart
-values that are >tstop
)
# each drive. | ||
|
||
net_sync = net.copy() | ||
net_sync.external_drives['evdist1']['dynamics']['sync_within_trial'] = True |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Future PR idea, function to sync all external drives automatically?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure :) I just don't have an idea of how much this syncing business is actually used?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The main benefit would be teasing apart the contribution of specific inputs. Namely, holding every drive fixed except for one that is allowed to vary.
Sorry for so much delay getting to the review. The internals are extremely clean and delightfully easy to review. Like last time most comments are nitpicks/clarifications that can certainly be held off for followup PR's. Really great work @cjayb, I'm excited to start using this API in my own research. Could the poor coverage be partly explained by the fact that most tests add drives without actually simulating the network? @jasmainak you definitely deserve the honors! |
def _reset_drives(self): | ||
# reset every time called again, e.g., from dipole.py or in self.copy() | ||
for drive_name in self.external_drives.keys(): | ||
self.external_drives[drive_name]['events'] = list() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Along with Nick's comment, should this function also reset self.external_biases
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would say there's nothing to reset there. Note that the _reset
doesn't actually remove the drives, only the event (spike) times from the appropriate dict key. The external_biases
are just a dict of dict, the parameter values in which get applied (HOC) in NetworkBuilder
when creating the cells (e.g., current clamp applied soma).
We discussed the possibility of delete
-methods for drives (and biases) way back. I don't think there's really a use case for that, do you? On the other hand, it would be as simple as deleting a dictionary key... We can consider it when discussing how seeding should occur in the post-GUI days.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As I mentioned above, let's save this to be discussed in a later PR. I do think that there should be a delete method for drives and biases so that there is both the functionality to add and remove drives/biases from a Network
instance (particularly since some of our examples demo the ability to modify attributes of a copied Network
instance to run different simulations).
Pending my one comment above, I haven't been able to break this branch. I'm good to go as soon as it's resolved! This has been a crazy undertaking @cjayb and, like Nick, I can't tell you how excited I am to use the new API! |
Thanks all for comments and reviews! I agree it was a mouthful, but between the four of us, Travis and CircleCI, I have a good feeling about the end result :) Time will tell what parts of the API turn out to be annoying...
I don't know! Honestly, I haven't tried to understand how those values get estimated. Apparently me hitting all the I've started a new Issue #244 to discuss tests, which I think needs to be the next step moving forward. |
Incredible, it's in!!! Congrats @cjayb 🥳 🍺 🍻 🚀 🧠 Next steps is to improve the documentation and clean/solidify/test the code for a bit. No more big PRs for a while ;-) Use the new API in your data analysis! Also cc @kohl-carmen |
Whoot🥳 |
A new API for external inputs to the network, both 'driving' and 'biasing' types. See #222 for a discussion on the nomenclature
The gist of this proposal is to separate the definition and instantiation of driving (and biasing) inputs to the network. 'Drives' are synaptic mechanisms, whereas 'biases' are (currently) applied directly to the model cells (e.g., 'tonic' current clamp to the soma).
Closes #222
Closes #228
Closes #238
(follow-ups at #230 #233)
Drives are defined by the number and timing of synaptic events, in combination with information on connectivity (which synapses are targeted). Because the former are defined in terms of statistics, the proposed API is a two-stage process
In the case of multiple trials, 2. is called multiple times with different random number generator seeds.
Progress:
feeds.py
andtest_feeds.py
need to be weeded outnet._p_common
andnet._p_unique
with less idiosyncratic methods (currently needed because they define connectivity inNetworkBuilder
should we consider a NeuroML-type specification format?add tests and examples for tonic biases(moved, see add example for tonic inputs #223 and [MRG] DOC: use morlet instead of multitaper #226)add new example showing how drives can be visualised (and tweaked) before building the network(open new issue once merged)simulate_dipole
logic to ensuren_trials>0
net.external_drives['evdist1']['dynamics']['sigma'] = 0
) to be effective (included inplot_simulate_evoked.py
with newNetwork.copy()
-method)Network
should have ann_trials
-argument to future PR (semantically, the number of trials is a feature of the simulation, not the network)bursty
parametersnumspikes
should be allowed to be other than 1 or 2initialise_hnn_drives=False
as default forNetwork.__init__
and change all tests to set it toTrue