ModelicaSpecification icon indicating copy to clipboard operation
ModelicaSpecification copied to clipboard

MCP-0012: Calling blocks as functions

Open modelica-trac-importer opened this issue 6 years ago • 28 comments

Reported by otter on 12 Jun 2014 04:18 UTC A new Modelica Change Proposal Idea (MCPI 0012) has been submitted on svn to call blocks as functions in a restricted form. The MCPI is found here

  https://svn.modelica.org/projects/MCP-Ideas/MAinternal/MCPI-0012_CallingBlocksAsFunctions/MCPI-0012_CallingBlocksAsFunctions.pdf

This ticket shall support the corresponding discussion for the MCPI and enable everyone to give feedback


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

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

Comment by msasena on 12 Jun 2014 19:09 UTC I'd like to better understand what restrictions are limiting the proposal to only applying this concept to the declaration section. There could be some real advantages to allowing it in the equation section as well. Let's explore what might happen if the concept were applied in an equation section and extended to allowing any multiple outputs. Consider this example:

block B1
  input Real u1, u2;
  output Real y1;
  Real x(start=0);
equation
  der(x) = x + u1 + u2;
  when u1 > 0 then
    reinit(x, 0);
  end when;
  y1 = 2 * x;
end B1;

block B2
  input Real u1, u2;
  output Real y1, y2;
  Real x(start=0);
equation
  der(x) = x + u1 + u2;
  y1 = x - u1;
  y2 = x + u2;
end B2;

model M
  Real x1(start=0), x2(start=0), x3(start=0);
  protected Real der_x1, der_x2;
equation
  (der_x1, der_x2) = B2(x1, x2); // anonymous instantiation of B2
  der(x1) = der_x1;
  der(x2) = der_x2;
  0 = x3 + B1(x1, x2); // anonymous instantiation of B1
end M;

This would be conceptually expanded to the following:

model instance m
  Real x1(start=0), x2(start=0), x3(start=0);
  B1 b1;
  B2 b2;
  protected Real der_x1, der_x2;
equation
  b2.u1 = x1;
  b2.u2 = x2;
  der_x1 = b2.y1;
  der_x2 = b2.y2;
  der(x1) = der_x1;
  der(x2) = der_x2;
  b1.u1 = x1;
  b1.u2 = x2;
  0 = x3 + b1.y1;
end m;

then to:

model instance m
  Real x1(start=0), x2(start=0), x3(start=0);
  Real b1.u1, b1.u2;
  Real b1.y1;
  Real b1.x(start=0);
  Real b2.u1, b2.u2;
  Real b2.y1, b2.y2;
  Real b2.x(start=0);
equation
  b2.u1 = x1;
  b2.u2 = x2;
  der_x1 = b2.y1;
  der_x2 = b2.y2;
  der(x1) = der_x1;
  der(x2) = der_x2;
  b1.u1 = x1;
  b1.u2 = x2;
  0 = x3 + b1.y1;
  der(b2.x) = b2.x + b2.u1 + b2.u2;
  b2.y1 = b2.x - b2.u1;
  b2.y2 = b2.x + b2.u2;
  der(b1.x) = b1.x + b1.u1 + b1.u2;
  when b1.u1 > 0 then
    reinit(b1.x, 0);
  end when;
  b1.y1 = 2 * b1.x;
end m;

And of course you can do this with any kind of type-compatible block. If the execution semantics is given by an algorithm, then simply add the corresponding algorithm to the final expanded class instance. Where does the difficulty lie in allowing calling blocks with functional syntax as shown above? What we gain with this feature is that:

  1. functions mostly become redundant in Modelica since a block can include algorithms just like functions (handling external code may be a separate issue), and
  2. we potentially have a clean way to deal with events, since blocks handle them properly (as regular Modelica models, contrary to functions)

The second point is the important one. Blocks already have a clearly defined way of handling events. Moreover they support both algorithm and equation sections. There have been a lot of discussions about event handling in functions and the problems posed because functions only allow algorithms. By allowing blocks to be used with a similar syntax to function calls, we've provided a clean way to handle events with "function-like" calls using the existing Modelica semantics. It's purely a syntatic addition and therefore relatively straightforward for tool vendors to implement. Also, as suggested in Design Meeting 82, this may address some of the problems with fluid properties models.

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

Comment by choeger on 12 Jun 2014 19:47 UTC Replying to [comment:1 msasena]:

  1. functions mostly become redundant in Modelica since a block can include algorithms just like functions (handling external code may be a separate issue), and

While I support the idea of generalization, I have to disagree with you on that point. As you mention in your second point, there is a distinct difference between a block-instance and a function application (namely that the former one has a clear influence on what the solver "sees" of the model, e.g. regarding event handling).

So I'd like to add the following proposal: Make block-application possible but syntactically distinguishable from function applications. IMO it is better in general, to make different things look differently. Overloading the syntax too much produces confusion and errors.

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

Comment by otter on 13 Jun 2014 14:55 UTC Replying to [comment:2 choeger]:

Replying to [comment:1 msasena]:

  1. functions mostly become redundant in Modelica since a block can include algorithms just like functions (handling external code may be a separate issue), and

While I support the idea of generalization, I have to disagree with you on that point. As you mention in your second point, there is a distinct difference between a block-instance and a function application (namely that the former one has a clear influence on what the solver "sees" of the model, e.g. regarding event handling).

So I'd like to add the following proposal: Make block-application possible but syntactically distinguishable from function applications. IMO it is better in general, to make different things look differently. Overloading the syntax too much produces confusion and errors.

Unfortunately, it is even worse: The goal is that a block instance with a modifier is used in a similar way as a function call. The grammar for a modifier and for a function call is, however, different:

// Class modification
class_modification :
   "(" [ argument_list ] ")"

argument_list :
   argument { "," argument }

argument :
   element_modification_or_replaceable 
   element_redeclaration


// Function arguments
function_arguments :
   function_argument [ "," function_arguments | for for_indices ]
 | named_arguments

named_arguments: named_argument [ "," named_arguments ]

named_argument: IDENT "=" function_argument

function_argument : 
   function name "(" [ named_arguments ] ")" | expression

Especially, in a modifier "replaceable" and "redeclare" are possible, as well as hierarchical modifiers (but not in a function call), whereas in a function call "for expressions" and "function partial applications" are possible (but not in a modifier).

This means one has either to further restrict the usages of blocks called as a function (allow only modifiers that follow the grammar for a function call), or distinguish block and function calls on the lexical level (as Christoph proposes it).

Since the grammar identifies a function call by "name(", one would have to find something else, for example by using a different parenthesis such as "name{". However, every change to a standard function call will be surprising and not natural for a user. For example:

Boolean y2 = OnDelay{u=OnDelay{u=u2, delayTime=0.1}, delayTime=0.05};

So this seems to be not a good solution.

The only remedy seems to be, to restrict the allowed modifiers in a block when called as a function:

  • no hierarchical modifier
  • no redeclaration
  • no replaceable element I think these restrictions are o.k. and one could live with them

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

Comment by anonymous on 13 Jun 2014 15:12 UTC Replying to [comment:3 otter]:

Since the grammar identifies a function call by "name(", one would have to find something else, for example by using a different parenthesis such as "name{". However, every change to a standard function call will be surprising and not natural for a user. For example:

Boolean y2 = OnDelay{u=OnDelay{u=u2, delayTime=0.1}, delayTime=0.05};

So this seems to be not a good solution.

Could you elaborate a little bit more? Except for the obvious fact that the concrete syntax is debatable, I would consider this a clarification and not a surprise. Especially, when the tool might produce high-quality error messages ala "you are using a block as a function, you should use curly braces [or whatever]". In what way do you think users would be surprised?

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

Comment by hansolsson on 15 Jun 2014 17:57 UTC Some comments:

  • The replaceable restriction does not make sense to me. I see no difference between redeclaring the component (in which the block-call occurs) and just modifying the variable using the block-call.
  • I don't see how the conditional issue is worse than normal variables inside conditional model-component - thus I would remove this one as well. (The problems with these restrictions is that they would limit reuse in larger models).
  • The restriction that the block-call must be the entire declaration equation seems to be missing, this is necessary since it is possible to construct bad example when using the block-call in a sub-expression.
  • It doesn't match with the current sub-typing, since a replaceable block-class may be redeclared to have more outputs. That would break any existing block-calls (and since block-subtyping doesn't care about order we cannot just use the first output).

I can see one way to keep the current sub-typing, and also allow blocks with multiple outputs: instead of Boolean y=OnDelay(u=..., delayTime=...) one would write Boolean y=OnDelay(u=..., delayTime=...).y

  • i.e. naming the output as well.

However, this would look different than existing function calls (yes, I'm aware of the curly braces instead of parentheis as well - we obviously need to combine them).

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

Comment by hansolsson on 15 Jun 2014 18:05 UTC Forgot to add that the subtyping argument also means that we really need to use named arguments (which is already part of the proposal - so only making the reason stronger), since otherwise a valid redeclare of a block-class could break existing block-calls.

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

Comment by sjoelund.se on 15 Jun 2014 18:57 UTC Wouldn't a much nicer syntax for multiple outputs be:

  Real r;
  Boolean y;
equation
  (r,y) = OnDelay(u=..., delayTime=...);

Anyway, I would prefer if the grammar did not need changing (so no redeclare modifiers, etc in the "call").

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

Comment by hansolsson on 15 Jun 2014 19:10 UTC Replying to [comment:7 sjoelund.se]:

Wouldn't a much nicer syntax for multiple outputs be:

  Real r;
  Boolean y;
equation
  (r,y) = OnDelay(u=..., delayTime=...);

Anyway, I would prefer if the grammar did not need changing (so no redeclare modifiers, etc in the "call").

That would look nicer, but the problem is that if OnDelay were a replaceable block then then subtyping would allow us to reorder the outputs that would break this call.

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

Comment by otter on 25 Jul 2014 13:35 UTC Updated the proposal according to the Dymola prototype of Hans:

// Instead of
Real r = OnDelay(...)

// Dymola prototype
Real r = OnDelay.y(...)   // so output name is appended to class name

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

Comment by petfr on 20 Nov 2014 09:47 UTC The problem with accessing outputs via OnDelay.y is that you can not anyway write block calls to blocks with more than one output. If you create a second call to access the other output, you introduce a second block. Therefore the language extension to require the .y is unnecessary.

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

Comment by petfr on 20 Nov 2014 09:54 UTC The restriction to only allow calls in the bindings of variables seems arbitrary and unnecessary. From an implementation point of view, it should be just as simple to add the new block declaration and equations regardless if the block call comes from an algorithm section, equation section, or modifier.

This is just a kind of macro expansion. Since the result of the macro-expansion is equations or assignments together with the appropriate block declaration and these constructs are allowed in general models, equation sections, and algorithm sections according to Modelica rules there should not be any special restriction for the macro call.

Type checking can be performed after the expansion. This provides good error-messages if the result is not a valid language construct while allowing maximum expressiveness.

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

Comment by petfr on 20 Nov 2014 10:00 UTC Proposed restriction says:

The expression in which the block is called is not conditional.

Why is this restriction necessary, and does it apply to conditional components or if-expressions?

Is it because it will cause the equations in the block to always be evaluated or something else? Why restrict this? Since the constructs in the macro expansion are allowed even in the conditional context there is no need for special rules to forbid this. It could be a tool issue to optimize away unused calculations.

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

Comment by petfr on 20 Nov 2014 10:08 UTC An alternative syntax for accessing more than one output could be:

  Real x,y;
equation
  (x,y) = MyBlock(...).{y1,y2}

This could be generalised for general function calls (accessing only the named outputs).

Another alternative could be:

  Real x,y;
equation
  (x=y1, y=y2) = MyBlock(...)

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

Comment by hansolsson on 20 Nov 2014 10:11 UTC Replying to [comment:10 petfr]:

The problem with accessing outputs via OnDelay.y is that you can not anyway write block calls to blocks with more than one output. If you create a second call to access the other output, you introduce a second block. Therefore the language extension to require the .y is unnecessary.

There are two problems with this argument: First, it assumes that you actually want the first output as well.

Consider a model such as Modelica.Electrical.QuasiStationary.SinglePhase.Sensors.PowerSensor it has three outputs: v, i, and y (the power). If I only want the power-output (it is a power-sensor after all) I could call this block as a function and do PowerSensor.y(....).

Second, without ".y" it doesn't work with the current subtyping rules, and I cannot see any simple change that make it consistent: Assume I have a replaceable SISO-block and call it as a block. If I then add a second output as in Modelica.Blocks.Continuous.SecondOrder - it no longer works, since that block has two outputs (the second is yd - the derivative). Even if not replaceable the restriction to "single output" would make it impossible to call SecondOrder as a block.

For this specific case the new output is after the existing one, but even that is not a requirement for block-subtyping (it is for function subtyping). -- Yes, the OnDelay.y(...) looks a bit odd, but to me it is the simplest variant that allows this extension to interact well with the rest of the language.

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

Comment by hansolsson on 20 Nov 2014 10:45 UTC Replying to [comment:12 petfr]:

Proposed restriction says:

The expression in which the block is called is not conditional.

Why is this restriction necessary, and does it apply to conditional components or if-expressions?

Is it because it will cause the equations in the block to always be evaluated or something else? Why restrict this? Since the constructs in the macro expansion are allowed even in the conditional context there is no need for special rules to forbid this. It could be a tool issue to optimize away unused calculations.

The restriction might later be lifted, but at least at the moment it is a useful one since we have a useful subset with well-defined semantics that later can be extended.

The call of a block as a function is not just a macro and it is not just a matter of optimizing away unused calculations but about defining the actual semantics, since blocks have memory - and conditional code and memory is tricky.

Specifically the reason for the restriction is that in Modelica 3.2 a block can not be switched on and off during the continuous simulation; and if we e.g. consider an Integrator we need to specify how it starts the first time (which may be after the initialization of the overall model) and what happens if it is switched off and then on later on again. A conditional component is simpler, since it is either on or off the entire simulation.

One way of specifying the semantics for calling block as function inside if-expressions would be to use the state machine semantics of Modelica 3.3. The exact details are not clear yet, and we haven't verified that it is the desired semantics (if you use an integrator as a block - do you want the integrator to stop if the if-condition is false?) There might also be issues for state machines related to this (e.g. initialization after the initial point).

Another idea would be to always introduce the blocks, and only get the outputs if the block is active. But that would not work well, e.g. if you do "if time<0.5 then Modelica.Blocks.Math.Asin.y(time) else 0.5"; the Asin-block would still be running after 0.5 seconds and cause an error after 1 second due to a value out of bounds.

I haven't seen any other ideas.

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

Comment by sjoelund.se on 20 Nov 2014 11:28 UTC Block calls in algorithm sections:

algorithm
for i in 1:1000 loop
  x := Block(x=x); // Bad, causes algebraic loop...
end for;
algorithm
for i in 1:1000 loop
  x := Block(x=i);
end for;
// Expands to
equation
  Block_1.x = i; // Bad; which i? The same is generated for reduction expressions in bindings; let it, catch the error
algorithm
for i in 1:1000 loop
  x := Block_1.y;
end for;
algorithm
for i in 1:1000 loop
  x := Block(x=y);
end for;
// Expands to
equation
  Block_1.x = y; // OK
algorithm
for i in 1:1000 loop
  x := Block_1.y; // OK; block evaluated only once; sorting rules are well-defined
end for;

Block calls in when-equations:

when time>0.5 then
  x = Block(x=y);
end when;
// Expands to (maybe):
when time > 0.7 then
  Block_1.y = Block_1.x;
end when;
Block_1.x = y;
when time>0.5 then
  x = Block_1.y;
end when;

I can't see any issues with it.

For-equations work differently depending on how you define the macro-expansion should be performed.

for i in 1:1000 loop
  x[i] = Block(x=i);
end for;
// Expands to either (1):
Block_1.x = i; // Which i?
x[1] = Block_1.y;
x[2] = Block_1.y;
...
// Or (2):
Block_1.x = 1;
x[1] = Block_1.y;
Block_2.x = 2;
x[2] = Block_2.y;
...

Note that if the loop variable is not used in any block call, we could expand them in a more compact way (general case requires creating one block instance for each unrolled iteration; if possible only one instance is perhaps preferable).

I don't see why we would want to restrict any of these though.

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

Comment by hansolsson on 20 Nov 2014 12:54 UTC Replying to [comment:16 sjoelund.se]:

Block calls in algorithm sections:

algorithm
for i in 1:1000 loop
  x := Block(x=x); // Bad, causes algebraic loop...
end for;
algorithm
for i in 1:1000 loop
  x := Block(x=i);
end for;
// Expands to
equation
  Block_1.x = i; // Bad; which i? The same is generated for reduction expressions in bindings; let it, catch the error

True, that's problematic - especially since 'i' is not in scope in that equation.

algorithm for i in 1:1000 loop x := Block_1.y; end for;


```mo
algorithm
for i in 1:1000 loop
  x := Block(x=y);
end for;
// Expands to
equation
  Block_1.x = y; // OK
algorithm
for i in 1:1000 loop
  x := Block_1.y; // OK; block evaluated only once; sorting rules are well-defined
end for;

I agree that it is reasonable, and many would do this optimization manually.

However, the block may contain contain call to external devices/uses or use inner/outer components and in those cases the optimization would have have an observable effect.

In addition the optimization needs to be carefully considered if we have 'for i in 1:n loop', due to the case 'n<1' (there was a similar issue in the early days of Fortran).

Block calls in when-equations:

when time>0.5 then
  x = Block(x=y);
end when;
// Expands to (maybe):
when time > 0.7 then
  Block_1.y = Block_1.x;
end when;
Block_1.x = y;
when time>0.5 then
  x = Block_1.y;
end when;

I can't see any issues with it.

I see several issues:

If the call is more complicated the input sent to the block when the when-statement is not active may not be valid (it may be a division by zero, square root of negative numbers) - same issue as for 'if-expressions' (see comments above).

In addition if 'y' is a continuous signal its use in a when-statement creates a sample signal; and one would expect that the block gets a sampled-input - but instead the block here gets a continuous signal - and we sample on the output. Those two operations don't commute.

Also consider the following:

when sample(1,1) then
  y=block.y(sin(time*1000)>0);
end when;

We don't generate state-events inside when-blocks; and shouldn't add new ones in this case (it would create a form of chattering). One could think that there is an implicit noEvent around 'sin(time*1000)>0', but that would generate a boolean continuous-time signal 'block_1.x' which is not legal in Modelica.

The point is that by restricting ourselves to only calling-blocks-as-functions as definition equations we avoid these issues and can first see if it works and is generally useful. (I believe that is the case, but I don't know if there is agreement on that.)

After that we can then start working on generalizing the semantics, and to me using the state-machine semantics is the most promising idea, but I haven't investigated how it works in these cases.

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

Comment by jmattsson on 1 Dec 2014 15:46 UTC Another possible use case is if you have a calculation that is expressed as a block rather than a function, it would be useful to be able to use that block to compute the value of a parameter.

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

Comment by hansolsson on 2 Dec 2014 16:59 UTC Discussed at meeting (will be in main minutes - just written so that I don't forget it).

Possibilities for output:

  1. f().y - this would be a new case and require grammar change (adding optional component_reference in several places)
primary : 
…
| ( name | der | initial ) function_call_args [ component_reference ]

(and similarly for statements in algorithms). Will not work well with having subscripting on general expression since function calls are automatically treated as the first output. Could view (f()).y as member y of first output, and f().y as output y of f, but non-intuitive.

  1. For any function f with output y then f.y constructs a new function with y as the only output (and similarly for blocks). This allows subscripting on general expressions later on.

  2. Some specific restrictions for replaceable/multiple outputs - possibly using constraing clause.

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

Comment by dietmarw on 19 Dec 2014 07:01 UTC Ticket retargeted after milestone closed

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

Comment by henrikt on 8 Jan 2015 07:47 UTC Looking at the minutes of the web meeting on December 17 regarding this MCPI, it seems like the conclusion was to go for something like foo(…).y2, where y2 is the name of one of the outputs. There are also several comments suggesting that constructs should work for expressions in general, whatever that is.

I think it would be very unfortunate if function calls ended up being non-expressions because of how this MCPI is resolved. It would probably make Modelica the only high level language with this kind of oddity, making adoption by new users even more difficult.

A syntax like foo.(y2)(…) would be one way to allow function calls to stay expressions; foo.(y2) would be a function (block) with a single output named y2, which is then called as any other function, meaning that the value of the function call is the value of the first (here, only) output.

I am not particularly found of the .(…) syntax; braces have been brought up earlier in this discussion, making foo{y2} another possibility. If one is uncomfortable defining too much syntax in terms of dots, parentheses, and braces, one could reuse a keyword, for instance (foo output y2) (making the parenthesis mandatory or not is a matter of taste; it's a non-intrusive way of using a parenthesis anyway).

(I originally proposed simply using foo.y2, but it was quickly pointed out that this is not a good option since for a block foo, this already has the meaning of referring to its output variable.)

Even if one doesn't like the added complexity of assigning meaning to foo.(y2) by itself right now, going for the composite syntax foo.(y2)(…) has important advantages over foo(…).y2:

  • It makes it possible to assign meaning to foo.(y2) as an expression in the future.
  • It doesn't contain the substring foo(…) which already has an incompatible meaning.

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

Comment by dzimmer on 23 Feb 2015 13:34 UTC Maybe when using:

  Real x,y;
  equation
  (x,y) = MyBlock(...).(y1,y2)

we have a tuple on the left and on the right.

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

Modified by otter on 18 Sep 2015 10:12 UTC

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

Modified by otter on 18 Sep 2015 10:19 UTC

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

Modified by otter on 18 Sep 2015 10:21 UTC

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

Modified by dietmarw on 2 Dec 2015 08:31 UTC

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

Comment by hansolsson on 4 Nov 2016 16:25 UTC Replying to [comment:21 henrikt]:

(I originally proposed simply using foo.y2, but it was quickly pointed out that this is not a good option since for a block foo, this already has the meaning of referring to its output variable.)

Thinking more I am not sure that is a problem - and I don't want this proposal to get stuck on that technicality.

Yes, Foo.y2(x=2) means the output component y2 for the block-class Foo - this is different from the same component for a block-component. But that is roughly what we are using it for here - we construct an instance of the block-class using the argument (x=2) - e.g. Foo bar(x=2), and then take the component y2 from the bar (anonymous bar-component!).

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

Comment by hansolsson on 19 May 2017 13:17 UTC Language group - remaining issues:

  • Decide on syntax (should be easy)
  • What about conditional ones:
  • Forbidden? (As in test-implementation).
  • Forbidden unless guard is parameter-expression?
  • Always computed
  • Use state-machine logic Need to check if state-machine logic would make sense here

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