-
Notifications
You must be signed in to change notification settings - Fork 222
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
Structured equations with equation templates #1244
base: master
Are you sure you want to change the base?
Conversation
I find it a very elegant way to deal with it: it's both simple and very effective. Readability is greatly improved (e.g. for the parameters and their meaning, compare the two versions^^). Conceptually it really makes sense to emphasize the |
Thanks for the feedback :) What do you think about the question of |
Mmmhhh... I understand that's not a trivial design choice. I liked the |
I worked a bit more on this, and I think I am now quite happy with the general approach (remember that there's still a lot of work to do to make the code robust, have proper error checking, etc.). I had another though about the issue of "scoping", and I think for now we don't have to do much about it. What I think is important, is that it is possible to change the name of e.g. a channel in a single place. This is now possible with a formulation like this: # Classical Na channel
ina = Equations('ina = gnabar*{m}**3*{h}*(ENa-v) : amp',
m=gating_var(name='m',
rate_expression=pos_sigmoid(midpoint=-38., scale=7.),
forward_rate=exp_voltage_dep(magnitude=5., midpoint=-60, scale=18.),
reverse_rate=neg_exp_voltage_dep(magnitude=36., midpoint=-60, scale=25.),
tau_base=0.04*ms, tau_scale=10*ms),
h=gating_var(name='h',
rate_expression=neg_sigmoid(midpoint=-65., scale=6.),
forward_rate=exp_voltage_dep(magnitude=7., midpoint=-60., scale=11.),
reverse_rate=neg_exp_voltage_dep(magnitude=10., midpoint=-60., scale=25.),
tau_base=0.6*ms, tau_scale=100*ms)) Here, if you are having a clash with the names The final addition to the syntax is this: eqs =Equations("""dv/dt = ({currents} + I)/C : volt
vu = v/mV : 1 # unitless v
I : amp""",
currents=[ileak, ina, ikht, ih, iklt, ika, ihcno], voltage='vu') Here, As you might have noticed, I have merged eqs = EquationTemplate("""dv/dt = ({currents} + I)/C : volt
vu = v/mV : 1 # unitless v
I : amp""")(currents=[ileak, ina, ikht, ih, iklt, ika, ihcno], voltage='vu') call, or do some dirty Python magic so that calling eqs = EquationTemplate("""dv/dt = ({currents} + I)/C : volt
vu = v/mV : 1 # unitless v
I : amp""", currents=[ileak, ina, ikht, ih, iklt, ika, ihcno], voltage='vu') would actually create an One problem of my current approach is that we now have two mechanisms to replace variables in equations. Before the changes in this branch, you could already do: eqs = Equations('dx/dt = -x/tau : 1', x='v') to replace the eqs = Equations('d{x}/dt = -{x}/tau : 1', x='v') Not sure whether we should deprecate the old syntax, it might still be useful. As always: comments/feedback welcome! I converted the full Rothman&Manis example so that it now uses templates for all currents: https://github.com/brian-team/brian2/blob/equation_template/examples/frompapers/Rothman_Manis_2003_with_templates.py |
@mstimberg Could you please give us an example of how to resolve a name clash? For example, if |
There is no mechanism to deal with this automatically at the moment, so you'd have to use a non-ambiguous name yourself: iklt = Equations('ih = gkltbar*{w}**4*{z}*(EK-v) : amp',
w=gating_var(name='m_klt',
rate_expression=power_sigmoid(midpoint=-48., scale=6., power=0.25),
forward_rate=exp_voltage_dep(magnitude=6., midpoint=-60., scale=6.),
reverse_rate=neg_exp_voltage_dep(magnitude=16, midpoint=-60, scale=45.),
tau_scale=100*ms, tau_base=1.5*ms),
z=gating_var(name='h_klt',
rate_expression=shifted_neg_sigmoid(shift=zss, midpoint=-71., scale=10.),
forward_rate=exp_voltage_dep(magnitude=1., midpoint=-60., scale=20.),
reverse_rate=neg_exp_voltage_dep(magnitude=1., midpoint=-60., scale=8.),
tau_scale=1*second, tau_base=50*ms)) What I find important is that in the above, you only had to only change the name in a single place, there is no room for an inconsistency between the Note that if you want to use the suffix approach more generally, you could also define things slightly different in the first place: gating_var = Equations('''d{name}_{suffix}/dt = q10*({name}_{suffix}__inf - {name}_{suffix})/tau_{name}_{suffix} : 1
{name}_{suffix}__inf = {rate_expression} : 1
tau_{name}_{suffix} = {tau_scale}/({forward_rate} +
{reverse_rate})
+ {tau_base} : second''')
# KLT channel (low threshold K+)
iklt = Equations('i_{suffix} = gkltbar*{m}**4*{h}*(EK-v) : amp',
m=gating_var(name='m',
rate_expression=power_sigmoid(midpoint=-48., scale=6., power=0.25),
forward_rate=exp_voltage_dep(magnitude=6., midpoint=-60., scale=6.),
reverse_rate=neg_exp_voltage_dep(magnitude=16, midpoint=-60, scale=45.),
tau_scale=100*ms, tau_base=1.5*ms),
h=gating_var(name='h',
rate_expression=shifted_neg_sigmoid(shift=zss, midpoint=-71., scale=10.),
forward_rate=exp_voltage_dep(magnitude=1., midpoint=-60., scale=20.),
reverse_rate=neg_exp_voltage_dep(magnitude=1., midpoint=-60., scale=8.),
tau_scale=1*second, tau_base=50*ms),
suffix='klt') Here, all variables are suffixed and there is only a single place where you have to define the suffix. As I mentioned earlier, I do not want to make this suffix (or prefix) approach mandatory for now. Otherwise, we'd have to introduce a new syntax (e.g. how do you initialize I get the whole idea of scoping/modularity and there is certainly potential to do something here. Since the other equations never have to refer to the gating variables etc. of a current, they could be treated differently from other variables. But I'd prefer to leave this for another time, since we'd have to come up with new syntax for recording/intializing/accessing such variables. I guess a dot-syntax could work (e.g. |
Implementation is still quick&dirty
Improve error checking
I've worked on this a bit more and the basic implementation is now mostly finished. It will now look at the dependencies between substitutions and replace things in the correct order. For example, you could have eqs = Equations('dv/dt = ((E_{leak_suffix} - v) + {currents})/C : 1', currents=['I_Na', 'I_{leak_suffix}'], leak_suffix='L') and it would use What's missing is mostly error checking, testing, and documentation. Assuming everyone is ok with the decision to use
>>> eqs = Equations('dx/dt = -x/tau_{x} : volt', x='v')
>>> print(eqs)
dv/dt = -v/tau_v : V i.e.
As always, comments welcome! |
Thinking about two questions above, it seems that |
Thanks for your opinion on that question, I agree that a warning is probably the best approach (I implemented this in the current PR).
I have the same concern, and I agree that something like replacing operators would really go too far into simple string replacement territory. I have now implemented the following approach: a string has to be either a name, or a full expression. The latter could be done by wrapping it in pos_sigmoid = Expression('1./(1+exp(-({voltage} - {midpoint}) / {scale}))') you can either use pos_sigmoid(midpoint=-60*mV, scale=7*mV, voltage='v') Or you can use a unitless pos_sigmoid(midpoint=60., scale=7., voltage='v/mV') The requirement that strings need to be valid expressions means that we can still check for basic syntax correctness (e.g. matching parentheses), without worrying about weird replacements. I'm quite happy with the way this is turning out. Would be grateful if some of you could try out the current code a bit and see whether there are still things that are cumbersome to do or confusing. If not, I'd go ahead with more code polishing, testing, and documentation. |
Aha, That is a great point @mstimberg! I use unitless or a mixture of "unitfull" and unitless models quite often. This ability to hide units in sub-expressions may be really handy. (^-^) I cannot commit to doing beta testing for 3-4 weeks, because my shoulder is broken and I'm quite behind the schedule. However, I have a model of a network with multicompartment neurons. They have gradients of channel densities along the main track of apical dendrite, collaterals, and tufts. I can try to move this model from NEURON to Brian. Honestly, up to now, my hands instinctively picked NEURON as soon as more than a few channels are needed for the model or as soon as complex morphology with channel distribution should is involved. Hopefully, this is going to change and Brian won't be a niche simulator with clean and simple code only for a subset of neural models. |
Just trying to get back into this after having not thought about it for a while. There's a lot of things pulling in different directions here, and I find it particularly tricky to work out what's the right thing to do. I think it's important that we have some feature like this, but I'm a bit concerned about coming up with an implementation that we'll have to support going forward but that isn't sufficient for all the things we'd like it to do and so adds technical debt, so I think it's worth taking a little bit more time to discuss. My other concern is that we avoid adding a syntax that has non-obvious or hidden behaviour. With those in mind, I'm immediately anxious about two parts of the proposed syntax:
The second one could potentially be fixed by having a "primary variable" or some such, where in the For the first one, I'm not sure what would be a good solution. Possibly a specific function for this, e.g. something like OK I didn't exactly mean to do it but I seem to have come up with a concrete proposal. Not sure what I think of it myself yet, so just throwing that out there for discussion for the moment. I have another point to discuss, but I think I'll put it in a second post to keep things simple. |
The other point I wanted to discuss was that it would be nice to keep track of the structured representation in the final equations. We could use this for (a) debugging/error messages, (b) for generating better representations of the equations for the automatic model descriptions package. I wonder if we could do this by somehow storing metadata about where substitutions came from when using the substitutions mechanisms. |
I'm also wondering if we should just bite the bullet and implement something like the dotted syntax. If we end up wanting to do it anyway, maybe it's better to do it now? I'm not sure I agree with this, but if we end up wanting to do it, and we don't do it now, we'll have to support two systems that might interfere with each other, which could turn into a nightmare. This possibility would be reduced if we don't allow all this as part of Equations, but instead add new classes which have to produce an Equations object in the end (but on the other hand, that's much less elegant and composable). |
On the specific questions: I think I would agree with only looking at name substitutions, not operator etc. |
Thanks for the feedback @thesamovar , all very reasonable suggestions. Let me start with the easiest point
What I had in mind were some slightly more complex situations where you mix up things from equations and templates. E.g. you'd have one equation that only uses
Yes, I agree! I'll have to think a bit more about how to do this in practice, but keeping this information around might certainly come handy.
IMHO, we shouldn't do this now. The nice thing about the proposed approach is that it is completely on the level of gating_var = EquationTemplate('''d{name}/dt = q10*({name}__inf - {name})/tau_{name} : 1 (local: {current})
{name}__inf = {rate_expression} : 1(local: {current})
tau_{name} = {tau_scale}/({forward_rate} +
{reverse_rate})
+ {tau_base} : second (local: {current})''')
ina = Equations('{current} = gnabar*{m}**3*{h}*(ENa-v) : amp', m=gating_var(name='m',
rate_expression=pos_sigmoid(midpoint=-38., scale=7.),
forward_rate=exp_voltage_dep(magnitude=5., midpoint=-60, scale=18.),
reverse_rate=neg_exp_voltage_dep(magnitude=36., midpoint=-60, scale=25.),
tau_base=0.04*ms, tau_scale=10*ms),
h=gating_var(name='h',
rate_expression=neg_sigmoid(midpoint=-65., scale=6.),
forward_rate=exp_voltage_dep(magnitude=7., midpoint=-60., scale=11.),
reverse_rate=neg_exp_voltage_dep(magnitude=10., midpoint=-60., scale=25.),
tau_base=0.6*ms, tau_scale=100*ms)) The idea would be that the "local" variables would not be accessible from elsewhere in the equations (and they could therefore have the same name), internally they would be renamed to something like |
About the questions regarding the summing and the "primary" variable. I also see the danger of hiding away details here, but at the same time we want to be concise and not introduce too much new syntax. I feel there is a danger of ending up with equations that have a lot of idiosyncratic stuff like For the "primary equation" question, I tend towards saying that using the first equation would be ok and consistent with the way you'd write these equations naturally. Adding new syntax in the equations would also come with new problems, e.g. what if you add two equations together that both have a For the summing, I agree that it is maybe a bit too magic. I find the |
gating_var = EquationTemplate('''d{name}/dt = q10*({name}__inf - {name})/tau_{name} : 1
{name}__inf = {rate_expression} : 1
tau_{name} = {tau_scale}/({forward_rate} + {reverse_rate})
+ {tau_base} : second ''')
ina = Equations('{name} = gnabar*{m}**3*{h}*(ENa-v) : amp/meter**2',
name = 'ina',
include =[
gating_var(name='m',
rate_expression=pos_sigmoid(midpoint=-38., scale=7.),
forward_rate=exp_voltage_dep(magnitude=5., midpoint=-60, scale=18.),
reverse_rate=neg_exp_voltage_dep(magnitude=36., midpoint=-60, scale=25.),
tau_base=0.04*ms),
gating_var(name='h',
rate_expression=neg_sigmoid(midpoint=-65., scale=6.),
forward_rate=exp_voltage_dep(magnitude=7., midpoint=-60., scale=11.),
reverse_rate=neg_exp_voltage_dep(magnitude=10., midpoint=-60., scale=25.),
tau_base=0.6*ms, tau_scale=100*ms)
]
)
i = Equations('{name} = ina+ik+ileak: amp/meter**2',
name = 'totcur',
include=[ina,ik,ileak]
)
neuron = SpatialNeuron(morphology=morpho,
model="""
{totalcurrent}
totcur.ina.m.tau_scale : second
""",
Cm=1*uF/cm**2, Ri=100*ohm*cm,
totalcurrent = i
)
neuron.totcur.ina.m.tau_scale = 1*ms
neuron.basal.totcur.ina.m.tau_scale = 10*ms
neuron.apical[10:].totcur.ina,m.tau_scale = 3*ms It may look a bit too much, but as soon as we depart from single compartment model, like Rothman&Manis, structural approach is really needed. |
I see your point, it would indeed be a possibility to make local variables the default when inserting an equation into another equation. I feel that this would limit things quite a bit to this specific way of constructing equations, though. For example, you could not concatenate together two equations that both are supposed to have local variables. Maybe this would be ok, not sure. But as I said in my comment to @thesamovar, I feel that we should improve things more incrementally. For me this change feels too big. Even if you have tens of different channels, giving them each a suffix is quite a straightforward solution and also what you'd do to describe the model in a paper, for example. I'm not too happy about your
I don't quite see why multi-compartment vs. single-compartment makes a difference here, to be honest. The equations will not look any different. |
Because we may have different subunits of ion channel in basal and apical dendrites (for example) and it is necessary to assess parameters of equations and set different taus or steady-state voltages /calcium consecrations in different parts of a neuron. Classical example is K_A in proximal and apical dendrites of CA1 pyramidal neuron (see Jarsky 2005 for example).
I tried to make notation homogeneous. If placeholder isn't found on local level it is recursively sent up with automatically generates dot prefix until it will be define. But this may not be a good way to do so. Another version is ina = Equations(
'''{name} = gnabar*{m}**3*{h}*(ENa-v) : amp/meter**2
m.tau_scale : seconds
''',
name = 'ina',
include =[
gating_var(name='m',
rate_expression=pos_sigmoid(midpoint=-38., scale=7.),
forward_rate=exp_voltage_dep(magnitude=5., midpoint=-60, scale=18.),
reverse_rate=neg_exp_voltage_dep(magnitude=36., midpoint=-60, scale=25.),
tau_base=0.04*ms),
gating_var(name='h',
rate_expression=neg_sigmoid(midpoint=-65., scale=6.),
forward_rate=exp_voltage_dep(magnitude=7., midpoint=-60., scale=11.),
reverse_rate=neg_exp_voltage_dep(magnitude=10., midpoint=-60., scale=25.),
tau_base=0.6*ms, tau_scale=100*ms)
]
) |
Maybe in this case you can introduce a flag
Yes should be an error only if both equations have the same local variable. However, one can concatenate two equations if variables are different, right? It will be just a new equation with all local variables from both:
|
Right, I get that. But this simply means that you have things that are parameters in the equations, not constants, and again you can handle this with suffixes, say calling it I still find that your proposed notation mixes up the placeholder ina = Equations('i_{suffix} = gnabar*{m}**3*{h}*(E_{suffix}-v) : amp/meter**2',
suffix='na',
m=gating_var(name='m_{suffix}',
rate_expression=pos_sigmoid(midpoint=-38., scale=7.),
forward_rate=exp_voltage_dep(magnitude=5., midpoint=-60, scale=18.),
reverse_rate=neg_exp_voltage_dep(magnitude=36., midpoint=-60, scale=25.),
tau_base=0.04*ms, tau_scale=Equations('tau_scale_{name} : second (constant)')),
h=gating_var(name='h_{suffix}',
rate_expression=neg_sigmoid(midpoint=-65., scale=6.),
forward_rate=exp_voltage_dep(magnitude=7., midpoint=-60., scale=11.),
reverse_rate=neg_exp_voltage_dep(magnitude=10., midpoint=-60., scale=25.),
tau_base=0.6*ms, tau_scale=100*ms)
) Apologies for being impressed by my own code, but look at the beauty of |
(Sorry, I wrote my reply in parallel to your second post. Will sleep over things and get back to you tomorrow!) |
I like your IMHO, prefixes are better than suffixes. As in any unix system, /var/log/system.log looks better than system.log/log/var/ . Also in dot notation However, here why I'm a bit concern of just free-handed placeholders: look at your perfect code, where is the suffix defined and where it is used? The same for the {name} placeholder. It seems legit to do this definition: ina = Equations('i_{suffix} = gnabar*{m}**3*{h}*(E_{suffix}-v) : amp/meter**2',
suffix='na',
name='ina',
m=gating_var(name='m_{suffix}', suffix='xxx',
rate_expression=pos_sigmoid(midpoint=-38., scale=7.),
forward_rate=exp_voltage_dep(magnitude=5., midpoint=-60, scale=18.),
reverse_rate=neg_exp_voltage_dep(magnitude=36., midpoint=-60, scale=25.),
tau_base=0.04*ms, tau_scale=Equations('tau_scale_{name} : second (constant)')),
h=gating_var(name='h_{suffix}',
rate_expression=neg_sigmoid(midpoint=-65., scale=6.),
forward_rate=exp_voltage_dep(magnitude=7., midpoint=-60., scale=11.),
reverse_rate=neg_exp_voltage_dep(magnitude=10., midpoint=-60., scale=25.),
tau_base=0.6*ms, tau_scale=100*ms)
) or even worse |
What isn't grate about your perfect code is that there is m=gating_var(name='m_{suffix}' and it is easy to mess what is there. In contrast, in my naive suggestion, everything is straightforward: each gating_var = EquationTemplate('''d{name}/dt = q10*({name}__inf - {name})/tau_{name} : 1
{name}__inf = {rate_expression} : 1
tau_{name} = {tau_scale}/({forward_rate} + {reverse_rate})
+ {tau_base} : second ''')
xxx = Equations('{name} = {gbar}*{m}**3*{h}*(ENa-v) : amp/meter**2',
name = 'ina',
include =[
gating_var(name='m',
rate_expression=pos_sigmoid(midpoint=-38., scale=7.),
forward_rate=exp_voltage_dep(magnitude=5., midpoint=-60, scale=18.),
reverse_rate=neg_exp_voltage_dep(magnitude=36., midpoint=-60, scale=25.),
tau_base=0.04*ms, tau_scale=Equations('{name} : second ', name="tau_scale")),
gating_var(name='h',
rate_expression=neg_sigmoid(midpoint=-65., scale=6.),
forward_rate=exp_voltage_dep(magnitude=7., midpoint=-60., scale=11.),
reverse_rate=neg_exp_voltage_dep(magnitude=10., midpoint=-60., scale=25.),
tau_base=0.6*ms, tau_scale=100*ms),
Equations('{name} : semens/meter**2', name="gbar"))
]
)
i = Equations('{name} = ina+ik+ileak: amp/meter**2',
name = 'totcur',
include=[xxx,ik,ileak]
)
neuron = SpatialNeuron(morphology=morpho,
model="{totalcurrent}",
Cm=1*uF/cm**2, Ri=100*ohm*cm,
totalcurrent = i
)
neuron.totcur.ina.m.tau_scale = 1*ms
neuron.basal.totcur.ina.m.tau_scale = 10*ms
neuron.apical[10:].totcur.ina.m.tau_scale = 3*ms
neuron.axon.totcur.ina.gbar = 5000*nS/cm**2 (Sorry for putting comments, while you are asleep, now is my turn ;) |
There is really a fundamental question here how radical we want the current system to change: at the moment, equations are strings, and all my proposed system does is help you create this string. There is no change needed to anything else in Brian, and you can easily inspect the final equations.
I don't think I agree with this as a problem: the first Regarding your example with multiple names and suffixes all over the place (which currently would not work, but it might actually make sense to remove the limitation that makes it fail), I agree that this would be confusing. But any reasonable complex system will make it possible for the user to mess things up. I hope you don't take this as defensive/returning the blame (I am sincerely grateful for this discussion!), but there are also things that I find confusing about your proposed system: there are two different ways to know what fills in a placeholder: a placeholder can either be filled directly from an argument as in Sorry for jumping around between topics, but one more comment:
I agree with this in general and from the computer science/programming language perspective. But one of the guiding principles of Brian's equation mechanism has always been that we strive to make the link between equations in a paper and the equations you specify in Brian as clear as possible. Equations in a paper traditionally use suffixes in variable names like Taking a step back and looking back at your proposal, I wonder about a general change that gives us more flexibility in the long-term: currently, eqs = Equations('d{x}/dt = -{x}/tau_{x}', replacements={'x': 'v'}) or eqs = Equations('d{x}/dt = -{x}/tau_{x}')
v_eqs = eqs(x='v') This would free up the My current proposal would look less clean with this unfortunately, so you could no longer do: ina = Equations('ina = gnabar*{m}**3*{h}*(ENa-v) : amp',
m=gating_var(name='m',
rate_expression=pos_sigmoid(midpoint=-38., scale=7.),
forward_rate=exp_voltage_dep(magnitude=5., midpoint=-60, scale=18.),
reverse_rate=neg_exp_voltage_dep(magnitude=36., midpoint=-60, scale=25.),
tau_base=0.04*ms, tau_scale=10*ms),
h=...) but would have to do either something like ina = Equations('ina = gnabar*{m}**3*{h}*(ENa-v) : amp',
replacements={'m': gating_var(name='m',
rate_expression=pos_sigmoid(midpoint=-38., scale=7.),
forward_rate=exp_voltage_dep(magnitude=5., midpoint=-60, scale=18.),
reverse_rate=neg_exp_voltage_dep(magnitude=36., midpoint=-60, scale=25.),
tau_base=0.04*ms, tau_scale=10*ms),
'h':...) or
which then renders my previous argument over Ok, I need to think about this a bit more and put some more examples for the various syntax proposals together. Let's ignore the currents/ |
Agreed!
This precisely highlights the problem with just using the first defined variable to me. If there is an implicit rule (first variable) then if you write
I like this! Overall, I think I feel more convinced by Marcel's argument for making a new system that fits with the current system rather than introducing dotted notation. We do want to make the correspondence between how you'd write it in a paper and the code as close as possible. It seems to me that there are two ways you might write it in a paper, either writing out all the equations in one huge set, with unique names for all variables, or as a seqeuence of separate sets of equations with explicit points of coupling between them. For large sets of equations, this latter approach is probably more readable and more or less corresponds to Marcel's construction. If we have metadata that shows how the equations were originally structured, then this correspondence could be maintained and pretty explicit I feel. Introducing dotted names breaks this connection somewhat. |
@mstimberg, you don't need to say this! It is nice and hopefully useful discussion between colleagues, good Brianstorm 😄
I bet you are incorrect! In the original hoc language, all gating variables and parameters in equations had module suffix Returning to your proposal...
|
Good Brianstorm indeed 😁
You are of course right – it has been a while since I wrote anything in hoc... But I wasn't so much focussed on the name syntax used in NEURON, but rather the fact that you typically define each channel/mechanism in a separate mod file. So while you can set all the (exposed) parameters from the main hoc script, all the mechanisms stay separated whereas Brian works with a single big equation in the end, i.e. something that would be more comparable to a merged mod file.
This was what my earlier example did (using a suffix instead of a prefix): ina = Equations('i_{suffix} = gnabar*{m}**3*{h}*(E_{suffix}-v) : amp/meter**2',
suffix='na',
m=gating_var(name='m_{suffix}',
rate_expression=pos_sigmoid(midpoint=-38., scale=7.),
forward_rate=exp_voltage_dep(magnitude=5., midpoint=-60, scale=18.),
reverse_rate=neg_exp_voltage_dep(magnitude=36., midpoint=-60, scale=25.),
tau_base=0.04*ms, tau_scale=Equations('tau_scale_{name} : second (constant)')),
h=gating_var(name='h_{suffix}',
rate_expression=neg_sigmoid(midpoint=-65., scale=6.),
forward_rate=exp_voltage_dep(magnitude=7., midpoint=-60., scale=11.),
reverse_rate=neg_exp_voltage_dep(magnitude=10., midpoint=-60., scale=25.),
tau_base=0.6*ms, tau_scale=100*ms)
) With the additional
Here, I did not yet get around preparing an overview over the different syntax proposals but hopefully I'll finish this soon. |
It seems you are proposing "flat" placeholder replacement. There is something confusing in your example (but it may be just me 0_0): in the line m=gating_var(name='m_{suffix}', you are creating an object of ina = Equations('i_{suffix} = gnabar*{m}**3*{h}*(E_{suffix}-v) : amp/meter**2',
suffix='na',
m=gating_var(name='m_{suffix}', .... )
h=gating_var(name='h_{suffix}', ... )
)
ical = Equations('i_{suffix} = gcal*{m}**3*{h}*(E_{suffix}-v) : amp/meter**2',
suffix='cal',
m=gating_var(name='m_{suffix}', .... )
h=gating_var(name='h_{suffix}', ... )
) I'm sure, you have a solution :), but if you don't, I would rather require that all placeholders must be substituted by replacements in the arguments unless the flag ina = Equations('i_{suffix} = gnabar*{m}**3*{h}*(E_{suffix}-v) : amp/meter**2',
suffix='na',
m=gating_var(name='m_{(parent):suffix}', .... )
h=gating_var(name='h_{(parent):suffix}', ... )
)
ical = Equations('i_{suffix} = gcal*{m}**3*{h}*(E_{suffix}-v) : amp/meter**2',
suffix='cal',
m=gating_var(name='m_{(parent):suffix}', .... )
h=gating_var(name='h_{(parent):suffix}', ... )
) Well, don't kill me please, now we can set a placeholder to the replacement which will be defined in a specific object, for example |
I think you are overcomplicating things a bit here, there is nothing problematic in your first example: ina = Equations('i_{suffix} = gnabar*{m}**3*{h}*(E_{suffix}-v) : amp/meter**2',
suffix='na',
m=gating_var(name='m_{suffix}', .... )
h=gating_var(name='h_{suffix}', ... )
) Python will first evaluate m_{suffix}__inf = (1./(1+exp(-({voltage} - (-38.0)) / 7.0))) : 1
tau_m_{suffix} = (100. * msecond)/((5.0*exp(({voltage}-(-60))/18.0)) + (36.0*exp(-({voltage}-(-60))/25.0))) + (40. * usecond) : s
dm_{suffix}/dt = q10*(m_{suffix}__inf - m_{suffix})/tau_m_{suffix} : 1 So
And then it will replace
So If you don't believe me you can try it out in this PR branch :) |
I got it. Now can we figure out how to write in new notation for (say) 3 Ca2+ ion channels CaL, CaH, and CaT, The equations for these channels are GHK and they use inner calcium consecration, which is computed in another equation. The later one has to sum all calcium currents + subportion of NMDA current to compute inner concentration? I wonder if placeholder/replacements will make this system more elegant and simpler. |
It would be definitely useful to have a look at something like this. Do you have a reference that uses the kind of equations you have in mind? |
Here an example Multitarget Multiscale Simulation for Pharmacological Treatment of Dystonia in Motor Cortex with ModelDB record. However, it is a very good but heavy model. I'll try to extract equations from there (will take a bit of time) and put them here, so you can work with equations, not a heavy NEURON model. |
That would be great, thanks! No need to hurry. |
n-calcium channel
l-calcium channel
similar equations for t-calcium channel Calcium influx through NMDA and AMPA receptors
Calcium dynamics in simplest way
but in a more complex model calcium have to be able to diffuse into neighboring compartments and deep into dendrite. There is a problem here. When we accurately model cable properties of a dendrite, we usually end up with pretty small segments and therefore, if a segment has a synapse, it will be overflown by calcium. To avoid this, longitudinal diffusion is needed.
|
Thanks for the models, I will have a closer look soon.
This is a general limitation in Brian at the moment, the only thing that can "diffuse" is the membrane potential. There are ways to work around the limitations but not with the same numerical accuracy as for the longitudinal currents. In principle, it should be possible to reuse the same mechanism, but it's some non-trivial work and our human resources to work on this are quite limited unfortunately... |
Wait, I thought we have access to the dynamical variables in the nearby segments! Am I missing something? If we don't have access to dynamical variables, i.e. one cannot write a diffusion implementation, I would suggest to return to @thesamovar post and "bite the bullet". It seems you, guys can apply for a grant to make Brian universal tools, not a niche product. Brian is very popular software and good tool, so make it better is an excellent point for a proposal. |
A segment is like a neuron, it does not have access to variables outside itself. The only way to get access is to connect it via
I don't quite get the reference to Dan's post, but we'd certainly be happy to get money and people to work on various aspects of Brian. We know from experience that getting money for this kind of work is incredibly hard, almost all related grants require you to propose new software that does something that wasn't possible before... Usability improvements, maintenance, etc. is almost impossible to fund via grants. I've seen some NSF grants in that direction, but these are of course US-only. |
Can be helpful for error messages, etc.
# Conflicts: # brian2/equations/codestrings.py
Continuing the discussion from #1051 and the Discourse forum here. No need to look at the actual code yet, it is just a quick&dirty dummy implementation to get things working.
As a recap, we started out in #1051 to add a special syntax to add prefixes/suffixes to variable names in equations. The idea was that you could reuse a generic
Equations
object, e.g. for an ion channel or a synaptic conductance, and then generate various specializations from it. But this turned out to be a bit of a dead-end, since complex examples rather seemed to need a more flexible templating mechanism. We somewhat settled on using a template syntax using{...}
which would be very similar to Python's string formatting (and not necessarily add much to it). Fast-forward to 2020 and the discussion on the forum, where @yzerlaut presented his own code to generate equations for complex equations in a structured way, based on classes and string-formatting (with generic equations using placeholders with{...}
syntax as well). So I revisited this issue, and I think it could really be a nice addition to Brian. At the first sight, it does not seem to offer much over Python's builtin string formatting syntax, but I think there are a number of potential advantages. Most importantly, we can check the basic syntax already in a template which should make errors much easier to find (think of complex nested expressions with many parentheses).So here is what my current proposal looks like. It is based on two new classes
EquationTemplate
andExpressionTemplate
, even though I am not 100% sure that we really should introduce these new classes. An alternative would be that anEquations
/Expression
object simply is a template as long as it has any{...}
placeholders in it. In contrast to our discussion in #1050, I'm not using asubstitute
orreplace
method here, but make the objects callable, which leads to a bit more compact code which I think is quite nice.So here's the example I used for testing. Note that the code is overall longer with the new method, but I think many users would appreciate the more structured representation. The original equations from the Rothman&Manis (2013) example:
For the structured representation, I first define a few templates:
(Potentially one could also nest the templates and put the
exp((vu-{midpoint})/{scale}
part into its own template that gets reused in all theExpressionTemplate
s). Then we can define the currents and make use of the templates to describe the gating variables:Potentially we could also have some syntax to make lines like
independent of the channels (so that it is easy to remove/add channels), but maybe leave this discussion for another time.
Feedback welcome!