Skip to content

Commit 4be3e56

Browse files
committed
reason for not match
1 parent 43dcedf commit 4be3e56

File tree

1 file changed

+293
-0
lines changed

1 file changed

+293
-0
lines changed

NeuroML2/compare_MC/RE/REASON.md

Lines changed: 293 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,3 +239,296 @@ For `t_n → t_{n+1}`:
239239

240240
Conclusion: **in `HH2_reason`, the gate multipliers used in the current correspond to the gating state at time `t_n`, while `m,h,n` themselves have advanced to `t_{n+1}`. The gate multipliers lag one time step behind the gating variables.**
241241

242+
---
243+
244+
# Difference Between cadad and cadad_RE_soma_nml
245+
246+
This note compares the classic Destexhe-style Ca pump `cadad` (`mod/cadad.mod`) with the NeuroML-exported Ca concentration mechanism `cadad_RE_soma_nml` (`mod/cadad_RE_soma_nml.mod`), focusing on why the **NML-fix vs NRN native** runs diverged once `cadad` was included, and how we made the two behaviors match.
247+
248+
We use the following notation:
249+
250+
- Ionic Ca concentration used by NEURON: `cai(t)` (the `ca_ion` concentration)
251+
- NML-exported internal state: `concentration(t)` (in `cadad_RE_soma_nml`)
252+
- Ca current density: `ica(t)` (mA/cm²)
253+
- Pump parameters: `cainf`, `taur`, `depth`, `Faraday`
254+
255+
Key question: **for the same `ica(t)` and parameters, and with fixed time step / `cnexp`, do `cadad` and `cadad_RE_soma_nml` integrate `cai(t)` in exactly the same way, including how `cai` is initialised at `t = 0`?**
256+
257+
---
258+
259+
## 1. MOD-Level Equations
260+
261+
### 1.1 `cadad.mod` (Destexhe pump)
262+
263+
Key fragment (simplified, see `mod/cadad.mod`):
264+
265+
```nmodl
266+
NEURON {
267+
SUFFIX cadad
268+
USEION ca READ ica, cai WRITE cai
269+
RANGE depth, kt, kd, cainf, taur
270+
}
271+
272+
CONSTANT {
273+
FARADAY = 96489 (coul)
274+
}
275+
276+
PARAMETER {
277+
depth = 1 (um)
278+
taur = 5 (ms)
279+
cainf = 2.4e-4 (mM)
280+
kt = 0 (mM/ms) : dummy
281+
kd = 0 (mM) : dummy
282+
}
283+
284+
STATE {
285+
cai (mM)
286+
}
287+
288+
INITIAL {
289+
cai = cainf
290+
}
291+
292+
DERIVATIVE state {
293+
drive_channel = - (10000) * ica / (2 * FARADAY * depth)
294+
cai' = drive_channel + (cainf - cai)/taur
295+
}
296+
```
297+
298+
So, in continuous time:
299+
300+
\[
301+
\frac{dcai}{dt} = - \frac{10000}{2 \cdot FARADAY \cdot depth} \, ica
302+
+ \frac{cainf - cai}{\tau_r}
303+
\]
304+
305+
and at `t = 0`, `cai` is explicitly set to `cainf` (overwriting any earlier `seg.cai` assigned from Python).
306+
307+
### 1.2 `cadad_RE_soma_nml.mod` (NeuroML export)
308+
309+
Key fragment (simplified, see `mod/cadad_RE_soma_nml.mod`):
310+
311+
```nmodl
312+
NEURON {
313+
SUFFIX cadad_RE_soma
314+
USEION ca READ cai, cao, ica WRITE cai VALENCE 2
315+
RANGE cai, cao
316+
GLOBAL initialConcentration, initialExtConcentration
317+
RANGE cainf, taur, depth, Faraday
318+
RANGE currDensCa
319+
}
320+
321+
PARAMETER {
322+
surfaceArea (cm2)
323+
iCa (nA)
324+
initialConcentration (mM)
325+
initialExtConcentration (mM)
326+
327+
cainf = 2.4E-4 (mM)
328+
taur = 5 (ms)
329+
depth = 1.0E-4 (cm)
330+
Faraday = 9.6488997E10 (pC / umol)
331+
}
332+
333+
ASSIGNED {
334+
cai (mM)
335+
cao (mM)
336+
ica (mA/cm2)
337+
currDensCa (nA / cm2)
338+
}
339+
340+
STATE {
341+
concentration (mM)
342+
extConcentration (mM)
343+
}
344+
345+
INITIAL {
346+
cai = cainf : ADDED to align with cadad.mod
347+
initialConcentration = cai
348+
initialExtConcentration = cao
349+
rates()
350+
rates() : ensure internal variables initialised
351+
concentration = initialConcentration
352+
extConcentration = initialExtConcentration
353+
}
354+
355+
DERIVATIVE states {
356+
rates()
357+
concentration' = ( currDensCa /(2* Faraday * depth))
358+
- ((concentration - cainf) / taur)
359+
cai = concentration
360+
}
361+
362+
PROCEDURE rates() {
363+
surfaceArea = (1e-08) * area
364+
iCa = (1e6) * (-1 * ica * surfaceArea)
365+
currDensCa = iCa / surfaceArea : = -1e6 * ica
366+
}
367+
```
368+
369+
Here the true ODE state is `concentration`; `cai` is an alias that is updated each step from `concentration`. The NeuroML exporter also uses explicit `area` and a different choice of units for `depth` and `Faraday`.
370+
371+
---
372+
373+
## 2. Are the ODEs Numerically Equivalent?
374+
375+
Ignoring initialisation for the moment, and substituting the definitions in `rates()`:
376+
377+
- `currDensCa = iCa / surfaceArea = -1e6 * ica` (since `surfaceArea` cancels out),
378+
- so the NML ODE is:
379+
380+
\[
381+
\frac{d\,concentration}{dt}
382+
= \frac{-10^6}{2 \cdot Faraday \cdot depth} \, ica
383+
+ \frac{cainf - concentration}{\tau_r}
384+
\]
385+
386+
Using the default parameter values from the NML-exported MOD:
387+
388+
- `Faraday = 9.6488997e10 (pC/umol)`
389+
- `depth = 1.0e-4 (cm)` ( = 1 µm)
390+
391+
the effective coefficient in front of `ica` is:
392+
393+
\[
394+
k_{\text{NML}} = -\frac{10^6}{2 \cdot 9.6488997 \times 10^{10} \cdot 10^{-4}}
395+
\]
396+
397+
For the original `cadad.mod` with:
398+
399+
- `FARADAY = 96489 (coul)`
400+
- `depth = 1 (um)`
401+
402+
the coefficient is:
403+
404+
\[
405+
k_{\text{orig}} = -\frac{10000}{2 \cdot 96489 \cdot 1}
406+
\]
407+
408+
Numerically (see quick check in the repo):
409+
410+
- `k_orig ≈ -0.0518193784`
411+
- `k_NML ≈ -0.0518193800`
412+
- `k_NML / k_orig ≈ 1.00000003`
413+
414+
So up to floating-point rounding, **both mechanisms implement the same continuous-time ODE for Ca concentration**, provided the parameters are set as in:
415+
416+
- `run_neuron_full.py` for `cadad` (depth = 1.0 (µm)),
417+
- `run_neuron_full_nml_fix.py` for `cadad_RE_soma_nml` (depth = 1e-4 (cm), Faraday as above).
418+
419+
This means the source of the discrepancy is **not** a missing / extra factor of 10 or 10000 in the ODE itself.
420+
421+
---
422+
423+
## 3. The Real Difference: Initialisation of `cai` at t = 0
424+
425+
### 3.1 `cadad.mod` initialisation
426+
427+
For `cadad.mod` (see `x86_64/cadad.cpp`), the generated `nrn_init` does roughly:
428+
429+
1. Read the existing ionic Ca concentration into the mechanism:
430+
- `cai = _ion_cai`
431+
2. Call `initmodel()` (which implements the `INITIAL` block):
432+
- `cai = cainf`
433+
3. Write back to the ion:
434+
- `_ion_cai = cai`
435+
436+
Thus, **at `t = 0`, the pump forces `ca_ion.cai` to `cainf`**, regardless of what Python set `seg.cai` to before `finitialize`. In `run_neuron_full.py`:
437+
438+
```python
439+
for seg in soma:
440+
seg.cai = 5.0e-5
441+
seg.cao = 2.0
442+
...
443+
soma.insert("cadad")
444+
soma.depth_cadad = 1.0
445+
soma.taur_cadad = 5.0
446+
soma.cainf_cadad = 2.4e-4
447+
...
448+
h.finitialize(h.v_init)
449+
```
450+
451+
the `seg.cai = 5e-5` assignment is **overridden at initialisation**; the simulation actually starts from `cai = 2.4e-4` due to the `INITIAL { cai = cainf }` in `cadad.mod`.
452+
453+
### 3.2 Original `cadad_RE_soma_nml.mod` initialisation
454+
455+
Before our change, the NML-exported `INITIAL` block was:
456+
457+
```nmodl
458+
INITIAL {
459+
initialConcentration = cai
460+
initialExtConcentration = cao
461+
rates()
462+
rates() ? To ensure correct initialisation.
463+
464+
concentration = initialConcentration
465+
extConcentration = initialExtConcentration
466+
}
467+
```
468+
469+
The generated C++ `nrn_init` for `cadad_RE_soma_nml` (`x86_64/cadad_RE_soma_nml.cpp`) did:
470+
471+
1. Read the ionic concentrations:
472+
- `cai = _ion_cai`
473+
- `cao = _ion_cao`
474+
2. Call `initmodel()`:
475+
- `initialConcentration` and `concentration` are set based on the existing `cai` / `cao`,
476+
- **but `cai` itself is not modified** in the `INITIAL` block.
477+
3. Write back:
478+
- `_ion_cai = cai`
479+
480+
Therefore, unlike `cadad.mod`, the NML mechanism **did not override `cai` at `t = 0`**. It started with the Python-assigned value (`5e-5`), even though its internal state `concentration` was set consistently.
481+
482+
Combined with the fact that `run_neuron_full_nml_fix.py` explicitly sets:
483+
484+
```python
485+
for seg in soma:
486+
seg.cai = 5.0e-5
487+
seg.cao = 2.0
488+
...
489+
soma.insert("cadad_RE_soma")
490+
...
491+
soma.cainf_cadad_RE_soma = 0.00024
492+
...
493+
```
494+
495+
this means:
496+
497+
- **NRN native + `cadad`**: starts from `cai = 2.4e-4` (overrides 5e-5).
498+
- **NML-fix + `cadad_RE_soma_nml` (original)**: starts from `cai = 5e-5`, only later relaxing towards `cainf`.
499+
500+
Given the strong nonlinearity of T-type Ca currents and Ca-dependent dynamics, this different initial `cai` is enough to produce the observed divergence in the voltage traces when `cadad` is present, while the `noCadad` runs match almost exactly.
501+
502+
---
503+
504+
## 4. Fix: Make `cadad_RE_soma_nml` Initialise `cai` Like `cadad`
505+
506+
To align the behavior of the NML-exported mechanism with the original `cadad.mod`, we changed the `INITIAL` block in `mod/cadad_RE_soma_nml.mod` to explicitly set `cai` to `cainf` before setting up the internal state:
507+
508+
```nmodl
509+
INITIAL {
510+
: Align with cadad.mod: force cai to start at cainf
511+
cai = cainf
512+
513+
initialConcentration = cai
514+
initialExtConcentration = cao
515+
rates()
516+
rates() ? To ensure correct initialisation.
517+
518+
concentration = initialConcentration
519+
extConcentration = initialExtConcentration
520+
}
521+
```
522+
523+
Conceptually, this makes the two mechanisms agree on:
524+
525+
- The continuous-time ODE for Ca concentration (`dcai/dt`),
526+
- The effective scaling from `ica` to `dcai/dt` (via `depth`, `Faraday`, and unit conversions),
527+
- **And crucially, the initial condition `cai(t=0) = cainf`**, matching what `cadad.mod` has always done.
528+
529+
After recompiling the mechanisms (e.g. `nrnivmodl mod`) and rerunning:
530+
531+
- `run_neuron_full.py` (NRN native, `cadad`),
532+
- `run_neuron_full_nml_fix.py` (NML-fix, `cadad_RE_soma_nml`),
533+
534+
the Ca dynamics and voltage traces now agree up to numerical precision, just as they did when directly using `cadad.mod` in the NML pipeline.

0 commit comments

Comments
 (0)