-
Notifications
You must be signed in to change notification settings - Fork 219
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
Index refactoring #135
Index refactoring #135
Conversation
standalone mode, works in runtime but not used automatically, doesn't work in standalone because _array_i is not defined
…nGroup (including subgroups), Synapses are broken.
…roups are broken, though.
…illed with numbers from 0 to size-1. This will be used for the variable "i" in a `NeuronGroup`.
…rking yet (Python-only currently)
…factoring Conflicts: brian2/groups/group.py
… translate_statement_sequence). For C++ code, SUPPORT_CODE, POINTERS, etc. have to be handled for all blocks (the variables/functions used might be different or the same)
… in the test suite
Wow, big changes! I'll need to go through this code quite carefully I think. Mostly it looks very good and resolves a lot of issues. One important issue though:
This worries me slightly: does this mean that the main loop of a state updater will do something like this? for(int _i=0; _i<N; _i++)
{
int i = _array_i[_i];
double V = _array_V[i];
...
} If so, this is a problem because it means we will lose many compiler optimisations, most importantly cache predictability and vectorised instructions.
Agreed! |
No, sorry that was phrased a bit misleadingly. The standard loop still looks like: for(int _idx=0; _idx<N; _idx++)
{
double V = _ptr_array_neurongroup_V[_idx];
...
} "Each NeuronGroup now stores an actual array containing all its indices" only referred to the use of the const int i = _ptr_array_neurongroup_i[_idx]; which is admittedly suboptimal but I don't think will have a noticeable performance impact. Besides, I don't see many uses for
Alright, I'll change this right now and revert the int-to-float conversion for literals. |
… kind of overflow issues), instead use int32 by default. Also revert the conversion of integer literals to floats.
OK that's great then. I'll take a look at this asap. |
One thought: we don't need to add |
Question: for multiple abstract code blocks, did you make sure that it all worked sensibly or just refactor the slightly hacky code I wrote for that? I wrote this on the wiki:
|
OK I went through in some detail but quite quickly. It looks great - much better! It seems to go some way towards resolving the issues mentioned in the wiki page referred to in #131. I would be happy to merge this as soon as it's ready (although would also like to see what you think about the previous two comments). |
You are right, we could do this using the standard mechanisms. I had the impression that doing all this would unnecessarily complicate things because this is done during initialisation where the normal Um, now that I wrote this, I realize that there is actually a problem with
No, I'm afraid I did not change it fundamentally. The only thing I changed is to make
Ok, I'll add some more tests and documentation tomorrow but then it's good to go from my side (if the additional testing does not reveal new bugs, of course ;) ) |
So if you did something like Also, it should be straightforward to do synapses on devices now, right? |
As I said before, for (int _idx=0; _idx<N; _idx++)
{
const int _presynaptic_idx = _ptr_array_synapses_synaptic_pre[_idx];
const int _postsynaptic_idx = _ptr_array_synapses_synaptic_post[_idx];
const int i = _ptr_array_neurongroup_i[_presynaptic_idx];
const int j = _ptr_array_neurongroup_1_i[_postsynaptic_idx];
double w;
w = f(i, j);
_ptr_array_synapses_w[_idx] = w;
} Again, for C++ mapping into the arrays is quite a waste but we need the arrays for Python. At some point we have to do a For the moment, I'm not seeing that the array indexing approach costs us a lot of performance but I see two ways how to improve it: either,
The main remaining problem is that we still have the second datatstructure (list of dynamic arrays) storing synapses that is needed for efficient propagation. I'm wondering whether we can abstract it away. Maybe a first simplifying step: let's not fill this data structure during synapse creation but instead calculate it during |
…t cannot be slice(None)
For the record: from my side this is ready for merging :) |
OK I see now. Let's test whether or not the Not quite sure I understood the issues about doing synapses, but I don't think it affects whether or not this is ready to merge so I'll go ahead and do that now. |
This is not quite ready to merge yet (the usual: testing and documentation is still too sparse), but after some major wrestling it is now working (i.e. the test suite passes and cpp_standalone works) and I'd like to get some feedback whether you see anything wrong with the approach that I missed so far.
I wrote, deleted and rewrote several approaches to the indexing system that had all their advantages and drawbacks, from fully automated nested indexing that is completely handled by codegen to almost no support from codegen where everything (synapse indices, subgroups) is handled in the templates. I think I finally found some middle ground that is reasonably understandable and simple and should work in complicated scenarios (accessing post-/presynaptic state variables in synapses via subgroups, etc.).
The major changes/assumptions/approaches are the following:
NeuronGroup
now stores an actual array containing all its indices (which is quite boring, just an arange from 0 to N). This is a bit of a waste of memory but not too bad since we know the number of neurons and can use the appropriate data type. And we don't store indices twice, the indicesi
andj
in aSynapses
class refer to the respective indices of their source/target, indexed appropriately.arange
method that works likearray
but initializes the array with integers fromstart
tostop
instead of with zeros. I added this method for the C++ standalone.ItemMapping
classes disappeared. There is now only acalc_indices
method that returns a mapping from arbitrary (tuple/slice/array) indices to flat 1d indices appropriate for the group. For aNeuronGroup
this does basically nothing but for synapses it converts from the[i, j, k]
index to the synapses index. This function no longer takes care of string indices._idx
that is defined in the templates and loops up toN
(formerly_num_idx
). If the index of anArrayVariable
is not'_idx'
, it has to be the name of anotherArrayVariable
in thevariables
dictionary. There is no longer a specialindices
dictionary orIndex
objects. Note that this does currently not allow for arbitrary nesting: The index for theArrayVariable
used as an index has to be_idx
, it can't be anotherArrayVariable
, therefore we do not have to do a sorting of the dependencies or something like that. Currently, this is not checked, though, it will just fail in the generated code.Subexpression
objects that subtract the offset from the index array of the referred group (i.e. subgroups do not store another array). TheSynapse
object stores the indices of the real targets (this allows direct access to target variables) but the list-of-synapses-per-neuron structure uses relative subgroup-specific indices. All this greatly reduces the number of places that have to be "subgroup-aware": everything that is going through code generation automatically takes subgroups into account, the only remaining spots in the code (apart fromSubgroup
itself) are thepush_spikes
method ofSynapses
, thecalc_indices
method ofSynapses
(used when indexingi
andj
with numbers, not with code), the creation of synapses, and theSpikeMonitor
template._presynaptic_idx
and_postsynaptic_idx
are automatically taken care of, we no longer need special synaptic templates for stateupdate or variable setting.G.v = -60*mV
is automatically interpreted asG.v = '-60*mV'
.i
index of aNeuronGroup
is now a standard (integer) array stored in the group I needed to get the data types right. I introducedAuxiliaryVariable
objects that one can use for specifying the type of auxiliary variables (very similar to the previousOutputVariable
objects). This is not used everywhere yet, though. This fixes _cond is double not bool in codegen #63.I = x + y
and is referred to asI_post
in the synapse code, this will simply use the codex + y
-- but these two variables could potentially be defined as synaptic variables and therefore evaluated incorrectly. I changed the way subexpressions are handled so that this is now checked and if there is a clash,x_post + y_post
would be used instead.In general, with all these changes it should now be possible to use
i
,j
, references to neuronal state variables, etc. everywhere which is pretty nice I think :)Some known issues:
i + j
in a synapse will lead to a wrap around... I currently reduce the number of situations where this occurs by treating all literal numbers as floats (i.e.1
becomes1.0
) but still... Probably we should simply not care about saving some kilobytes of memory and always use int32 to be safe?k
in synaptic string expressions.Subexpression
issue I mentioned above, there's still some possibility for mistakes if the external namespace is manually defined for aNeuronGroup
. If theSubexpression
is evaluated in the context of aSynapse
, it will use the synapses's namespace, not the one from theNeuronGroup
.