Skip to content

Commit ef8ff40

Browse files
author
adam
committed
Fix issue with _mult ids when Parameters are used
Support Kineitic Block AST compilation.
1 parent 3f6b69a commit ef8ff40

5 files changed

Lines changed: 133 additions & 56 deletions

File tree

share/lib/python/neuron/nmodl/ode.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -507,12 +507,18 @@ def optimize_odes(
507507
- new_local_vars: list of new temporary variable name strings from CSE
508508
"""
509509
from sympy.codegen.rewriting import create_expand_pow_optimization
510+
from sympy import Abs
511+
510512
custom_fcts = _get_custom_functions(function_calls)
513+
custom_fcts["Abs"] = "fabs"
511514

512515
# Build sympy symbols the variables
513516
sympy_vars = {}
514517
for var in set(var_names + constants + list(local_consts)):
515-
sympy_vars[var] = sp.Symbol(var, real=True)
518+
if "_absmult_" in var:
519+
sympy_vars[var] = Abs(sp.Symbol(f"_mult_{int(var.split('_')[-1])}"))
520+
else:
521+
sympy_vars[var] = sp.Symbol(var, real=True)
516522
local_consts = {sympy_vars[var]: value for var, value in local_consts.items()}
517523
# Parse each RHS expression
518524
expand_pow = create_expand_pow_optimization(10)
@@ -522,7 +528,6 @@ def optimize_odes(
522528

523529
code = []
524530
local_vars = []
525-
526531
if do_cse:
527532
my_symbols = sp.utilities.iterables.numbered_symbols(prefix="tmp_")
528533
sub_exprs, reduced = sp.cse(
@@ -539,7 +544,9 @@ def optimize_odes(
539544
rhs_exprs = reduced
540545

541546
for i, expr in enumerate(rhs_exprs):
542-
rhs = sp.ccode(expand_pow(expr).subs(local_consts).evalf(), user_functions=custom_fcts)
547+
rhs = sp.ccode(
548+
expand_pow(expr).subs(local_consts).evalf(), user_functions=custom_fcts
549+
)
543550
if rhs_ids:
544551
code.append(f"{rhs_ids[i]} = {rhs}")
545552
else:

share/lib/python/neuron/rxd/multiCompartmentReaction.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -472,9 +472,7 @@ def ast(self, regions=None):
472472
):
473473
species.append(sp().ast().get_node_name())
474474

475-
if kinetic_block == "off" or (
476-
kinetic_block == "mass_action" and self._custom_dynamics
477-
):
475+
if kinetic_block == "off" or self._custom_dynamics:
478476
# represent the reaction in a derivative block
479477
rates = []
480478

@@ -505,7 +503,7 @@ def ast(self, regions=None):
505503
return rates, species
506504
else:
507505
# represent the reaction in a kinetic block
508-
rast = self._scheme.ast()
506+
rast = self._scheme.ast(use_react_var=True)
509507

510508
# fill in the correct rates
511509
if ">" in self._dir:

share/lib/python/neuron/rxd/region.py

Lines changed: 107 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,13 @@
4343
ReactionStatement,
4444
)
4545

46+
def _int_or_double(c):
47+
return (
48+
Integer(c, Name(String(str(c))))
49+
if isinstance(c, int)
50+
else Double(str(c))
51+
)
52+
4653
# used for optimization
4754
from neuron.nmodl.visitor import AstLookupVisitor
4855
from neuron.nmodl import dsl
@@ -330,6 +337,7 @@ def ast(self):
330337
from . import Rate, rxd, MultiCompartmentReaction
331338
from .species import (
332339
SpeciesOnRegion,
340+
Parameter,
333341
ParameterOnRegion,
334342
SpeciesOnExtracellular,
335343
ParameterOnExtracellular,
@@ -369,13 +377,18 @@ def parse_kinetic_block(reactions, blocks):
369377
s = sptr()
370378
sid = self._species_ids[s._id]
371379
for r in s._regions:
380+
# skip regions not local here
381+
if r._id not in self._region_ids:
382+
continue
372383
rid = self._region_ids[r._id]
373384
flat_name = s.ast(r).get_node_name()
374385
self._name_to_index[flat_name] = ("species", sid, rid)
375386
for sptr in self._react_params:
376387
s = sptr()
377388
sid = self._params_ids[s._id]
378389
for r in s._regions:
390+
if r._id not in self._region_ids:
391+
continue
379392
rid = self._region_ids[r._id]
380393
flat_name = s.ast(r).get_node_name()
381394
self._name_to_index[flat_name] = ("params", sid, rid)
@@ -414,32 +427,62 @@ def parse_kinetic_block(reactions, blocks):
414427
rates = []
415428
multicompartmentReactions = []
416429
states = []
417-
mc_flux = []
430+
# maps for kinetic-blocks
418431
mc_kinetic_blocks = []
419432
local_consts = {}
433+
mc_mult_count = 0
420434
for rptr, rlst in self._react_regions.items():
421435
r = rptr()
422436
rast, sp = r.ast(rlst)
423437
species += sp
424438
rast = rast if hasattr(rast, "__len__") else [rast]
425439
mc_rates_ast = []
426440
if isinstance(r, MultiCompartmentReaction):
427-
for react in rast:
428-
if isinstance(react, ReactionStatement):
429-
mc_kinetic_blocks.append(react)
430-
else:
431-
mc_rates_ast.append(react)
432-
multicompartmentReactions += mc_rates_ast
433-
flux = []
441+
# keep track for the fluxes and area/volume multipliers
442+
mults = []
443+
fluxes = []
434444
for i, sp in enumerate(r._sources + r._dests):
435445
s = sp()
436-
if r._membrane_flux and isinstance(s, SpeciesOnRegion):
437-
sid = self._species_ids[s._id]
438-
rid = self._region_ids[s._region()._id]
439-
flux.append((sid, rid, r._cur_charges[i]))
446+
if r._membrane_flux:
447+
if isinstance(s, SpeciesOnRegion) and not isinstance(
448+
s, ParameterOnRegion
449+
):
450+
sid = self._species_ids[s._id]
451+
rid = self._region_ids[s._region()._id]
452+
flux = (sid, rid, r._cur_charges[i])
453+
454+
# keep track of direction
455+
kin_coeff = (
456+
-r._cur_charges[i]
457+
if i < len(r._sources)
458+
else r._cur_charges[i]
459+
)
460+
flux = (sid, rid, r._cur_charges[i], kin_coeff)
461+
else:
462+
flux = None
440463
else:
441-
flux.append(None)
442-
mc_flux += flux
464+
flux = None
465+
if not isinstance(
466+
s, (Parameter, ParameterOnRegion, ParameterOnExtracellular)
467+
):
468+
mults.append(mc_mult_count)
469+
fluxes.append(flux)
470+
mc_mult_count += 1
471+
for react, mid, flux in zip(rast, mults, fluxes):
472+
if isinstance(react, ReactionStatement):
473+
# ReactionStatement and mult with both account for direction
474+
flx = [
475+
(f[0], f[1], f[3]) if f is not None else f for f in fluxes
476+
]
477+
mc_kinetic_blocks.append((react, mults, flx))
478+
else:
479+
if flux is None:
480+
flx = None
481+
else:
482+
sid, rid, charge, _ = flux
483+
flx = (sid, rid, charge)
484+
mc_rates_ast.append((react, mid, flx))
485+
multicompartmentReactions += mc_rates_ast
443486
elif (
444487
isinstance(rptr(), Rate)
445488
or _ast_config["kinetic_block"] == "off"
@@ -460,67 +503,84 @@ def parse_kinetic_block(reactions, blocks):
460503
)
461504
if states != []:
462505
blocks.append(StateBlock(states))
463-
464506
# convert KineticBlock to DerivativeBlock to compile
465507
if reactions != []:
466508
rast, lc = parse_kinetic_block(reactions, blocks)
467509
rates += rast
468510
local_consts |= lc
469-
if mc_kinetic_blocks != []:
470-
rast, lc = parse_kinetic_block(mc_kinetic_blocks, blocks)
471-
multicompartmentReactions += rast
511+
mc_div_block_count = len(multicompartmentReactions)
512+
mc_kinetic_blocks_mults = []
513+
for react, mult, flux in mc_kinetic_blocks:
514+
rast, lc = parse_kinetic_block(
515+
[react],
516+
blocks,
517+
)
518+
519+
for react, mid, flx in zip(rast, mult, flux):
520+
multicompartmentReactions.append((react, mid, flx))
472521
local_consts |= lc
522+
mc_kinetic_blocks_mults += mult
473523
# Merge rates for common species or states
474524
if rates != [] or multicompartmentReactions != []:
475525
lookup = AstLookupVisitor()
476526
merged = {}
477527
constants = set()
478-
mult_id = 0
479-
for rid, stmt in enumerate(rates + multicompartmentReactions):
528+
mc_stmt = 0
529+
530+
def _add_flux(flx, frhs):
531+
# accumulate a membrane-flux contribution under key `flx`
532+
if flx in merged:
533+
merged[flx] = (
534+
Name(String(flx)),
535+
BinaryExpression(
536+
merged[flx][1],
537+
BinaryOperator(BinaryOp.BOP_ADDITION),
538+
frhs,
539+
),
540+
)
541+
else:
542+
merged[flx] = (Name(String(flx)), frhs)
543+
constants.add(flx)
544+
545+
for rid, statement in enumerate(rates + multicompartmentReactions):
546+
if rid < len(rates):
547+
stmt = statement
548+
else:
549+
stmt, mult_id, flux = statement
550+
480551
diffeq = stmt.expression
481552
binexpr = diffeq.expression
482553
var_name = binexpr.lhs.get_node_name()
483554
rhs = ParenExpression(binexpr.rhs)
484555
if rid >= len(rates):
485556
# MCR have to be multiplied by mult[]
486-
mult = Name(String(f"_mult_{mult_id}"))
487-
if mc_flux[mult_id] is not None:
488-
sid, rid, charge = mc_flux[mult_id]
489-
flx = f"_flux_{sid}_{rid}_"
490-
fast = Name(String(flx))
491-
cast = (
492-
Integer(charge, Name(String(str(charge))))
493-
if isinstance(charge, int)
494-
else Double(str(charge))
495-
)
557+
if mult_id in mc_kinetic_blocks_mults:
558+
# Kinetic-block MC reaction
559+
# sign is already in d/dt so use abs(_mult[id])
560+
mult = Name(String(f"_absmult_{mult_id}"))
561+
constants.add(f"_absmult_{mult_id}")
562+
else:
563+
# Derivative MC reaction
564+
mult = Name(String(f"_mult_{mult_id}"))
565+
constants.add(f"_mult_{mult_id}")
566+
if flux is not None:
567+
sid, frid, charge = flux
496568
if charge == 1:
497569
frhs = rhs
498570
else:
499571
frhs = BinaryExpression(
500-
cast,
572+
_int_or_double(charge),
501573
BinaryOperator(BinaryOp.BOP_MULTIPLICATION),
502574
rhs,
503575
)
504-
if flx in merged:
505-
merged[flx] = (
506-
fast,
507-
BinaryExpression(
508-
merged[flx][1],
509-
BinaryOperator(BinaryOp.BOP_ADDITION),
510-
frhs,
511-
),
512-
)
513-
else:
514-
merged[flx] = (fast, frhs)
515-
constants.add(flx)
516-
# multiple by a constant to scale units
576+
_add_flux(f"_flux_{sid}_{frid}_", frhs)
577+
578+
# multiply by a constant to scale units
517579
rhs = BinaryExpression(
518580
mult,
519581
BinaryOperator(BinaryOp.BOP_MULTIPLICATION),
520582
rhs,
521583
)
522-
constants.add(f"_mult_{mult_id}")
523-
mult_id += 1
524584
if var_name in merged:
525585
merged[var_name] = (
526586
binexpr.lhs,
@@ -587,6 +647,7 @@ def parse_kinetic_block(reactions, blocks):
587647
"var_names": list(merged.keys()),
588648
"code": code,
589649
"tmp_vars": tmp_vars,
650+
"merged": merged,
590651
}
591652

592653
blocks.append(

src/nrnpython/rxd.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -671,6 +671,7 @@ extern "C" NRN_EXPORT void setup_currents(int num_currents,
671671
// initialize memory here to allow currents from an intracellular species
672672
// with no corresponding nrn_region='o' or Extracellular species
673673
memset(induced_currents_ecs_idx, SPECIES_ABSENT, sizeof(int) * _memb_curr_total);
674+
memset(induced_currents_grid_id, SPECIES_ABSENT, sizeof(int) * _memb_curr_total);
674675

675676
for (i = 0, k = 0; i < num_currents; i++) {
676677
_memb_cur_ptrs[i].resize(num_species[i]);
@@ -727,7 +728,7 @@ extern "C" NRN_EXPORT void setup_currents(int num_currents,
727728

728729
for (i = 0, k = 0; k < _memb_curr_total; k++) {
729730
if (induced_currents_grid_id[k] == id)
730-
_rxd_induced_currents_scale[k] = current_scales[i];
731+
_rxd_induced_currents_scale[k] = current_scales[i++];
731732
}
732733
}
733734
}

test/rxd/test_ast.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -283,9 +283,19 @@ def test_multicompartment_reaction_ast(setup_section):
283283

284284
ast = {
285285
"ReactionStatement": [
286-
{"VarName": [{"Name": [{"String": [{"name": "ca_cyt_0"}]}]}]},
286+
{
287+
"ReactVarName": [
288+
{"Integer": [{"Name": [{"String": [{"name": "1"}]}]}]},
289+
{"VarName": [{"Name": [{"String": [{"name": "ca_cyt_0"}]}]}]},
290+
]
291+
},
287292
{"ReactionOperator": [{"name": "<->"}]},
288-
{"VarName": [{"Name": [{"String": [{"name": "ca_er_2"}]}]}]},
293+
{
294+
"ReactVarName": [
295+
{"Integer": [{"Name": [{"String": [{"name": "1"}]}]}]},
296+
{"VarName": [{"Name": [{"String": [{"name": "ca_er_2"}]}]}]},
297+
]
298+
},
289299
{"Double": [{"name": "0.1"}]},
290300
{"Double": [{"name": "0.05"}]},
291301
]

0 commit comments

Comments
 (0)