Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name = "OM"
uuid = "2f925a0b-2436-437b-858e-49aee461894b"
version = "0.7.1"
version = "0.8.0"
authors = ["John Tinnerholm <john.tinnerholm@liu.se>"]

[deps]
Expand Down
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ A Modelica Environment in Julia.

Documentation: <https://jkrt.github.io/OM.jl/dev/> (installation, examples, [Modelica libraries](https://jkrt.github.io/OM.jl/dev/libraries/), large array models, [scripting in Julia](https://jkrt.github.io/OM.jl/dev/julia/) and with `.mos`, [coverage](https://jkrt.github.io/OM.jl/dev/coverage/), [troubleshooting](https://jkrt.github.io/OM.jl/dev/troubleshooting/), API).

In the REPL, `OM.help()` lists the main functions by task and `OM.help(:examples)` shows worked examples:
models of the MSL, of libraries of your own and of installed ones (their files are in
[`examples/Libraries`](examples/Libraries)).

Please leave a star or make an issue to support the repository!
* Note that this package is still under development

Expand Down Expand Up @@ -239,7 +243,7 @@ flatModelica = OM.exportModelica("Influenza", "./Models/Influenza.mo")
print(modelName)
```
### Using different versions of the Modelica Standard Library
MSL 3.2.3 and 4.1.0 are bundled; `MSL_Version` picks one for a model in a file:
MSL 4.1.0 (the default) and 3.2.3 are bundled; `MSL_Version` picks one for a model in a file:

```julia
jl = OM.translate("<model_using_msl_4>", "<filepath>"; MSL = true, MSL_Version = "MSL:4.1.0")
Expand Down
16 changes: 10 additions & 6 deletions docs/src/examples.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
# Examples

The examples run in the OM.jl project environment (`julia --project` in the
repository), which also provides Plots and DifferentialEquations.
repository), which also provides Plots and DifferentialEquations. MSL models run
on MSL 4.1.0, the default (`MSL_Version = "MSL:3.2.3"` picks the other bundled
version); the showcase at the end runs on 3.2.3.

## A simple pendulum

```julia
using OM
sol = OM.simulate("Modelica.Mechanics.MultiBody.Examples.Elementary.Pendulum";
MSL_Version = "MSL:3.2.3", stopTime = 1.0)
MSL_Version = "MSL:4.1.0", stopTime = 1.0)
```

## Your own model, with events
Expand Down Expand Up @@ -115,7 +117,7 @@ sol = DE.solve(prob, DE.Rodas5P(); callback = callbacks)
longer = DE.solve(DE.remake(prob; tspan = (0.0, 60.0)), DE.Rodas5P(); callback = callbacks)
```

MSL models work the same way after `OM.translate(name; MSL_Version = "MSL:3.2.3")`.
MSL models work the same way after `OM.translate(name; MSL_Version = "MSL:4.1.0")`.

## An MSL model with friction events

Expand All @@ -124,7 +126,7 @@ lock as they are engaged and released:

```julia
name = "Modelica.Mechanics.Rotational.Examples.CoupledClutches"
cc = OM.simulate(name; MSL_Version = "MSL:3.2.3", stopTime = 1.5)
cc = OM.simulate(name; MSL_Version = "MSL:4.1.0", stopTime = 1.5)

p = plot(; xlabel = "time [s]", ylabel = "angular velocity [rad/s]")
for n in ("J1.w", "J2.w", "J3.w", "J4.w")
Expand All @@ -144,7 +146,7 @@ double-scroll attractor.

```julia
name = "Modelica.Electrical.Analog.Examples.ChuaCircuit"
chua = OM.simulate(name; MSL_Version = "MSL:3.2.3", stopTime = 5000.0, saveat = 1.0)
chua = OM.simulate(name; MSL_Version = "MSL:4.1.0", stopTime = 5000.0, saveat = 1.0)
plot(OM.OMBackend.getVariableValues(chua, "C1.v"), OM.OMBackend.getVariableValues(chua, "C2.v");
xlabel = "C1.v [V]", ylabel = "C2.v [V]", label = "")
```
Expand All @@ -154,7 +156,7 @@ plot(OM.OMBackend.getVariableValues(chua, "C1.v"), OM.OMBackend.getVariableValue
## A model from another library: Buildings

Libraries besides the MSL are installed once and passed by their key (more in
[Libraries](libraries.md)). The first
[Libraries](libraries.md), with examples of libraries of your own). The first
step of the Buildings library's space-cooling tutorial: a room with an air
volume, a wall to the outside and an air supply.

Expand Down Expand Up @@ -191,6 +193,8 @@ controllers, and hybrid events. They are useful end-to-end demonstrations of
the frontend, backend, ModelingToolkit code generation, and SciML simulation
pipeline.

Both run on MSL 3.2.3.

!!! note "First-run compilation"
These are intentionally demanding models. Their first translation and
simulation can take several minutes while Julia compiles the generated
Expand Down
2 changes: 1 addition & 1 deletion docs/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ The typical user flow is a single call:
```julia
using OM
sol = OM.simulate("Modelica.Mechanics.MultiBody.Examples.Elementary.Pendulum";
MSL_Version = "MSL:3.2.3", stopTime = 1.0)
MSL_Version = "MSL:4.1.0", stopTime = 1.0)
```

The [examples showcase](examples.md#Showcase:-validated-multibody-systems)
Expand Down
179 changes: 175 additions & 4 deletions docs/src/libraries.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ OM.jl flattens a model together with the Modelica libraries it uses. A library
comes from one of three places: the MSL bundled with OM.jl, an installed library
(OM.jl's own or OpenModelica's), or a file or directory of your own.

## The bundled MSL: 3.2.3 and 4.1.0
## The bundled MSL: 4.1.0 and 3.2.3

OMFrontend ships MSL 3.2.3 and 4.1.0 as single files (`lib/Modelica/MSL_3_2_3.mo`,
`MSL_4_1_0.mo`). `MSL_Version` selects one for a model given by name:
OMFrontend ships MSL 4.1.0 and 3.2.3 as single files (`lib/Modelica/MSL_4_1_0.mo`,
`MSL_3_2_3.mo`). A model given by name, or a model file with `MSL = true`, uses MSL 4.1.0
(`OM.DEFAULT_MSL_VERSION`); `MSL_Version` selects 3.2.3:

```julia
OM.simulate("Modelica.Blocks.Examples.PID_Controller"; MSL_Version = "MSL:4.1.0", stopTime = 4.0)
OM.simulate("Modelica.Blocks.Examples.PID_Controller"; stopTime = 4.0) # MSL 4.1.0
OM.simulate("Modelica.Blocks.Examples.PID_Controller"; MSL_Version = "MSL:3.2.3", stopTime = 4.0)
```

Only these two versions are bundled. For any other, such as MSL 4.0.0, use an
Expand Down Expand Up @@ -104,6 +106,9 @@ OM.simulate("MyLib.Examples.Test"; libraries = ["path/to/MyLib"])
## Examples

Each of these was run as shown (macOS, MSL 4.1.0 installed with OpenModelica, Buildings 13.0.0 installed).
The models of the examples from "Your own model on the MSL" to "The same in a `.mos` script" are in OM.jl's
[`examples/Libraries`](https://github.com/JKRT/OM.jl/tree/master/examples/Libraries); `include("run.jl")` there runs
them, and `OM.help(:examples)` lists them.

### What is installed

Expand All @@ -130,6 +135,172 @@ end
A simulate by name translates again when the library changes (another `MSL_Version` or `libraries`), and reuses
the translation when it is the same.

### Your own model on the MSL

`HeatedMass.mo`: a 500 J/K mass heated with 100 W, losing heat through a 2 W/K wall to 20 °C.

```modelica
model HeatedMass "a mass heated with 100 W, losing heat through a wall"
Modelica.Thermal.HeatTransfer.Components.HeatCapacitor mass(C = 500, T(start = 293.15, fixed = true));
Modelica.Thermal.HeatTransfer.Components.ThermalConductor wall(G = 2);
Modelica.Thermal.HeatTransfer.Sources.FixedTemperature ambient(T = 293.15);
Modelica.Thermal.HeatTransfer.Sources.FixedHeatFlow heater(Q_flow = 100);
equation
connect(heater.port, mass.port);
connect(mass.port, wall.port_a);
connect(wall.port_b, ambient.port);
end HeatedMass;
```

```julia
sol = OM.simulate("HeatedMass", "HeatedMass.mo"; stopTime = 1000.0)
OM.OMBackend.getVariableValues(sol, "mass.T")[end] # 342.234 = 293.15 + 50 (1 - exp(-1000 / 250))
sol = OM.simulate("HeatedMass", "HeatedMass.mo"; MSL = true, MSL_Version = "MSL:3.2.3", stopTime = 1000.0)
```

A model that uses `Modelica` without loading it gets the bundled MSL 4.1.0, as omc loads it from its library
path. `MSL = true` with `MSL_Version` picks the version: the same result on 3.2.3.

### A library model with other parameters

A modifier on `extends` changes a library model without copying it. `MoreTorque.mo` raises the torque limit of the
MSL's `PID_Controller` example from 12 to 20 N·m:

```modelica
model MoreTorque "the MSL's PID_Controller example with the torque limit raised from 12 to 20 N.m"
extends Modelica.Blocks.Examples.PID_Controller(PI(yMax = 20));
end MoreTorque;
```

```julia
speedError(sol) = maximum(abs.(OM.OMBackend.getVariableValues(sol, "PI.u_s") .-
OM.OMBackend.getVariableValues(sol, "PI.u_m")))
speedError(OM.simulate("Modelica.Blocks.Examples.PID_Controller"; MSL_Version = "MSL:4.1.0", stopTime = 4.0)) # 0.343
speedError(OM.simulate("MoreTorque", "MoreTorque.mo"; stopTime = 4.0)) # 0.0221
```

With 12 N·m the drive saturates while it accelerates; with 20 N·m it follows the reference.

### A library of your own in one file

`Controllers.mo` holds a package; `TankLevel.mo`, a model that uses it:

```modelica
package Controllers "a library in one file"
model PI "a PI controller on a scalar signal"
parameter Real k = 1 "gain";
parameter Real Ti = 1 "integral time";
input Real u;
output Real y;
Real x(start = 0, fixed = true) "integral of u / Ti";
equation
der(x) = u / Ti;
y = k * (u + x);
end PI;
end Controllers;
```

```modelica
model TankLevel "a tank filled to 1 m by the PI controller of Controllers.mo"
parameter Real A = 2 "tank area";
Controllers.PI pi(k = 2, Ti = 5);
Real h(start = 0, fixed = true) "level";
equation
pi.u = 1 - h;
A * der(h) = pi.y;
end TankLevel;
```

```julia
sol = OM.simulate("TankLevel", "TankLevel.mo"; libraries = ["Controllers.mo"], stopTime = 30.0)
OM.OMBackend.getVariableValues(sol, "h")[end] # 1.0002: the level overshoots to 1.113, then settles
key = OM.loadLibrary("Controllers.mo") # "Controllers": the same, loaded once
sol = OM.simulate("TankLevel", "TankLevel.mo"; libraries = [key], stopTime = 30.0)
```

### A library of your own as a folder, using the MSL

`Heating/` is a package folder: `package.mo` with a `uses` annotation, and one file per class.

```modelica
// Heating/package.mo
package Heating "a library of your own, as a folder, that uses the MSL"
annotation(uses(Modelica(version = "4.1.0")), version = "0.1.0");
end Heating;

// Heating/Thermostat.mo
within Heating;
model Thermostat "a heater switched on below T_set - 0.5 K and off above T_set + 0.5 K"
parameter Modelica.Units.SI.HeatFlowRate Q = 400 "heating power";
parameter Modelica.Units.SI.Temperature T_set = 294.15 "set point";
Boolean on(start = true, fixed = true);
Modelica.Thermal.HeatTransfer.Interfaces.HeatPort_b port;
equation
when port.T > T_set + 0.5 then
on = false;
elsewhen port.T < T_set - 0.5 then
on = true;
end when;
port.Q_flow = if on then -Q else 0;
end Thermostat;

// Heating/Room.mo
within Heating;
model Room "a room losing heat to 5 °C outside, heated under thermostat control"
Modelica.Thermal.HeatTransfer.Components.HeatCapacitor air(C = 2e5, T(start = 288.15, fixed = true));
Modelica.Thermal.HeatTransfer.Components.ThermalConductor wall(G = 10);
Modelica.Thermal.HeatTransfer.Sources.FixedTemperature outside(T = 278.15);
Thermostat heater;
equation
connect(heater.port, air.port);
connect(air.port, wall.port_a);
connect(wall.port_b, outside.port);
end Room;
```

```julia
sol = OM.simulate("Heating.Room"; libraries = ["Heating"], stopTime = 21600.0, saveat = 10.0)
T = OM.OMBackend.getVariableValues(sol, "air.T")
extrema(T[sol.t .> 6000.0]) # (293.65, 294.65): T_set +- 0.5 K once warm
```

The room reaches 294.65 K after about 4900 s; from then on the thermostat's `when` equations switch the heater at
each band edge. `OM.loadPackage("Heating")` loads the folder once and returns its key, `"Heating"`. The MSL comes
with it as above (4.1.0); for OpenModelica's installed MSL add its key:
`libraries = ["Heating", OM.loadInstalledLibrary("Modelica"; version = "4.1.0")]`.

### A parameter sweep over an MSL model

Parameters kept tunable at `translate` are set per run, and the model is not translated again. The bearing damping
of the MSL's first rotational example:

```julia
name = "Modelica.Mechanics.Rotational.Examples.First"
OM.translate(name; MSL_Version = "MSL:4.1.0", tunableParameters = ["damping"])
for d in (2.0, 10.0, 50.0)
sol = OM.resimulate(name; saveat = 0.001, parameters = Dict("damping" => d))
w = OM.OMBackend.getVariableValues(sol, "inertia3.w")
println(d, ": ", maximum(w[sol.t .>= 0.5])) # 0.526, 0.472, 0.343: the load swings less
end
```

The translation takes a few seconds; each run after the first, milliseconds.

### The same in a `.mos` script

`HeatedMass.mos`, an OpenModelica script; `loadFile` reads a path relative to the script:

```
loadModel(Modelica, {"4.1.0"}); getErrorString();
loadFile("HeatedMass.mo"); getErrorString();
simulate(HeatedMass, stopTime = 1000); getErrorString();
val(mass.T, 1000)
```

```julia
OM.runScript("HeatedMass.mos").values[end] # 342.234: the value of the last statement
```

### Your own model on an installed library

`Smoothing.mo`:
Expand Down
12 changes: 12 additions & 0 deletions examples/Libraries/Controllers.mo
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package Controllers "a library in one file"
model PI "a PI controller on a scalar signal"
parameter Real k = 1 "gain";
parameter Real Ti = 1 "integral time";
input Real u;
output Real y;
Real x(start = 0, fixed = true) "integral of u / Ti";
equation
der(x) = u / Ti;
y = k * (u + x);
end PI;
end Controllers;
10 changes: 10 additions & 0 deletions examples/Libraries/HeatedMass.mo
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
model HeatedMass "a mass heated with 100 W, losing heat through a wall"
Modelica.Thermal.HeatTransfer.Components.HeatCapacitor mass(C = 500, T(start = 293.15, fixed = true));
Modelica.Thermal.HeatTransfer.Components.ThermalConductor wall(G = 2);
Modelica.Thermal.HeatTransfer.Sources.FixedTemperature ambient(T = 293.15);
Modelica.Thermal.HeatTransfer.Sources.FixedHeatFlow heater(Q_flow = 100);
equation
connect(heater.port, mass.port);
connect(mass.port, wall.port_a);
connect(wall.port_b, ambient.port);
end HeatedMass;
5 changes: 5 additions & 0 deletions examples/Libraries/HeatedMass.mos
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// The MSL 4.1.0 and HeatedMass.mo loaded, HeatedMass simulated, the mass temperature at the end
loadModel(Modelica, {"4.1.0"}); getErrorString();
loadFile("HeatedMass.mo"); getErrorString();
simulate(HeatedMass, stopTime = 1000); getErrorString();
val(mass.T, 1000)
11 changes: 11 additions & 0 deletions examples/Libraries/Heating/Room.mo
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
within Heating;
model Room "a room losing heat to 5 °C outside, heated under thermostat control"
Modelica.Thermal.HeatTransfer.Components.HeatCapacitor air(C = 2e5, T(start = 288.15, fixed = true));
Modelica.Thermal.HeatTransfer.Components.ThermalConductor wall(G = 10);
Modelica.Thermal.HeatTransfer.Sources.FixedTemperature outside(T = 278.15);
Thermostat heater;
equation
connect(heater.port, air.port);
connect(air.port, wall.port_a);
connect(wall.port_b, outside.port);
end Room;
14 changes: 14 additions & 0 deletions examples/Libraries/Heating/Thermostat.mo
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
within Heating;
model Thermostat "a heater switched on below T_set - 0.5 K and off above T_set + 0.5 K"
parameter Modelica.Units.SI.HeatFlowRate Q = 400 "heating power";
parameter Modelica.Units.SI.Temperature T_set = 294.15 "set point";
Boolean on(start = true, fixed = true);
Modelica.Thermal.HeatTransfer.Interfaces.HeatPort_b port;
equation
when port.T > T_set + 0.5 then
on = false;
elsewhen port.T < T_set - 0.5 then
on = true;
end when;
port.Q_flow = if on then -Q else 0;
end Thermostat;
3 changes: 3 additions & 0 deletions examples/Libraries/Heating/package.mo
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package Heating "a library of your own, as a folder, that uses the MSL"
annotation(uses(Modelica(version = "4.1.0")), version = "0.1.0");
end Heating;
3 changes: 3 additions & 0 deletions examples/Libraries/MoreTorque.mo
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
model MoreTorque "the MSL's PID_Controller example with the torque limit raised from 12 to 20 N.m"
extends Modelica.Blocks.Examples.PID_Controller(PI(yMax = 20));
end MoreTorque;
16 changes: 16 additions & 0 deletions examples/Libraries/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Simulating models of libraries

The models of `OM.help(:examples)`: the Modelica Standard Library (4.1.0, and 3.2.3), libraries of
your own, an installed library, and a `.mos` script. `include("run.jl")` runs all of them and prints
each result with the value expected.

| File | What it shows |
|------|---------------|
| [`HeatedMass.mo`](HeatedMass.mo) | Your own model on the MSL: 4.1.0 comes with it, or `MSL = true` picks the version |
| [`MoreTorque.mo`](MoreTorque.mo) | A library model with other parameters: `extends Modelica.Blocks.Examples.PID_Controller(PI(yMax = 20))` |
| [`Controllers.mo`](Controllers.mo), [`TankLevel.mo`](TankLevel.mo) | A library of your own in one file, used by a model in another file |
| [`Heating/`](Heating) | A library of your own as a folder (`package.mo`), using the MSL, with events |
| [`HeatedMass.mos`](HeatedMass.mos) | The same in an OpenModelica `.mos` script |
| [`run.jl`](run.jl) | All of them, an MSL model by name, Buildings (when installed), a parameter sweep |

The [Libraries](https://jkrt.github.io/OM.jl/dev/libraries/) page of the documentation walks through them.
8 changes: 8 additions & 0 deletions examples/Libraries/TankLevel.mo
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
model TankLevel "a tank filled to 1 m by the PI controller of Controllers.mo"
parameter Real A = 2 "tank area";
Controllers.PI pi(k = 2, Ti = 5);
Real h(start = 0, fixed = true) "level";
equation
pi.u = 1 - h;
A * der(h) = pi.y;
end TankLevel;
Loading
Loading