modelica / ModelicaSpecification

Specification of the Modelica Language
https://specification.modelica.org
Creative Commons Attribution Share Alike 4.0 International
97 stars 41 forks source link

MCP-0027 Units of Literal Constants #2127

Open modelica-trac-importer opened 5 years ago

modelica-trac-importer commented 5 years ago

Reported by fcasella on 11 Dec 2016 19:03 UTC I have added a proposal for the clarification of the units of literal constants, which is currently unspecified in Modelica 3.3r1. I hope there's still time to have it in Modelica 3.4, the proposal is very simple and the evaluation process should be straightforward.

Documents can be accessed directly here: Overview and Specification Changes.


Migrated-From: https://trac.modelica.org/Modelica/ticket/2127

modelica-trac-importer commented 5 years ago

Comment by fcasella on 14 Dec 2016 09:49 UTC Discussion at the 92nd Design Meeting.

Conclusions

Make sure it works with

modelica-trac-importer commented 5 years ago

Comment by fcasella on 19 May 2017 14:12 UTC Updated the MCP text on SVN (r9715) at the 94th Design Meeting in Prague

modelica-trac-importer commented 5 years ago

Comment by stefanv on 20 May 2017 07:43 UTC One issue that came up at the meeting yesterday was the need to define a large number of unit symbols, so expressions such as v = 2 * ohm * i can be written. Instead of defining these symbols, I'd like to propose adding a new built-in function instead, defined by the following pseudo-Modelica:

function Unit
    input String unit;
    output Real y(unit=unit) := 1;
end Unit;

After writing the above, I'm not sure this even needs to be built-in, because I think the above might already be valid Modelica.

Then, an expression like the one above could be written as v = 2 * Unit("ohm") * i. This makes it clear what is going on, and doesn't necessitate the introduction of a huge number of constants, many of which would have simple names like m that are likely to clash with variable names.

modelica-trac-importer commented 5 years ago

Comment by hansolsson on 22 May 2017 10:22 UTC Replying to [comment:3 Stefan Vorkoetter]:

One issue that came up at the meeting yesterday was the need to define a large number of unit symbols, so expressions such as v = 2 * ohm * i can be written. Instead of defining these symbols, I'd like to propose adding a new built-in function instead, defined by the following pseudo-Modelica:

function Unit
    input String unit;
    output Real y(unit=unit) := 1;
end Unit;

After writing the above, I'm not sure this even needs to be built-in, because I think the above might already be valid Modelica.

Then, an expression like the one above could be written as v = 2 * Unit("ohm") * i. This makes it clear what is going on, and doesn't necessitate the introduction of a huge number of constants, many of which would have simple names like m that are likely to clash with variable names.

I agree that this seems like a better solution and might not need anything built-in. It also seems to work better with more complicated units, e.g. "Ohm.m" (Resistivity) - rad/s2" (AngularAcceleration), and "J/(kg.K)" (SpecificEntropy).

However, it might be that it is clearer to write InUnit(2, "Ohm") than 2*Unit("Ohm") (except it needs a better name).

A somewhat related issue is to construct units for blocks (#921) - think of building this expression with block-diagrams with units. Previously I thought that using unit-names directly for that was more problematic than using the SIunits-types; but if units are more intuitive for equations it might be that they are more intuitive there as well and it is more a matter of making that even more convenient. (The risk of errors is mitigated by the fact that the units are checked; people are about as likely to misspell enthalpy as "J/(kg.K)" - and both errors can be detected).

modelica-trac-importer commented 5 years ago

Comment by stefanv on 22 May 2017 14:30 UTC I guess it's a matter of preference, but I prefer 2 * Unit("ohm"). Another possibility is to introduce a new syntax, such as 2 * "ohm", which is even simpler to read (the grammar probably allows that already; it just has no meaning at this point).

Also, making use of * (and /) makes it possible to write inverse units in a natural way, such as velocity := 3 * distance / Unit("s").

modelica-trac-importer commented 5 years ago

Comment by cbuerger on 22 May 2017 15:12 UTC I do not feel comfortable with any of the expression-based notations like 2 * Unit("ohm") or 2 * "ohm" and much prefer a special built-in function that takes a literal and a unit specification like Unit(2, "ohm").

My rational is, that an expression-based approach requires special checks that are in their nature syntactical but must be realised and specifified on a semantic level. Consider the following examples; are they valid and if not what are the exact restrictions?:

modelica-trac-importer commented 5 years ago

Comment by fcasella on 22 May 2017 16:04 UTC Replying to [comment:6 Christoff Bürger]:

I guess what is currently in the MCP i.e., adding constants with units to Modelica.SIUnits and using them in expressions, implicitly addresses these concerns, using syntax and semantics which are already defined in the specification as of today.

When defining the constants, we should try to follow authoritative recommendations such as this, or better ones if you know them. That is outside the scope of the MCP.

modelica-trac-importer commented 5 years ago

Comment by stefanv on 22 May 2017 17:07 UTC Replying to [comment:6 Christoff Bürger]:

I do not feel comfortable with any of the expression-based notations like 2 * Unit("ohm") or 2 * "ohm" and much prefer a special built-in function that takes a literal and a unit specification like Unit(2, "ohm").

My rational is, that an expression-based approach requires special checks that are in their nature syntactical but must be realised and specifified on a semantic level.

No special checks are required at all, beyond any unit checking already done by the code. For example, my proposed function Unit simply returns the value 1.0 with the specified unit. Other than implementing the Unit function, no further changes are required in the tool. All of your examples except the last are well-defined under this scheme (the last is probably a good reason not to use the 2 * "ohm" scheme).

modelica-trac-importer commented 5 years ago

Comment by stefanv on 22 May 2017 17:10 UTC Replying to [comment:7 Francesco Casella]:

Replying to [comment:6 Christoff Bürger]:

I guess what is currently in the MCP i.e., adding constants with units to Modelica.SIUnits and using them in expressions, implicitly addresses these concerns, using syntax and semantics which are already defined in the specification as of today.

And as I have pointed out, what I suggest also does, and doesn't require the introduction of any new symbols (except for Unit), and thus also no need to choose these symbols (we just use the existing unit strings as the argument to Unit).

modelica-trac-importer commented 5 years ago

Comment by anonymous on 23 May 2017 11:38 UTC Replying to [comment:8 Stefan Vorkoetter]:

Replying to [comment:6 Christoff Bürger]:

I do not feel comfortable with any of the expression-based notations like 2 * Unit("ohm") or 2 * "ohm" and much prefer a special built-in function that takes a literal and a unit specification like Unit(2, "ohm").

My rational is, that an expression-based approach requires special checks that are in their nature syntactical but must be realised and specifified on a semantic level.

No special checks are required at all, beyond any unit checking already done by the code. For example, my proposed function Unit simply returns the value 1.0 with the specified unit. Other than implementing the Unit function, no further changes are required in the tool. All of your examples except the last are well-defined under this scheme (the last is probably a good reason not to use the 2 * "ohm" scheme).

Given that Unit("ohm") simply returns the value 1.0 with the specified unit, what is the semantic of my 4th example 2 + Unit("ohm"). Should the value of that expression be 3 ohm or should it be an error?

Remember, that we do not enforce units everywhere, but just check that if units are given the equation system is well-typed (to which end we automatically derive units). That is why I am cautious with introducing a built-in function that just returns a unit; we have no such thing as an value-less expression with just a unit in the language so far. Using a two argument built-in function that combines value and unit makes it more explicit and less fragile to unexpected unit derivations and value computation combinations.

modelica-trac-importer commented 5 years ago

Comment by cbuerger on 23 May 2017 11:40 UTC Above post was me; forgot to login.

modelica-trac-importer commented 5 years ago

Comment by stefanv on 23 May 2017 12:13 UTC Replying to [comment:10 anonymous]:

Given that Unit("ohm") simply returns the value 1.0 with the specified unit, what is the semantic of my 4th example 2 + Unit("ohm"). Should the value of that expression be 3 ohm or should it be an error?

The semantics of 2 + Unit("ohm") are the identical to the semantics of the following that we can already write:

    constant Modelica.SIunits.Resistance R = 1;
equation
    x = 2 + R;

and also identical to writing 2 + Unit(1,"ohm") in the two-argument form you are preferring.

That is why I am cautious with introducing a built-in function that just returns a unit; we have no such thing as an value-less expression with just a unit in the language so far.

And with my proposal, we still won't. Unit("ohm") is not a "value-less expression with just a unit", it is 1 Ohm. It is equivalent to writing Unit(1,"ohm") in the two-argument form.

Using a two argument built-in function that combines value and unit makes it more explicit and less fragile to unexpected unit derivations and value computation combinations.

I disagree. It is semantically equivalent, but harder to read.

I'm not sure what you mean by "unexpected unit derivations". Doing unit arithmetic is a well understood problem. In the 1970s, there even existed a slide rule that could do this (i.e., it worked purely with units, not numbers).

modelica-trac-importer commented 5 years ago

Comment by fcasella on 23 May 2017 12:34 UTC Replying to [comment:10 anonymous]:

Given that Unit("ohm") simply returns the value 1.0 with the specified unit, what is the semantic of my 4th example 2 + Unit("ohm"). Should the value of that expression be 3 ohm or should it be an error?

According to the text of the MCP, the constant 2 will have unit "1" and Unit("ohm") will have unit ohm, so a unit checker should report that this expression is dimensionally inconsistent. Whether this is a warning or an error is a tool issue, and may also depend on the settings.

modelica-trac-importer commented 5 years ago

Comment by hansolsson on 23 May 2017 13:11 UTC Replying to [comment:7 Francesco Casella]:

Replying to [comment:6 Christoff Bürger]:

I guess what is currently in the MCP i.e., adding constants with units to Modelica.SIUnits and using them in expressions, implicitly addresses these concerns, using syntax and semantics which are already defined in the specification as of today.

When defining the constants, we should try to follow authoritative recommendations such as this, or better ones if you know them. That is outside the scope of the MCP.

That recommendation is a good guide, and not primarily about defining constants - but about how to use units in general - and I agree that we should follow it if possible.

Note in particular that 2*kg is not according to those recommendations - it is always 2 kg (both numbers and units are written in roman style - but variables in italics).

In particular recommendation 12 says: It is clear to which unit symbol a numerical value belongs and which mathematical operation applies to the value of a quantity.

It seems that if we have something like Unit(2, "kg") that fulfills the requirement and it would be fairly simple to render equation y=Unit(2,"kg"); as y=2 kg (similarly as Dymola already generates a more "mathematical notation").

modelica-trac-importer commented 5 years ago

Comment by fcasella on 23 May 2017 13:41 UTC Replying to [comment:14 Hans Olsson]:

When defining the constants, we should try to follow authoritative recommendations such as this, or better ones if you know them. That is outside the scope of the MCP.

That recommendation is a good guide, and not primarily about defining constants - but about how to use units in general - and I agree that we should follow it if possible.

Yeah, that's what I meant :)

Note in particular that 2*kg is not according to those recommendations - it is always 2 kg (both numbers and units are written in roman style - but variables in italics).

I think this is no big deal. In mathematical notation you would write v = sqrt(2gh) with no explicit multiplications, but in programming languages you always have to put the {*} sign explicitly. As I see it, 2 kg means 2 times a kilogram, and I don't see a problem if we have to make the "times" explicit.

Anyway, let me remind everyone that in its current form, this MCP is not really about how you write literal constants with units. To the contrary, it only specifies when a literal constant is actually a nondimensional number, so as to avoid bogus unit checking when such constants are involved in expressions and may end up being used as "unit slack variables".

I would recommend that we don't mix this issue up with how we can write 4 kg in a Modelica expression, which is also probably bad modelling style, as I'd define a constant with the proper unit to store that numerical value, and then use that instead of the literal constant, rather than hard-wire it in an expression. The suggestion about defining and using unit constants is in the non-normative part and I can actually remove it if it causes controversy. That won't change the proposal significantly.

modelica-trac-importer commented 5 years ago

Comment by hansolsson on 23 May 2017 14:48 UTC Replying to [comment:15 Francesco Casella]:

Replying to [comment:14 Hans Olsson]:

Note in particular that 2*kg is not according to those recommendations - it is always 2 kg (both numbers and units are written in roman style - but variables in italics).

I think this is no big deal. In mathematical notation you would write v = sqrt(2gh) with no explicit multiplications, but in programming languages you always have to put the {*} sign explicitly. As I see it, 2 kg means 2 times a kilogram, and I don't see a problem if we have to make the "times" explicit.

The guides and recommendation make it clear that it is multiplication, but that the normal multiplication sign is not used.

Recommendation 15 says (NIST guide 7.2): "There is a space between the numerical value and unit symbol, even when the value is used in an adjectival sense, except in the case of superscript units for plane angle."

Recommendation 5 says: "A space or half-high dot is used to signify the multiplication of units" (in the other document it is described that a normal dot can be used as fallback - as we do in Modelica).

Note that they use "x" for multiplication of values, e.g. 1 Ci = 3.7 x 1010 Bq - i.e. not the same symbol used when multiplying units (see NIST guide 10.5.4 - https://www.nist.gov/pml/nist-guide-si-chapter-10-more-printing-and-using-symbols-and-numbers-scientific-and-technical#1054).

Note that they also make a minor difference between how "2" is written as part of "2 kg" and as a general number (10.5.1).

The main part here is that they don't have "kg" free-floating in expressions, it is always "2 kg" or similarly.

BTW - division is allowed on the other hand so both of the following are ok: m=2 kg m/kg=2 (Quite useful for tables.)

However, the proposal will not unit-check the one with division as I understand it.

modelica-trac-importer commented 5 years ago

Comment by hansolsson on 24 Oct 2017 12:20 UTC Calling a function with a literal should be included in the list of exceptions.

E.g. if foo takes an argument of type Temperature then foo(293.15) is ok; we might view the call of the function as "assigning" to the input - but I believe it deserves its own item. -- A different, but related topic is the unit for Reals without any specified unit.

Assuming that they have unit "1" works in some cases, e.g. Modelica.Blocks.Sources.CombiTimeTable: nextTimeEventScaled but also fails for some cases e.g. Modelica.Blocks.Sources.KinematicPTP: sdd_max (which has unit "1/s").

modelica-trac-importer commented 5 years ago

Comment by anonymous on 24 Oct 2017 13:28 UTC For functions, I think we must be careful so that we don't destroy the possibility to do good unit checking of function calls.

In a far future, I hope that we will be able to say that Modelica functions with empty unit on some of the inputs and outputs should be interpreted as being parametrically polymorphic with respect to the units of these inputs and outputs, and that tools should be able to infer those types by looking at the function bodies. (For external functions, this is obviously not an option, and then one strategy might be to assume that all empty units must be inferred to the same unit at each call site.)

In order to keep the possibility open for doing this in the future, I think it's best to leave the unit of function return values undefined (not empty) when the output has empty unit. This means that a tool cannot do unit checking (although it would be stupid to report this as an error) of such function calls, but that we have the possibility to find unit inconsistencies in such calls in the future.

For example, consider

function f
  input Real r;
  input Real s;
  input Real t;
  output Real y;
algorithm
  y := r * s + t;
end f;

For this function, where all inputs and outputs have empty unit, a tool should be able to infer the polymorphic type:

∀a: Real(unit=a) x Real(unit=a) x Real(unit=a*a) → Real(unit=a*a)

Using this type, it can detected that this is inconsistent:

   Length r, s, t;
   Length y = f(r, s, t);

By allowing a to be instantiated as the empty unit, and defining a * a to also be empty in this case, the function f will still be usable in a completely unitless setting.

Note that the polymorphism used here doesn't have to be exposed in the Modelica language, but this is something that is only used internally in the unit checking tools.

modelica-trac-importer commented 5 years ago

Comment by henrikt on 24 Oct 2017 13:29 UTC Sorry, forgot to log in.

modelica-trac-importer commented 5 years ago

Comment by henrikt on 24 Oct 2017 13:32 UTC Would it really be such an issue if one wouldn't be able to call Modelica.Blocks.Sources.KinematicPTP with a Real literal? I think that there are better solutions, such as the proposed unit function, for making Modelica.Blocks.Sources.KinematicPTP convenient to use without an intermediate component declaration:

Modelica.Blocks.Sources.KinematicPTP(42 * unit("1/s"))
modelica-trac-importer commented 5 years ago

Comment by henrikt on 24 Oct 2017 13:56 UTC For equations, I'd like to make the proposal more strict by only allowing unit inference for the empty unit when the magnitude is a translation time constant 0. Then, the displayUnit can be used to edit all other Real literals, and there is no risk of being so used to working with the displayUnit (through a GUI that helps with the conversion from/to base unit) that one forgets to think in the base unit when editing literals in textual equations (a GUI is of no help here).

modelica-trac-importer commented 5 years ago

Comment by hansolsson on 24 Oct 2017 14:30 UTC Replying to [comment:20 Henrik Tidefelt]:

Would it really be such an issue if one wouldn't be able to call Modelica.Blocks.Sources.KinematicPTP with a Real literal?

It's more that complaining about h_pT(1e5, 293.15) and require the use of h_pT(1e5*unit("Pa"), 293.15*unit("K")) seems likely to be seen as tedious work with no benefit from users - making it likely that they skip other, more important, unit-errors.

Similar reasons explain why the proposal allows Temperature T=293.15; in addition to Temperature T=293.15*unit("K");

Clearly this is a trade-off; and I would prefer that we progress slowly - and we can then later make it more strict - instead of going too far and not getting the users onboard.

Allowing unit-deduction (including polymorphic functions) is interesting - and maybe that is the correct solution for both the variables in KinematicPTP and other cases.

However, if units are specified they should be correct - I will open another ticket on that, https://github.com/modelica/Modelica/issues/2379

modelica-trac-importer commented 5 years ago

Comment by henrikt on 25 Oct 2017 06:03 UTC I would expect the proper way of writing a model to be inlining all the literals as in h_pT(1e5*unit("Pa"), 293.15*unit("K")). Instead, I would expect that the pressure and temperature in question should be parameters, so that there is one place where such values are changed for all uses in the entire model. Making them disabled (or __Wolfram_show=false etc), they can still be hidden from the user interface if that's desired, while at the same time making the model less error prone and easier to maintain:

  parameter Modelica.SIunits.AbsolutePressure nominalPressure = 1e5;
  parameter Modelica.SIunits.Temperature nominalTemperature = 293.15;
…
  h_pT(nominalPressure, nominalTemperature)

Sure, it's a much less alarming case than some other, but since there is a way to deal with it, we could demand that it is reported with a warning rather than a plain error (like when someone is trying to add 10 to a Length).

Allowing Temperature T = 293.15 is different for two reasons. First, as long as we don't have unit("K"), we simply have to allow this since there is no other way we can introduce a first value with unit K from which all other values could be boot-strapped. Second, by being attached to the component T, the value 293.15 can be edited with GUI support where the value should always be displayed together with the displayUnit of T, so to the end user this is no longer just a Real literal, but 20°C.

Let me try to repeat one of my core ideas of how to proceed here. By making a difference between the empty unit (unit = "") and an undefined unit, we can concentrate on defining the semantics of the empty unit in a few important cases to start with, while leaving the unit undefined in other cases that we don't want to decide on in a first iteration. For tools that do unit checking, the only reasonable action to take when encountering an undefined unit is then to just ignore unit checking for the expression at hand (reporting a warning/error would be more true to the specification, but wouldn't be a viable option as the number of warnings would become overwhelming and of no help). This is similar to allowing any unit to be inferred when the unit isn't defined, but with the difference that we as authors of the specification keep the possibility to define the unit later without introducing backwards incompatible changes.

The specification should make the distinction between empty unit and undefined unit clear, but apart from that there should be no need to specify when the unit is undefined; when it is defined it is defined and may be empty, but otherwise it is undefined. In particular, the specification should make clear that unit inference is allowed according to some rules for just the empty unit, not when the unit is undefined.

modelica-trac-importer commented 5 years ago

Comment by henrikt on 25 Oct 2017 07:24 UTC Right now, we are discussing details of the proposal I made at the last design meeting, so the necessary background is missing here in the ticket. Therefore, I'll try to write something concrete now, so that further comments can be made with respect to this one. My starting point is not so Mediterranean, but that should allow us to focus on when and how the rules in the proposal should be relaxed. What I am trying to achieve is a solution where we can start with a small addition to the specification that only allows the empty unit to be used in a few different ways, leaving the meaning of having an empty unit undefined in many cases. If not being able to check unit consistency of an expression is seen as a model error (as will be the case when having an empty unit is undefined), this means that we start with very strict rules for unit checking. As said in the previous comment, it won't be a viable option for unit checking tools to report undefined unit as a warning/error, even though that would be most true to the specification. What then remains for future improvements of the specification in this area is then to make unit checking more relaxed by adding more ways in which the empty unit may be used. Hence, what I suggest below is probably more like a road map than what would need to go into the MCP (future improvements would probably be less drastic, and could probably be resolved as ordinary tickets).

I think we need a term to use for the unit "", and I don't think undefined is a good term. I think it is better to let undefined refer to the situations where the specification has failed/omitted to define the unit of an expression, which means that such expressions cannot be unit checked and that there is no guarantee that they will be considered to have consistent unit in future revisions of the specification. Since the unit string is empty, I think empty is a pretty good term, but alternatives to consider also include wildcard. I don't think inferred is a good term, since inferred unit already has a meaning in this context. I'll stick to empty below.

Allowing unit inference for the empty unit in some situations gets more dangerous (as in not being able to detect unit inconsistencies) the more expressions we have with empty unit. Hence, to be on the safe side, I suggest that we don't make the empty unit a fallback that may be used just because we (authors of the specification) or tool makers are lazy, but instead take the approach that it is only when the specification explicitly says that an expression has empty unit that the unit is empty.

Base rule: The empty unit can always be implicitly cast to unit "1". In some cases, it can also be implicitly cast to an inferred unit:

Base rule: In addition to components declared with empty unit (not including function outputs in order to allow proper unit inference based on the function bodies in the future), the following expression primitives have empty unit:

Using only the base rules, you can't do much. For instance:

Length a = 1.0; /* OK: 1.0 has unit "", which is implicitly cast to "m". */
Length x = 0.5 + 0.5; /* Error: 0.5 + 0.5 gets unit "1", since empty unit is not propagated from terms. */

To make the system more useful, we'll need to include more or less of the relaxation rules below.

Relaxation rule: Built-in non-array operators, functions, and special expressions that propagate any unit (including empty):

Relaxation rule: Array-functions and special array expressions should have the units that are naturally implied by the underlying scalar operations. The same holds for special functions such as semiLinear.

Relaxation rule: Special situations in which the empty unit can be propagated up the expression tree:

In addition to the relaxation rules, we should also specify units of built in function return values. This doesn't really have to do with the empty unit, but rather with the possibility to do unit checking in general. Examples:

modelica-trac-importer commented 5 years ago

Comment by henrikt on 25 Oct 2017 07:39 UTC An interesting question is whether a non-empty unit is ever compatible with the empty unit. For example:

 Length x;
 Real y;
algorithm
 y := x; /* Error? Can't throw away unit "m" in assignment to empty type? */

By making this an error, functions will have to have the proper units on their outputs, making it possible to detect more unit inconsistencies where the function is called. It would be good to look at examples of when it would be a problem to consider throwing away a non-empty unit an error.

Note that this question is related to the idea about inferring types of functions from the function bodies, since that would make the unit of the output y inferred in the type of the function (allowing proper unit checking at the call site), so it might be enough to just report a warning for the assignment inside the function body.

modelica-trac-importer commented 5 years ago

Comment by hansolsson on 25 Oct 2017 11:13 UTC Looking back at the ticket I noticed that I had forgotten the strong arguments in favor of Unit(2, "kg") instead of 2*Unit("kg").

Additionally I noticed two things related to this:

modelica-trac-importer commented 5 years ago

Comment by hansolsson on 25 Oct 2017 15:05 UTC Replying to [comment:23 Henrik Tidefelt]:

I would expect the proper way of writing a model to be inlining all the literals as in h_pT(1e5*unit("Pa"), 293.15*unit("K")). Instead, I would expect that the pressure and temperature in question should be parameters, so that there is one place where such values are changed for all uses in the entire model. Making them disabled (or __Wolfram_show=false etc), they can still be hidden from the user interface if that's desired, while at the same time making the model less error prone and easier to maintain:

  parameter Modelica.SIunits.AbsolutePressure nominalPressure = 1e5;
  parameter Modelica.SIunits.Temperature nominalTemperature = 293.15;
…
  h_pT(nominalPressure, nominalTemperature)

I agree that this is usually better modeling, but sometimes it is a matter of just setting a start-value for one case: ... T(start=from_degC(20)), h(start=h_pT(1e5, 293.15))... and in those cases it seems as clear as: ... T(start=from_degC(unit(20, "degC"))), h(start=h_pT(unit(1e5, "Pa"), unit(293.15, "K")))... Obviously if the intent is that someone should be able to change it in a good way then a parameter with correct type (and unit) is clearly preferable.

However, if the tools will anyway do all the work of changing from_degC(20) to from_degC(unit(20, "degC")) I don't see a benefit of storing that in the model. In addition I fear that some users will redefine a variant of from_degC that takes an input without unit - to circumvent this check.

modelica-trac-importer commented 5 years ago

Comment by henrikt on 26 Oct 2017 19:36 UTC Replying to [comment:26 Hans Olsson]:

Looking back at the ticket I noticed that I had forgotten the strong arguments in favor of Unit(2, "kg") instead of 2*Unit("kg").

Good point. To the list of reasons, I'd like to add that demanding that the units are introduced together with a magnitude is discouraging use that is just about silencing the unit checker by multiplying with the "missing" unit in the end:

  Velocity v = 42;
  Length xError = 43 * v; /* Unit checker error: Can't bind value with unit m/s to component with unit m. */
  Length xHotFix = 43 * v * unit("s"); /* OK, but very bad style. */
  Length xGood = Unit(43, "s") * v; /* Much better. */

Anyway, I don't think that introducing Unit can be used as an easy way of deciding on the unit of numeric literals; if we make numeric literals always have unit "1", then we end up having to use Unit in places where it quite a burden without adding much value at all:

  parameter Length x(displayUnit = "mm") = Unit(0.0635, "m"); /* Respect displayUnit or not in the GUI? */
  parameter Length y(displayUnit = "mm") = 0.0635; /* No ambiguity: User will se "63.5 mm" in the GUI. */

Assuming that Unit results in an expression with empty quantity, which in turn may be implicitly cast to an inferred quantity according to similar rules as suggested above for the empty unit, makes the example a little more interesting, but still not compelling:

  parameter Length x(displayUnit = "mm") = Unit(2.5, "in"); /* Inferred quantity "" allows conversion to unit "m". */
  parameter Length y(displayUnit = "in") = 0.0635; /* User will se "2.5 in" in the GUI, without need to infer quantity. */

Additionally I noticed two things related to this: * A variant of this is to specialize the function to have e.g. Temperature(293.15) * If we want to gradually introduce unit for literals then Unit(2, "kg") is already legal and should allow unit-checking even before the MCP is accepted; whereas 2*Unit("kg") only works once bare numbers are seen as having unit "1".

Sure, introducing Unit(2, "kg") allows gradual introduction of units for literals, but without this MCP it may not always be possible to verify that the introduced units are correct:

  Distance diameter = 2 * pi * Unit(42, "s"); /* Can't detect error as long as 2 * pi doesn't get unit "1". */
modelica-trac-importer commented 5 years ago

Comment by henrikt on 26 Oct 2017 20:56 UTC Replying to [comment:27 Hans Olsson]:

I agree that this is usually better modeling, but sometimes it is a matter of just setting a start-value for one case: ... T(start=from_degC(20)), h(start=h_pT(1e5, 293.15))...

I'm just not sure that this use case is important enough to sacrifice the rule that (non-zero) Real values with non-empty unit are always edited in the lexical presence of a unit (no need to go to a function definition to find out what unit will be used for the value at hand).

However, if the tools will anyway do all the work of changing from_degC(20) to from_degC(unit(20, "degC")) I don't see a benefit of storing that in the model.

Isn't the correct solution to actually make use of quantity, with similar kind of rules as for the empty unit? Then:

  … Temperature T = Unit(20, "degC"); /* Implicit conversion to 293.15 K. */
  … TemperatureDifference deltaT = Unit(20, "degC"); /* Implicit conversion to 20 K. */

By being more liberal with where quantity is allowed to be inferred when empty (which I think is fine since quantity doesn't contain any hidden scaling factors), but more restrictive about how the empty quantity can be propagated (since the rules for how to do this correctly may become too complicated to understand for many end users), one would be able to do this:

  h_pT(Unit(1, "bar"), Unit(20, "degC"))

which I actually find pretty neat compared to h_pT(1e5, 293.15).

In addition I fear that some users will redefine a variant of from_degC that takes an input without unit - to circumvent this check.

We can't prevent all bad habits, but hopefully we can at least keep this kind of habit out of the MSL, and instead use the MSL as a good example of how to do things right.

modelica-trac-importer commented 5 years ago

Comment by hansolsson on 27 Oct 2017 07:59 UTC In order to handle displayUnit for literals - wouldn't it make sense to extend Unit with an additional argument:

function Unit
  input Real x;
  input String unit;
  input String displayUnit=unit;
  output Real y(unit=unit, displayUnit=displayUnit)=x;
end Unit;

so that you can write: A=Unit(0.127, "m", "in")*Unit(0.02, "m", "cm"); to compute the area of an rectangle that is 5 inches by 2 centimeters. The actual values are in meters - but a GUI can present them with the right displayUnit when appropriate.

This goes together with the previous idea that values in the code should be in SI-units (without prefix); and displayUnit is for presentation.

And here there could be an operation to say insert "5 inches" in the equation and Unit(0.127, "m", "in") would be added in the code.

Unfortunately that doesn't solve the problem with temperature.

modelica-trac-importer commented 5 years ago

Comment by henrikt on 27 Oct 2017 09:07 UTC I don't really see the point of not allowing prefixes in unit, except that it makes life for tool vendors a little bit easier. Compared to your example, I find A = Unit(5, "in") * Unit(2, "cm") much more clear. How would a newcomer to Modelica know what Unit(0.127, "m", "in") means without downloading the specification and finding the right paragraph? (A GUI is probably not going to help out here, since this may be used in equations where we rely on the textual form being human readable/writable.)

modelica-trac-importer commented 5 years ago

Comment by henrikt on 13 Nov 2017 07:06 UTC Two weeks have passed. Can we see a future for this MCP without making it involve the entire system for units in Modelica? In particular, will be be able to focus on the topic of the MCP without getting side-tracked by discussions about a Unit function?

The proposal I made in comment:24 does not involve Unit, and I still think it sets out a possible way forward, but I can also see that the complexity of the solution is on a somewhat bigger scale compared to the current state of the MCP.

modelica-trac-importer commented 5 years ago

Comment by hansolsson on 13 Nov 2017 09:32 UTC Replying to [comment:32 Henrik Tidefelt]:

Two weeks have passed. Can we see a future for this MCP without making it involve the entire system for units in Modelica? In particular, will be be able to focus on the topic of the MCP without getting side-tracked by discussions about a Unit function?

The proposal I made in comment:24 does not involve Unit, and I still think it sets out a possible way forward, but I can also see that the complexity of the solution is on a somewhat bigger scale compared to the current state of the MCP.

There are two parts:

Note that I worked on doing some of these tests for MSL and the following reported issues are not yet resolved: https://github.com/modelica/Modelica/pull/2375 https://github.com/modelica/Modelica/issues/2376 https://github.com/modelica/Modelica/issues/2377 https://github.com/modelica/Modelica/pull/2378 https://github.com/modelica/Modelica/issues/2379 (And there seems to be more issues in the Machines-library - I just haven't figured out the exact causes. In addition to older reported unit-problems.)

Based on the large number of actual problems - even with loose rules I would prefer to make the rules as forgiving as possible, so that when users see these messages their natural reaction is that something is wrong in the model - and not that they have to work around a check.

modelica-trac-importer commented 5 years ago

Comment by henrikt on 13 Nov 2017 11:06 UTC The whole point of comment:24 is to improve referential transparency by handle the empty unit using a bottom-up approach, rather than specifying certain contexts where exceptions to the normal rule have to be propagated down the AST.

modelica-trac-importer commented 5 years ago

Comment by henrikt on 13 Nov 2017 11:10 UTC For a start, we would just leave all the relaxation rules out, making a whole lot of things errors according to the specification, but assume that tool vendors won't be that pedantic and instead find out the necessary relaxations that will make their tool usable with existing modeling habits. Maybe this is more or less what you mean by saying things should behave naturally?

modelica-trac-importer commented 5 years ago

Comment by cbuerger on 13 Nov 2017 11:26 UTC Replying to [comment:35 Henrik Tidefelt]:

For a start, we would just leave all the relaxation rules out, making a whole lot of things errors according to the specification, but assume that tool vendors won't be that pedantic and instead find out the necessary relaxations that will make their tool usable with existing modeling habits. Maybe this is more or less what you mean by saying things should behave naturally?

In my opinion, the main reason to have a standard in the first place is to prohibit exactly such tool-dependent behavior. In particular, an error according to the standard must be an error; that is the minimum tool vendors have to agree upon, otherwise the standard is just a fairy-tale. If we want that freedom of acceptance of "errors", the answer is to leave it undefined, because that is what it boils-down to.

modelica-trac-importer commented 5 years ago

Comment by henrikt on 13 Nov 2017 13:04 UTC As an alternative to leaving all relaxation rules out, we could investigate which relaxation rules that would be needed in order to make the MSL valid. It might turn out that none are actually needed. That would allow us to start from a point where only the most obvious ways of using the empty unit are allowed, and later give meaning to usages that would be errors in the first iteration.

On the other hand, if undefined behavior is preferred over error, then we should both give:

I prefer the error approach over undefined, since undefined opens up for using empty units in ways that are not compatible with things we want to define in the future, making the transition to a powerful system for dimensional analysis more painful for everybody in the long run.

modelica-trac-importer commented 5 years ago

Comment by hansolsson on 20 Mar 2018 13:10 UTC Language group: Henrik - prefer to underspecify at first, to allow tools to gracefully handle the odd cases; and only specify unit-checking for the really clear cases.

Also noted that Real x=y; should not use "unit" as an example of attributes that are not equal (start-attribute is a better example).

Henrik - minimum: Arithmetic operations Equality Leave function return values unspecified -- Check the rules based on MSL; Dymola has unit-checking, OpenModelica has several variants (none exactly corresponding to the proposal).

No strong use-cases for inline unit-constructs, can introduce constants with units.

Some uses-cases for non-base-siunits for external interfaces (e.g. FMI - see Modelica.Fluid.Examples.DrumBoiler.DrumBoiler): Three possibilities:

HansOlsson commented 4 years ago

Francesco thinks this has gotten to complicated and proposed to restart:

Hans will check extended tests in Dymola kernel, handling of non-builtin functions. Henrik proposes bottom up check, literals get undefined, and undefined normally get unit "1" - but in some cases it is trivially inferred. Delay functions (for the most part).

casella commented 2 years ago

I'd like to reignite the discussion once more.

As @HansOlsson wrote in his last comment, I started the discussion in #2127 some time ago, but then the scope of the discussion drifted from "how can we make sure that non-dimensional literal constants are treated as such in equations" (in a fully backwards-compatible way) to "how can we add units to literals" (possibly by changing the language syntax) and eventually the whole thing got stuck because of too many options and too many opinions.

Can we try to refocus it on the original intent, i.e. avoiding the wildcard effect of literal constants in equations? These are very widely used in physical models

and it is a shame that unit checking is not performed in those cases, because it may prevent finding bugs. This actually happened to me one year ago while developing the MEV project, eventually I serendipitously found the bug (I forgot a multiplication by the length of a finite volume), but it would have been far better if unit checking had done it in the first place.

At this point I'm no longer sure what is the best (backwards-compatible!) way to solve this other issue, but I believe it is actually a relevant one, not just an academic problem.

henrikt-ma commented 2 years ago

Please have a look at https://github.com/modelica/ModelicaSpecification/issues/2127#issuecomment-435699575 above. It describes a roadmap where we can do some very basic things at first (this MCP), and then gradually refine the mechanism in the future.

HansOlsson commented 2 years ago

Can we try to refocus it on the original intent, i.e. avoiding the wildcard effect of literal constants in equations? These are very widely used in physical models

  • 2 often showing up every time kinetic energy/kinetic pressure, depending on v^2/2, are involved
  • sqrt(2), which often shows up to relate peak values with RMS values
  • 3 and sqrt(3), which are ubiquitous in three-phase AC models
  • 2, 1/2, 4, 1/3, 4/3, which show up in the geometrical formulae for the area of circle, area of triangle, surface of sphere, volume of pyramid, volume of sphere
  • etc.

and it is a shame that unit checking is not performed in those cases, because it may prevent finding bugs. This actually happened to me one year ago while developing the MEV project, eventually I serendipitously found the bug (I forgot a multiplication by the length of a finite volume), but it would have been far better if unit checking had done it in the first place.

At this point I'm no longer sure what is the best (backwards-compatible!) way to solve this other issue, but I believe it is actually a relevant one, not just an academic problem.

I agree and I still think that the only realistic way forward is to start small, so:

  1. Define desired result from both ends for mixing units:

    • That is that we clearly define what we want to forbid, such as w+der(w)
    • We want to allow: w1=2*w;, AngularVelocity w=2;, and h_pT(1e5, 293.15) - but forbid 2*w+der(w)
  2. Define that units will be deduced in some cases: so that Real x=w means that x will have the same unit as w.

HansOlsson commented 2 years ago

Note that the main part about starting small is that there will be a large grey area.

That is because we don't have the necessary information at the moment, and by leaving a grey area we reduce the risk of later changes for things that are currently decided. For similar reasons a detailed roadmap is counter-productive.

henrikt-ma commented 2 years ago
  1. Define that units will be deduced in some cases: so that Real x=w means that x will have the same unit as w.

This sounds convenient, but seems outside the scope of this MCP. On the other hand, to me it seems like a change small enough to fit in a normal issue (no need for another MCP).

henrikt-ma commented 2 years ago

Note that the main part about starting small is that there will be a large grey area.

That is because we don't have the necessary information at the moment, and by leaving a grey area we reduce the risk of later changes for things that are currently decided. For similar reasons a detailed roadmap is counter-productive.

I agree that leaving a large grey area seems unavoidable at the moment. In the end, however, I hope that the specification will completely define how the unit of an expression is determined, and clearly tell what is a unit error and what is not.

Until we are there we must put a name on the grey area, so that it can be distinguished from parts of the unit checking that are actually specified. To me, calling it undefined is the most natural terminology, and the specification should probably include examples of undefined unit of an expression or undefined unit consistency to make it clear that there's a distinction between being defined as an error and being undefined.

The point of mentioning the existence of a roadmap in https://github.com/modelica/ModelicaSpecification/issues/2127#issuecomment-435699575 is not at all to make us decide on a detailed roadmap now. The point is just to show that we can start small and do it in a way that will be extensible when/if we want to reduce the amount of undefinedness in the future.

henrikt-ma commented 2 years ago

We want to allow: w1=2*w;, AngularVelocity w=2;, and h_pT(1e5, 293.15) - but forbid 2*w+der(w)

I wonder if we could avoid h_pT(1e5, 293.15) at least for this MCP, as I find it problematic to have quantities expressed with units in places where the user cannot make a choice of displayUnit (the displayUnit belongs to the function, and is not something one can override on a per-call basis).

casella commented 2 years ago

I wonder if we could avoid h_pT(1e5, 293.15)

We use this all the time in Modelica.Media. I would find it weird that we cannot pass literal values to such functions.

bilderbuchi commented 2 years ago

I'd like to reignite the discussion once more.

As @HansOlsson wrote in his last comment, I started the discussion in #2127 some time ago, but then the scope of the discussion drifted from "how can we make sure that non-dimensional literal constants are treated as such in equations" (in a fully backwards-compatible way) to "how can we add units to literals" (possibly by changing the language syntax) and eventually the whole thing got stuck because of too many options and too many opinions.

Can we try to refocus it on the original intent, i.e. avoiding the wildcard effect of literal constants in equations? These are very widely used in physical models

* `2` often showing up every time kinetic energy/kinetic pressure, depending on `v^2/2`, are involved

* `sqrt(2)`, which often shows up to relate peak values with RMS values

* `3` and `sqrt(3)`, which are ubiquitous in three-phase AC models

* `2`, `1/2`, `4`, `1/3`, `4/3`, which show up in the geometrical formulae for the area of circle, area of triangle, surface of sphere, volume of pyramid, volume of sphere

* etc.

and it is a shame that unit checking is not performed in those cases, because it may prevent finding bugs. This actually happened to me one year ago while developing the MEV project, eventually I serendipitously found the bug (I forgot a multiplication by the length of a finite volume), but it would have been far better if unit checking had done it in the first place.

At this point I'm no longer sure what is the best (backwards-compatible!) way to solve this other issue, but I believe it is actually a relevant one, not just an academic problem.

Indeed, it would be great to achieve at least a small-scoped fix. Avoiding the "wildcard" effect of literal constants would already have an appreciable effect. A colleague/new Modelica user recently encountered this, when he inadvertently wrote part of an equation in reciprocal form (hard to spot at times). Unit checking was enabled, all involved variables typed properly, but the presence of a simple literal 1 in 1/bla... meant that the unit checking could not report the error. I only found the error after a deeper (and totally avoidable) investigation of puzzling model behaviour.

What could be the smallest change that would achieve fixing/improving that one issue, while retaining enough flexibility/grey area to not paint the specification into a corner regarding future modifications? Maybe a TBD portion of @henrikt-ma proposed "action list" (to avoid the term roadmap)? I tried to absorb this long discussion, but it is indeed very involved/complex at this point.

By the way, the text of MCP0027 seems unavailable -- the link in the OP is dead, and there does not seem to be a branch in this repository for it.

bilderbuchi commented 2 years ago

I wonder if we could avoid h_pT(1e5, 293.15)

We use this all the time in Modelica.Media. I would find it weird that we cannot pass literal values to such functions.

For handling unitful quantities in Python, I am fond of the Pint package. This package also has a way to gradually convert a unitless codebase to unitful handling. There are wrapping methods that allow one to specify the units of inputs/outputs of unitless Python functions:

>>> import pint
>>> ureg = pint.UnitRegistry()
>>> from math import pi, sqrt

>>> @ureg.wraps(ureg.second, ureg.meter)  # output in seconds, input in meter
... def pendulum_period(length):
...     return  2*pi*sqrt(length/9.81)

>>> pendulum_period(10*ureg.centimeter)
0.6343739849219413 <Unit('second')>

In Modelica we don't necessarily need that, as our inputs/outputs can be/are already unitful. However, one detail is the "non-strict mode" flag. In this mode, the given value is assumed to have the correct units.

>>> @ureg.wraps(ureg.second, ureg.meter, strict=False)
... def pendulum_period(length):
...     return  2*pi*sqrt(length/9.81)

>>> pendulum_period(0.1)
0.6343739849219413 <Unit('second')>

Maybe an implicit "non-strict" mode could be helpful to deal with the case/convenience of passing unitless literals like in h_pT(1e5, 293.15)?

In general, I think the way pint handles units can serve as a good inspiration, as the devs have accounted for a number of issues already mentioned herein (polymorphic types, temperature conversion, how to construct units, also logarithmic units ).

casella commented 2 years ago

@bilderbuchi thanks for setting the ball rolling again, it's good (or maybe bad?) to hear you had exactly my same experience with literals acting as wildcards and preventing unit checking from rescuing you from stupid mistakes.

I think a good proposal to resolve this ticket should be minimally invasive, i.e.,

  1. not require any change to the grammar or to the language specifications
  2. not require to add anything to existing code to catch dimensionally challenged expressions that get away with unit checking because of the literal unit wildcard effect
  3. avoid generating unit checking errors for any commonly used Modelica coding pattern (that is not dimensionally challenged, that is)
  4. require minimum discussion, so it can get into Modelica 3.6 without too much effort
  5. not prevent by any means any possible future development such as those laid out in the #2127 roadmap, by introducing half-baked solutions that prevent us to address the more general problem in better way later on

I hope everybody agrees to these points. A proposal according to them will solve some problems (not all, but some), while not creating any others. If anyone wants to do more, I have no objections, but please do so in another ticket. This one is only meant to define the units of literal constants in existing Modelica code. Anything we do according to the previous five points will not hamper by design any more elaborate proposal.

My proposal: add a Section 2.4.5 Units of Literal Constants to the language specification with the following content:

Literal Real and Integer constants are implicitly meant to have unit = "1" if they show up in expressions containing variables, parameters, or constants that have a non-empty unit attribute string. The inputs of transcendental built-in functions such as sin(), cos(), exp(), etc. should have unit "1" if they involve expressions containing variables, parameters, or constants that have a non-empty unit attribute string; their outputs are implicitly assumed to have unit "1" if they are called within expressions containing variables, parameters, or constants that have a non-empty unit attribute string.

With this proposal, we have the following outcome of dimensional analysis in these interesting cases (the units of variables is obvious from the context):

Note that rejected doesn't mean that the model cannot be compiled, it just means that one gets a warning by the unit checking module. One is the free to do whatever he wants to with it, including plain ignoring it, or suppressing it with some flag.

In order to avoid the discussion from blowing up again, I would kindly ask you to react to this proposal with comments or improvements if you think it doesn't adequately address the five points I mentioned, mainly because there are some existing use patterns that make sense but are not covered by the proposal.

Please do not try to add more features to this proposal here, feel free to do it in a different ticket. This one is only meant to define what is the unit attribute of literal constants in existing Modelica models.

If we manage to get some consensus on it, possibly with some amendments or improvments, then I can (re)start the proper MCP process.