Comments (13)
What type of content is allowed in a subexpression? Will it refer to variables? Then option 3 would necessitate creating and storing additional arrays for the evaluated subexpression?
I wonder whether not
4. Push the sub-expression into a support code function would be easy and efficient enough?
But I may not fully appreciate the idea and range of the subexpression concept.
from brian2genn.
Subexpressions can refer to other subexpressions or to other variables, the code generation system normally deals with all this automatically.
Then option 3 would necessitate creating and storing additional arrays for the evaluated subexpression?
Yes, the constant over dt
flag is basically a shorthand, if you write:
group = NeuronGroup(..., '''subexpr = ... : volt (constant over dt)''', ...)
then this is equivalent to
group = NeuronGroup(..., '''subexpr: volt''')
group.run_regularly('subexpr = ...', when='before_start')
So for brian2genn, subexpr
would be handled just like any other array.
- Push the sub-expression into a support code function would be easy and efficient enough?
Converting a sub-expression into a function would mean identifying its dependencies and rewriting the expressions where it is called (e.g. in my example above, rate
would turn into rate(t)
when used). This would be nice, but is far from trivial (I mentioned this approach in a Brian issue: brian-team/brian2#412, but the fact that subexpressions can refer to other subexpressions complicates things quite a bit...).
from brian2genn.
Ok, I can see that the function idea is quite ambitious and we might not want to spend too much time on it.
Short of this, option 2 would probably be the most performant in the GPU context but carries some of the same problems. If sub-expressions include each other, substituting everything into everything could become a bit of a job.
Short of doing that, I am thinking, again with my GPU hat on, option 1 is probably more efficient in most scenarios than 3 because most likely local variables would be in registers and no global memory access would be necessary. Whereas in 3, the subexpressions would be written to arrays in global memory and read from them - two additional costly memory accesses.
To do version 1, the cheapest way, even if it's looking a bit hacky, would be to add the early lines of threshold code to the neuron update code. This, however, only works if GENN_PREFERENCES::autoRefractory
is false (which is our default position in brian2genn for compatibility to Brian 2 conventions I believe?)
(if GENN_PREFERENCES::autoRefractory
is on, the spike condition is also tested before the neuron update code is executed)
from brian2genn.
Hmm, I'm not sure of the performance penalty. In most cases, constant over dt
should be better for performance since it will be only evaluated once even if the subexpression is used in several places. But this question is a bit separate, in general the ideal thing would be to allow both types of subexpression as for non-brian2genn Brian code.
If I understand your proposed approach for (1), then this would be purely on the brian2genn level, right? This might indeed work, I think we do not use the autoRefractory
feature. However, I think we might run into problem if the neuron update code also refers to the subexpression, but I'll check it, maybe our codegen code would actually be smart enough to figure this out.
Thinking about this, we could probably have a slightly cleaner and somewhat less hacky solution: add the full Brian threshold code to the neuron update code (which will assign to a boolean variable _cond
) and then just use a fixed threshold condition code _cond
for GeNN. This should work, doesn't it?
from brian2genn.
Yes, that should work indeed (autoRefractory
off, obviously).
I am not sure what you meant with there would be a problem if the neuron update code also refers to the subexpression. If it's the same subexpression (not a name clash with a differently defined one) then adding the subexpressions to the beginning of the neuron update code in GeNN should work?
Or are you cencerned it would be added twice?
from brian2genn.
Adding it to the beginning would be incorrect, since the values of the subexpression used in the neuron update code are the values before the update while the values used in the threshold should be the values after the update. If we just combine Brian's generated C++ code for the updater and the threshold, then we'd end up with duplicate local variable declarations. So I think the correct solution would be to combine the abstract code of the neuron update and the thresholder, then Brian's codegen mechanism would handle everything automatically (re-evaluate the subexpressions if necessary, but not re-declare them). This is a bit different from the approach we generally use in brian2genn (where we already deal with the CodeObject
s generated by Brian), but I think it shouldn't be too complicated. I'll look into this.
from brian2genn.
I had a go at this and managed to implement it from the brian2genn side, but then I realized this won't work: GeNN uses separate "namespace brackets" for the neuron update step and the thresholder so we can't use variables from the former in the latter...
Maybe adding support for the "constant over dt" mechanism would be a simpler solution for now (in simple cases like my example, users might be able to simply insert the expression directly into the threshold, anyway, so this is more a matter of clear error messages).
from brian2genn.
hum ... I don't think the namespaces are a problem. Both the neuron code and the thresholding happen in the same namespace. I know for a fact that it works for the oldspike
variable that is defined early on in the neuron kernel and then used in the spike detection much later ... Are you sure you are having trouble with this?
from brian2genn.
The namespace itself is not a problem, but it introduces a new local variable scope.
Here's what my version of brian2genn produced for the merged stateupdater+threshold (in this example, the state update code is empty:
// calculate membrane potential
{
using namespace neurongroup_neuron;
bool _cond = lv > 1;
}
// namespace bracket closed
// test for and register a true spike
{
using namespace neurongroup_neuron;
if (_cond) {
glbSpkneurongroup[glbSpkCntneurongroup[0]++] = n;
}
}
// namespace bracket closed
The variable _cond
is not accessible in the second scope.
I know for a fact that it works for the oldspike variable that is defined early on in the neuron kernel and then used in the spike detection much later ...
I had a look at GeNN's generateKernels.cc
and it seems that the "namespace brackets" are only added if there's support code. I did not find any GeNN example in the userprojects folder that uses support code, so it might actually be that the oldspike
variable leads to an error in GeNN as well as soon as neuronModel.supportCode
is not an empty string...?
from brian2genn.
argh ... we need to look into this. It is slightly annoying that one cannot do using namespace xxx;
in a limited region without creating the unnamed local variable scope ... or can one? Sorry if I am clumsy with this.
from brian2genn.
Of course, one solution would be for GeNN to stop doing the
using namespace neurongroup_neuron;
and instead for all calls to support code, substitute
neurongroup_neuron::call_to_support_code
which is unfortunately not entirely trivial.
Yet, alternatively, I am actually now wondering whether there is any potential for conflict within the kernel that would necessitate the (essentially 3) local variable scopes or whether it may be safe to just combine it into one. I am now thinking why would there be as we are using the same support code namespace for all elements of the neuron model... this may be a very easy fix in GeNN.
from brian2genn.
It is slightly annoying that one cannot do using namespace xxx; in a limited region without creating the unnamed local variable scope ... or can one?
I don't think you can do this, I think the standard recommendation would be to use fully qualified names, or define your local variables outside the new scope.
But looking at the GeNN code again, couldn't we merge all the "using namespace" directives that refer to the same namespace (neurongroup_neuron
in this case)? I.e. in the simple example from my initial comment, we currently (with my changes to merge stateupdater and thresholder code) have:
extern "C" __global__ void calcNeurons(double t)
{
//[...]
// neuron group neurongroup
if (id < 128) {
// only do this for existing neurons
if (id < 100) {
// pull neuron variables in a coalesced access
int32_t li = dd_ineurongroup[id];
uint64_t l_seed = dd__seedneurongroup[id];
// test whether spike condition was fulfilled previously
// calculate membrane potential
{
using namespace neurongroup_neuron;
double rate = ((_brian_pow(sin((((2 * (3.14159)) * t) * 1) * (1.00000)), 2)) * 100) * (1.00000);
bool _cond = _rand(l_seed) < (rate * DT);
}
// namespace bracket closed
// test for and register a true spike
{
using namespace neurongroup_neuron;
if (_cond) {
spkIdx = atomicAdd((unsigned int*)&spkCount, 1);
shSpk[spkIdx] = id;
}
}
// namespace bracket closed
dd_ineurongroup[id] = li;
dd__seedneurongroup[id] = l_seed;
}
__syncthreads();
//[...]
}
}
Instead we could use:
extern "C" __global__ void calcNeurons(double t)
{
//[...]
// neuron group neurongroup
if (id < 128) {
// only do this for existing neurons
if (id < 100) {
// pull neuron variables in a coalesced access
int32_t li = dd_ineurongroup[id];
uint64_t l_seed = dd__seedneurongroup[id];
using namespace neurongroup_neuron;
// test whether spike condition was fulfilled previously
// calculate membrane potential
double rate = ((_brian_pow(sin((((2 * (3.14159)) * t) * 1) * (1.00000)), 2)) * 100) * (1.00000);
bool _cond = _rand(l_seed) < (rate * DT);
// namespace bracket closed
// test for and register a true spike
if (_cond) {
spkIdx = atomicAdd((unsigned int*)&spkCount, 1);
shSpk[spkIdx] = id;
}
// namespace bracket closed
dd_ineurongroup[id] = li;
dd__seedneurongroup[id] = l_seed;
}
__syncthreads();
//[...]
}
}
The using namespace
doesn't necessarily need brackets here, it is simply valid until the end of the scope (given by the if (id < 100)
here). As a bonus, this would make the generated code shorter and more readable :)
from brian2genn.
Yes, agreed. I am going to fix this in GeNN. I was thinking one bracket instead of three but you are right, all of these are already encapsulated by an if statement on the neuron ID range, so none of the brackets are necessary. It will all become a lot simpler. Will do this Monday first thing.
from brian2genn.
Related Issues (20)
- Question: How to work around restriction of synaptic output variable or summed variable? HOT 4
- Cannot open include file: 'brianlib/stdint_compat.h from clocks.h HOT 22
- Building genn executable fails after a certain number of successful runs when parameter fitting HOT 12
- Workaround for network-operation? HOT 30
- Use GeNN's new run_regularly mechanisms for run_regularly statements
- Use GeNN's initvar snippets for synapse creation
- Brian2genn model build is completed, but system cannot find the path specified HOT 17
- RuntimeError: Project compilation failed HOT 2
- Undeclared Identifier Error for Multiple Runs HOT 6
- potential problem with handling of build_options
- Custom header files are missing when used in `device.insert_code` calls HOT 1
- C99 math functions fail on integer type arguments HOT 18
- Inaccurate updates when having multiple differential equations in neuron groups HOT 3
- StateMonitor fails to collect data from some Synapses HOT 3
- use individually named dir instead of GeNNworkspace HOT 1
- [Feature request] Support for slower clocks in Monitors HOT 3
- RNG & seeding HOT 4
- SpikeGenerator problem HOT 2
- Error code 50 HOT 3
- Failed to build, error: ‘spikeCount_neurongroup’ was not declared in this scope HOT 1
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from brian2genn.