-
Notifications
You must be signed in to change notification settings - Fork 52
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
[ENH] Added read/write method for network object #651
[ENH] Added read/write method for network object #651
Conversation
hnn_core/tests/test_network.py
Outdated
# Check cell_types | ||
# assert net_jones.cell_types == net_jones_read.cell_types | ||
print(net_jones.cell_types) | ||
print(net_jones_read.cell_types) |
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.
For test-driven development, you can use pytest
with --pdb
to directly drop into the interpreter when a test fails. Then you can use pdb
commands like p net_jones.cell_types
etc. Just letting you know the different options on debugging efficiently
hnn_core/network.py
Outdated
def __eq__(self, other): | ||
if not isinstance(other, Network): | ||
return NotImplemented | ||
# Check for all attributes (Discuss) |
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 right way to implement this is to first implement __eq__
for Section
and Cell
object and call them from here. Python builtins already have it implemented so presumably net.external_drives
etc should be straightforward to check for equality
For partial function, I think you should expand it out. Iterate over the segments and get the hnn-core/hnn_core/tests/test_network.py Lines 57 to 58 in 07374a3
and store them. It's less compact but I think the only way to make it work |
Codecov ReportAttention:
❗ Your organization needs to install the Codecov GitHub app to enable full functionality. Additional details and impacted files@@ Coverage Diff @@
## master #651 +/- ##
==========================================
+ Coverage 91.62% 92.24% +0.61%
==========================================
Files 22 27 +5
Lines 4488 4897 +409
==========================================
+ Hits 4112 4517 +405
- Misses 376 380 +4
☔ View full report in Codecov by Sentry. |
I tried rebasing but it didn't work. I followed the following steps
I encountered no errors but I couldn't see the changes present only in master. Where did I go wrong? |
@raj1701 I think you just need to do: $ git rebase master after checking out the |
0f4513e
to
0053f53
Compare
Hey @jasmainak @ntolley, I found a bug while saving Bug Description Solution
|
Yes, you should open a new PR and rebase this branch when that PR is merged |
if not (self.sections[key] == other.sections[key]): | ||
return False | ||
|
||
return 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.
you should add a test in test_cell.py
for this ... simple way to test is:
cell2 = cell.copy()
assert cell2 == cell
@@ -412,6 +415,43 @@ def __repr__(self): | |||
len(self.pos_dict['L5_basket']))) | |||
return '<%s | %s>' % (class_name, s) | |||
|
|||
def __eq__(self, other): |
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.
add a test for this method
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.
In net.copy()
method, the simulation data related to external drives
and rec_arrays
eg- events
is deleted.
Is a similar behavior intended for read\write functionality?
hnn_core/network.py
Outdated
@@ -1335,6 +1407,280 @@ def plot_cells(self, ax=None, show=True): | |||
""" | |||
return plot_cells(net=self, ax=ax, show=show) | |||
|
|||
def write(self, fname): |
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.
docstring missing, also add overwrite
parameter
d64acd6
to
3783e72
Compare
Let's do docstrings for now |
hnn_core/network.py
Outdated
@@ -1340,6 +1415,310 @@ def plot_cells(self, ax=None, show=True): | |||
""" | |||
return plot_cells(net=self, ax=ax, show=show) | |||
|
|||
def write(self, fname, overwrite=True, save_unsimulated=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.
This is a fair amount of code, I propose moving this to a new file ... io.py
maybe? You'd basically house all the write_xxx
functions and read_xxx
functions there. Then here you'd just call the write_network
function
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.
There would then be a new test file test_io.py
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 will do this
hnn_core/network.py
Outdated
# Tasks | ||
# 1) Remove params dict (Partial done) | ||
# 2) Add overwrite warnings (Done) | ||
# 3) Add boolean to read/write un-simulated network (Done) | ||
# 4) Add boolean to read/write simulated network (Done) | ||
# 5) Think about documentation | ||
# 6) Add tests | ||
# 7) Store the type of object saved and issue warning (Done) |
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.
suggest moving this to the PR description ... github has a nice checkbox feature you can use
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.
Ooh its a pretty cool feature. I just saw it.
hnn_core/cell.py
Outdated
@@ -443,6 +529,9 @@ def build(self, sec_name_apical=None): | |||
with this section. The section should belong to the apical dendrite | |||
of a pyramidal neuron. | |||
""" | |||
# Confirm correctness here | |||
from .network_builder import load_custom_mechanisms | |||
load_custom_mechanisms() |
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.
does it need to be in the other place now that you have the function call here?
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'll try removing it from the other place
hnn_core/cell.py
Outdated
h.pop_section() | ||
p_mech[attr] = [seg_xs, seg_vals] |
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'm assuming the purpose of this is to overwrite the callable stored in p_mech
? If so, this will require calling cell.build()
any time you need to write write cells attached to a Network
instance, which will in turn imply that we're going to instantiate NEURON objects from within Network
. I'm just thinking out loud here, but will that cause any unintended issues with the NEURON network? In the past, we've maintained the convention that NEURON objects are only instantiated from NetworkBuilder
in order to silo how, when, and where such objects are created.
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.
Yes, calling cell.build()
outside the NetworkBuilder
does throw an error later when Network is simulated. For resolving it, I first made a copy of the cell and called build()
on the copy instead of the original.
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.
Do we know if the NEURON objects are getting properly destroyed and de-referenced after the cell copy is destroyed by the garbage collector? The only reason I'm bringing this up is because great care needs to be taken when handling NEURON network objects which are generally global and have in the past caused caused segmentation faults that are hard to diagnose and/or make tests for.
Is there a way around this that allows us to maintain our convention of only building NEURON objects from within NetworkBuilder
? CC'ing @jasmainak and @ntolley for their input.
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.
yeah I agree we need to be careful here. See:
hnn-core/hnn_core/network_builder.py
Line 658 in c81e5b8
def _clear_last_network_objects(self): |
The trade-off is between doing our computation ... which can be error-prone if we don't get the positions of the segments exactly right. Neuron does some mumbo-jumbo here:
Lines 431 to 434 in c81e5b8
# be explicit about letting sec.L dominate over the 3d points used by | |
# h.pt3dadd(); see | |
# https://nrn.readthedocs.io/en/latest/python/modelspec/programmatic/topology/geometry.html?highlight=pt3dadd#pt3dadd # noqa | |
h.define_shape() |
vs
potentially slowing up or causing segmentation faults in all our simulations that have run the save
method
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 fact that the segment coordinates prior to NEURON instantiation aren't their final values (and thus need to be re-set using length modifiers) is annoying. Maybe we should rip the bandaid off and fix this, which I think would also nullify the need to build the NEURON cells in order to write the network.
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'm all for it. Do we need the length for any purpose? I seem to remember that either you or @ntolley were experimenting with changing the length of the neurons ... or do you propose changing the coordinates of the neuron to match the length, that may lead to some tiny numerical inaccuracies
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'm proposing the latter, which I think would allow us to get rid of the length parameter. While this may introduce small numerical differences compared to what we currently have, it's a much more sustainable solution.
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 would be concrete steps for @raj1701 ?
- Raise warning if length is defined in
param
file? It should say that it has no effect. Same with network attribute : they should not be modifiable - Update the coordinates here to match the default length parameters:
hnn-core/hnn_core/cells_default.py
Lines 69 to 77 in c81e5b8
end_pts = { 'soma': [[-50, 0, 765], [-50, 0, 778]], 'apical_trunk': [[-50, 0, 778], [-50, 0, 813]], 'apical_oblique': [[-50, 0, 813], [-250, 0, 813]], 'apical_1': [[-50, 0, 813], [-50, 0, 993]], 'apical_tuft': [[-50, 0, 993], [-50, 0, 1133]], 'basal_1': [[-50, 0, 765], [-50, 0, 715]], 'basal_2': [[-50, 0, 715], [-156, 0, 609]], 'basal_3': [[-50, 0, 715], [56, 0, 609]], - Remove
h.define_shape
line
Anything else I missed? Does this sound doable @raj1701 ? Any inputs @ntolley ?
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 there are a quite a few params in the param files that are silently ignored already, right? Since most of this stuff is already specified in params_default.py
, is the param file warning necessary?
We should also remove the lines where the NEURON length and radius attributes are set (see here).
hnn_core/io.py
Outdated
key : cell type name | ||
value : All co-ordintes of the cell types | ||
|
||
params description |
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.
get rid of params
hnn_core/io.py
Outdated
A hdf5 file containing the Network object | ||
|
||
File Content Description | ||
------------------------ |
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.
hnn_core/tests/test_io.py
Outdated
match="The object should be of type Network."): | ||
read_network(tmpdir.join('not_net.hdf5')) | ||
|
||
# Add test to check weights are equal in connections and drives (todo) |
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.
add test to check each attribute is documented
hnn_core/tests/test_io.py
Outdated
if network_model == "jones_2009": | ||
net = jones_2009_model(add_drives_from_params=True) | ||
elif network_model == "law_2021": | ||
net = law_2021_model(add_drives_from_params=True) | ||
elif network_model == "calcium": | ||
net = calcium_model(add_drives_from_params=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.
if network_model == "jones_2009": | |
net = jones_2009_model(add_drives_from_params=True) | |
elif network_model == "law_2021": | |
net = law_2021_model(add_drives_from_params=True) | |
elif network_model == "calcium": | |
net = calcium_model(add_drives_from_params=True) | |
net = network_model(add_drives_from_params=True) |
hnn_core/network.py
Outdated
if not (self.cell_types == other.cell_types): | ||
return False | ||
|
||
# Check gid_ranges | ||
if not (self.gid_ranges == other.gid_ranges): | ||
return False | ||
|
||
# Check pos_dict | ||
if not (self.pos_dict == other.pos_dict): | ||
return False | ||
|
||
# Check cell_response | ||
self.cell_response == other.cell_response | ||
|
||
# Check external drives | ||
if not (self.external_drives == other.external_drives): | ||
return False | ||
|
||
# Check external biases | ||
if not (self.external_biases == other.external_biases): | ||
return 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.
if not (self.cell_types == other.cell_types): | |
return False | |
# Check gid_ranges | |
if not (self.gid_ranges == other.gid_ranges): | |
return False | |
# Check pos_dict | |
if not (self.pos_dict == other.pos_dict): | |
return False | |
# Check cell_response | |
self.cell_response == other.cell_response | |
# Check external drives | |
if not (self.external_drives == other.external_drives): | |
return False | |
# Check external biases | |
if not (self.external_biases == other.external_biases): | |
return False | |
for attr in ['cell_response', 'external_drives']: | |
if getattr(self, attr) != getattr(other, attr): | |
return False |
hnn_core/network.py
Outdated
# Check extracellular arrays | ||
if not (self.rec_arrays == other.rec_arrays): | ||
return False | ||
|
||
# Check threshold | ||
if not (self.threshold == other.threshold): | ||
return False | ||
|
||
# Check delay | ||
if not (self.delay == other.delay): | ||
return 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.
include these as well
|
||
==================== | ||
Network File Content | ||
==================== |
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.
can you add some more information what the file structure is like? what is the file format etc? we need to provide some more information for users if they open the file using other tools and also for documentation
doc/conf.py
Outdated
@@ -117,6 +117,7 @@ | |||
("Examples", "auto_examples/index"), | |||
("GUI", "gui/index"), | |||
("API", "api"), | |||
("Network File", "network_file_desc"), |
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.
don't add it here. You can add a line in the Readme linking to this file. It should say something to the effect that hnn-core uses hdf5 for IO and that the description of the file structure can be found here and the API of the functions for the IO can be found here
hnn_core/io.py
Outdated
|
||
|
||
@fill_doc | ||
def write_network(net, fname, overwrite=True, save_unsimulated=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.
you need to change save_unsimulated
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 was the name we decided? save_output
?
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.
Then accordingly read_raw
will become read_output
?
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.
yep, exactly! save_output
and read_output
While reviewing Nick's PR I realized that we haven't updated any of the examples to use |
Surely can be done. We can have a discussion on this tomorrow |
|
||
File Content Description | ||
------------------------ | ||
hdf5 is the file format used for storing the Network object. The network is stored in a layered format. The first layer consists of the network attributes. |
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.
hdf5 is the file format used for storing the Network object. The network is stored in a layered format. The first layer consists of the network attributes. | |
hdf5 is the file format used for storing the Network object. The network is stored in a multi-level format. The first level consists of the network attributes. |
Using "layer" as the terminology might get confusing, as there is the biologically "layers" of the model
------------------------ | ||
hdf5 is the file format used for storing the Network object. The network is stored in a layered format. The first layer consists of the network attributes. | ||
The attributes of the network are then broken down until the network can be representated as a collection of key value pairs. For example - cell_types is a network | ||
attribute therefore in the first layer. The description of each cell type is in layer 2. Each cell has various sections. The description of a section is in layer 3. |
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.
attribute therefore in the first layer. The description of each cell type is in layer 2. Each cell has various sections. The description of a section is in layer 3. | |
attribute therefore in the first level. The description of each cell type is in level 2. Each cell has various sections. The description of a section is in level 3. |
N_pyr_x : int | ||
Nr of cells (x). | ||
N_pyr_y : int | ||
Nr of cells (y). |
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.
N_pyr_x : int | |
Nr of cells (x). | |
N_pyr_y : int | |
Nr of cells (y). | |
N_pyr_x : int | |
Number of cells (x). | |
N_pyr_y : int | |
Number of cells (y). |
assert net_copy == net | ||
|
||
# Test other not NotImplemented for Network Class | ||
assert (net == "net") is 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.
assert (net == "net") is False | |
with pytest.raises(NotImplemented): | |
net == "net" |
This is a more robust test for this error
@@ -33,6 +33,9 @@ def test_extracellular_api(): | |||
assert len(net.rec_arrays) == 2 | |||
assert len(net.rec_arrays['arr1'].positions) == 2 | |||
|
|||
# Test other not NotImplemented for ExtracellularArray Class | |||
assert (net.rec_arrays['arr1'] == "extArr") is 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.
assert (net.rec_arrays['arr1'] == "extArr") is False | |
with pytest.raises(NotImplemented): | |
net.rec_arrays['arr1'] == "extArr" |
assert (cell1 == "cell") is False | ||
|
||
# Test other not NotImplemented for Section Class | ||
assert (cell1.sections['soma'] == "section") is 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.
assert (cell1 == "cell") is False | |
# Test other not NotImplemented for Section Class | |
assert (cell1.sections['soma'] == "section") is False | |
with pytest.raises(NotImplemented): | |
cell1 == "cell" | |
# Test other not NotImplemented for Section Class | |
with pytest.raises(NotImplemented): | |
cell1.sections['soma'] == "section" |
@@ -220,6 +224,55 @@ def __init__(self, L, diam, Ra, cm, end_pts=None): | |||
def __repr__(self): | |||
return f'L={self.L}, diam={self.diam}, cm={self.cm}, Ra={self.Ra}' | |||
|
|||
def __eq__(self, other): | |||
if not isinstance(other, Section): | |||
return NotImplemented |
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 I think this is why the pytest.raises test didn't work. You're returning NotImplemented
, but you actually need to do raise NotImplemented
(check out how the other errors get raised)
@raj1701 can you make the CIs happy? you have |
@ntolley @jasmainak |
@gtdang can you open a PR with the new branch? I've figured out the problem you found in |
GSoC 2023 Project - Develop IO routines for HNN-core outputs
The idea of the project is to add read/write capabilities for all HNN objects such as Network, Cell, Section, Dipole etc. in the HDF5 format. During the summer, I wrote IO functions for all HNN objects, the associated tests as well as refactored some existing code along the journey.
The following PR contains read/write functions and the associated tests for the Network object. The network class encapsulates objects such as Cell, Section, Cell response and Extracellular arrays. These objects are stored along the network itself. Each and every aspect of the functions is tested and documented. The IO functions are capable of handling all network models in HNN core as well any modified network instances. Capability to saving and loading up simulated networks is also present.
Some other PRs related to the project are -
define_shape()
anddistance()
. This is done to reduce the dependency on NEURON for saving a Network. NEURON objects are only needed while simulating a Network now.Plans for the project after GSoC - Add load/save buttons in HNN GUI for the Network and Dipole object.