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

Add support for graph operations #355

Open
wants to merge 67 commits into
base: main
Choose a base branch
from

Conversation

jnsbck
Copy link
Contributor

@jnsbck jnsbck commented Apr 25, 2024

This PR adds support for graph methods. It implements a to_graph and from_graph method in module.

Todos:

  • Add to_graph
    • Add to_graph
    • add tests
  • Add from_graph
  • implement .swc -> nx.DiGraph -> Cell functionality
    • add tests
    • ensure original tests are passing for new swc reader.

Thoughts I had while coding this up:

  • We could make the graph object a more central part of jaxley and populate self.nodes and self.edges from it.
  • Since all attrs from in module.edges, module.branch_edges and module.nodes, are stored as node / edge attrs in the DiGraph, this could also be used for plotting or in vis.
  • Since all relevant info can be stored in DiGraph.graph,DiGraph.nodes,DiGraph.edges, this could also be used to save and share modules.
  • It would be nice to have the group idendity of nodes showin in module.nodes, not sure if one group column that contains lists of groups or several boolean cols with group_name is the better solution.
  • Makes only sense to be added to view once Make View behave more like Module #351 is merged, since this implements all properties that are attached to the nodes / edges for views of modules.

EDIT:

  • ensure this matches with NEURON / old swc pipeline
  • add tutorial
  • add tests and refactor them
  • update test_swc test to better match compartments with NEURON

@jnsbck jnsbck linked an issue Apr 25, 2024 that may be closed by this pull request
@jnsbck
Copy link
Contributor Author

jnsbck commented Apr 25, 2024

Currently I have only implemented to_graph and it works for comp, branch, cell, net, although I have not yet added tests. The functionality is very neat and makes looking at the networks / cells really intuitive. For the following network,

comp = jx.Compartment()
branch = jx.Branch([comp for _ in range(4)])
cell = jx.Cell([branch for _ in range(5)], parents=jnp.asarray([-1, 0, 1, 2, 2]))
net = jx.Network([cell]*3)
connect(net[0,0,0], net[1,0,0], IonotropicSynapse())
connect(net[0,0,1], net[1,0,1], IonotropicSynapse())
connect(net[0,0,1], net[1,0,1], TestSynapse())
net.cell(2).add_to_group("cell2")
net.cell(2).branch(1).add_to_group("cell2brach1")


net.cell(0).insert(Na())
net.cell(0).insert(Leak())

net.cell(1).branch(1).insert(Na())
net.cell(0).insert(K())
net.compute_xyz()

net.cell(0).branch(0).loc(0.0).record()
net.cell(0).branch(0).loc(0.0).record("m")
current = jx.step_current(i_delay, i_dur, i_amp, dt, t_max)
net.cell(0).branch(2).loc(0.0).stimulate(current)
net.cell(0).branch(1).make_trainable("Na")
net.cell(1).make_trainable("K")

net.compute_xyz()
net.cell(1).move(0,30,0)
net.cell(2).move(0,-30,0)

we can just to_graph, plot it

module_graph = net.to_graph()
# plot the graph
pos = {i: (n["x"], n["y"]) for i, n in module_graph.nodes(data=True)} 
plt.figure(figsize=(8, 8))
nx.draw(module_graph, pos, with_labels=True, node_size=200, node_color="skyblue", font_size=8, font_weight="bold", font_color="black", font_family="sans-serif")
plt.show()

image

and look at all its properties, i.e. checking out the soma (0) and the synapse going from node 0 to node 20

print(module_graph.nodes[0])
print(module_graph.edges[(0,20)])
{'comp_index': 0, 'branch_index': 0, 'cell_index': 0, 'length': 10.0, 'radius': 1.0, 'axial_resistivity': 5000.0, 'capacitance': 1.0, 'v': -70.0, 'Na': True, 'Na_gNa': 0.05, 'Na_eNa': 35.0, 'Na_m': 0.2, 'Na_h': 0.2, 'Leak': True, 'Leak_gLeak': 5e-05, 'Leak_eLeak': -67.0, 'K': True, 'K_gK': 0.012, 'eK': -75.0, 'K_n': 0.1, 'x': 5.0, 'y': 0.0, 'z': 0.0, 'recordings': array(['v', 'm'], dtype=object)}

{'pre_locs': 0.125, 'pre_branch_index': 0, 'pre_cell_index': 0, 'post_locs': 0.125, 'post_branch_index': 0, 'post_cell_index': 1, 'type': 'IonotropicSynapse', 'type_ind': 0, 'global_pre_comp_index': 0, 'global_post_comp_index': 20, 'global_pre_branch_index': 0, 'global_post_branch_index': 5, 'IonotropicSynapse_gS': 0.5, 'IonotropicSynapse_e_syn': 0.0, 'IonotropicSynapse_k_minus': 0.025, 'IonotropicSynapse_s': 0.2, 'TestSynapse_gC': nan, 'TestSynapse_c': nan}

@jnsbck
Copy link
Contributor Author

jnsbck commented Apr 26, 2024

Both to_graph and from_graph work. If you want to give this a preliminary review, that would be awesome! Please also chime in if you have specific ideas about the format in which to best store the attributes in the graph. I think we do not have to stick with how it is stored in Module and should rather ensure that they are easy to parse and work with.

trainable_params = {i: {} for i in trainable_inds}
for i in trainable_inds:
for inds, params in zip(
self.indices_set_by_trainables, self.trainable_params
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@michaeldeistler is there a scenario where there is more than one default value for an item in Module.trainable_params? I.e. the items are all dicts of form {"Na_gNa": np.array([0.1])} I think. Additionally, is there a case where there is more than one entry in the dict?"

Copy link
Contributor Author

@jnsbck jnsbck Apr 26, 2024

Choose a reason for hiding this comment

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

Ok I just saw you can have multiple defaults, this code is wrong then.
But you cannot have multiple keys correct?

Copy link
Contributor

Choose a reason for hiding this comment

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

Indeed, you cannot have multiple keys.

@jnsbck jnsbck requested a review from michaeldeistler April 26, 2024 18:19
Copy link
Contributor Author

Choose a reason for hiding this comment

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

ignore this

Copy link
Contributor

@michaeldeistler michaeldeistler left a comment

Choose a reason for hiding this comment

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

I will do a more thorough review on Monday, but for now: I really like that the methods are standalone. I guess in the long run we would try to use from_graph also for reading SWC readers?

jaxley/graph.py Outdated Show resolved Hide resolved
@jnsbck
Copy link
Contributor Author

jnsbck commented Apr 29, 2024

Yes, as mentioned in the PR desc, reading swc to graph is the plan.

@michaeldeistler
Copy link
Contributor

michaeldeistler commented May 2, 2024

Potential use-case of to_graph:

  • reordering parents to have a different "initial" parent node
  • search operations such as shortest path

jaxley/io/graph.py Outdated Show resolved Hide resolved
Copy link
Contributor

@michaeldeistler michaeldeistler left a comment

Choose a reason for hiding this comment

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

  • new type -> new branch
  • new type -> no radius interpolation see here
  • types have to be returned and directly added to group if assign_group=True
  • single point soma. Length should be 2x radius see here

@jnsbck
Copy link
Contributor Author

jnsbck commented Jun 10, 2024

It took way more time than I would have suspected or liked to spend and two mayor iterations, but its finally mostly there! Both export and import from/to graph are working now and also the swc pipeline passes the tests now. Some more tests and cleanup, but then I think you can do a review @michaeldeistler. Small benefit of this PR is also that it reduces the difference of jaxley and NEURON very slightly (by about 20%).
image

@jnsbck
Copy link
Contributor Author

jnsbck commented Jul 12, 2024

TLDR: I think I have a working version of the graph pipeline now, but tests are still not passing and I don't quite no why. Everything looks good to me. Help is much appreciated!

I have been trying for ages to get from_graph working. For this I have written a networkx -> jaxley converter. I think it works quite well. Running:

graph = swc_to_graph(fname)
cell = from_graph(graph, nseg=8, max_branch_len=2000.0)

reads an swc file into a networkx graph and imports the cell into jaxley. As part of from_graph the afformentioned converter is run, but it can also be run seperately via: graph = make_jaxley_compatible(graph). This produces a jx.Cell object that runs and does things. Additionally, inspecting the morphologies I get visually and comparing the compartment coordinates to NEURON / the current swc pipeline also looks good. As a bonus the compartment centers assigned via the graph are even closer to NEURON than the current ones. See the left plot.

image

While this all looks very promising, I have struggled to get the tests passing and I really dont know why:
For this I have replaced the hardcoded branch indices and pathlength matching in the old tests and I now just match the closest compartment to NEURON. This works quite well and I can even assign trunk_ids etc. based on the swc ids which is nice. The test should be much more robust to changes in the test_morphology or other things now! Of course it also passes for the old swc pipeline. However, when I run this test on the graph imported jx.Cell the voltages are consistently. See here:
image

While they look very similar, they are all slightly delayed. The MSEs also show this.

MSEs between networkX and NEURON voltages [10.14612018 10.35377231 10.84316607 11.17640196 11.06866644 11.1941421
 10.72721014 10.44691712 10.55542459  9.83932744  9.47578905 10.63712368
 13.2997599   9.5394632  10.3148209  10.06744197 10.11404793  9.99624842
 11.15663607 11.10845196 10.55436891 10.17346833 10.95011269 11.09521677
 11.24092038 10.65548104 11.36646986]
MSEs between jaxley_swc_reader and NEURON voltages [0.72412308 0.7978693  0.81918308 0.84724678 0.80464105 1.43258348
 1.47786316 0.73113843 1.14953477 1.12154859 1.18076938 1.30564702
 1.56853646 2.9105652  0.38078612 1.00738273 0.79437486 0.81636714
 0.48941009 0.47586723 0.75917783 1.04822786 1.35910234 1.56591299
 1.14104993 1.34106949 1.72583906]

Would really appreciate help. I will now be moving on to sth else for the moment though, so it is not urgent.
@michaeldeistler

Best,

Jonas

@jnsbck jnsbck mentioned this pull request Jul 16, 2024
@jnsbck jnsbck force-pushed the 334-support-for-graph-operations branch from 26432b5 to c2bcb26 Compare August 15, 2024 10:13
@michaeldeistler michaeldeistler force-pushed the main branch 8 times, most recently from a1b70d8 to fddb19a Compare August 22, 2024 14:36
@jnsbck jnsbck force-pushed the 334-support-for-graph-operations branch from 0bb338c to e0e3f2a Compare August 27, 2024 15:49
@jnsbck
Copy link
Contributor Author

jnsbck commented Sep 2, 2024

I think this is finally ready for a first round of reviews.

This has become quite the mammoth PR, but the functionality it enables is neat imo. For a rundown see the updated 08_morphologies.ipynb

All tests are passing now, which turned out to be an immense amount of work, but the imported morphologies are similar enough to NEURON both at the compartment level (x,y,z,r,l) and they also simulate correctly. I have essentially cloned the tests in tests/test_swc.py and added them to tests/test_graph.py. I have kept the tests and methods separated from everything else (although a refactor / integration might make it easier to maintain later). The import and export functionality also works correctly.

Notable changes are:

  • new io submodule

    • with swc_to_graph, from_graph and to_graph functions
    • moved read_swc there from cell.py
    • added io.graph
    • fixed the _update_nodes_with_xyz since it had the same issue that caused bug in compute_xyz #410.
    • added __eq__ to Module (I needed this to test whether re-imported modules are the same as the original.)
    • test_graph.py matches voltages between NEURON and Jaxley by matching compartment centers, this makes the test much more robust. Could also be added to test_swc.py if desired (see tests/helpers.py).
  • Some open questions are:

    • __eq__ is only implicitely being tested, and I dont know whether there could be a more efficient implementation
    • there is a lot of overlap between test_graph and test_swc. We could potentially move them to tests/io/ and reduce overlap.

Lemme know your thoughts. Would also be happy to go through this in person.

This was referenced Oct 22, 2024
@manuelgloeckler
Copy link
Contributor

Just noting that in the branch graph_distance_and_more, there is some preliminary API idea that works, e.g., with, e.g., "distance" as a new function. Implementation may be inefficient yet, but can be improved if we can also add the graph to the module...

It is a branch from this branch at the current stage.

@jnsbck jnsbck mentioned this pull request Dec 3, 2024
@jnsbck jnsbck force-pushed the 334-support-for-graph-operations branch from fac9e23 to 53895a4 Compare December 18, 2024 14:34
@jnsbck
Copy link
Contributor Author

jnsbck commented Dec 20, 2024

Here is a little update on the current state of the PR:

  1. I have rebased onto main and adapted + improved the code.
  2. The following is working / not working
  • WORKING: read swc into networkx graph
  • WORKING: pre-processing (handling single soma, adding root branch, etc.)
  • WORKING: tracing out branches
  • WORKING: inserting compartments into morphology graph
  • WORKING: extracting the compartment graph
  • WORKING: from_graph, i.e. comp graph -> module (works for node and edge attrs, but does not attach recordings, trainables etc. I am in favour of not supporting this.)
  • WORKING: to_graph, i.e. export (also only for morph)
  • NOT WORKING: tests

Also added an updated tutorial.

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.

support for graph operations
3 participants