Conversation
Change-Id: I48038cd5d10ebb4342675e3dde9cf55f7f5ab7b8
* Enable flexible dimension order for TimeGriddedData by adding the dimension_order argument to the constructor. * Adding get_event and get_time_gridded method for all data classes to retrieve data in a consistent manner. Change-Id: I6747d06e6e528aef0d657ae473189da452d42a4c
Change-Id: I498b766f20d279e4cc0029452d888510229dc31e
Change-Id: If6649c2dd4ab26d884423c915f53ec11f3460562
Jegp
left a comment
There was a problem hiding this comment.
Very very nice. I have one question around the use of coordinate-based representations in the EventData class. And there's a merge conflict after the Ruff branch merged.
|
|
||
| def to_time_gridded( | ||
| self, dt: float # pylint: disable=invalid-name | ||
| def get_event(self, n_events: int | None) -> EventData: |
There was a problem hiding this comment.
Is it correct that EventData is collecting a series (what you call sample) of time-stepped events? So, that's basically a "batched" coordinate AER view, correct?
And is it correct that n_events cap each sample to n_events? If so,
- why not ask for a lower cap as well? Would you want to "slice" the samples, like you'd slice a normal list?
- and if you are more or less slicing, what's the logic below good for? Why not just do
idx[:, start:end]? I can see you're doing checks for invalid events, but I'm puzzled why that has to happen in the getter. Shouldn't that be a constructor check to make sure the data itself is well-formed upon construction?
There was a problem hiding this comment.
So EventData is not time-stepped data but continuous/event-based. For each spike you have a time when it occured and and index of the spiking neuron idx.
Maybe the phrase "each event is discrete" in the docstring is misleading. What I meant by that is that an event does not has a length but happens at one point in time.
And yes, n_events cap the events per sample to this number. One could say that n_events is the number of events per sample. If a sample has 5 spikes but n_events is 10, the last 5 events would be "empty/invalid events" with idx=-1`. But if there were 15 spikes, the last 5 would be dropped. I implemented this for a better data handling.
Does this clear things up on your side?
| raise ValueError("idx, time and value must have the same shape") | ||
|
|
||
| def to_time_gridded( | ||
| def get_event(self, n_events: int | None) -> ValuedEventData: |
| Dictionary of observables for a NIRNode. | ||
| """ | ||
|
|
||
| observables: Dict[str, Union[EventData, TimeGriddedData]] |
|
Additionally, I wonder whether it would make sense to actually model the relationship between the NIRNode and the NIRData. Currently, if I'm understanding it correctly, you're simply checking that the dimensionality of the data fits with the shape of the node. Wouldn't it be nicer to have a direct link, so you can access the node in the data format? Something like an augmented @dataclass
class NIRNodeData(NIRNode):
observables: dict[str, EventData | TimeGriddedData]
def check_observables(self):
"""
Check that the shapes of the observables match the node's output shapes
"""
output_shape = self.output_type["output"]
return all(obs.n_neurons == output_shape for obs in self.observables.values())Wouldn't that make the relationship unambiguous? |
|
(Ben and I are just sitting together and thinking about this :) .) Regarding your last comment @Jegp — you suggest Advantage: no separate observable data structure that has a "string-based" link to the topology data structure (and no possibility to have a mismatch in "keys", i.e. no need for a "key-match" check). We don't have a strong opinion ;) … |
|
Thank you for your thoughts @muffgaga and @benkroehs. Hmmm, yes I agree, the relationship is more a "has-a" rather than an "is-a". I found that I'm not saying that the string-keyed link is bad. But note that What about putting it on the data side? The extension should know about NIR, not the other way around. And it would keep the topology IR free of observables, as you say, @muffgaga. As an example: class NIRNodeData:
observables: Dict[str, ObservableData]
node: NIRNode | None = NoneThis doesn't change the file format (HDF5 will store a name). Here, Separately, on And where the default does apply it's Truncating starts as soon as the man rate exceeds |
With this PR I propose adding the following features to NIRData:
TimeGriddedDataor(Valued)EventData) , they can just calldata.get_event()time_shiftparameter is substituted by a booleandynamic_before_transitionto represent the two existing implementations for time-gridded simulators: propagating the the dynamics first and checking if the threshold has been exceeded after or on the contrary first checking for the threshold-crossing and then calculating the dynamics accordinglyTimeGriddedDatais no longer fixed but stored in thedimension_orderattribute. The conversion happens lazily if a different format is requested. The__getitem__and__setitem__functions use per default the most popular ordering (time, batch, neuron), but this is flexible.An exemplary implementation of these changes can be seen in the most recent NIRTorch PR.