Skip to content

Avoid that Constants.pi gets the unit "rad"#4774

Open
henrikt-ma wants to merge 1 commit intomodelica:masterfrom
henrikt-ma:pi-unit
Open

Avoid that Constants.pi gets the unit "rad"#4774
henrikt-ma wants to merge 1 commit intomodelica:masterfrom
henrikt-ma:pi-unit

Conversation

@henrikt-ma
Copy link
Copy Markdown
Contributor

@henrikt-ma henrikt-ma commented Apr 21, 2026

The current definition of Modelica.Constants.pi gives it the unit "rad", but based on the ongoing discussions about standardizing a definition of unit consistency in the Modelica specification, I find it clear that pi should have "empty" unit, making the constant behave just like a numeric literal.

For example, the following is expected to be allowed if pi has empty unit, but not if it has unit "rad":

Modelica.Units.SI.Time piTime = Modelica.Constants.pi "π seconds";

With the unit "rad", the workaround would be to insert a conversion factor,

Real conversionFactor(unit = "s/rad") = 1;
Modelica.Units.SI.Time piTime = conversionFactor * Modelica.Constants.pi "π seconds";

but I have a clear impression that our community wouldn't like to have to do it this way (even though it would be much more convenient once the factor can be given in-line as 1's/rad').

In this PR, the change for pi avoids that it gets the unit "rad", and e is updated to keep the two definitions consistent.

The change for 'pi' avoids that it gets the unit "rad", and 'e' is updated to keep the two definitions consistent.
@henrikt-ma
Copy link
Copy Markdown
Contributor Author

By the way, I am intentionally avoiding the question of what units to have on the interface of Modelica.Math.asin. In SystemModeler, for comparison, we are providing the experimental function SystemModelerExtras.Experimental.Units.Radian.asin with unit "1" on the input and unit "rad" on the output, for those who prefer to do their trigonometry with units.

@AHaumer
Copy link
Copy Markdown
Contributor

AHaumer commented Apr 21, 2026

In my understand, "rad" is a pseudo unit as good as "1", just declaring "This is an angle, not a dimensionless ratio.".
With that point of view, "rad" is a helpful reminder.

Copy link
Copy Markdown
Contributor

@HansOlsson HansOlsson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would argue that pi logically should have unit "rad", e.g., when you call sin(2*pi*f*time) it makes sense that f is in Hz and time in seconds, so that pi ensures that the argument has unit "rad".

Obviously there are cases where radians for pi are a bit confusing.

That's why it is important BIPM states that radians exist (for clarity), but it is expressed as having unit "1" in base units.

Additionally, for a user seeing these lines it doesn't seem clear that asin instead of Modelica.Math.asin has such an interpretation. I would even say that the built-in asin should behave the same as Modelica.Math.asin in this respect, so in that sense it seems like a non-change.

@henrikt-ma
Copy link
Copy Markdown
Contributor Author

In my understand, "rad" is a pseudo unit as good as "1", just declaring "This is an angle, not a dimensionless ratio.". With that point of view, "rad" is a helpful reminder.

Yes, I'd phrase it as "rad" being an alias for "1", which sounds more welcoming than calling it a "pseudo unit". However, this misses the point of this PR.

The point of this PR has to do with the sort of unit inconsistency you see here:

  Modelica.Units.SI.DimensionlessRatio x = 5; // x has unit "1"
  Modelica.Units.SI.Time t = x; // Inconsistent units!
  Modelica.Units.SI.Diameter d = x; // Inconsistent units!

If we want to use x in the declaration equations like that, it needs to come with eithI per empty unit (since it can't be consistent with both "s" and "m"). The current design idea for unit checking in Modelica is that we can make x a constant with empty unit:

  constant Real x = 5; // x has empty unit

Applied to pi, there are many places where π is just a number. I got the following examples from Claude:

  • The Basel problem (∑1/n² = π²/6)
  • The Gaussian integral
  • Stirling's approximation
  • The volume of n-balls

Add to that the desire to easily use pi in Modelica empty unit expressions like in the example with x above.

To make all this work nicely, we need pi to have empty unit and just let it decay to "1" in the places where one wants to think of it as having "rad", thanks to "1" and "rad" being aliases in SI.

When speaking about angles, I personally prefer saying:

π radians is the same as 180°

not this:

π is the same as 180°

Then over to @HansOlsson's argument:

I would argue that pi logically should have unit "rad", e.g., when you call sin(2pif*time) it makes sense that f is in Hz and time in seconds, so that pi ensures that the argument has unit "rad".

Well, as long as "rad" is an alias for "1" there is no difference to ensuring the argument has unit "1". Thus, with pi having empty unit you would get the same amount of unit safety in this expression as when it would have unit "rad" (or "1" for that matter); in both cases it will be detected when the unit if f is not compatible with "1/s". Anyway, the ambiguity of how to interpret Hz is a much too big question to be solved here. For now I think we should just be happy that SI avoids most of these tricky issues by making "rad" an alias for "1".

@HansOlsson
Copy link
Copy Markdown
Contributor

Then over to @HansOlsson's argument:

I would argue that pi logically should have unit "rad", e.g., when you call sin(2_pi_f*time) it makes sense that f is in Hz and time in seconds, so that pi ensures that the argument has unit "rad".

Well, as long as "rad" is an alias for "1" there is no difference to ensuring the argument has unit "1".

Exactly. However, the exact handling of "rad" is a bit complicated, and it seems more discussion is needed.

I see two ways of handling "rad" in unit-checks:

  • Just use "rad" as an alias for "1".
  • Have "rad" work slightly different than "1", but decaying to it in some cases. (Dymola has implemented this since a long time - it kind of works, and is one of the reasons the FMI-standard has "rad" as a base-unit.)

I don't see that this PR gives any benefit with either alternative. Additionally, it relies on the yet-to-be-specified semantics of the built-in function asin instead of the MSL function with the same name. In my opinion they should work the same.

The problem I see with having pi treated similarly as a numeric literal is that it would seem to suggest that similarly as SI.Length len1=12; one could use SI.Length len2=pi;, and I don't see that the latter is desirable.

Basically I see three possible units for pi:

  • "rad"
  • "1"
  • empty unit similarly as numeric literal.

and my preference is clearly for the first one, with the second one as the second best.

@henrikt-ma
Copy link
Copy Markdown
Contributor Author

I don't see that this PR gives any benefit with either alternative. Additionally, it relies on the yet-to-be-specified semantics of the built-in function asin instead of the MSL function with the same name. In my opinion they should work the same.

In the future, they might be made the same simply by removing all units from the interface of Math.asin, but as we all know we have some serious work to do for unit propagation through function calls before this can become reality. Until then, we need to make sure that at least the language function's unit design is clever enough.

One thing I find necessary that the language function supports is to take part in empty unit expressions, meaning that asin(1.0) should be usable anywhere an empty unit expression is allowed, for example when defining a constant that shall behave as an empty unit expression where referenced.

@henrikt-ma
Copy link
Copy Markdown
Contributor Author

The problem I see with having pi treated similarly as a numeric literal is that it would seem to suggest that similarly as SI.Length len1=12; one could use SI.Length len2=pi;, and I don't see that the latter is desirable.

If we think about the other uses of pi that has nothing to do with angles, I don't find it strange that it can also be used instead of 3.14 to initialize a peculiar length. Yes, it is peculiar, but not particularly problematic. You can also initialize the length to other peculiar values such as $e$ given by exp(1.0).

@henrikt-ma
Copy link
Copy Markdown
Contributor Author

henrikt-ma commented Apr 22, 2026

Maybe some of the disagreement could be resolved by also having a constant for one revolution:

final constant SI.Angle rev = 2 * pi "Angle corresponding to 1 revolution";

Then,

Real angle = f * rev * time; // Unit is "rad"
Real area = r^2 * pi / 2; // Unit is "m2", not "m2.rad"

@mestinso
Copy link
Copy Markdown
Collaborator

mestinso commented Apr 22, 2026

I'm firmly in the camp that pi should have unit="1", which was the original change made. I had thought that the plan was to revert to that once the implied unit issues throughout the library were resolved.

@henrikt-ma I'm also aligned with your last comment or at least the intent of your comment regarding how we could connect pi and angles.

And regarding this PR specifically: I don't have a problem wth it and would gladly approve if others see value, but I see it as a half measure. Instead, I think the unit for pi should be set back to 1.

@HansOlsson
Copy link
Copy Markdown
Contributor

If we think about the other uses of pi that has nothing to do with angles, I don't find it strange that it can also be used instead of 3.14 to initialize a peculiar length. Yes, it is peculiar, but not particularly problematic. You can also initialize the length to other peculiar values such as e given by exp(1.0).

You can - but it could also indicate that something is missing, and to me unit checking is intended to catch such issues (you might still do it - but then with some extra work arounds to indicate that you know what you are doing).

Consider an actual use case , Modelica.Electrical.Analog.Examples.SeriesResonance which has:

  Basic.Inductor inductor2(i(fixed=true), L=0.1/(2*pi))
...
  Basic.Capacitor capacitor2(v(fixed=true), C=0.001/(2*pi))

Using pi in that way can cause unit-issues (the goal is that SI.Frequency f0=1/(2*pi*sqrt(L*C)); corresponds to the 100Hz resonance frequency), but to me the solution is to update the calculations in some way instead of just viewing pi as having the empty unit in general.

Similarly for Modelica.Electrical.Analog.Examples.CauerLowPassAnalog that didn't/doesn't have pi, but had similar odd computations:

  parameter SI.Inductance l1=1.304 "Filter coefficient I1";
  ...
  parameter SI.Capacitance c2=1/(1.704992^2*l1)

@HansOlsson
Copy link
Copy Markdown
Contributor

HansOlsson commented Apr 22, 2026

I'm firmly in the camp that pi should have unit="1", which was the original change made. I had thought that the plan was to revert to that once the implied unit issues throughout the library were resolved.

Yes, and it is more than 2 years since #4191 was merged with that hope, and it's a pity that we haven't progressed further.

Updated: missed negation.

@henrikt-ma
Copy link
Copy Markdown
Contributor Author

Basic.Inductor inductor2(i(fixed=true), L=0.1/(2pi))
...
Basic.Capacitor capacitor2(v(fixed=true), C=0.001/(2
pi))

Let's try the obvious:

  SI.Inductance L = 0.1'H' / (2 * pi);
  SI.Capacitance C = 0.001'F' / (2 * pi);
  SI.Frequency f0 = 1 / (2 * pi * sqrt(L * C));

You can have any power of "rad" in the unit of pi here; it will just cancel out in the unit of the f0 binding. That is, "1" is just as good as "rad" when it comes to these equations.

However, the effect of setting unit "1" for pi, as opposed to having empty unit, is primarily that any expression including a factor of pi can no longer be an empty unit expression. It will force the entire expression to suddenly have a consistent concrete unit. There can be many reasons for enforcing unit checking of an expression, but including a factor of π should not be one of them. For expressions where there already are units, the unit "1" or empty unit will make no difference.

@henrikt-ma
Copy link
Copy Markdown
Contributor Author

henrikt-ma commented Apr 22, 2026

I'm firmly in the camp that pi should have unit="1", which was the original change made. I had thought that the plan was to revert to that once the implied unit issues throughout the library were resolved.

Since then, I think we have developed a better idea for how units of constants should behave. The idea is that a constant with empty unit shall behave just like a numeric literal; there should be just as little "wild-card effect" when using the factor pi as when using the factor 3.14. An important part of the design is that the unit of a constant should only be determined as the unit of its binding expression (note that all constants must have a binding expression, so the constants can be sorted and unit-checked one after another); when the binding expression has empty unit, the constant gets the empty unit too, and will not get a unit inferred from how the constant is being used.

@AHaumer
Copy link
Copy Markdown
Contributor

AHaumer commented Apr 22, 2026

Just my 5 cent:

  SI.Inductance L = 0.1'H' / (2 * pi);
  SI.Capacitance C = 0.001'F' / (2 * pi);

This is a misleading formulation - I would avoid that.

  Basic.Inductor inductor2(i(fixed=true), L=0.1/(2*pi));
  Basic.Capacitor capacitor2(v(fixed=true), C=0.001/(2*pi));

This is really a bad example, frequency is missing. it should read as:

  parameter SI.Frequency f=1;
  Basic.Inductor inductor2(i(fixed=true), L=0.1/(2*pi*f));
  Basic.Capacitor capacitor2(v(fixed=true), C=0.001/(2*pi*f));

@henrikt-ma
Copy link
Copy Markdown
Contributor Author

Just my 5 cent:

Your input is appreciated.

This is really a bad example, frequency is missing. it should read as:

  parameter SI.Frequency f=1;
  Basic.Inductor inductor2(i(fixed=true), L=0.1/(2*pi*f));
  Basic.Capacitor capacitor2(v(fixed=true), C=0.001/(2*pi*f));

That won't work out of the box since regardless of what of the unit alternatives we pick for pi, the f turns the expressions into unit business, and then the entire bindings need to have unit consistent with the modified components.

Anyway, I can see that 2 * pi * f looks like it is meant to be angular frequency, so it would be neat if it would have the unit "rad/s" and not just "1/s". However, in my mind the 2 * pi stands for "2π radians", and the introduction of the radian factor is not clearly seen in the expression. Since a syntax for attaching the unit "rad" to pi on the fly is not on the table at the moment (in System Modeler we would do it with the cumbersome operator form withUnit(pi, "rad")), this brings me back to using a more clear constant (trying a different name than rev this time):

final constant SI.Angle two_pi_rad = 2 * pi "Angle corresponding to 1 revolution";

So, two_pi_rad * f would clearly be an angular frequency, and unless we want to introduce more factors in the expressions, we end up with this to make units consistent:

L = 0.1'H.rad/s' / (two_pi_rad * f)

That looks strange to me, but maybe it somehow makes sense to you?

@mestinso
Copy link
Copy Markdown
Collaborator

ok, I'll bite.

So, looking at these recent examples, I want to rewind slightly to an upstream equation here that is connected to the equation that has been discussed.

So for inductors we have
$V = L \frac{dI}{dt}$
By all measures, this is dimensionally consistent, no explanation needed.

But now, for sinusoidal current, we can derive the relationship for inductive reactive:
$X_l = \omega L$
I won't show the derivation, but take note that in the process, we differentiated a sine function (we'll come back to this).

Looking at the units, and if we take units for angle seriously, we have
$[X_l] = \ohm$
$[L] = \ohm s$
$[\omega] = rad/s$

But wait, this apparently isn't dimensionally consistent anymore (Assuming we care about the angle units). What happened? Where did this $rad$ come from, and why isn't it cancelling out?

Following the logic in this article: https://iopscience.iop.org/article/10.1088/1681-7575/ac7bc2
... the answer is that the full equation for inductive reactance is actually
$X_l = C \omega L$
where $C$ has units and shows up when we differentiate a sine function. $C$ is unity but with units equal to $1/[rad]$. Additionally, this is actually fully generic for any angle measurements, and radians aren't special except for the fact that radians give a unity correction.
for frequency
$X_l = C f L$ and $C = 2 \pi / [Hz s]$
for degrees
$X_l = C \omega_{deg} L$ and $C = 2 \pi / 360 [deg]$

So we can see that we are dimensionally correct again (regardless of angle choice).

To conclude, you can see that it's not $\pi$ that we need to stick units on. Rather, it's that the equation itself has this $C$ term that shows up when we differentiate a sine or cosine function. This is not rigorously included 99.9% of the time. But if you follow the derivation and logic, you can see it resolves any angle unit inconsistencies.

Now, of course, every equation with angle units has a different story and way angle units are balanced out, but this is the story for this one.

@HansOlsson
Copy link
Copy Markdown
Contributor

This is really a bad example, frequency is missing. it should read as:

  parameter SI.Frequency f=1;
  Basic.Inductor inductor2(i(fixed=true), L=0.1/(2*pi*f));
  Basic.Capacitor capacitor2(v(fixed=true), C=0.001/(2*pi*f));

As noted above it is more that $f=1/(2\cdot\pi\cdot\sqrt{L \cdot C})$, so $(L 2 \pi)\cdot(C 2 \pi)=1/f^2$ or $C=1/f^2/(L 2 \pi)/(2 \pi)$.

There's clearly one degree of freedom here, i.e,, as long as the product of L and C stays the same it doesn't matter for the frequency. Whether that freedom is just used for setting L, C, or the ratio between them just depends.

If we use the square root of the ratio as the degree of freedom the formula is (unless I made some mistake):

  parameter SI.Frequency f=100 "As in problem description";
  parameter SI.Resistance R=10 "Not a real resistance or?";
  Basic.Inductor inductor2(i(fixed=true), L=R/(2*pi*f));
  Basic.Capacitor capacitor2(v(fixed=true), C=1/(R*2*pi*f));

Nice symmetry - I would say.
The problems are that it takes some effort to find it; and I'm not sure if it is better for users.

And as far as I understand we have understood that radians are useful in formulas (and pi sort of has that unit), but technically is the same as unit "1"; so we want to allow Area=pi*radius^2, but detect errors for Area=pi*radius and Area=pi .

@HansOlsson
Copy link
Copy Markdown
Contributor

For example, the following is expected to be allowed if pi has empty unit, but not if it has unit "rad":

Modelica.Units.SI.Time piTime = Modelica.Constants.pi "π seconds";

Thus, the relevant question is: is allowing that a good thing?

Consider the following model:

model Unnamed
  Real x(unit="1", start=1);
  Real v;
  parameter Modelica.Units.SI.Time timeScale=0.1;
  final parameter Modelica.Units.SI.Time onePeriod=(2*Modelica.Constants.pi*timeScale);
equation 
  der(x)=v/timeScale;
  der(v)=-x/timeScale;
  when time>=onePeriod then
    terminate("1 oscillation");
  end when;
end Unnamed;

(Should likely rewrite in some more advanced way, and have a better unit for x and v.)
And possibly change timeScale to 1.0 - you might have to change the stopTime.

After changing it to 1.0, one could skip the time-scale in both the equations and in the computation of onePeriod, making it look similar to piTime - but to me the unit handling is intended to find both that it was missing in the equations and in the computations of onePeriod. I'm fully aware that there are simplified models that fail that (cp. the original Modelica.Media.Examples.IdealGasH2O). That is not related to pi, but to people caring more about simplicity than about unit-consistency.

Note that I deliberately introduced it as timeScale - not TimeUnit; as it isn't intended to add a unit-factor "s" - but to correspond to an actual time-scale (or with a clearer name that captures the 2*pi part).

@casella
Copy link
Copy Markdown
Contributor

casella commented Apr 23, 2026

I'll repeat here what I already posted somewhere else in similar form, but can't find right now. Apologies for any duplicate content and for the long post.

1. Definition of pi

According to Wikipedia and to any mathematics textbook I know of, $\pi$ is defined as the ratio of a circle's circumference to its diameter. Both the circumference and its diameter are undoubtedly lengths. Whatever unit they are measured with, the ratio of two lengths is a nondimensional quantity in per unit. Hence, IMHO the unit of $\pi$ should be "1".

Unfortunately, this is far from settling the matter, see below.

2 The unit Entscheidungsproblem

I may be mistaken, but I am afraid that with units in Modelica 3.8 we are repeating David Hilbert's mistake. Hilbert (one of the greatest mathematicians of all time, mind you) had a grand plan to completely formalize mathematics. In particular, he argued that the Entscheidungsproblem could be solved mechanically, by an algorithm. Until Alonso Church and Alan Turing proved this was not possible. I'm of course not even remotely as smart as Church or Turing, but I'd like to try doing the same in this context 😅

My fundamental argument is that, when considering physical equations, context matters. Unfortunately, context is not completely formalized: a Modelica compiler does not understand what equations actually mean and where they come from. This is a big deal.

2.1 An example problem, which formalizes requirements on dimensional consistency checking

Consider the following Modelica code. It contains some basic equations from mechanics, which are correct and thus also dimensionally consistent, as well as some equations which are not dimensionally consistent, so they are for sure wrong and ought to be rejected by a unit-conscious compiler.

Some equations are redundant, but that's not an issue here, nor is the variability of the variables (constants vs. parameters vs. time-varying variables). The only thing we care about in this model is the dimensional consistency of the equations, not their solvability.

model Examples
  constant Real pi =  3.141592653589793;
  Real D(unit = "m") "Diameter of the wheel";
  Real p(unit = "m") "Perimeter of the wheel";
  Real r(unit = "m") "Radius of the wheel";
  Real phi(unit = "rad") "Rotation angle of the wheel";
  Real phi_t(unit = "rad") "Target rotation angle of the wheel";
  Real w(unit = "rad/s") "Angular velocity of the wheel";
  Real T(unit = "s") "Period of rotation of the wheel";
  Real f(unit = "Hz") "Frequency of rotation of the wheel";
  Real L(unit = "m") "Distance travelled by the wheel";
  Real v(unit = "m/s") "Translational velocity of the wheel";
  Real J(unit = "kg.m2") "Moment of inertia of the wheel";
  Real M(unit = "kg") "Mass of the wheel";
  Real tau(unit = "N.m") "Torque applied to the wheel";
  Real E(unit = "J") "Mechanical energy of the wheel";
  Real P(unit = "W") "Power applied to the wheel";
  Real pi;
equation
  // Dimensionally consistent equations, should be accepted
  der(phi) = omega;  // (1)
  der(phi) = 2*pi*f ;  // (2)
  der(L) = pi*D*f;  // (3)
  J*der(w) = tau; // (4)
  der(E) = P;  // (5)
  p = pi*D;  // (6)
  p = 2*pi*R;  // (7)
  D = 2*r;  // (8)
  f = 1/T;  // (9)
  L = phi*r;  // (10)
  E = J*omega^2/2 + M*v^2/2; // (11)
  P = omega*tau; // (12)
  phi_t = pi; // (13)

  // Dimensionally inconsistent equations, should be rejected
  der(phi) = 2*pi*f^2; // (14)
  p = pi*D^2;  // (15)
  p = 2*pi*R^2;  // (16)
  D = 2*r^2;  // (17)
  f = 1/T^2;  // (18)
  L = phi*r^2;  // (19)
  der(E) = omega;  // (20)
end Examples;

Whatever decision we take on the unit of $\pi$ and on how we handle units in Modelica in the future, my requirement as a modeller is that equations (1)-(13) should be accepted, since that is what any first-year engineering student would write, straight out of a physics textbook, whereas equations (14)-(20) should be rejected, because they are dimensionally inconsistent and thus they are for certain wrong.

I assume we can all agree about this requirement. If not, then I would suggest to discuss this matter separatly and settle it down first, because that's a pre-requisite for any further discussion.

2.2 What is the unit of $\pi$?

In this ticket, I read arguments in favour of:

  1. constant Real pi = 3.141592653589793 "unitless";
  2. constant Real pi(unit = "1") = 3.141592653589793 "dimensionless, per-unit";
  3. constant Real pi(unit = "rad") = 3.141592653589793 "pi is a flat angle, so radians";

As I understand, unfortunately none of these choices is acceptable, because each one leads to bad consequences.

  • If we choose 1., then pi becomes a wildcard, so (15) will not be rejected. As to (16), it will not be rejected as well, though this issue compounds with the status of literal constants MCP-0027 Units of Literal Constants ModelicaSpecification#2127, which is still not settled.
  • If we choose 2., (13) will be rejected, because the LHS has unit "rad" and the RHS has unit "1"
  • If we choose 3., (10) will be rejected, because the LHS has unit "m" and the RHS has unit "rad.m"

The crucial point here is that there is no single choice of units for $\pi$ that can make both (10) and (13) unit-consistent. Please let that sink in. There's nothing wrong with equations (10) and (13): one is basic kinematics and another one is just setting a threshold angle to $\pi$. Unfortunately, deciding about their correctness is only possible if you consider what these equations mean. That information is not included in the unit strings. Maybe it's a good job for AI, if you are willing to accept the occasional hallucination (I don't, for the record).

The only way out of this quagmire that I can see is to consider both "rad" and "1" as dimensionless units, so they are effectively equivalent when doing dimensional consistency checks, not unit checking. If we do so, then both 2. and 3. ar acceptable, though I'm slightly in favour of 2. because of how $\pi$ is defined. 1. remains unacceptable because it allows clearly invalid equations to be accepted.

2.3 Is it possible to automatically decide whether a Modelica equation is dimensionally consistent or not?

As far as I understand, by looking at my Examples above, this is indeed possible, but only if:

  • we consider "rad" and "1", as well as "Hz" and "s-1", as interchangeable
  • we define $\pi$ to either have unit "1" or "rad"
  • we find a means to make sure that the factor 2 is considered as a non-dimensional factor, not as a wildcard

2.4 Is it possible to automatically decide whether a Modelica equation is unit-consistent?

This task is trickier than just determining dimensional consistency. Consider this example

model Examples2
  Real Ltot(unit = "m") "Total length";
  Real L1(unit = "m") "Length of first part";
  Real alpha(unit ="1") "Fraction of part to total, in p.u.";
  Real beta(unit = "%") "Fraction of part to total in p.u.";
equation
  L = alpha*Ltot; // (1)
  L = beta*Ltot // (2)
end Examples2;

Equation (1) is correct, and in fact has units "m" and "m.1" = "m" on both sides, whereas equation (2) is wrong, and in fact it has unit "m" on the LHS and unit "m.%" on the RHS, so there is a factor 100 missing to make it unit consistent. Note, however, that also (2) is dimensionally consistent.

Now, what I determined before is that in order to fulfill my requirements on Example, you need to forgo the difference between "rad" and "1", which are both non-dimensional but differ by a factor $\pi$. So, in general, automatically finding if the proper factors are in place (or performing unit conversions automatically if they don't) doesn't seem to me something that can always be done. At least when quantities involving rotation and angles are involved.

As far as I understand, this fundamentally undermines the possibility of general, rigorous and automatic unit-checking and automatic unit conversions in Modelica.

3. Summary

I'm afraid I don't have the time and the compentences to get into the details of unit checking algorithms. My contribution to the whole unit discussion, as a modeller, is to provide the Examples model as a litmus test. My requirement is that the first batch of equation should be accepted, and the second rejected. I hope there's agreement on that, I will open a separate ticket to formalize this in the Modelica Specification issue tracker, where it belongs.

To my understanding, if that requirement is fulfilled:

  • it is only possible do perform dimensional consistency checking, not unit checking, in general.
  • whether the unit of Modelica.Constants.pi is "rad" or "1" doesn't really matter, as both are dimensionless units.

My 2 cts 😃

@HansOlsson
Copy link
Copy Markdown
Contributor

3. Summary

I'm afraid I don't have the time and the compentences to get into the details of unit checking algorithms. My contribution to the whole unit discussion, as a modeller, is to provide the Examples model as a litmus test. My requirement is that the first batch of equation should be accepted, and the second rejected. I hope there's agreement on that, I will open a separate ticket to formalize this in the Modelica Specification issue tracker, where it belongs.

Good. One could add p = 2*pi; // (16B)

To my understanding, if that requirement is fulfilled:

  • it is only possible do perform dimensional consistency checking, not unit checking, in general.
  • whether the unit of Modelica.Constants.pi is "rad" or "1" doesn't really matter, as both are dimensionless units.

My 2 cts 😃

Agreed, so the unit-checking has to sort of accept that "rad" and "1" can be mixed, but still try to differentiate between them when possible.

@AHaumer
Copy link
Copy Markdown
Contributor

AHaumer commented Apr 23, 2026

I totally agree with @casella looking at his chapter 2.3, tht's what I meant.

@AHaumer
Copy link
Copy Markdown
Contributor

AHaumer commented Apr 23, 2026

The problem of removing "rad" that I see for the usage in drives:
The rotational speed n is given in "1/s" (or even rev/s).
The angular velocity w is given in "rad/s".
w and n both stand for the same effect: rotation, but you have to know "which unit".
Easy to convert: w = 2pin; Therefore pi has the unit "rad" (and I keep in mind that "rad" is equivalent to "1").
I told my students: In Modelica internally we use w in rad/s, but for humans it's easier to deal with n in 1/s.
With w is easier to calculate speed of a rolling wheel: v = rw; (not using 2pi*n).

@henrikt-ma
Copy link
Copy Markdown
Contributor Author

henrikt-ma commented Apr 23, 2026

No! This is what I made very clear above: Our design work for unit consistency has converged to a good design long time ago:

  • Literals, having "empty unit", are not acting as multiplicative wildcards in equations; they "decay to unit 1".
  • Constants may also have "empty unit", enabling us to use a constant named pi instead of writing 3.1415926535897931 everywhere.
  • Do not mistake the "empty unit" for being the state of a unit which has yet to be inferred!

Yes, there are remaining topics in Modelica unit consistency which are still being debated, but I have a very clear picture of the handling of constants not being one of them.

Regarding

  phi_t = pi; // (13)

we have too much legacy to be able to reject

  phi_t = 3.14;

so therefore (13) will also be accepted when pi has empty unit.

I don't see what this has to do with the unit of pi:

  L = phi*r;  // (10)

However I just want to point out that, as @mestinso explained in #4774 (comment), this sort of relation may actually be missing a hidden factor with unit, and because of the widespread use of sloppy relations like this we end up being forced to make the radian an alias for the unit 1.

Also remember that when comparing to other literature on units, there may be no distinction between a numeric value and a "value of quantity" with unit 1. In Modelica this distinction is important, since we have a long tradition relying on the so-called empty unit expressions being accepted where the unit 1 would be wrong. For example,

Real p(unit = "m") = 6.28; // OK
Real p(unit = "m") = 6.28'1'; // Inconsistent

Hence, adequate support for also working without units is important in Modelica, and forcing the user to just throw units away is not adequate support. Failure to stay in the unitless world should be rejected in the same way as unit inconsistency is rejected. If the MSL provides a pi that cannot be used in empty unit expressions, then I am afraid that other libraries will define their own pi which they can use just like a numeric literal.

I just can't see the big point of rejecting

Real p(unit = "m") = 2 * pi;  // (16B)

and forcing the user to use a literal instead:

Real p(unit = "m") = 2 * 3.14;

@mestinso
Copy link
Copy Markdown
Collaborator

It seems there are two separate discussion points here:

  1. Units of pi --> still think "1" is the right choice..
  2. Assigning with literals. This issue seems to be beyond just pi. What if I want to define a length that has the numeric number equal to the speed of light? Isn't this the same situation?
    Let's take that case (to not conflate with the first issue):

In dymola i see the following:

Real p(unit = "m") = c; // option 1: this gives unit warnings (seems reasonable)
Real p(unit = "m") = 1*c; // option 2: this is ok (seems reasonable, 1 has an implied conversion)
Real p(unit = "m") = 1.0*c; // option 3: this is ok (seems reasonable, 1.0 has an implied conversion)

Why don't we have the same logic for pi? Give it unit="1" and the same way out if we want to set the length equal to pi?

@henrikt-ma
Copy link
Copy Markdown
Contributor Author

In dymola i see the following:

Real p(unit = "m") = c; // option 1: this gives unit warnings (seems reasonable)

So far so good.

Real p(unit = "m") = 1*c; // option 2: this is ok (seems reasonable, 1 has an implied conversion)
Real p(unit = "m") = 1.0*c; // option 3: this is ok (seems reasonable, 1.0 has an implied conversion)

Here Dymola is not aligned with where the standardization is converging to; there must not be a "wild-card" effect when including a numeric literal as a factor. With its current behavior it I guess it is only rejecting (15), (18) and (19) on @casella's list. Maybe Dymola will be more aligned in its next version?

By the way, with the unitful literals from modelica/ModelicaSpecification#3688 it will be easy to resolve the inconsistencies like so:

Real p(unit = "m") = 1's' * c;

Once we have unitful literals, it could be argued that this is bad style and should be rejected:

Real p(unit = "m") = 4;

just because there is a cleaner way to do it:

Real p(unit = "m") = 4'm';

However, since the variant with an empty unit expression (often a literal) is still today the only way of doing it in Modelica, the legacy is enormous and it is too late to get it "right". However, as long as the unit is immediately available from context, it isn't really a problem to allow an empty unit expression to implicitly be associated with the unit from the context. (It is the use of empty unit expressions where the unit is not immediately available from context that is the real problem, and here I don't see an enormous legacy that would prevent us from gradually phasing it out from Modelica.)

This the reason why it is so important for the definition of unit consistency in Modelica that there are ways to work both in the physical domain of values of quantity (expressions with unit), and in the mathematical domain of real numbers (expressions with empty unit). Also, there must be a clear separation of the two so that there is no question whether an expression has empty unit allowing it to take on a unit from context or an expression with unit that must be used consistently. In Modelica, giving pi a unit is to not support working in the mathematical domain of real numbers.

Why don't we have the same logic for pi? Give it unit="1" and the same way out if we want to set the length equal to pi?

It would be thanks to actually having unit "1" for pi that Dymola would reject (15), but the point is that we don't need it to reject all of (14)–(20). All we need is that empty unit factors don't not turn into wildcards, and this mechanism is sufficient to also reject (15) also when pi has empty unit.

I don't see a strong analogy between π and c. While π appears in a variety of purely mathematical relations on real numbers, c is used in physical relations involving values of quantity. π is not only the ratio between the circumference of a circle of radius 4m and its diameter, it is also the ratio between the real unit circle's circumference and radius. To support both uses in Modelica, it should behave as a numeric literal.

@mestinso
Copy link
Copy Markdown
Collaborator

@henrikt-ma To clarify, I understand that today pi doesn't have units on it, so the behavior is not comparable to the speed of light behavior. What I mean to say or am trying to imply is that I'd like the behavior to be analogous.

So yes, I'd like to see this future state after we have the unitful literals features come online:
I'd like both of these to work:

Real p(unit = "m") = 1's' * c;
Real p(unit = "m") = 1'm' * pi;

And I'd like both of these to give a unit warning:

Real p(unit = "m") = c;
Real p(unit = "m") = pi;

...and until that new unitful literals feature comes online, I'm ok with this being a sort of workaround:

Real p(unit = "m") = 1 * c;
Real p(unit = "m") = 1 * pi;

@HansOlsson
Copy link
Copy Markdown
Contributor

It seems there are two separate discussion points here:

  1. Units of pi --> still think "1" is the right choice..
  2. Assigning with literals. This issue seems to be beyond just pi. What if I want to define a length that has the numeric number equal to the speed of light? Isn't this the same situation?
    Let's take that case (to not conflate with the first issue):

In dymola i see the following:

Real p(unit = "m") = c; // option 1: this gives unit warnings (seems reasonable)
Real p(unit = "m") = 1*c; // option 2: this is ok (seems reasonable, 1 has an implied conversion)
Real p(unit = "m") = 1.0*c; // option 3: this is ok (seems reasonable, 1.0 has an implied conversion)

Just to clarify - that is with default settings (and after import Modelica.Constants.c).

If you set Advanced.Modelica.CheckUnitsProposedSimple = true; they all give similar diagnostics, as a multiplicative literal (like 1 or 1.0) is then assumed to have unit "1".

Note that setting that flag will trigger lots of diagnostics from MSL, and I would have hoped we could focus on actually improving them to help users - and to find potential errors. (There are some PRs to improve the situation even for the 3.6 version of the Modelica Language; like #4591 #4398 #4051 there are some marked requires37 and then for additional proposals we have something like https://github.com/HansOlsson/Modelica/tree/UnitTest2 - their might be more needed for pi - I haven't investigated that in detail)

Obviously, there can be different opinions on whether Real p(unit = "m") = 1*c; should be legal or not. One of the items to work on for the specification to ensure that there are clear ways to configure that when needed (in ways yet to be specified).

The constant issue (in particular for package constants) is that they should only infer unit from their declaration not from their use, since something like "constant Real eps=2...e-16;" and then have assert(length>eps, "Length cannot be too small"); and `assert(voltage>eps,"Voltage cannot be too small"); seems to cause more problems than it solves.

Obviously some constants like c should have unit.

Why don't we have the same logic for pi? Give it unit="1" and the same way out if we want to set the length equal to pi?

That would be good (or using "rad" as indicated before), but it requires actually working on improving MSL to be better at handling units.

Simply put - pi should have unit "rad" or "1"; at least in the future. Now we need to work on improving MSL.

@HansOlsson
Copy link
Copy Markdown
Contributor

The problem of removing "rad" that I see for the usage in drives: The rotational speed n is given in "1/s" (or even rev/s).

(And in some cases in "rev/min".)

Note that the SI-standard has another solution for this: "Hz" instead of "1/s".
To quote the SI-brochure:

The hertz shall only be used for periodic phenomena and the becquerel shall only be used for stochastic processes in activity referred to a radionuclide.

So, if you have "rad/s" and "Hz" it is clear which is which, but when we infer units we often get "1/s" and don't know if it is one, the other, or something completely different (and don't use becquerel just because it is "1/s"). (We might have some over-use of "rad" in Dymola.)

That's why we have:

  type Frequency = Real (final quantity="Frequency", final unit="Hz");
  type AngularFrequency = Real (final quantity="AngularFrequency", final unit="rad/s");

@henrikt-ma
Copy link
Copy Markdown
Contributor Author

henrikt-ma commented Apr 27, 2026

And I'd like both of these to give a unit warning:

This is where we need the discussion to be focused, and we need two more variations on the theme:

Real p(unit = "m") = c; // c is speed of light
Real p(unit = "m") = 3.14;
Real p(unit = "m") = pi; // pi is π
Real p(unit = "m") = 5 * sin(pi / 3); // Intention: (5 * 0.866…) meters

Ultimately, I think we must accept that the unit of asin(1.0) (a language design question) might not have to be the same as that of Modelica.Constants.pi (a MAP-Lib design question). Right now, however, I think we are too few people involved in this discussion to be able to draw conclusions. I think it would be an excellent topic to bring up on the next MAP-Lib monthly meeting, don't you think, @casella?

Edit: I added one more variation where pi is used in a more complicated expression which needs to end up with either unit "m" or empty unit.

@mestinso
Copy link
Copy Markdown
Collaborator

Ultimately, I think we must accept that the unit of asin(1.0) (a language design question) might not have to be the same as that of Modelica.Constants.pi (a MAP-Lib design question).

Agreed. I think the following reference lays it out the best: https://iopscience.iop.org/article/10.1088/1681-7575/ac7bc2/pdf

The following screenshots demonstrate the basic idea for unit handling on trig functions.

image image

Unitwise, the trig functions in modelica are the standard "rad" variety. So sin, cos, tan should expect unit="rad" as input, and return unit="1" as the output. And the inverses asin, acos, atan, should expect unit="1" as the input and return unit="rad" as the output.

And pi itself is dimensionless/unit="1"

@henrikt-ma
Copy link
Copy Markdown
Contributor Author

Agreed. I think the following reference lays it out the best: https://iopscience.iop.org/article/10.1088/1681-7575/ac7bc2/pdf

Yes, they make it very clear that π is not an angle by itself, and I love the way they stress the importance of "complete functions". However, they do not pay much attention to "analytical angle" (introduction to section 4) and dimensionless functions (section 4.3.4), while the Modelica ecosystem comes with a legacy that require a much stronger recognition of these concepts.

And pi itself is dimensionless/unit="1"

The point is that in Modelica, we need to make a real distinction between dimensionless (empty unit) and "1". Since it is a weirdness of Modelica, we can't expect the metrologists to solve this problem for us, and it is not surprising that the paper doesn't put emphasis on this distinction.

Besides the weirdness of allowing an expression with empty unit to take on a unit given by the context, computations with empty units also have an important role to play for empirical relations that relate values of quantities when expressed in some particular choice of units, and if we are serious about supporting cyber-physical systems (section 1.1 in the specification) I think it has a value in itself to properly support computations on real numbers without any presence of units.

It is in this context that I would like to have a constant for π which may be used in all those truly dimensionless computations where there should be no presence of units whatsoever.

By the way, it is also nice to see the very clear notation they use for the transition between quantities and "coefficients" in section 2, which we are also able to express using the experimental operators in System Modeler. Take the angle a and the unit "deg" for example, then regardless of the unit of a, it is the same quantity as withUnit(withoutUnit(a, "deg"), "deg"). These operators give a controlled way of transitioning between physical modeling and crunching of real numbers.

@henrikt-ma
Copy link
Copy Markdown
Contributor Author

Regarding the built-in trigonometric functions in Modelica, one could actually gain some unit safety by only supporting "analytical angle", corresponding to empty unit. This would avoid the need for somewhat complicated overloaded behavior, while functions such as sin_rad can act as a geometric angle wrapper:

function sin_rad
  input SI.Angle u;
  output Real y(unit = "1") = withUnit(.sin(withoutUnit(u, "rad")), "1");
end sin_rad;

(The use of withUnit above only makes explicit what would otherwise depend on the Modelica "legacy" treatment of empty unit expressions.)

The degree variant of sin would look almost identical:

function sin_deg
  input NonSI.Angle_deg u;
  output Real y(unit = "1") = withUnit(.sin(withoutUnit(u, "rad")), "1");
end sin_deg;

With some intelligence in unit inference for function calls, one could even make a generic variant:

function sin
  input u(quantity = "Angle");
  output Real y(unit = "1") = withUnit(.sin(withoutUnit(u, "rad")), "1");
end sin;

This would actually be the safest variant of all, as withoutUnit(u, "rad") will not allow u to have empty unit. The variants sin_rad and sin_deg will silently accept empty unit arguments without knowing whether that number was meant to have the correct unit for the function at hand.

@HansOlsson
Copy link
Copy Markdown
Contributor

Unitwise, the trig functions in modelica are the standard "rad" variety. So sin, cos, tan should expect unit="rad" as input, and return unit="1" as the output. And the inverses asin, acos, atan, should expect unit="1" as the input and return unit="rad" as the output.

Agreed.

And pi itself is dimensionless/unit="1"

Dimensionless for sure, but I think we need to consider "1" or "rad" this a bit more, and to the most pragmatic solution (after fixing unit issues in MSL like merging #4780 - and more) is to make Modelica.Constants.pi correspond to $\pi \text{ rad}$ in that paper.

That is consistent with all of the uses of Modelica.Constants.pi/2 (etc) for angles, as people don't write $\pi/2 \text{ rad}$ in Modelica (and it is not entirely clear how to write it), and to me it seems that the majority of uses of pi are for angles. The downside is that circle areas and areas of circle segments either gets awkward units (that tools can handle) or one needs to convert the angle in "rad" to the same number with unit "1" (corresponding to the SI-definition that "radian" is just a dimensionless number).

But, that is not urgent as we first need to correct the units of MSL in general; and I still find it sad that we spend so much time on this - and so little time on actually fixing the real unit issus in MSL.

@henrikt-ma
Copy link
Copy Markdown
Contributor Author

But, that is not urgent as we first need to correct the units of MSL in general; and I still find it sad that we spend so much time on this - and so little time on actually fixing the real unit issus in MSL.

I opened this because of a unit error in a library caused by pi being used where an empty unit expression was required.

@henrikt-ma
Copy link
Copy Markdown
Contributor Author

Dimensionless for sure, but I think we need to consider "1" or "rad" this a bit more, and to the most pragmatic solution (after fixing unit issues in MSL like merging #4780 - and more) is to make Modelica.Constants.pi correspond to π  rad in that paper.

At least with this way of understanding π in the paper – as something to which a unit can be attached – we get that π in the paper would correspond to an empty unit 3.14… in Modelica. So far so good.

But to then say that the MSL should have a pi constant which represents π rad would mean to break alignment with the very nice analysis they do in the paper. Sounds like asking for trouble, and very confusing in my opinion.

If we would only get those unitful literals in the language, and if we additionally get implicit unit conversion in some places where it is safe, then there will be no more need to write Modelica.Constants.pi / 2 to express an angle when setting an SI.Angle parameter; simply saying 90'deg' will be so much better.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants