Skip to content

Loop unroll visitor does not handle bare loop variables properly #3593

@JCGoran

Description

@JCGoran

Context

In NMODL we have statements like:

FROM i=0 TO N {
...
}

which gets unrolled in NMODL by the LoopUnrollVisitor.

Overview of the issue

If there is a bare loop variable somewhere in an if condition (such as if (i == 0)), NMODL does not unroll it properly. For instance, NMODL reports that this snippet from a KINETIC block:

 FROM i = 0 TO 2 {
    dsqvol = dsq*vol[i]*0.81
    dsqvoler = dsq*vol[i]*0.12
    jip3[i] = 0
    IF (i == 0) {
        ~ caip3ri << (dsqvol*jip3[0])
    }
    jcicr[i] = (vcicr*(ca[i]/(kcicr+ca[i]))*(caer[i]-ca[i]))
    ~ ca[i] << (dsqvol*jcicr[i])
    ~ caer[i] << (-dsqvoler*jcicr[i])
}

gets unrolled into:

{
    dsqvol = dsq*vol[0]*0.81
    dsqvoler = dsq*vol[0]*0.12
    jip3[0] = 0
    IF (i == 0) {
        ~ caip3ri << (dsqvol*jip3[0])
    }
    jcicr[0] = (vcicr*(ca[0]/(kcicr+ca[0]))*(caer[0]-ca[0]))
    ~ ca[0] << (dsqvol*jcicr[0])
    ~ caer[0] << (-dsqvoler*jcicr[0])
    dsqvol = dsq*vol[1]*0.81
    dsqvoler = dsq*vol[1]*0.12
    jip3[1] = 0
    IF (i == 0) {
        ~ caip3ri << (dsqvol*jip3[0])
    }
    jcicr[1] = (vcicr*(ca[1]/(kcicr+ca[1]))*(caer[1]-ca[1]))
    ~ ca[1] << (dsqvol*jcicr[1])
    ~ caer[1] << (-dsqvoler*jcicr[1])
    dsqvol = dsq*vol[2]*0.81
    dsqvoler = dsq*vol[2]*0.12
    jip3[2] = 0
    IF (i == 0) {
        ~ caip3ri << (dsqvol*jip3[0])
    }
    jcicr[2] = (vcicr*(ca[2]/(kcicr+ca[2]))*(caer[2]-ca[2]))
    ~ ca[2] << (dsqvol*jcicr[2])
    ~ caer[2] << (-dsqvoler*jcicr[2])
}

which generates broken C++ code since the i is not defined.

Expected result/behavior

It should generate correct code.

Minimal working example - MWE

Probably not minimal, but demonstrates the problem:

NEURON{
    SUFFIX cadyn_min
    USEION ca READ ica, cai WRITE cai VALENCE 2
    USEION caer READ caeri WRITE caeri VALENCE 2
    USEION caip3r READ caip3ri WRITE caip3ri VALENCE 2
    
    RANGE ktcicr, kcicr, vcicr
    GLOBAL vol
    THREADSAFE
}

DEFINE NANN 3

UNITS{
    PI      = (pi) (1)
}

PARAMETER{
    diam    (um)
    
    cai0    = 136e-6    (mM)
    caeri0  = 0.4       (mM)
    
    : CICR Parameters
    kcicr   = 0.00198   (mM)
    ktcicr  = 0.0006    (mM)
    vcicr   = 5e-7      (/ms)
}

ASSIGNED{
    ica         (mA/cm2)
    cai         (mM)
    caeri       (mM)
    vol[NANN]   (1)
    jip3[NANN]  (mM/ms)
    jcicr[NANN] (mM/ms)
}

STATE{
    ca[NANN]        (mM)     <1e-8>
    caer[NANN]      (mM)
    caip3ri         (mM)
}


INITIAL{
    
}

BREAKPOINT{
    SOLVE state METHOD sparse
    ica = 0  : Simplified - no actual current
}

LOCAL frat[NANN]

LOCAL dsq, dsqvol, dsqvoler

KINETIC state {
    COMPARTMENT ii, diam*diam*vol[ii]*0.81 {ca}
    COMPARTMENT     diam*diam*vol[0]*0.81 {caip3ri}
    COMPARTMENT jj, diam*diam*vol[jj]*0.12 {caer}

    dsq = diam*diam
     
    FROM i=0 TO NANN-1{
        dsqvol = dsq*vol[i]*0.81
        dsqvoler = dsq*vol[i]*0.12
        
        : Minimal jip3 for the snippet
        jip3[i] = 0
        
        if (i==0) {
            ~ caip3ri << (dsqvol*jip3[0])
        }
        jcicr[i] = (vcicr* (ca[i]/(kcicr+ca[i])) * (caer[i]-ca[i]) )
        ~ ca[i] << (dsqvol * jcicr[i])
        ~ caer[i] << (-dsqvoler * jcicr[i])
    }
    
    cai = ca[0]
    caeri = caer[0]
}

The above generates correct C++ code with NOCMODL (whether or not it does something stupid is not relevant, this was reduced from this mod file which works).

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions