Skip to content

Add model transformer for non-dimensionalisation #984

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

Open
7 tasks
clinssen opened this issue Nov 10, 2023 · 8 comments · May be fixed by #1217
Open
7 tasks

Add model transformer for non-dimensionalisation #984

clinssen opened this issue Nov 10, 2023 · 8 comments · May be fixed by #1217

Comments

@clinssen
Copy link
Contributor

clinssen commented Nov 10, 2023

Currently, physical units and their factors (nano-, micro-, kilo-, etc.) are stored in the model AST and converted by the printers during code generation.

I would suggest to replace this architecture with a transformer, that converts a model with quantities in terms of physical units, to a model containing only real-typed quantities (and int, bool, string, etc). Then, the printers and code generator do not have to deal with conversion factors.

  • A possible downside of this is that the units information is not available at all any more to the printers. So they would not be able to print the original units any longer, even in code comments. We would need to ensure there is a workaround for this; possibly an attribute inside the ASTVariable class.
  • Change NESTML grammar: needed for changing units into quantities? Not necessary, same syntax as physical units. However, can change the name of the rule to keep quantities and units conceptually seperate.
  • Create new ASTPhysicalQuantity class. Change the AST nodes involved: ASTDeclaration would have a physical quantity member instead of data_type member.
  • Change the parser (e.g. visitDeclaration() and visitDataType()) in ast_builder_visitor.py to plug in the quantity instead of the unit when creating a declaration.
  • Change the ASTExpressionTypeVisitor
  • Change the units for physical quantities also in NESTML function parameter types and return values.
  • Fix the units consistency testing (see e.g. OdeConsistentUnitsVisitor)

See also #608.

@clinssen
Copy link
Contributor Author

Suggestion from @heplesser: to ward against confusion between variable names and unit literals, NESTML should allow unit literals to appear only directly after numeric literals, for example, V' = 2 * V - V_L would have V as voltage, exp(-V / (1 V)) would have the voltage in the numerator, divided by 1 V to make it unitless.

@mwhy
Copy link

mwhy commented Apr 1, 2025

As discussed in Meeting of 01.04.2025:

A model_transformer should be implemented, that can convert the variable magnitudes according to their units simulation-target specific. For example, the hardware accelerators on SpiNNaker2 only support a 16.15 fixed point format.

In .nestml files variables should have their quantity symbol on the left hand side and their unit Symbol on the right.
Example:

from iaf_cond_alpha_neuron.nestml

    parameters:
        C_m C = 250 pF          # Membrane capacitance
        g_L G = 16.6667 nS      # Leak conductance
        E_L V = -70 mV          # Leak reversal potential (aka resting potential)
        refr_T t = 2 ms         # Duration of refractory period
        V_th V = -55 mV         # Spike threshold potential
        V_reset V = -60 mV      # Reset potential

        E_exc V = 0 mV          # Excitatory reversal potential
        E_inh V = -85 mV        # Inhibitory reversal potential
        tau_syn_exc t = 0.2 ms  # Synaptic time constant of excitatory synapse
        tau_syn_inh t = 2 ms    # Synaptic time constant of inhibitory synapse

        # constant external input current
        I_e I = 0 pA

This way it can be kept track of what type a variable is, e.g. electrical current, and what the magnitude is, e.g. pA.


A possibility to deal with the units is to handle them similarly to LEMS/NeuroML where every supported unit is described as a combination of the 7 base SI units needed but AstroPy already handles dimensional analysis so there is no real benefit in that. A list of the supported physical types(dimensions) and units can be found here.
I think it has to be decided wether or not the dimension symbol, e.g. "I", should be useable for the corresponding physical quantity, e.g. electrical current. As the same symbol is sometimes used for different units/quantities/dimensions confusion can be caused which is why a convention needs to be found. For example, "T" is the dimension symbol for time, however a capital t is also often used for temperature. On the other hand, "t" is often used for time.
If there is going to be a "short-hand" allowed instead of typing out the full name of the pysical type as used in AstroPy as for example "electrical resistance" (as can be read here) it will need to be stated clearly in the documentation that for example "R" is to be used in this case.

It seems like the IEC 60027 standard does regulate such symbols, however I am unable to find an open copy of the text, here is a link to the wikipedia page.

If such a short-hand should not be allowed, the grammar will need to be adjusted more than previously thought as the syntax will not be the same as for the units used at the moment. The advantage of using the full names as in Astropy would be that dimensional analysis should already be possible and it can be checked if the rhs of a calculation is in fact a unit of the physical type defined on the lhs of the equation.

tl;dr

I tink there are 3 options

  1. LHS includes short hand for physical type, RHS includes units
    
+ dimensional analysis can be used, check if resulting RHS is a unit of LHS type
    
+ Existing code structure doesn’t have to be changed that much
    
+ easy for user
    
- short hand has to be decided and defined in documentation as no consistent standard exists, e.g. t or T can mean a number of things

  2. LHS includes names as in AstroPy for physical type, RHS includes units
    
+ dimensional analysis can be used, check if resulting RHS is a unit of LHS type
    
+ no confusion about what is being meant by LHS symbol
    
- Existing code structure needs to be changed more, Grammar doesn’t expect physical types as words after variable name on LHS, e.g. ‘R electrical resistance = …’
- more complicated for user to type out full names of types

  3. LHS only includes variable name - no type, RHS includes units
    
+ easiest to do with existing code
    
- user can not be warned if resulting RHS unit not consistent with LHS type as not provided

@clinssen
Copy link
Contributor Author

clinssen commented Apr 1, 2025

The fundamental question is: what determines the representation format of the quantity, the (platform-agnostic) NESTML model, or the (platform-specific) code generator options?

Heretofore, it was always assumed (although not implemented correctly) that the unit on the left-hand side of the defining equation meant "this is the unit that the floating-number implicitly has", for instance:

state:
    x mV = 70 mV

would be represented as x = 70, whereas

state:
    x V = 70 mV

would be represented as x = 0.07.

However, these two forms are mathematically equivalent, and we tend to assume that the NESTML model describes a mathematically idealised reality. Instead, representational formats are platform-specific (e.g. float vs. 16.15 fixed-point on SpiNNaker-2).

So instead of having variants of iaf_psc_exp like iaf_psc_exp_spinnaker2_s1615.nestml, iaf_psc_exp_float.nestml etc., we would have one single model, and a specific code generator JSON options file for each individual target platform.

The left-hand side physical units in equations can then be replaced by a physical quantity.

@clinssen
Copy link
Contributor Author

clinssen commented Apr 1, 2025

This syntax is similar to the LEMS approach, see

See also [2] and [3].

[1] Cannon RC, Gleeson P, Crook S, Ganapathy G, Marin B, Piasini E and Silver RA (2014) LEMS: a language for expressing complex biological models in concise and hierarchical form and its use in underpinning NeuroML 2. Front. Neuroinform. 8:79. doi: 10.3389/fninf.2014.00079

[2] Ankur Sinha Padraig Gleeson Bóris Marin Salvador Dura-Bernal Sotirios Panagiotou Sharon Crook Matteo Cantarelli Robert C Cannon Andrew P Davison Harsha Gurnani Robin Angus Silver. (2025) The NeuroML ecosystem for standardized multi-scale modeling in neuroscience eLife 13:RP95135.

[3] https://docs.astropy.org/en/stable/units/index.html

@clinssen
Copy link
Contributor Author

clinssen commented Apr 2, 2025

In NESTML, we have a mathematical idealisation of the model, not caring about how it is represented, for instance, w real.

In NEST, we have, for instance:

  • synapse weight w in [-1..1]
  • represented as float

In SpiNNaker-2, we have, for instance:

  • the same synapse weight w in [-1..1]
  • represented as fixed point 1.4 (?) 4-bit plus sign

Do we write them both as stdp_synapse.nestml? Or is there a stdp_synapse_nest.nestml and also a stdp_synapse_spinnaker.nestml?

We prefer to have only one STDP synapse model. This is to be combined with a table of preferred units for each simulation platform, for instance (hypothetical):

NEST PREFERRED DATA REPRESENTATION

"little endian"

physical quantity      preferred prefix   representation

time                   m                  double
voltage                m                  double
conductance            n                  double


SPINNAKER DATA REPRESENTATION

"big endian"

physical quantity      preferred prefix   representation

time                   m                  S16.16
voltage                u                  S16.16
conductance            p                  S1.3

Then, the proposal for new NESTML syntax, is that the left-hand side has physical quantities instead of units, e.g.

 x V = 70 mV   # V for voltage
 C_m C = 250 pF   # C for capacitance 
 I_syn I = 42 pA   # I for current
 substance_amt n = 250 mmol   # n for "amount of substance"

What about notation -- for example, the following would probably give a parse error due to the space between "luminous" and "intensity":

foo luminous intensity / area = 42 cd/m**2

@clinssen
Copy link
Contributor Author

exp(-.07) = 32736182793 (some number)
exp(-70) = 90148088192 (some other number)
exp(-70 mV) = ??????? (undefined!)

So now let's say we have the expression exp(... V_m ... ) in the model and we only know of V_m that it has quantity "voltage".

-> exp(V_m) not allowed!
-> exp(V_m / 10 mV) ?????????? does it make sense? Do we need to know if V_m is in V or mV here?

@clinssen
Copy link
Contributor Author

Notes from Bernhard:

  • The representation of weights and delays might be different on the host and on SpiNNaker2. In the user interface, the weights should be provided as float (or double). The delays might be a float as well, but ultimately we have to turn this into multiples of time steps on SpiNNaker2, so this needs to be discretized at some point.
  • It is fine to store weights as float32 on SpiNNaker2 (double is not recommended, as the ARM core only has a 32-bit FPU and a 64-bit floating point would need to be emulated --> slow).
  • Delays need to be integers on SpiNNaker2, representing multiples of the simulation time step. As for each delay value, "input ring buffers" are needed for the receiving neurons, the number of delay values should not be too high. I would say max 4 delay bits (16 delay values). Note: as on SpiNNaker1, longer delays can be implemented through dedicated "delay neurons" which just repeat the spikes after a predefined number of timesteps.
  • If we want to keep the "Synapse Word structure", then I see the following options:
    1. 32-bit synapse word with 16-bit weight, where the 16-bit are either used for an integer, fixed point, or float16 number.
    2. 48-bit synapse word with 16bits for delay, type and target idx, and 32bits for floating point. (this might become a bit tricky or inefficient, due to the 32-bit memory alignment in the SRAM. but maybe this could be split in different "synapse sections" as done for plastic and static weights in spinnaker-1 (this is the recommended option)

@clinssen
Copy link
Contributor Author


    CASE 1:

        >>>>> in combination with preferred prefix for CONDUCTANCE = nano
        >>>>> in combination with preferred prefix for CURRENT = milli
        >>>>> in combination with preferred prefix for VOLTAGE = M (megavolt)

        I_foo A = 42 mA
        V_3 V = I_foo / 5 nS

            ---> we expect I_foo to be 42 mA
            ---> we expect "float I_foo" to be 42

            ---> we expect V_3 to be 8.4 MV
            ---> we expect "float V_3 = 8.4"


    CASE 2:

        >>>>> in combination with preferred prefix for CONDUCTANCE = nano
        >>>>> in combination with preferred prefix for CURRENT = 1 (ampere)
        >>>>> in combination with preferred prefix for VOLTAGE = M (megavolt)

        I_foo A = 42 mA
        V_3 V = I_foo / 5 nS

            ---> we expect I_foo to be 42 mA
            ---> we expect "float I_foo" to be 0.042

            ---> we expect V_3 to be 8.4 MV
            ---> we expect "float V_3 = 8.4"


Approach:

        For each variable and for each numeric literal: multiply by its preferred prefix; then result will be in SI units!

        V_3 V = I_foo / 5 nS
              = (42 * 1E-3) / (5 * 1E-9)   # in Volt
              = 8.4E6  # in Volt

        Then divide the whole thing by preferred prefix of left-hand side variable, in this case, Mega (1E6):

              = 8.4E6 / 1E6
              = 8.4

@clinssen clinssen linked a pull request May 21, 2025 that will close this issue
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants