Modelica Expandable Connector Best Practices - modelica
Here is a minimum working example of some expandable connector usage occuring in my models:
model TestExpandableConnector
expandable connector ControlBus
extends Modelica.Icons.SignalBus;
Real signal1;
Real signal2;
end ControlBus;
ControlBus controlBus;
// example models to connect signals to
Modelica.Blocks.Math.Gain gain1;
Modelica.Blocks.Math.Gain gain2;
// and so on
equation
connect(controlBus.signal1, gain1.u);
connect(controlBus.signal2, gain2.u);
// and so on
end TestExpandableConnector;
This works fine and no concerns here.
Note that normally this model would be created in the diagram layer with graphical objects and connections between the bus and the components (gains in this case).
While the above example is trivial, in many real world examples I have many connections emerging from that one expandable connector. This quickly can become messy in the diagram layer and I'm trying to learn/develop some best practices here for cleaning up the diagrams.
One option seems to be to use the RealExpression block in a way almost equivalent to Simulink's From/Goto elements. For example:
model TestExpandableConnectorRevised
expandable connector ControlBus
extends Modelica.Icons.SignalBus;
Real signal1;
Real signal2;
end ControlBus;
ControlBus controlBus;
// example models to connect signals to
Modelica.Blocks.Math.Gain gain1;
Modelica.Blocks.Math.Gain gain2;
// and so on
// using RealEpressions like goto tags
Modelica.Blocks.Sources.RealExpression realExpression1(y=controlBus.signal1);
Modelica.Blocks.Sources.RealExpression realExpression2(y=controlBus.signal2);
// and so on
equation
connect(realExpression1.y, gain1.u);
connect(realExpression2.y, gain2.u);
// and so on
end TestExpandableConnectorRevised;
Now with this change, Dymola complains about this being illegal since causality cannot be determined. I seem to be able to resolve this last issue by either 1) adding the "input" prefix to the signal1 and signal2 declarations in the bus, or 2) positioning the declaration for the realExpressions before the contolBus declaration (this 2nd solution is a bit odd to me).
Overall, I'm reasonably happy with these solutions from a decluttering my diagram point of view, but they also feel at least a little bit "hacky". My basic goal in this question is to inquire if this approach is OK or if it's a bad idea? Additionally, if there are any other suggestions regarding how to handle the organization of all the connections in a big model (especially with expandable connectors), I'm all ears. As an additional thought, it seems to a me that a more dedicated "From/Goto" feature for the Modelica language might be really nice in Modelica, purely for the purpose of decluttering diagrams but being exactly equivalent to a connect statement under the hood.
Dymola complains that
The variable controlBus.signal1 is part of an expandable connector, and was only used
outside of connect. That is not legal since we cannot determine its causality.
Your revised solution works, as soon as you write the signal somewhere using a connect
statement. Below I further reduced your example to contain only signal1. An additional real expression is used to set its value.
model TestExpandableConnectorRevised
expandable connector ControlBus
Real signal1;
end ControlBus;
ControlBus controlBus;
Modelica.Blocks.Math.Gain gain1;
Modelica.Blocks.Sources.RealExpression realExpression1(y=controlBus.signal1);
// Added to write the bus signal
Modelica.Blocks.Sources.RealExpression realExpression3(y=1);
equation
connect(realExpression1.y, gain1.u);
// Added to write the bus signal
connect(realExpression3.y, controlBus.signal1);
end TestExpandableConnectorRevised;
This example compiles in Dymola pedantic mode and OpenModelica, so it should be perfectly fine.
Alternative approach using bus adapters
As you see, expandable connectors are full of pitfalls. The problem above can also happen easily if you decide to rename signal1 to mysignal on the expandable connector, but forget to update the connect statement to connect(realExpression3.y, controlBus.mysignal).
Therefore some Modelica libraries decided read and write bus signals only via bus adapters. You have to create 2 additional blocks for every variable: one to read and one to write its value. This is a lot of boring work, but it avoids the problem above.
Here is a minimal example to read and write signal1.
package BusAdapters
partial block BusWriter
// Dialog allows to set the value of y in the parameter window, like for the real expression
Modelica.Blocks.Interfaces.RealInput u annotation (Dialog);
ControlBus controlBus;
end BusWriter;
block Write_signal1
extends BusWriter;
equation
connect(u, controlBus.signal1);
end Write_signal1;
partial block BusReader
Modelica.Blocks.Interfaces.RealOutput y;
ControlBus controlBus;
end BusReader;
block Read_signal1
extends BusReader;
equation
connect(y, controlBus.signal1);
end Read_signal1;
expandable connector ControlBus
extends Modelica.Icons.SignalBus;
Real signal1;
end ControlBus;
model TestBusConnectors
ControlBus controlBus;
Modelica.Blocks.Math.Gain gain1;
// setting bus variables: using modifiers in write blocks
Write_signal1 write1(u=sin(time));
// accessing bus variables part 1: creating instance of reader
Read_signal1 read1;
equation
// connect all read and write blocks to the same bus instance
connect(write1.controlBus, controlBus);
connect(read1.controlBus, controlBus);
// accessing bus variables part 2: connecting reader with component of interest
connect(read1.y, gain1.u);
end TestBusConnectors;
end BusAdapters;
Graphically this will look something like below. x is written directly using the bus adapters. For y real expressions are used, to reduce the number of lines in larger models.
Related
Determine the number of a specific component in a Modelica model at translation (using Dymola)
How could you determine the number of instances of a given class within this model at translation. This size is to be used to set the size of a vector. model ModelToBeCounted end ModelToBeCounted; model Example ModelToBeCounted A; ModelToBeCounted B; ModelToBeCounted C; end Example; So in this example I would like to like to determine the number instances of ModelToBeCounted. These instances could also be within instances of other classes. The code at the top level could be altered to do this and the ModelToBeCounted could also be altered. I tried using external objects however I could not find a way to ensure that the external objects are all created before the function used to report the total number of external objects was run. Any ideas? Thanks
If you can modify the model ModelToBeCounted you can do this using inner/outer: connector C Real totalNum; flow Real addNum; end C; model InnerObjectCounter C c; equation c.totalNum+c.addNum=0 "Correct sign"; annotation(missingInnerMessage="Add inner component to count objects"); end InnerObjectCounter; model FlowSource C c; equation c.addNum=1; end FlowSource; model AddCounter outer InnerObjectCounter objectCounter; final Integer totalNum=integer(flowSource.c.totalNum); FlowSource flowSource; equation connect(flowSource.c, objectCounter.c); end AddCounter; model ModelToBeCounted AddCounter c; end ModelToBeCounted; All components are automatically connected to the inner object using inner/outer-mechanism, and they have a flow of 1 that is summed together. If you cannot modify the model ModelToBeCounted, and you are willing to use non-standarized methods you can set the flag: Hidden.AllowAutomaticInstanceOf=true; and then use: final parameter Integer m=sum(1 for m in P.ModelToBeCounted); From: https://github.com/modelica/ModelicaSpecification/blob/MCP/0021/RationaleMCP/0021/
The ModelManagement library can do that. In fact, there is exactly what you need available as a function: ModelManagement.Structure.Instantiated.CountUsedModels("Example", {"ModelToBeCounted"}); = {3} The library is installed with Dymola and available with the standard license. You just have to open it from the Libraries menu. A problem could be, that this function has to translate your model. I am not sure how well this will work for your requirement: This size is to be used to set the size of a vector.
In Modelica, how to call a variable in a different block without wiring the 2 blocks?
We are modeling various industrial component blocks, with each having CAPEX, labour cost, maintenance cost, total OPEX, etc. We would like to have 1 block, in the best case not wired to the other blocks, to account for the total OPEX, total CAPEX, total labour costs, etc. induced by the blocks present in a model : the number of blocks is not fixed. Is there a way of not connecting the blocks with a wire ? In case there is no way, we found the solution of using the RealOutput y vector, as defined in Modelica.Blocks.Interfaces.MO : nout is defined as the number of actual variables we would like to add up (e.g. if CAPEX, OPEX and maintenance are of interest, then nout = n = 3). However, we struggle for 2 points : How can we pass a matrix through this RealOutput ? This would be useful for instance when the CAPEX has 3 values : estimated, optimistic and pessimistic. How can we take up this y in only 1 block ? For now we managed to use n Modelica.Blocks.Math.MultiSum blocks to take up each variable separately, but is there a way of adding them up respectively, but in the same block ? Thank you already for your much appreciated help and answers !
I think you should be able to do this with the inner/outer construct and a custom connector with a flow variable for, e.g., your capex, as demonstrated in section 5.8 of Fritzson's book "Principles of Object-Oriented Modeling and Simulation with Modelica 3.3". I could have sworn there is already an example around that sums masses of components to a total, but I could not find it anywhere... package SO_69945088 model System FinancialAggregator fin_totals() "Collects financial info"; inner CapexConn common_connection "autoshared connection"; Component comp1(capex_info.amount={0, 3, 5}); Component comp2(capex_info.amount={1, 10, 12}); Component comp3(capex_info.amount={5, 6, 7}); equation //Conveniently collect financial info in fin_totals connect(common_connection, fin_totals.agg_conn); end System; connector CapexConn // the amount being a "flow" variables means all contributions // sum at a connection point. flow Real[3] amount; // every "flow" variable should have a non-flow variable, // at least for "physical" connections. Let's add a dummy: Real [3] period; end CapexConn; model CapexEmitter CapexConn conn; Real[3] amount = fill(0, 3); Real[3] period; equation // Here you could also have more logic, like computing emitted capex // depending on the period conn.amount = -amount; //negative because of sign of flow direction in connectors conn.period = period; end CapexEmitter; model Component "Industrial component block with capex tracking" // (you would extend your other components from this to use it) outer CapexConn common_connection; // make outside connection visible CapexEmitter capex_info; equation // all Component instances automatically connect, without manual wiring connect(capex_info.conn, common_connection); end Component; model FinancialAggregator CapexConn agg_conn; Real[3] capexsum; Real period[3] = {1,1,1}; equation capexsum = agg_conn.amount; period = agg_conn.period; end FinancialAggregator; end SO_69945088; Simulating System gives a fin_totals.capexsum of {6, 19, 24} (optimistic, estimated, pessimistic). This should give you a starting point showing the principles.
Division by zero depending on parameter
I am using the FixedRotation component and get a division by zero error. This happens in a translated expression of the form var = nominator/fixedRotation.R_rel_inv.T[1,3] because T[1,3] is 0 for the chosen parameters: n={0,1,0} angle=180 deg. It seems that Openmodelica keeps the symbolic variable and tries to be generic but in this case this leads to division by zero because it chooses to put T[1,3] in the denominator. What are the modifications in order to tell the compiler that the evaluated values T[1,3] for the compilation shall be considered as if the values were hard coded? R_rel is internally in fixedRotation not defined with Evaluate=true... Should I use custom version of this block? (when I copy paste the source code to a new model and set the parameters R_rel and R_rel_inv to Evalute=true then the simulation works without division by zero)... BUT is there a modifier to tell from outside that a parameter shall be Evaluate=true without the need to make a new model? Any other way to prevent division by zero?
Try propagating the parameter at a higher level and setting annotation(Evaluate=true) on this. For example: model A parameter Real a=1; end A; model B parameter Real aPropagated = 2 annotation(Evaluate=true); A Ainstance(a=aPropagated); end B;
I don't understand how the Evaluate annotation should help here. The denominator is obviously zero and this is what shall be in fact treated. To solve division by zero, there are various possibilities (e.g. to set a particular value for that case or to define a small offset to denominator, you can find examples in the Modelica Standard Library). You can also consider the physical meaning of the equation and handle this accordingly. Since the denominator depends on a parameter, you can also set an assert() to warn the user there is wrong parameter value. Btw. R_rel_inv is protected and shall, thus, not be used. Use R_rel instead. Also, to deal with rotation matrices, usage of functions from Modelica.Mechanics.MultiBody.Frames is a preferrable way. And: to use custom version or own implementation depends on your preferences. Custom version is maintained by the comunity, own version is in your hands.
how to connect multi-dimensional components with multi-dimensional connectors in Modelica?
I tried to connect a 2-dimensional component array to a 1-dimensional component array including 1-dimensional connectors, but when checking the model, there is an error showing unmatched dimensions. But I could connect a 1-dimensional component array to a component including 1-dimensional connectors, So Why can't this work for multi-dimensional situations? Did I do it wrong? I checked the code, it seems I can't use connect(tubeWall.port_b,surface.q_port); but if I use the following code, it works fine. for i in 1:x loop for j in 1:y loop connect(tubeWall[i].port_b[j], surface[i,j].q_port); end for; end for; I did more test, here is the test code which worked fine: model Unnamed gain_1[3] gain_1_1 Modelica.Blocks.Sources.Sine[3,3] sine Modelica.Blocks.Math.Cos[3,3] cos equation connect(sine.y, gain_1_1.u); connect(gain_1_1.y, cos.u); end Unnamed; model gain_1 Modelica.Blocks.Math.Gain[3] gain Modelica.Blocks.Interfaces.RealOutput[3] y Modelica.Blocks.Interfaces.RealInput[3] u equation connect(gain.y, y) connect(u, gain.u) end gain_1; Here is the screenshot of the connections: So it seems the idea is right, but I am not sure why it doesn't work in my model. Hope someone could give a hint or direction of the unmatched error in my model.
Quoting Fritzon's Principles of object-oriented modeling and simulation with Modelica 3.3: The connect contruct can be used to directly connect arrays of connectors. For such array connections the following holds: The array dimensions of the connected arrays of connectors must match Each corresponding pair of elements is connected as a pair of scalar connectors That is, referring to connect(tubeWall.port_b,surface.q_port); it does not know which dimension of surface[:,:] goes to tubeWall[:] and which to port_b[:] the for loop works, because you are taking over the task of connecting the pair of elements as scalar connectors My suggestion for your modeling task is that you create an interface block to put between surface and tubeWall, in which you implement the element-wise connections the way they should be. The connection between surface and interface might then look like: connect(surface, interface.surfacePort);
I played around to see if I can figure it out. Here three points that might bring you closer to a canonical answer on why there's a different behavior between physical connections (thermal, in your case) and signal connections (Real input/output, in your case): Real input/output are causal, and declared differently than physical connectors connector RealInput = input Real "'input Real' as connector" annotation (...); connector PhysConnector Real pt; flow Real flw; annotation (...); end PhysConnector; Real input/output look more like functions than connectors. I suppose the rule The array dimensions of the connected arrays of connectors must match does not apply/is not enforced for them. I can think of different reasons for this; two of them could be: There's a general accepted framework to deal with tables, the same way the majority of us agree that looking at a geographical map the top-left corner is north-west. So the connections are sorted automatically according to the sequence: 1st dim, 2nd dim, 3rd dim,... Multidimensional physical connections on the other hand might represent all sorts of scenarios. Better leave the model designer the responsibility to build it up correctly Real input/output generate one assignment instead of a set of equations, therefore they don't mess up too much with the sorting algorithms when figuring out the causality of the system I tried eventually to test a standard connector with only a potential variable, to see if the problem was due to the two equations generated when also a flow variable is present. The flat Modelica shows there's only one equation generated (as expected), but still the connection matrix[:,:],array[:].array[:] is not allowed. package MultidimConnections connector RealConnector Real r; annotation(Icon(coordinateSystem(preserveAspectRatio=false)),Diagram(coordinateSystem(preserveAspectRatio=false))); end RealConnector; partial model RealInterface RealConnector realConnector annotation(Placement(transformation(extent={{90,-10},{110,10}}))); annotation(Icon(coordinateSystem(preserveAspectRatio=false),graphics={Rectangle(extent={{-100,100},{100,-100}},lineColor={28,108,200},fillColor={170,213,255},fillPattern=FillPattern.None)}),Diagram(coordinateSystem(preserveAspectRatio=false))); end RealInterface; model Source extends RealInterface; parameter Real k = 0; equation k = realConnector.r; annotation(Icon(coordinateSystem(preserveAspectRatio=false),graphics={Rectangle(extent={{-80,80},{80,-60}},lineColor={28,108,200},fillColor={151,226,75},fillPattern=FillPattern.Solid), Text(extent={{-100,-60},{100,-100}},lineColor={28,108,200},fillColor={151,226,75},fillPattern=FillPattern.Solid,textString="%name")}),Diagram(coordinateSystem(preserveAspectRatio=false))); end Source; model User extends RealInterface; Real double; equation double = 2*realConnector.r; annotation(Icon(coordinateSystem(preserveAspectRatio=false), graphics={Rectangle(extent={{-80,80},{80,-60}},lineColor={28,108,200},fillColor={85,170,255},fillPattern=FillPattern.Solid), Text(extent={{-100,-60},{100,-100}},lineColor={28,108,200},fillColor={85,170,255},fillPattern=FillPattern.Solid,textString="%name")}),Diagram(coordinateSystem(preserveAspectRatio=false))); end User; model User_multi MultidimConnections.User user annotation(Placement(transformation(extent={{-10,40},{10,60}}))); MultidimConnections.User user1 annotation(Placement(transformation(extent={{-10,-10},{10,10}}))); MultidimConnections.User user2 annotation(Placement(transformation(extent={{-10,-60},{10,-40}}))); RealConnector realConnector[3] annotation(Placement(transformation(extent={{110,-10},{90,10}}))); equation connect(user.realConnector, realConnector[1]) annotation(Line(points={{10,50},{98,50},{98,-6.66667},{100,-6.66667}}, color={0,0,0})); connect(user1.realConnector, realConnector[2]) annotation(Line(points={{10,0},{98,0},{98,4.44089e-16},{100,4.44089e-16}}, color={0,0,0})); connect(user2.realConnector, realConnector[3]) annotation(Line(points={{10,-50},{98,-50},{98,6.66667},{100,6.66667}}, color={0,0,0})); annotation(Icon(coordinateSystem(preserveAspectRatio=false), graphics={Rectangle(extent={{-80,80},{80,40}},lineColor={28,108,200},fillColor={85,170,255},fillPattern=FillPattern.Solid),Text(extent={{-100,-60},{100,-100}},lineColor={28,108,200},fillColor={85,170,255},fillPattern=FillPattern.Solid,textString="%name"),Rectangle(extent={{-80,28},{80,-12}},lineColor={28,108,200},fillColor={85,170,255},fillPattern=FillPattern.Solid),Rectangle(extent={{-80,-20},{80,-60}},lineColor={28,108,200},fillColor={85,170,255},fillPattern=FillPattern.Solid),Rectangle(extent={{-100,100},{100,-102}}, lineColor={28,108,200})}),Diagram(coordinateSystem(preserveAspectRatio=false))); end User_multi; model TestCustomReal extends Modelica.Icons.Example; Source source(k=1) annotation(Placement(transformation(extent={{-60,40},{-40,60}}))); User user annotation(Placement(transformation(extent={{60,40},{40,60}}))); User_multi user_multi annotation(Placement(transformation(extent={{60,-10},{40,10}}))); Source source_arr[3](k=1) annotation(Placement(transformation(extent={{-60,-10},{-40,10}}))); User_multi user_multi_array[3] annotation(Placement(transformation(extent={{60,-60},{40,-40}}))); Source source_mat[3,3](k=1) annotation(Placement(transformation(extent={{-60,-60},{-40,-40}}))); equation connect(source.realConnector, user.realConnector) annotation(Line(points={{-40,50},{40,50}}, color={0,0,0})); connect(source_arr.realConnector, user_multi.realConnector) annotation(Line(points={{-40,0},{40,0}}, color={0,0,0})); connect(source_mat.realConnector, user_multi_array.realConnector) annotation(Line(points={{-40,-50},{40,-50}}, color={0,0,0})); end TestCustomReal; annotation(uses(Modelica(version="3.2.3"))); end MultidimConnections;
The connect construct works only if the array dimensions match. You could provide indices on the create connection window, to make the connection right between tubeWall and surface. which is exactly what the code is doing. The model Unnammed works because gain_1_1.u is a connector with sizes [3,3]. If you change the size of the instance gain_1, you will see the difference. Therefore you can either connect same size arrays or explicitly mention the indices during the connection. Hope this helps.
Modelica: use of der() in a custom class/model
I'm trying to learn Modelica and I have constructed a very simple model using the multibody library. the model consists of a world object and a body (mass) connected to to beams which are then connected to 2 extended PartialOneFrame_a classes (see below) which I modified to create a constant force in one axis. essentially all this group of objects does is fall under gravity and spin around due to the two forces acting at a longituidnal offset from the body center creating a couple about the cg. model Constant_Force extends Modelica.Mechanics.MultiBody.Interfaces.PartialOneFrame_a; parameter Real force = 1.0; equation frame_a.f = {0.0,0.0,force}; frame_a.t = {0.0,0.0,0.0}; end Constant_Force; I next wanted see if I could create a very simple aerodynamic force component which I would connect to the end of one the rotating 'arms'. My idea was to follow the example of the Constant_force model above and for my first simple cut generate forces based on the local frame velocities. This where my problem arose - I tried to compute the velocity using der(frame_a.r_0) which I was then going to transform to local frame using resolve2 function but adding the der(...) line caused the model to not work properly - it would 'successfully' simulate (using OpenModelica) but the v11b vector (see below) would be all zeros, so would der(frame_a.r_0) that appeared for plot plotting - not only that, all the other component behaviors were now also just zero constantly - (frame_a.r_0, w_a etc of the body). model Aerosurf extends Modelica.Mechanics.MultiBody.Interfaces.PartialOneFrame_a; import Modelica.Mechanics.MultiBody.Frames; import Modelica.SIunits; //Real[3] v11b; SIunits.Velocity v11b[3]; //initial equation // v11b={0.0,0.0,0.0}; algorithm //v11b:=der(frame_a.r_0); equation v11b=der(frame_a.r_0); frame_a.f = {0.0,0.0,0.0}; frame_a.t = {0.0,0.0,0.0}; end Aerosurf; I tried a number of ways just to simply compute the velocities (you will see from the commented lines) so i could plot to check correct behavior but to no avail. I used algorithm or equation approach - I did acheive some different (but also incorrect behaviours) with the different approaches. Any tips? I must be missing something fundamental here, it seems the frame component does not inherently carry the velocity vector so I must have to compute it??
The simplest way is to use the Modelica.Mechanics.MultiBody.Sensors.AbsoluteVelocity block from MSL and connect it to your MB frame, then just use the variable of the output connector in your equation.