Message Boards Message Boards

GROUPS:

Can a type be set globally using inner/outer and also be replaceable?

Posted 1 month ago
489 Views
|
11 Replies
|
4 Total Likes
|

Problem Description & Motivation: Using Non-SI-Units for time

This post culminates earlier questions I voiced on Community (Unit checking and making use of unit attributes and Why can't a parameter be used to set the unit attribute?). I am writing a library for System Dynamics modeling for business, economics, and social sciences. From what I have learned in the discussions cited above, treating time in models one should preferably do what engineers do: Use SI-units whenever possible and then use displayUnit to convert them to whatever you want.

There are a few inconveniences though: The displayUnit for the Modelica var time cannot be modified and unfortunately in diagrams there is no nice drop-down selection for a different displayUnit. Also, everything internally is stored in seconds making parameter values rather bizarre when we think in months or years.

So, writing a library I would like the user to make a choice of a global type called ModelTime which ideally would be declared as inner and replaceable at some top-level class. Then any component within a model written using the libary could use the global type to consistently treat any time-related vars.

Minimal Example

The following example shows how I would like to implement this.

  • package Units declares two Non-SI Unit types ( Time_year, Time_month)
  • package Interfaces contains a partial model class GenericSimulationModel which will be the top-level scope for any model written using the library. It is supposed to provide the type ModelTime as an inner and replaceable class
  • package Components defines a simple block class that uses ModelTime via an outer construct to define its output y that simply shows time in the globally chosen units of time
  • model Example ties all of this together to provide an example how any model using the library should work out

Here is the code:

model MinimalExample
  package Units
    type Time_year = Real(final quantity = "Time", final unit = "yr");    
    type Time_month = Real(final quantity = "Time", final unit = "mo");
  end Units;

  package Interfaces
    partial model GenericSimulationModel "Top-level model scope providing global vars"
      inner replaceable type ModelTime = Years "Set to : Months, Years";
    protected
      type Years = Units.Time_year;
      type Months = Units.Time_month;
    end GenericSimulationModel;
  end Interfaces;

  package Components
    block ComponentUsingTime
      outer type ModelTime = MinimalExample.Units.Time_year;
      output ModelTime y;
    equation
      y = time;
    end ComponentUsingTime;
  end Components;

  model Example
    extends Interfaces.GenericSimulationModel(
      redeclare replaceable type ModelTime = Months
    );
    Components.ComponentUsingTime c;
  end Example;
equation

end MinimalExample;

While this model compiles without error it does not work out as I intended it: The redelcared type is not used within the component so that it remains set to "years" and not "months".

What can I do to achieve what I want to do?

Note: I crossposted the question on StackOverflow.

11 Replies

... continued from above

Inner/Outer with a simple constant

I am even more perplexed by the following. Let's simplify the above example, instead of using complicated type constructs, we might simply define a constant String UnitOfTime that will be set to provide the string-expression needed to define unit = "some unit" for a var in a sub-component. Again, that constant expression will be given the prefix inner (note, that we should not be needing replaceable here, as we may use simple modification instead).

Minimal Library

The "library" now only consists of a partial model GenericSimulationModel where the unit-string is defined and a very simple component SimpleClock that is to return the simulation time in the Non-SI-Unit chosen. Within that component we use the unit-string given as a global constant directly to configure the output y:

output Real y( final quantity = "Time", unit = UnitOfTime );

Here is the complete MinimalLibrary - code for this:

package MinimalLibrary "Example for setting a global unitString constant"

  package Interfaces "Partial models and connectors"

    partial model GenericSimulationModel "Top-level class defining global parameters for simulation model"
      inner constant String UnitOfTime = "yr";
    end GenericSimulationModel;

  end Interfaces;

  package Components

    block SimpleClock "Return the simulation time in Non-SI-Unit of time"
      outer constant String UnitOfTime;
      output Real y(final quantity = "Time", unit = UnitOfTime);
    equation
      y = time;
    end SimpleClock;

  end Components;

  model Example "Show a simple clock functionality"
    extends Interfaces.GenericSimulationModel( UnitOfTime = "mo");
    Components.SimpleClock clock;
  end Example;

end MinimalLibrary;

Errors and Warnings

Warning issued for missing start value for outer declaration

While the whole library code above validates sucessfully, there is a warning given when validating the SimpleClock in the Components package which honestly I do not understand:

Warning

System Modeler seems to issue a warning, because within the outer declaration there is no start value given? But that in my opinion is how you are supposed to write outer declarations. I will cite Peter Fritzon:

Outer class declarations should be defined using the short class definition. Modifiers to such outer class declarations are only allowed if the inner keyword is also present, i.e., a simultaneous inner outer class declaration.

The last point is illustrated by the following erroneous model:

class A 
  outer parameter Real p = 2; // Error, since declaration equation needs inner 
end A;

Fritzson, Peter. Principles of Object-Oriented Modeling and Simulation with Modelica 3.3: A Cyber-Physical Approach (Kindle-Positionen8875-8881). Wiley. Kindle-Version.

Error given when running the Example model in Simulation Center

Trying to run the model Example will not finish since the model compiles with an error:

Error

Note: The error will arise also, when the modification (UnitOfTime = "mo") is removed.

After having learned, that we must use constants to set the unit attribute (here), I am rather perplexed to now find, that this will not work out when we use inner/outer declarations.

Is there something wrong with the way inner and outer are implemented?

Posted 1 month ago

The behavior you see for MinimalExample.example and MinimalLibrary.Example are bugs, and from what I can see they should work, I have forwarded them to a developer working on these things.

As for the validation of MinimalLibrary.Components.SimpleClock, what happens is that you have an outer component (outer constant String unitOfTime). The rules for inner/outer in Modelica (this was updated recently) is that an outer element requires that an inner element exists or can be created in a unique way. In this case, there exists no inner element, but a unique inner element can be created, by simply taking the outer declaration as it is (this is basically what the first warning says). The second warning appears because the constant has no binding (although I am not sure why it is referred to as a parameter).

We have recently implemented some support for setting the displayUnit for the x-axis in SimulationCenter, currently it only goes up to days, maybe we should see if we could add years as well. I guess you would like a way to specify this in the model so you don't have to change the plots all the time.

As for the parameters in the diagram view, I assume you are referring to things like this:

Inertia with parameter

which seems to always use the unit. So, it would be better for you if it was displayed in the displayUnit, maybe with the displayUnit as well? So, in the picture above, it would read "J = 1.7 kgm^2", or if the displayUnit was changed to lb ft^2, it would say "J = 40.34 lb ft^2".

Carl,

Thank you for confirming the impression I had about the examples given. Making these things work out in WSM would be very helpful. The warning (besides falsly referring to a parameter) given for the outer declarationg does make sense as we talk about it. It may be better to work with inner outer instead if one cannot be sure about when and where eventually an inner construct is placed?

With regard to displayUnit it would be nice, if this could be influenced globally (maybe then to be overriden by user choice?) in a model for:

  • display of time in plots
  • display of parameters in icon- and diagram-view
  • actually, any output of model entities and possibly also for inputs (cf. Time Scale parameter for MCTT)...

Regarding the conversion of times and (!) rates I would suggest to have: years (yr), quarter years (qtr), months (mo), and weeks (wk) available (next to d, h, min, s) , as these are all reasonable units of time depending upon the setting and they also reflect the choices a user has in most dedicated SD modeling software.

Note: The abbreviation "y" , which is by default already included in the unit conversion table, to me looks a lot less common - I would suggest to go for "yr" instead.

The convenient treatment of rates is (unfortunately) a matter I have not really solved yet: To my understanding, one would have to define a conversion for each and every rate possible, e.g. there needs to be different conversion for apples/second and oranges/second. Thus, working with seconds as the principal unit of time necessitates, in my opinion, that one drops all units for counting stocks in System Dynamics (e.g. money, people, equipment, factories, shops, etc.) so that we only need to convert dimensionless rates (e.g. 1/s)? -- I would be very happy to hear, that this understanding of mine were wrong, and I would greatly appreciate, if you could offer a better way to deal with rates.

Guido

I cannot see that SystemModeler comes with a conversion to/from years. I assume that the reason for that is that there are many years to choose from:

  • Calendar year
  • Calendar leap year
  • Calendar year average
  • Tropical year

Otto

But WSM as of Version 12.0 already ships with a predefined conversion:

Screenshot

The first entry is the predefined value and the second one is my custom entry. As you can see, assuming 365 days/year most often suffices.

Your doubts about a single, unchallenged definition for one year in seconds is of course very to the point (scientifically and technically) and they concisely illustrate the difficulties in adapting Modelica/WSM to the more abstract and diffuse requirements of business and social sciences modeling.

Very often, e.g. in strategic business modeling, we will have much more important uncertainties to worry about than imprecise definitions for a "year". If accuracy really matters, then we should indeed go back to days. Most data we get are concerned with some kind of period average and even then other factors matter (e.g. revenue as a function of business days not days in a calendar year; a leap year might have actually less business days than a regular year...).

Ultimately, you are imo making a case for leaving it all up to the modeler and the model's user to judge for themselves what they need in a given context:

Indeed agood case in point for allowing to have a custom conversion table that can easily be applied to modify the display of the system time.

Guido

I was focused looking for the time unit so I scrolled immediately down to s and there isn't a s to y conversion.

Indeed agood case in point for allowing to have a custom conversion table that can easily be applied to modify the display of the system time.

I totally agree with you, in a dream scenario you should also be able to get a date/time display of system time given a specified epoch.

Otto

Otto,

what about rates? Is there a remedy for the apples/second and oranges/second problem I mentioned above?

Thanks.

Guido

Posted 1 month ago

Hello Guido,

We have had some discussions and we think we possibly could implement some automatic support for handling conversion of rates. We have considered a scenario where you have a variable with unit "apples/s" and a displayUnit "apples/year". Given that you have a conversion "1/s" to "1/year" the conversion for "apples/s" to "apples/year" would automatically be inferred. Would that be what you want?

Carl,

that would indeed be a great help. At the moment I am simply reducing everything (using an enumeration called "UnitChoices") to:

  • Dmnl ("1")
  • Rate ("1/s")
  • Time ("s")

Thinking about this from a model user's perspective: You would want to place a stock or a converter (Block) element somewhere and then assign a unit to its output (more tricky for complex sub-system elements with lots of different outputs, which rather call for bus-like output connectors - which would not be expandable per se ?).

That user-dialog has to be made "easy to use" (which is why I simply go for drop-down enumeration) and "reliable". If the user has to enter a string, say "apples/s", then there is some danger of a typo and everything breaks down.

Maybe with what you are saying one could come up with a mix: use a drop-down field to distinguish the choices like the ones listed above and additionally allow the user to overwrite the dimensionless part of the drop-down choice: "1". In that case, the user can choose a Rate and then the string "apple" or "apples".

Hope I have written this clearly?

Guido

PS: My understanding is, that what I have described for the user is the reverse procedure of what you will probably be doing, e.g. parse a string for "something/s" and then replace something -> 1?

Posted 1 month ago

It might be that I don't know enough about what you are modeling, but it seems to me that an output of a specific block would be either dimensionless, a rate or time, and for a specific block you don't really want to change it?

In the package below I have demonstrated some ways to set units in the GUI, and the model Test shows all four of them.

The block Item has an output that is something, e.g. "orange". What this is is set by the constant itemUnit, which shows up in the GUI in the "General" tab in the group "Units" (this is set by the annotation following the declaration of itemUnit).

The block Rate has an output that is a rate of something, e.g. "apple/s". You can set what "X" in "X/s" through the"rateUnit constant, which also shows up in the GUI. Here I have also set a displayUnit to be "X/h", but since we don't have the feature described in my last post, SimulationCenter won't be able to display in "X/h" unless X is 1.

In the block Generic I have done something like what you described. The block output has a unit that is either "1", "s" or "1/s", and this can be chosen with a drop-down list. The drop-down list is created from the choices part of the annotation after the declaration of the output, each which has a part that is the modification to be applied (e.g. "= 1/s") and a description of what it is (e.g. "rate"). The description is what is shown in the drop-down menu.

Finally, in the block GenericWithX you can specify both a base unit (e.g. "banana" or "second") and specify it is a rate or not.

Concerning your PS, yes, that would in principle be what it would do; read "X/s", then use for example,the rule for "1/s" -> "1/year" to convert to "X/year".

package UnitsAndRates
  model Test
    Item item1(itemUnit = "apple") annotation(Placement(visible = true, transformation(origin = {-30, 57.954}, extent = {{-10, -10}, {10, 10}}, rotation = 0)));
    Rate rate1(rateUnit = "orange") annotation(Placement(visible = true, transformation(origin = {-10, 10}, extent = {{-10, -10}, {10, 10}}, rotation = 0)));
    Generic generic1(genericUnit = "1/s") annotation(Placement(visible = true, transformation(origin = {-65, -40}, extent = {{-10, -10}, {10, 10}}, rotation = 0)));
    GenericWithX genericWithX1(base = "banana") annotation(Placement(visible = true, transformation(origin = {50, -2.046}, extent = {{-10, -10}, {10, 10}}, rotation = 0)));
    GenericWithX genericWithX2(base = "kiwi", rate = "/s") annotation(Placement(visible = true, transformation(origin = {30, -62.465}, extent = {{-10, -10}, {10, 10}}, rotation = 0)));
    annotation(Diagram(coordinateSystem(extent = {{-150, -90}, {150, 90}}, preserveAspectRatio = true, initialScale = 0.1, grid = {5, 5})), Icon(coordinateSystem(extent = {{-100, -100}, {100, 100}}, preserveAspectRatio = true, initialScale = 0.1, grid = {10, 10}), graphics = {Rectangle(visible = true, lineColor = {0, 114, 195}, fillColor = {255, 255, 255}, extent = {{-100, -100}, {100, 100}}, radius = 25), Text(visible = true, textColor = {64, 64, 64}, extent = {{-150, 110}, {150, 150}}, textString = "%name")}));
  end Test;

  block Rate
    constant String rateUnit = "1" annotation(Dialog(group = "Units"));
    Modelica.Blocks.Interfaces.RealOutput y(unit = rateUnit + "/s", displayUnit = rateUnit + "/h") annotation(Placement(visible = true, transformation(origin = {155, 0}, extent = {{-10, -10}, {10, 10}}, rotation = 0), iconTransformation(origin = {101.511, 0}, extent = {{-10, -10}, {10, 10}}, rotation = 0)));
  equation
    y = 1 + cos(sin(time));
    annotation(Diagram(coordinateSystem(extent = {{-150, -90}, {150, 90}}, preserveAspectRatio = true, initialScale = 0.1, grid = {5, 5})), Icon(coordinateSystem(extent = {{-100, -100}, {100, 100}}, preserveAspectRatio = true, initialScale = 0.1, grid = {10, 10}), graphics = {Rectangle(visible = true, lineColor = {0, 114, 195}, fillColor = {255, 255, 255}, fillPattern = FillPattern.Solid, extent = {{-100, -100}, {100, 100}}), Text(visible = true, textColor = {64, 64, 64}, extent = {{-150, 110}, {150, 150}}, textString = "%name"), Text(visible = true, origin = {-0, 3.332}, extent = {{-100, -46.668}, {100, 46.668}}, textString = "Rate")}));
  end Rate;

  block Item
    constant String itemUnit = "1" annotation(Dialog(group = "Units"));
    Modelica.Blocks.Interfaces.RealOutput y(unit = itemUnit, displayUnit = itemUnit) annotation(Placement(visible = true, transformation(origin = {153.595, 0}, extent = {{-10, -10}, {10, 10}}, rotation = 0), iconTransformation(origin = {102.397, 0}, extent = {{-10, -10}, {10, 10}}, rotation = 0)));
  equation
    y = 1 + cos(sin(time));
    annotation(Diagram(coordinateSystem(extent = {{-150, -90}, {150, 90}}, preserveAspectRatio = true, initialScale = 0.1, grid = {5, 5})), Icon(coordinateSystem(extent = {{-100, -100}, {100, 100}}, preserveAspectRatio = true, initialScale = 0.1, grid = {10, 10}), graphics = {Rectangle(visible = true, lineColor = {0, 114, 195}, fillColor = {255, 255, 255}, extent = {{-100, -100}, {100, 100}}, radius = 25), Text(visible = true, textColor = {64, 64, 64}, extent = {{-150, 110}, {150, 150}}, textString = "%name"), Text(visible = true, extent = {{-100, -40}, {100, 40}}, textString = "Item")}));
  end Item;

  block Generic
    constant String genericUnit = "1" annotation(Dialog(group = "units"), choices(choice = "1" "dimensionless", choice = "s" "time", choice = "1/s" "rate"));
    Modelica.Blocks.Interfaces.RealOutput y(unit = genericUnit) annotation(Placement(visible = true, transformation(origin = {155, 0}, extent = {{-10, -10}, {10, 10}}, rotation = 0), iconTransformation(origin = {103.333, 0}, extent = {{-10, -10}, {10, 10}}, rotation = 0)));
  equation
    y = 1 + sin(cos(sin(time)));
    annotation(Diagram(coordinateSystem(extent = {{-150, -90}, {150, 90}}, preserveAspectRatio = true, initialScale = 0.1, grid = {5, 5})), Icon(coordinateSystem(extent = {{-100, -100}, {100, 100}}, preserveAspectRatio = true, initialScale = 0.1, grid = {10, 10}), graphics = {Rectangle(visible = true, lineColor = {0, 114, 195}, fillColor = {255, 255, 255}, fillPattern = FillPattern.Solid, extent = {{-100, -100}, {100, 100}}), Text(visible = true, textColor = {64, 64, 64}, extent = {{-150, 110}, {150, 150}}, textString = "%name")}));
  end Generic;

  block GenericWithX
    constant String base = "1" annotation(Dialog(group = units));
    constant String rate = "" annotation(Dialog(group = units), choices(choice = "" "not rate", choice = "/s" "rate"));
    Modelica.Blocks.Interfaces.RealOutput y(unit = base + rate) annotation(Placement(visible = true, transformation(origin = {156.679, 0}, extent = {{-10, -10}, {10, 10}}, rotation = 0), iconTransformation(origin = {101.922, -2.301}, extent = {{-10, -10}, {10, 10}}, rotation = 0)));
  equation
    y = 1 + sin(sin(time));
    annotation(Diagram(coordinateSystem(extent = {{-150, -90}, {150, 90}}, preserveAspectRatio = true, initialScale = 0.1, grid = {5, 5})), Icon(coordinateSystem(extent = {{-100, -100}, {100, 100}}, preserveAspectRatio = true, initialScale = 0.1, grid = {10, 10}), graphics = {Rectangle(visible = true, lineColor = {0, 114, 195}, fillColor = {255, 255, 255}, fillPattern = FillPattern.Solid, extent = {{-100, -100}, {100, 100}}), Text(visible = true, textColor = {64, 64, 64}, extent = {{-150, 110}, {150, 150}}, textString = "%name"), Text(visible = true, origin = {1.009, 0.32}, extent = {{-101.009, -99.68}, {101.009, 99.68}}, textString = "X")}));
  end GenericWithX;
  annotation(Diagram(coordinateSystem(extent = {{-150, -90}, {150, 90}}, preserveAspectRatio = true, initialScale = 0.1, grid = {5, 5})), Icon(coordinateSystem(extent = {{-100, -100}, {100, 100}}, preserveAspectRatio = true, initialScale = 0.1, grid = {10, 10}), graphics = {Polygon(visible = true, origin = {0.248, 0.044}, lineColor = {56, 56, 56}, fillColor = {128, 202, 255}, fillPattern = FillPattern.Solid, points = {{99.752, 100}, {99.752, 59.956}, {99.752, -50}, {100, -100}, {49.752, -100}, {-19.752, -100.044}, {-100.248, -100}, {-100.248, -50}, {-90.248, 29.956}, {-90.248, 79.956}, {-40.248, 79.956}, {-20.138, 79.813}, {-0.248, 79.956}, {19.752, 99.956}, {39.752, 99.956}, {59.752, 99.956}}, smooth = Smooth.Bezier), Polygon(visible = true, origin = {0, -13.079}, lineColor = {192, 192, 192}, fillColor = {255, 255, 255}, pattern = LinePattern.None, fillPattern = FillPattern.HorizontalCylinder, points = {{100, -86.921}, {50, -86.921}, {-50, -86.921}, {-100, -86.921}, {-100, -36.921}, {-100, 53.079}, {-100, 103.079}, {-50, 103.079}, {0, 103.079}, {20, 83.079}, {50, 83.079}, {100, 83.079}, {100, 33.079}, {100, -36.921}}, smooth = Smooth.Bezier), Rectangle(visible = true, origin = {0, -5}, lineColor = {198, 198, 198}, fillColor = {238, 238, 238}, pattern = LinePattern.None, fillPattern = FillPattern.Solid, extent = {{-100, -25}, {100, 25}}), Polygon(visible = true, origin = {-0, -10.704}, lineColor = {113, 113, 113}, fillColor = {255, 255, 255}, points = {{100, -89.296}, {50, -89.296}, {-50, -89.296}, {-100, -89.296}, {-100, -39.296}, {-100, 50.704}, {-100, 100.704}, {-50, 100.704}, {0, 100.704}, {20, 80.704}, {50, 80.704}, {100, 80.704}, {100, 30.704}, {100, -39.296}}, smooth = Smooth.Bezier)}));
end UnitsAndRates;

Carl,

Actually, the choice in the GenericWithX case would be nested like this:

  1. Time or not Time? (If Time then unit = "s" /* choice ends here */ else NestedChoice 2)
  2. Rate or Not Rate? (In both cases determine base unit (e.g. replacement for "1"); If Rate then add "/s" to the base unit)

Something like this would be helpful.

I am hoping for some of the functionality mentioned by you and Otto being added soon and thus I will go for "seconds" in time and rates. ;-)

Note: That setting up an experiment (e.g. start time and stop time) for a simulation that goes from start time = 2010 [yr] to 2030 [yr] in seconds is PITA. One also has to be very disciplined in setting up TimeTables (e.g. start values with 0 or an actual date).

Guido

Reply to this discussion
Community posts can be styled and formatted using the Markdown syntax.
Reply Preview
Attachments
Remove
or Discard

Group Abstract Group Abstract