Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 27 additions & 6 deletions RationaleMCP/0027/ReadMe.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Modelica Change Proposal MCP-0027<br/>Units of Literal Constants
Francesco Casella, Henrik Tidefelt
Francesco Casella, Henrik Tidefelt, Hans Olsson

**(In Development)**

Expand All @@ -11,24 +11,45 @@ The problem with undefined unit is that it gets in the way of carrying out check
| Date | Description |
| --- | --- |
| 2022-10-04 | Henrik Tidefelt. Filling this document with initial content. |
| 2025-12-18 | Hans Olsson, simple proposal https://github.com/modelica/ModelicaSpecification/issues/2127#issuecomment-349162852 |
| 2026-01-07 | Hans Olsson, improved - based on feedback |

## Contributor License Agreement
All authors of this MCP or their organizations have signed the "Modelica Contributor License Agreement".

## Rationale
FIXME
The basic rationale for using units is to reduce the risk of errors.

For the specific rules the rationale is that treating literals as having unit `"1"` in multiplicative contexts will catch many simple errors, without requriring excessive changes.
Thus e.g., `SI.Temperature T=293.15` and `SI.Enthalphy h=Medium.h_pT(1e5, 298.15)` are allowed.
In order to handle connectors in a good way, `zeros(n)` is treated the same as literals, allowing `a.f+b.f=zeros(3);` and `a.f=f0*zeros(3);`, but `a.f=T0*zeros(3);` (when `a.f` is a force and `T0` a temperature) is detected as an error.

The rationale for only giving the rules for variables instead of providing a specific implementation is to make the expectation clear for library authors.
And at the same time allow tool vendors to implement the rules to varying degrees (there is sufficient experience with the prototypes to ensure that it will work).

The rationale for considering implementing the rules to varying degrees, and even considering rules beyond the proposed ones is to ensure that they are consistent.
That means that rules are designed such that a model unit-consistent with the most restrictive rules will also be unit-consistent with less restrictive rules with a consistent subset of inferred units.

Many libraries, including the Modelica Standard Library already largely follow this rule.

## Backwards Compatibility
As current Modelica doesn't clearly reject some models with non-sensical combination of units, this MCP will break backwards compatibility by turning at least some of these invalid.

It is thus necessary to have the possibility to disable the rules for specific libraries (and specific equations in other libraries) to ease the adoption.

## Tool Implementation
None, so far.
For scalars implemented in some version of Wolfram System Modeler (to be given).
Almost fully implemented on a flag in Dymola 2026x, and 3D Experience platform.

### Experience with Prototype
N/A
Generally it correctly finds some issues in libraries, but some libraries have systematic issues.
E.g., the buildings library uses a large number of multiplicative literals without unit for converting between different time and power units.

## Required Patents
To the best of our knowledge, there are no patents that would conflict with the incorporation of this MCP.

## References
(None.)
## Details

[Design details](design.md)


130 changes: 130 additions & 0 deletions RationaleMCP/0027/design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
## References
https://doi.org/10.3384/ecp21817 and its references.

## Detailed rules

Unit restrictions for variables
- Each scalar variable and array element may only have one unit during the simulation.
- Arrays may have heterogenous units. Notes:
- This is for each instance of the variable, so different component instances and function calls (of the same model/function) may have different units.
- This applies for each value of the [evaluable parameters](#evaluable-parameter).
- The s-parametrization needed for diodes and friction requires special work-arounds in models.

General rules

- Expressions (including equations, binding equations, and start values) must be unit consistent, except for listed exceptions, and can be used to infer units.
- If a variable has a non-empty unit-attribute that is the unit of the variable. The unit-attributes should preferable be in base SI-units.
- Variables that are declared without unit-attribute (or with empty one) have unspecified unit, which may be inferred if there is a unique unit that makes the expressions unit consistent.

Detailed default rules:

- Literals without unit and zeros() are treated as empty unit except in multiplicative context (multiplication and division operators) where it has multiplicative-unit with the following rules:
- If both operands have multiplicative-unit the result has multiplicative-unit.
- If one operand has multiplicative-unit and the other not, the multiplicative-unit decays to unit `"1"`.
- There's a future refinement for the literal 0 (both real and integer) and `zeros()` in arrays see [advanced arrays](#advanced-array-handling) for details.
- If a constant is declared without unit, and with a binding equation that lacks units (even after inference) the constant is treated as empty unit. (This is primarily for package constants, where we don't want to infer a unit for `pi` and use it at unrelated places; but that also applies in models.)
- An expression having empty unit will match any constraint, and inference will not give it a unit.
- The rules for operands are fairly logical, but see appendix A in https://doi.org/10.3384/ecp21817 for the details.

## Arrays

Arrays with heterogenous units are somewhat rare but needed for state-space forms etc, and for some connectors in the electrical library.

Without scalarizing arrays there are a number of options:
- Ignore units for arrays.
- Only infer units for elements, and arrays that are inferred/declared to be homogenous. (Proposed here.)
- Something more advanced. (Not proposed as it hasn't been tested.)

This simple array handling implies that unit-handling is consistent between MultiBody mechanics and Rotational/Translational mechanics.

### Advanced array handling

The advanced array handling is not included yet as part of the proposal as it is not fully clear and not test-implemented, even if models ideally should fulfill something along these lines.

It is included in this document, because there are some non-trivial issues if tools were to support this.
Based on the experience with existing models it will likely infer units for a number of variables, but find few, if any, new errors.

One considerations is whether the arrays are just arrays or have more structure.
Many (likely most) arrays are used as vectors, matrices, etc in the linear algebra sense, but not all.
E.g., Modelica.Blocks.Tables.CombiTable2Ds has a table where the first row and column effectively has different units from the rest of the table.
It seems that tools could identify whether a 2d-array is used as a matrix (or even bilinear form) based on the equations, i.e. `A*x` imply that `A` is a matrix, and `x*A*x` that it is a bilinear form.

The changes needed to support a more advanced array handling would be something like:
- Arrays built using `cat`, `[,;]` should (at least in some cases) support heterogenous inputs giving heterogenous unit-results, replacing parts of appendix A of https://doi.org/10.3384/ecp21817
- Literal 0 (both real and integer) and `zeros()` inside arrays should be treated as the empty unit, and not decay to unit `"1"` as other literals without unit.
- If something is used as a matrix its units are restricted (it must be representable as an outer product); as noted above.
- Potentially more.

The reason for the second rule can be seen from considering structured matrices in equations.
E.g., a simple state-space system without direct terms:
`[der(x);y]=[A,B;C,zeros(...)]*[x;u]`
(and correspondingly with a literal 0 if it has a single input and a single output).
In text books those zeros are often omitted, but that is not allowed in Modelica.

Basically the zeros are seen as structural zeros and one would expand it as `y=C*x` (not `y=C*x+zeros(...)*u`), imposing no unit-constraint between `u` and `y`.
In contrast for `f1+f2=0*f1` it seems natural to have unit `"1"` for the literal, even if it is unlikely that someone makes a mistake for that equation.
And if one writes `[der(x);y]=[A,B;C,1]*[x;u]` then `u` and `y` should be scalars (or vectors of length 1) that both have the same unit.

This also apply to the literal zeros in `diagonal()` and `skew()`, they are seen as having empty unit - not impacting the result.

## Evaluable parameter

The reason it applies for each value of the evaluable parameters is to make it sufficiently general to handle even models where evaluable parameters switch between different unit-configurations.
In practice the handling will depend on whether the parameter has been evaluated or not.

### Evaluated evaluable parameter

This is the simpler case, but still requires care, since expanding and evaluating expressions is normally mixed in tools.
For practical reasons one wants to perform the unit check on the original non-expanded expressions, where the evaluable parameters were not yet evaluated.

This can be handled in various ways:
- Simplifying the original expressions based on the values of evaluable parameters. (In some sense this will be a form of double-work.)
- Having conditional constraints as in https://github.com/modelica/ModelicaSpecification/pull/3491.

### Non-evaluated evaluable parameter

This is a particular concern.
Tools should avoid having the unit-handling (except for the unit-attribute) cause evaluation of evaluable parameters.
Note that it doesn't suffice to separate parameters in evaluated and non-evaluated as different tools may evaluate different evaluable parameters with different result for unit consistency.

However, in many cases it does not matter, e.g., an evaluable mass-parameter will have unit `"kg"` regardless of its value, and many boolean evaluable conditions don't influence the units.

When it does matter (in particular for boolean conditions) it's a quality-of-implementation issue for tools to handle it in a good way, and possibilities include:
- Ignore the unit-constraints in such expressions. (Not good.)
- Temporarily evaluate the evaluable parameters, without impacting the translation. (This will only check the model for one set of values.)
- Treat them as conditional constraint in some advanced way. (This is more advanced that the conditional constraints in https://github.com/modelica/ModelicaSpecification/pull/3491 )

## Varying quality-of-implementation

These are just rules for models, and doesn't require tools to diagnose all issues in models.

The rules are compatible with:
- The traditional unit inference in Dymola (Mattsson&Elmqvist https://modelica.org/events/conference2008/sessions/session1a2.pdf )
- Hindley-Milner for scalars (https://github.com/modelica/ModelicaSpecification/pull/3491)
- The advanced combination(s) thereof in https://doi.org/10.3384/ecp21817

They are seen as different quality-of-implementations, but we could recommend a minimum for tools.

The unit-handling doesn't prioritize different operands (in contrast to selecting initial conditions and states), since that might give different results for the different levels for unit-consistent models.
For unit-inconsistent models it *possible* that using different set of rules will infer different units without detecting errors; the solution is to improve the quality-of-implementation to detect the underlying error in that case.
Additionally, this only occurs the different rules are not sub-sets of each other, so having a common understanding of hieararchy of the rules may reduce this risk.

## Notes

The rules for literals break the substitution principles for equality, so even if `A=[1,2;3,4]` it doesn't follow that `A` and `[1,2;3,4]` behave the same in terms of units.

It says:
- "default rules" to allow allow stricter or less strict variants, e.g., as described in https://github.com/modelica/ModelicaSpecification/issues/3690
- "except for the listed exceptions" to allow exceptions for specific equations etc https://github.com/modelica/ModelicaSpecification/issues/3690#issuecomment-2866443687
- "multiplicative context", but it is for both multiplication and division (the division explains why the multiplicative-unit decays to `"1"` instead of just using the other one).

Treating the empty unit-attribute as unspecified is needed, since it is the default - but it normally doesn't make sense to explicitly give `unit=""` for a variable declaration.
If the goal is just to remove an existing unit an alternative is `unit=break`.

Specific exceptions for equations, and libraries, should preferably be added to the proposal.

The restriction that variables only having one unit could be violated in different ways in models:
- Temporaries in algorithms in functions (and even models) may be re-used to store expressions with different units.
- The s-parametrization is an example of a variable that switches e.g., from voltage to current.

For s-parametrization one solution is to divide out the unit and generate an expression with unit `"1"` and store that.