ModelicaSpecification icon indicating copy to clipboard operation
ModelicaSpecification copied to clipboard

MCP-0027 Units of Literal Constants

Open modelica-trac-importer opened this issue 5 years ago • 91 comments

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 avatar Nov 04 '18 19:11 modelica-trac-importer

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

Conclusions

  • only specify when the unit is "1"

Make sure it works with

  • if expressions
  • relations
  • ideal diode model in MSL
  • degC to K conversion in MSL

modelica-trac-importer avatar Nov 04 '18 19:11 modelica-trac-importer

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 avatar Nov 04 '18 19:11 modelica-trac-importer

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 avatar Nov 04 '18 19:11 modelica-trac-importer

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 avatar Nov 04 '18 19:11 modelica-trac-importer

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 avatar Nov 04 '18 19:11 modelica-trac-importer

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?:

  • Unit("ohm") * 2
  • 2 * Unit("m") * Unit("m") or 2 * Unit("m")^2
  • 2 * f() whereas f returns Unit("ohm")
  • 2 + Unit("ohm")
  • 2 * Unit(f()) with f returning a string
  • 2 * f() with f returning the string "ohm" (is that a type error or a proper unit specification for the literal 2 considering the proposed 2 * "ohm" notation)

modelica-trac-importer avatar Nov 04 '18 19:11 modelica-trac-importer

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 avatar Nov 04 '18 19:11 modelica-trac-importer

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 avatar Nov 04 '18 19:11 modelica-trac-importer

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 avatar Nov 04 '18 19:11 modelica-trac-importer

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 avatar Nov 04 '18 19:11 modelica-trac-importer

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

modelica-trac-importer avatar Nov 04 '18 19:11 modelica-trac-importer

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 avatar Nov 04 '18 19:11 modelica-trac-importer

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 avatar Nov 04 '18 19:11 modelica-trac-importer

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 avatar Nov 04 '18 19:11 modelica-trac-importer

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 avatar Nov 04 '18 19:11 modelica-trac-importer

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 avatar Nov 04 '18 19:11 modelica-trac-importer

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 avatar Nov 04 '18 19:11 modelica-trac-importer

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 avatar Nov 04 '18 19:11 modelica-trac-importer

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

modelica-trac-importer avatar Nov 04 '18 19:11 modelica-trac-importer

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 avatar Nov 04 '18 19:11 modelica-trac-importer

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 avatar Nov 04 '18 19:11 modelica-trac-importer

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 avatar Nov 04 '18 19:11 modelica-trac-importer

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 avatar Nov 04 '18 19:11 modelica-trac-importer

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:

  • In binding equations and modifications:
    • The entire expression of the binding or modification.
    • When the entire expression is an array construction, array concatenation and array range, then apply rules recursively for the direct subexpressions.
  • For a translation-time constant with value 0.0:
    • Expressions constituting the entire side of an equality or relation.
    • The right hand side of an assignment.
    • The direct subexpressions of array construction, array concatenation, and array range.

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:

  • Real literals.
  • Integer expressions implicitly cast to Real.

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):

  • Negation, addition, subtraction (scalar or element-wise)
  • abs, mod, rem, ceil, floor
  • fill, linspace, min, max, sum
  • transpose, symmetric, skew
  • delay
  • pre
  • ...

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:

  • Multiplication and division when both operands have empty unit.
  • Certain built-in functions, like sqrt.
  • der
  • ...

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:

  • atan2 takes two argument of the same non-empty unit, and returns unit "1".
  • Trigonometric functions take argument of unit "1", and return unit "1".
  • integer takes argument of unit "1".
  • sqrt takes argument of unit u^2, and returns unit u.
  • der takes argument of unit u, and returns u/s.

modelica-trac-importer avatar Nov 04 '18 19:11 modelica-trac-importer

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 avatar Nov 04 '18 19:11 modelica-trac-importer

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:

  • 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".

modelica-trac-importer avatar Nov 04 '18 19:11 modelica-trac-importer

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 avatar Nov 04 '18 19:11 modelica-trac-importer

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 avatar Nov 04 '18 19:11 modelica-trac-importer

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 avatar Nov 04 '18 19:11 modelica-trac-importer

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 avatar Nov 04 '18 19:11 modelica-trac-importer