From 363cf1bcf6127a572f0101f392385346dc0246b5 Mon Sep 17 00:00:00 2001 From: JKRT Date: Tue, 27 Oct 2026 16:06:37 +0100 Subject: [PATCH] OM.jl 0.8.0: MSL 4.1.0 the default; OM.help() up to date, OM.help(:examples), library examples - MSL 4.1.0 is the default MSL_Version (OM.DEFAULT_MSL_VERSION) of flatten, componentInterface, explainIndexReduction, simulate, translate, writeModelToFile and exportModelica; MSL_Version = "MSL:3.2.3" selects the other bundled version. - OM.help() (src/help.jl) lists the current API by task; OM.help(:examples) shows worked examples on examples/Libraries: your own model on the MSL, a library model modified with extends, a library of your own in one file and as a folder, an installed library, a parameter sweep, a .mos script; run.jl runs them. - Docs: the Libraries page walks through them; the Examples and home pages use MSL 4.1.0 (same results as 3.2.3), the multibody showcase stays on 3.2.3. - Fixes: exportModelica(name, file; MSL = true) defaulted to the unbundled MSL 4.0.0 (KeyError); the .mos clear() reset a script to 4.0.0; docstrings named MTK_MODE as the default mode. - Tests: help names only defined functions and shipped files; MSL-4-only models by default; .mos clear(); runModelMTK takes MSL_Version (ElectricalComponentTest pins 3.2.3: SineVoltage freqHz). Assisted-by: Claude Opus 5.5 --- Project.toml | 2 +- README.md | 6 +- docs/src/examples.md | 16 +- docs/src/index.md | 2 +- docs/src/libraries.md | 179 ++++++++++++++++++++++- examples/Libraries/Controllers.mo | 12 ++ examples/Libraries/HeatedMass.mo | 10 ++ examples/Libraries/HeatedMass.mos | 5 + examples/Libraries/Heating/Room.mo | 11 ++ examples/Libraries/Heating/Thermostat.mo | 14 ++ examples/Libraries/Heating/package.mo | 3 + examples/Libraries/MoreTorque.mo | 3 + examples/Libraries/README.md | 16 ++ examples/Libraries/TankLevel.mo | 8 + examples/Libraries/run.jl | 63 ++++++++ src/MosScripting/mosScripting.jl | 2 +- src/MosScripting/test/runtests.jl | 12 ++ src/OM.jl | 149 +++++++------------ src/help.jl | 124 ++++++++++++++++ test/Models/MSL/MSL4Units.mo | 5 + test/helpTests.jl | 26 ++++ test/libraries.jl | 1 + test/runtests.jl | 1 + test/testUtils.jl | 7 +- 24 files changed, 563 insertions(+), 114 deletions(-) create mode 100644 examples/Libraries/Controllers.mo create mode 100644 examples/Libraries/HeatedMass.mo create mode 100644 examples/Libraries/HeatedMass.mos create mode 100644 examples/Libraries/Heating/Room.mo create mode 100644 examples/Libraries/Heating/Thermostat.mo create mode 100644 examples/Libraries/Heating/package.mo create mode 100644 examples/Libraries/MoreTorque.mo create mode 100644 examples/Libraries/README.md create mode 100644 examples/Libraries/TankLevel.mo create mode 100644 examples/Libraries/run.jl create mode 100644 src/help.jl create mode 100644 test/Models/MSL/MSL4Units.mo create mode 100644 test/helpTests.jl diff --git a/Project.toml b/Project.toml index efd4661..adbf40b 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "OM" uuid = "2f925a0b-2436-437b-858e-49aee461894b" -version = "0.7.1" +version = "0.8.0" authors = ["John Tinnerholm "] [deps] diff --git a/README.md b/README.md index 1c1dd04..f9d70fd 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,10 @@ A Modelica Environment in Julia. Documentation: (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 @@ -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("", ""; MSL = true, MSL_Version = "MSL:4.1.0") diff --git a/docs/src/examples.md b/docs/src/examples.md index e904531..56fad7b 100644 --- a/docs/src/examples.md +++ b/docs/src/examples.md @@ -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 @@ -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 @@ -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") @@ -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 = "") ``` @@ -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. @@ -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 diff --git a/docs/src/index.md b/docs/src/index.md index 8bfd18a..bedb6f2 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -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) diff --git a/docs/src/libraries.md b/docs/src/libraries.md index d16833c..2b89205 100644 --- a/docs/src/libraries.md +++ b/docs/src/libraries.md @@ -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 @@ -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 @@ -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`: diff --git a/examples/Libraries/Controllers.mo b/examples/Libraries/Controllers.mo new file mode 100644 index 0000000..4aa2dc1 --- /dev/null +++ b/examples/Libraries/Controllers.mo @@ -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; diff --git a/examples/Libraries/HeatedMass.mo b/examples/Libraries/HeatedMass.mo new file mode 100644 index 0000000..f238b22 --- /dev/null +++ b/examples/Libraries/HeatedMass.mo @@ -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; diff --git a/examples/Libraries/HeatedMass.mos b/examples/Libraries/HeatedMass.mos new file mode 100644 index 0000000..ac2eb33 --- /dev/null +++ b/examples/Libraries/HeatedMass.mos @@ -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) diff --git a/examples/Libraries/Heating/Room.mo b/examples/Libraries/Heating/Room.mo new file mode 100644 index 0000000..c0588ed --- /dev/null +++ b/examples/Libraries/Heating/Room.mo @@ -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; diff --git a/examples/Libraries/Heating/Thermostat.mo b/examples/Libraries/Heating/Thermostat.mo new file mode 100644 index 0000000..2578f79 --- /dev/null +++ b/examples/Libraries/Heating/Thermostat.mo @@ -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; diff --git a/examples/Libraries/Heating/package.mo b/examples/Libraries/Heating/package.mo new file mode 100644 index 0000000..44b4043 --- /dev/null +++ b/examples/Libraries/Heating/package.mo @@ -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; diff --git a/examples/Libraries/MoreTorque.mo b/examples/Libraries/MoreTorque.mo new file mode 100644 index 0000000..9aa73db --- /dev/null +++ b/examples/Libraries/MoreTorque.mo @@ -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; diff --git a/examples/Libraries/README.md b/examples/Libraries/README.md new file mode 100644 index 0000000..46b4d9c --- /dev/null +++ b/examples/Libraries/README.md @@ -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. diff --git a/examples/Libraries/TankLevel.mo b/examples/Libraries/TankLevel.mo new file mode 100644 index 0000000..dd5ad8b --- /dev/null +++ b/examples/Libraries/TankLevel.mo @@ -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; diff --git a/examples/Libraries/run.jl b/examples/Libraries/run.jl new file mode 100644 index 0000000..92dd2ba --- /dev/null +++ b/examples/Libraries/run.jl @@ -0,0 +1,63 @@ +#= The examples of OM.help(:examples), on the models in this folder: each simulated, with the + value it shows and the value expected. Run with include("run.jl") in a session that has OM. =# + +using OM + +ex(file::String)::String = joinpath(@__DIR__, file) +valuesOf(sol, name::String)::Vector{Float64} = OM.OMBackend.getVariableValues(sol, name) + +""" +Print one example's result: what was simulated, the value shown and the value expected. +""" +function report(what::String, sol, shown::String, value::Real, expected::String)::Nothing + println(rpad(what, 58), string(sol.retcode), " ", shown, " = ", round(value; sigdigits = 6), " (", expected, ")") + return nothing +end + +maxError(sol)::Float64 = maximum(abs.(valuesOf(sol, "PI.u_s") .- valuesOf(sol, "PI.u_m"))) + +for version in ("MSL:4.1.0", "MSL:3.2.3") # 4.1.0 is the default + local sol = OM.simulate("Modelica.Blocks.Examples.PID_Controller"; MSL_Version = version, stopTime = 4.0) + report("PID_Controller by name, " * version, sol, "max speed error", maxError(sol), "the drive saturates at 12 N.m") +end + +for kw in ((;), (MSL = true, MSL_Version = "MSL:3.2.3")) + local sol = OM.simulate("HeatedMass", ex("HeatedMass.mo"); stopTime = 1000.0, overwriteCache = true, kw...) + report("HeatedMass.mo on MSL " * (isempty(kw) ? "4.1.0" : "3.2.3"), sol, "mass.T(1000)", valuesOf(sol, "mass.T")[end], + "293.15 + 50 (1 - exp(-4)) = 342.234") +end + +let sol = OM.simulate("MoreTorque", ex("MoreTorque.mo"); stopTime = 4.0) + report("MoreTorque.mo: PID_Controller with PI(yMax = 20)", sol, "max speed error", maxError(sol), "0.343 with the 12 N.m limit") +end + +let sol = OM.simulate("TankLevel", ex("TankLevel.mo"); libraries = [ex("Controllers.mo")], stopTime = 30.0) + report("TankLevel.mo on the library Controllers.mo", sol, "h(30)", valuesOf(sol, "h")[end], "the set point 1.0") +end + +let sol = OM.simulate("Heating.Room"; libraries = [ex("Heating")], stopTime = 21600.0, saveat = 10.0) + local T = valuesOf(sol, "air.T") + report("Heating.Room of the folder Heating", sol, "max T after 2 h", maximum(T[sol.t .> 7200.0]), + "held within 294.15 +- 0.5 K") +end + +if haskey(OM.libraries(), "Buildings") + local key = OM.loadInstalledLibrary("Buildings") + local sol = OM.simulate("Buildings.Controls.OBC.CDL.Reals.Validation.Add"; libraries = [key]) + report("Buildings ... Validation.Add, installed", sol, "add1.y(end)", valuesOf(sol, "add1.y")[end], "2.0 + 1.0") +else + println("Buildings is not installed: OM.installLibrary(\"Buildings\"; version = \"13.0.0\") installs it") +end + +let name = "Modelica.Mechanics.Rotational.Examples.First" + OM.translate(name; tunableParameters = ["damping"]) + for d in (2.0, 10.0, 50.0) + local sol = OM.resimulate(name; saveat = 0.001, parameters = Dict("damping" => d)) + report("First, damping = $(d), not translated again", sol, "max load speed", + maximum(valuesOf(sol, "inertia3.w")[sol.t .>= 0.5]), "lower with more damping") + end +end + +let result = OM.runScript(ex("HeatedMass.mos")) + println(rpad("HeatedMass.mos", 58), "val(mass.T, 1000) = ", round(result.values[end]; sigdigits = 6), " (342.234)") +end diff --git a/src/MosScripting/mosScripting.jl b/src/MosScripting/mosScripting.jl index 1d133e5..5c8d5fb 100644 --- a/src/MosScripting/mosScripting.jl +++ b/src/MosScripting/mosScripting.jl @@ -1221,7 +1221,7 @@ function _default_commands() empty!(context.loadedFiles) empty!(context.sourceFiles) empty!(context.libraries) - context.mslVersion = "MSL:4.0.0" + context.mslVersion = "MSL:" * _newest_bundled_msl(context.api) context.mslLoaded = false context.lastSolution = nothing context.lastModel = nothing diff --git a/src/MosScripting/test/runtests.jl b/src/MosScripting/test/runtests.jl index 82209cf..b2638af 100644 --- a/src/MosScripting/test/runtests.jl +++ b/src/MosScripting/test/runtests.jl @@ -144,6 +144,18 @@ end end end +module MockClear +clearCaches!() = nothing +end + +#= clear() returns to the MSL a new script starts with (the newest bundled), not one that is not bundled. =# +@testset "clear() keeps a bundled MSL" begin + context = ScriptContext(MockClear; output=devnull) + MosScripting.run("loadModel(Modelica, {\"3.2.3\"}); clear();", context) + @test context.mslVersion == ScriptContext(MockClear; output=devnull).mslVersion == "MSL:4.1.0" + @test !context.mslLoaded +end + @testset "MOS syntax diagnostics" begin separatorError = try MosScripting.run("x := 1 y := 2", fresh_context()) diff --git a/src/OM.jl b/src/OM.jl index 243c2fe..671566c 100644 --- a/src/OM.jl +++ b/src/OM.jl @@ -80,6 +80,12 @@ condition). """ const ModelicaAssertionError = OMBackend.CodeGeneration.ModelicaAssertionError +""" +The MSL version `translate`, `simulate`, `flatten`, `exportModelica` and the other functions use when +no `MSL_Version` is given: the bundled `"MSL:4.1.0"`. The other bundled version is `"MSL:3.2.3"`. +""" +const DEFAULT_MSL_VERSION = "MSL:4.1.0" + function printWelcomeMessage() printstyled("Open", bold=true, color=:light_blue) printstyled("Modelica", bold=true, color=:white) @@ -87,7 +93,7 @@ function printWelcomeMessage() println() print("Running ") Pkg.status("OM") - println("For help run OM.help()") + println("For help run OM.help(), for examples OM.help(:examples)") end if isinteractive() @@ -106,58 +112,7 @@ function listAvailableModels() println(OMBackend.availableModels()) end -""" - help() - -Print a summary of the main OM.jl workflow functions. -""" -function help() - printstyled("OM.jl Workflow\n", bold=true) - println() - printstyled(" Translate and simulate:\n", color=:cyan) - println(" OM.translate(modelName, file) Compile a Modelica model") - println(" OM.simulate(modelName) Simulate a compiled model") - println(" OM.simulate(modelName, file) Translate + simulate in one step") - println(" OM.resimulate(modelName) Re-simulate with different parameters") - println() - printstyled(" Inspect and export:\n", color=:cyan) - println(" OM.exportCSV(modelName, sol) Export results to CSV (OMEdit compatible)") - println(" OM.writeModelToFile(name, file, out) Write generated Julia code to file") - println(" OM.exportModelica(name, file) Get flat Modelica as a string") - println(" OM.listAvailableModels() List compiled models") - println() - printstyled(" Intermediate representations:\n", color=:cyan) - println(" OM.flatten(name, file) Flatten to FlatModel (default)") - println(" OM.flatten(name, file; repr=:DAE) Flatten to DAE representation") - println(" OM.flatten(name, file; MSL=true) Flatten with MSL") - println(" OM.flatten(name, file; libraries=[]) Flatten with user libraries") - println(" OM.flatten(name; MSL_Version=...) Flatten an MSL model by name") - println(" OM.parseFile(file) Parse a Modelica file to AST") - println(" OM.translateToSCode(file) Parse and convert to SCode") - println(" OM.runScript(file) Execute an OpenModelica .mos script") - println() - printstyled(" Debugging:\n", color=:cyan) - println(" OM.explainIndexReduction(name, file) Which states and why (index reduction)") - println(" OM.LogBackend() Enable backend debug logging") - println(" OM.LogFrontend() Enable frontend debug logging") - println(" ...; warnMissingStartValues=true Show warnings for implicit 0.0 start values") - println() - printstyled(" Libraries:\n", color=:cyan) - println(" OM.libraries() List installed Modelica libraries") - println(" OM.installLibrary(name; version=...) Download and install a library") - println(" OM.loadInstalledLibrary(name; ...) Load an installed library by name") - println(" OM.loadLibrary(path) Load a single .mo file") - println(" OM.loadPackage(dir) Load a directory-based package") - println(" OM.translate(n, f; libraries=[...]) Translate with user libraries") - println(" OM.simulate(n, f; libraries=[...]) Simulate with user libraries") - println() - printstyled(" MSL support:\n", color=:cyan) - println(" OM.loadInstalledLibrary(\"Modelica\"; version=\"4.0.0\")") - println(" OM.translate(name, file; MSL=true) Translate with MSL") - println(" OM.translate(name; MSL_Version=...) Translate an MSL model by name") - println(" OM.simulate(name; MSL_Version=...) Simulate an MSL model by name") - println(" OM.writeModelToFile(name, path) Write generated code to file") -end +include("help.jl") """ clearCaches!(; models=true, implementations=true, wrappers=true, extractors=true) @@ -442,7 +397,7 @@ end """ flatten(modelName, modelFile; repr=:FM, scalarize=true, MSL=false, - MSL_Version="MSL:3.2.3", libraries=String[]) + MSL_Version="MSL:4.1.0", libraries=String[]) Flatten a Modelica model from a file. Returns a Tuple of the flattened representation and the function cache. @@ -452,7 +407,7 @@ representation and the function cache. - `scalarize`: scalarize arrays (default `true`; `false` keeps array variables, array equations and for-equations, as omc's `-d=-nfScalarize`; only applies to `:FM`) - `MSL`: load the Modelica Standard Library alongside the model file -- `MSL_Version`: MSL version string (default `"MSL:3.2.3"`) +- `MSL_Version`: MSL version string (default `"MSL:4.1.0"`) - `libraries`: cache keys or file/directory paths for user libraries # Examples @@ -467,7 +422,7 @@ function flatten(modelName::String, modelFile::String; repr::Symbol = :FM, scalarize::Bool = true, MSL = false, - MSL_Version = "MSL:3.2.3", + MSL_Version = DEFAULT_MSL_VERSION, libraries::Vector{String} = String[])::Tuple resolvedLibs = _resolveLibraries(libraries) if !isempty(resolvedLibs) || MSL @@ -488,7 +443,7 @@ function flatten(modelName::String, modelFile::String; end """ - flatten(modelName; MSL_Version="MSL:3.2.3", libraries=String[], scalarize=true) + flatten(modelName; MSL_Version="MSL:4.1.0", libraries=String[], scalarize=true) Flatten a model by name: an MSL model, or one of `libraries`. Returns a Tuple of the flattened representation and the function cache. @@ -506,7 +461,7 @@ key = OM.loadInstalledLibrary("Buildings"; version = "13.0.0") OM.flatten("Buildings.Controls.OBC.CDL.Reals.Validation.Add"; libraries = [key]) ``` """ -function flatten(modelName::String; MSL_Version = "MSL:3.2.3", libraries::Vector{String} = String[], +function flatten(modelName::String; MSL_Version = DEFAULT_MSL_VERSION, libraries::Vector{String} = String[], scalarize::Bool = true)::Tuple isempty(libraries) || return flatten(modelName, ""; libraries = libraries, scalarize = scalarize) return OMFrontend.flattenModelWithMSL(modelName; MSL_Version = MSL_Version, scalarize = scalarize) @@ -514,7 +469,7 @@ end """ componentInterface(modelName, [modelFile]; component, MSL = modelFile === nothing, - MSL_Version = "MSL:3.2.3", libraries = String[]) -> NamedTuple + MSL_Version = "MSL:4.1.0", libraries = String[]) -> NamedTuple The interface of `component` (its name in the model: `"spring"`, `"engine.damper"`) in the flattened `modelName`, as plain values (no frontend types): @@ -536,7 +491,7 @@ OM.componentInterface("Modelica.Mechanics.Rotational.Examples.First"; component ``` """ function componentInterface(modelName::String, modelFile::Union{Nothing, String} = nothing; component::String, - MSL::Bool = modelFile === nothing, MSL_Version::String = "MSL:3.2.3", + MSL::Bool = modelFile === nothing, MSL_Version::String = DEFAULT_MSL_VERSION, libraries::Vector{String} = String[])::NamedTuple local fm::OMFrontend.Frontend.FlatModel = modelFile === nothing ? first(flatten(modelName; MSL_Version = MSL_Version, libraries = libraries)) : @@ -614,8 +569,8 @@ function _componentInterface(fm::OMFrontend.Frontend.FlatModel, component::Strin end """ - explainIndexReduction(modelName, modelFile; MSL=false, MSL_Version="MSL:3.2.3", libraries=String[]) - explainIndexReduction(modelName; MSL_Version="MSL:3.2.3", libraries=String[]) + explainIndexReduction(modelName, modelFile; MSL=false, MSL_Version="MSL:4.1.0", libraries=String[]) + explainIndexReduction(modelName; MSL_Version="MSL:4.1.0", libraries=String[]) What the backend's index reduction does to a model and why, as a report: the equations it differentiates and how often, the candidates of each choice of states with their StateSelect @@ -628,13 +583,13 @@ OM.explainIndexReduction("Modelica.Mechanics.MultiBody.Examples.Elementary.Pendu OM.explainIndexReduction("PendulumCartesian", "Pendulum.mo") ``` """ -function explainIndexReduction(modelName::String, modelFile::String; MSL::Bool = false, MSL_Version::String = "MSL:3.2.3", +function explainIndexReduction(modelName::String, modelFile::String; MSL::Bool = false, MSL_Version::String = DEFAULT_MSL_VERSION, libraries::Vector{String} = String[]) local (fm, cache) = flatten(modelName, modelFile; MSL = MSL, MSL_Version = MSL_Version, libraries = libraries) return OMBackend.explainIndexReduction(fm; functionList = OMFrontend.cacheToFunctionList(cache)) end -function explainIndexReduction(modelName::String; MSL_Version::String = "MSL:3.2.3", libraries::Vector{String} = String[]) +function explainIndexReduction(modelName::String; MSL_Version::String = DEFAULT_MSL_VERSION, libraries::Vector{String} = String[]) local (fm, cache) = flatten(modelName; MSL_Version = MSL_Version, libraries = libraries) return OMBackend.explainIndexReduction(fm; functionList = OMFrontend.cacheToFunctionList(cache)) end @@ -716,11 +671,11 @@ to `translate(modelName, modelFile; ...)` followed by # Keyword arguments - `startTime`, `stopTime`: simulation time span (default 0.0 to 1.0). - `MSL`: set `true` to also load the Modelica Standard Library. -- `MSL_Version`: MSL version string (default `"MSL:3.2.3"`). +- `MSL_Version`: MSL version string (default `"MSL:4.1.0"`). - `libraries`: cache keys or file/directory paths for user libraries (see `loadLibrary` / `loadPackage` / `loadInstalledLibrary`). - `solver`: ODE solver (default `OMBackend.defaultSolver()`: Rodas5P with finite-difference Jacobians). -- `mode`: backend mode (default `OMBackend.MTK_MODE`). +- `mode`: backend mode (default `IMTK_MODE`). - `warnMissingStartValues`: override missing-start-value warnings. - `eliminateNonDynamic`: elimination of non-dynamic variables before MTK code generation, reducing ODEProblem compilation time. Accepts: @@ -750,7 +705,7 @@ function simulate(modelName::String, startTime= 0.0, stopTime= 1.0, MSL = false, - MSL_Version = "MSL:3.2.3", + MSL_Version = DEFAULT_MSL_VERSION, libraries::Vector{String} = String[], solver = OMBackend.defaultSolver(), mode = OMBackend.DEFAULT_BACKEND_MODE[], @@ -800,19 +755,19 @@ _builtAtTranslate(modelName::String)::Bool = haskey(OMBackend.IMTKGen.BUILT, OMBackend.canonicalName(modelName)) """ - simulate(modelName; MSL_Version="MSL:3.2.3", startTime=0.0, stopTime=1.0, ...) + simulate(modelName; MSL_Version="MSL:4.1.0", startTime=0.0, stopTime=1.0, ...) Translate and simulate a model by name, an MSL model or one of `libraries`. Defaults to `MSL=true`. # Keyword arguments - `startTime`, `stopTime`: simulation time span (default 0.0 to 1.0) -- `MSL_Version`: MSL version string (default `"MSL:3.2.3"`) +- `MSL_Version`: MSL version string (default `"MSL:4.1.0"`) - `libraries`: the libraries the model is in, instead of the MSL: cache keys (`loadInstalledLibrary`, `loadLibrary`, `loadPackage`) or file/directory paths. The libraries an installed library uses come along with its key. - `solver`: ODE solver (default `OMBackend.defaultSolver()`: Rodas5P with finite-difference Jacobians) -- `mode`: backend mode (default `OMBackend.MTK_MODE`) +- `mode`: backend mode (default `IMTK_MODE`) - `warnMissingStartValues`: override missing-start-value warnings - `eliminateNonDynamic`: elimination of non-dynamic variables before MTK code generation, reducing ODEProblem compilation time. Accepts: @@ -835,15 +790,15 @@ Defaults to `MSL=true`. # Example ```julia +sol = OM.simulate("Modelica.Mechanics.MultiBody.Examples.Elementary.Pendulum"; stopTime=1.0) +# On MSL 3.2.3: sol = OM.simulate("Modelica.Mechanics.MultiBody.Examples.Elementary.Pendulum"; MSL_Version="MSL:3.2.3", stopTime=1.0) # With observed filter for faster compilation: -sol = OM.simulate("Modelica.Mechanics.MultiBody.Examples.Elementary.Pendulum"; - MSL_Version="MSL:3.2.3", stopTime=1.0, +sol = OM.simulate("Modelica.Mechanics.MultiBody.Examples.Elementary.Pendulum"; stopTime=1.0, observedFilter=["^rev_", "^body_"]) # Force re-translation (e.g. after code generation changes): -sol = OM.simulate("Modelica.Mechanics.MultiBody.Examples.Elementary.Pendulum"; - MSL_Version="MSL:3.2.3", overwriteCache=true) +sol = OM.simulate("Modelica.Mechanics.MultiBody.Examples.Elementary.Pendulum"; overwriteCache=true) # A model of an installed library: key = OM.loadInstalledLibrary("Buildings"; version = "13.0.0") sol = OM.simulate("Buildings.Controls.OBC.CDL.Reals.Validation.Add"; libraries = [key]) @@ -853,7 +808,7 @@ function simulate(modelName::String; startTime = 0.0, stopTime = 1.0, MSL = true, - MSL_Version = "MSL:3.2.3", + MSL_Version = DEFAULT_MSL_VERSION, libraries::Vector{String} = String[], solver = OMBackend.defaultSolver(), mode = OMBackend.DEFAULT_BACKEND_MODE[], @@ -987,13 +942,13 @@ _eventCount(cbs::Union{Tuple, AbstractVector})::Int = sum(_eventCount, cbs; init Translate a Modelica model from a file and load it in memory. The model can be simulated at a later stage by calling `simulate` with the name of the model. -If `MSL = true` the compiler will use the Modelica Standard Library (MSL) version 3.2.3 by default. -The bundled versions are `MSL:3.2.3` and `MSL:4.1.0`. +If `MSL = true` the compiler will use the Modelica Standard Library (MSL) version 4.1.0 by default. +The bundled versions are `MSL:4.1.0` and `MSL:3.2.3`. # Keyword arguments - `MSL::Bool = false`: whether to load the MSL alongside the model file. -- `MSL_Version::String = "MSL:3.2.3"`: which MSL version to use. +- `MSL_Version::String = "MSL:4.1.0"`: which MSL version to use (`DEFAULT_MSL_VERSION`). - `libraries::Vector{String} = String[]`: cache keys or file paths for user libraries to load alongside the model. Load libraries first with `OM.loadLibrary` or pass `.mo` file paths directly. @@ -1024,7 +979,7 @@ OM.translate("MyModel", "model.mo"; eliminateNonDynamic=true) function translate(modelName::String, modelFile::String; MSL = false, - MSL_Version = "MSL:3.2.3", + MSL_Version = DEFAULT_MSL_VERSION, libraries::Vector{String} = String[], mode = OMBackend.DEFAULT_BACKEND_MODE[], warnMissingStartValues = nothing, @@ -1072,13 +1027,13 @@ function translate(modelName::String, end """ - translate(modelName; MSL_Version="MSL:3.2.3", libraries=String[], mode, eliminateNonDynamic=nothing) + translate(modelName; MSL_Version="MSL:4.1.0", libraries=String[], mode, eliminateNonDynamic=nothing) Translate a model by name, an MSL model or one of `libraries`, and load it in memory. # Keyword arguments -- `MSL_Version::String = "MSL:3.2.3"`: which MSL version to use. +- `MSL_Version::String = "MSL:4.1.0"`: which MSL version to use (`DEFAULT_MSL_VERSION`). - `libraries`: the libraries the model is in, instead of the MSL: cache keys (`loadInstalledLibrary`, `loadLibrary`, `loadPackage`) or file/directory paths. The libraries an installed library uses come along with its key. @@ -1100,16 +1055,15 @@ Translate a model by name, an MSL model or one of `libraries`, and load it in me # Examples ```julia -OM.translate("Modelica.Mechanics.MultiBody.Examples.Elementary.Pendulum"; - MSL_Version="MSL:3.2.3") +OM.translate("Modelica.Mechanics.MultiBody.Examples.Elementary.Pendulum") # With non-dynamic variable elimination for faster ODEProblem compilation: OM.translate("Modelica.Mechanics.MultiBody.Examples.Elementary.Pendulum"; - MSL_Version="MSL:3.2.3", eliminateNonDynamic=true) + eliminateNonDynamic=true) ``` """ function translate(modelName::String; - MSL_Version = "MSL:3.2.3", + MSL_Version = DEFAULT_MSL_VERSION, libraries::Vector{String} = String[], mode = OMBackend.DEFAULT_BACKEND_MODE[], warnMissingStartValues = nothing, @@ -1166,7 +1120,7 @@ function logScalarizedFallback(fm::OMFrontend.Frontend.FlatModel)::Nothing end """ - writeModelToFile(modelName, modelFile, filePath; MSL=false, MSL_Version="MSL:3.2.3", + writeModelToFile(modelName, modelFile, filePath; MSL=false, MSL_Version="MSL:4.1.0", mode=OMBackend.DEFAULT_BACKEND_MODE[], keepComments=true, keepBeginBlocks=true) Translate a file-based Modelica model and write the generated Julia code to @@ -1185,7 +1139,7 @@ OM.writeModelToFile("MyModel", "MyModel.mo", "/tmp/MyModel_debug.jl") """ function writeModelToFile(modelName::String, modelFile::String, filePath::String; MSL = false, - MSL_Version = "MSL:3.2.3", + MSL_Version = DEFAULT_MSL_VERSION, mode = OMBackend.DEFAULT_BACKEND_MODE[], keepComments = true, keepBeginBlocks = true) @@ -1195,7 +1149,7 @@ function writeModelToFile(modelName::String, modelFile::String, filePath::String end """ - writeModelToFile(modelName, filePath; MSL_Version="MSL:3.2.3", + writeModelToFile(modelName, filePath; MSL_Version="MSL:4.1.0", mode=OMBackend.DEFAULT_BACKEND_MODE[], keepComments=true, keepBeginBlocks=true) Translate an MSL model by name and write the generated Julia code to @@ -1205,11 +1159,11 @@ the file-based form. # Example ```julia OM.writeModelToFile("Modelica.Mechanics.MultiBody.Examples.Elementary.Pendulum", - "/tmp/pendulum_debug.jl"; MSL_Version="MSL:3.2.3") + "/tmp/pendulum_debug.jl") ``` """ function writeModelToFile(modelName::String, filePath::String; - MSL_Version = "MSL:3.2.3", + MSL_Version = DEFAULT_MSL_VERSION, mode = OMBackend.DEFAULT_BACKEND_MODE[], keepComments = true, keepBeginBlocks = true) @@ -1290,14 +1244,14 @@ toString(flatModel) = OMFrontend.toString(flatModel) #Base.string(flatModel) = toString """ - exportModelica(modelName, file; MSL=false, MSL_Version="MSL:4.0.0", + exportModelica(modelName, file; MSL=false, MSL_Version="MSL:4.1.0", libraries=String[], printBindingTypes=false, scalarize=false) Returns the flat Modelica representation as a String. # Keyword arguments - `MSL`: load the Modelica Standard Library alongside the model file -- `MSL_Version`: MSL version string (default `"MSL:4.0.0"`) +- `MSL_Version`: MSL version string (default `"MSL:4.1.0"`; bundled: 4.1.0 and 3.2.3) - `libraries`: cache keys or file/directory paths for user libraries - `printBindingTypes`: include type annotations in bindings (debugging only) - `scalarize`: enable scalarization (default `false`). Note that the omc of @@ -1315,7 +1269,7 @@ function exportModelica(modelName::String, file::String; printBindingTypes = false, MSL = false, - MSL_Version = "MSL:4.0.0", + MSL_Version = DEFAULT_MSL_VERSION, libraries::Vector{String} = String[], scalarize = false) local fmStr::String @@ -1333,19 +1287,18 @@ function exportModelica(modelName::String, end """ - exportModelica(modelName; MSL_Version="MSL:3.2.3", printBindingTypes=false, scalarize=false) + exportModelica(modelName; MSL_Version="MSL:4.1.0", printBindingTypes=false, scalarize=false) Export flat Modelica for an MSL model by name. # Examples ```julia -OM.exportModelica("Modelica.Mechanics.MultiBody.Examples.Elementary.Pendulum"; - MSL_Version="MSL:3.2.3") +OM.exportModelica("Modelica.Mechanics.MultiBody.Examples.Elementary.Pendulum") ``` """ function exportModelica(modelName::String; printBindingTypes = false, - MSL_Version = "MSL:3.2.3", + MSL_Version = DEFAULT_MSL_VERSION, scalarize = false) local fmStr::String try @@ -1406,7 +1359,7 @@ LogFrontend() = setDebug(frontend=true) Load the requested MSL version into the frontend library cache and return its SCode program. Accepted values are `"MSL:3.2.3"` / `"MSL_3_2_3"` / -`"3.2.3"` and `"MSL:4.0.0"` / `"MSL_4_0_0"` / `"4.0.0"`. +`"3.2.3"` and `"MSL:4.1.0"` / `"MSL_4_1_0"` / `"4.1.0"`. For most workflows prefer `loadInstalledLibrary("Modelica"; version = ...)`, which discovers installed Modelica libraries on disk and is consistent with diff --git a/src/help.jl b/src/help.jl new file mode 100644 index 0000000..3f0d21f --- /dev/null +++ b/src/help.jl @@ -0,0 +1,124 @@ +#= OM.help(): the main functions by task. OM.help(:examples): worked examples on the models in + examples/Libraries. =# + +const DOCUMENTATION_URL = "https://jkrt.github.io/OM.jl/dev/" + +const HELP_SECTIONS = [ + "Simulate" => [ + "OM.simulate(name, file; stopTime = 10.0)" => "a model in a .mo file", + "OM.simulate(name)" => "a model of the MSL (bundled 4.1.0, the default)", + "OM.simulate(name; MSL_Version = \"MSL:3.2.3\")" => "the same on the bundled MSL 3.2.3", + "OM.simulate(name; libraries = [key])" => "a model of another library", + "OM.translate(name, file; tunableParameters = [\"k\"])" => "compile; keep parameters settable", + "OM.resimulate(name; parameters = Dict(\"k\" => 2.0))" => "run again, not translated again", + "...; solver = Tsit5(), reltol = 1e-8, saveat = 0.01" => "a SciML solver; the rest goes to solve", + ], + "Results" => [ + "OM.OMBackend.getVariableValues(sol, \"body.v\")" => "a variable's values, Modelica name", + "sol.t, sol.retcode" => "time points, solver status", + "OM.exportCSV(name, sol)" => "a CSV file for OMEdit", + "OM.getMTKProblem(name; tspan = (0.0, 10.0))" => "ODEProblem and callbacks, to solve", + ], + "Libraries" => [ + "OM.libraries()" => "installed: ~/OMJL/libraries, OpenModelica's", + "OM.installLibrary(name; version, url)" => "install from git (Buildings: no url)", + "key = OM.loadInstalledLibrary(name; version)" => "load one, with the libraries it uses", + "key = OM.loadLibrary(\"Lib.mo\")" => "your library in one file", + "key = OM.loadPackage(\"Lib\")" => "your library as a folder (package.mo)", + "...; libraries = [key, \"Lib.mo\", \"Lib\"]" => "keys or paths: simulate, translate, flatten", + "" => "A model using Modelica without loading it gets the bundled MSL 4.1.0.", + ], + "Inspect" => [ + "OM.exportModelica(name, file)" => "the flat model as Modelica text", + "OM.flatten(name, file)" => "the flat model, frontend data", + "OM.componentInterface(name; component = \"spring\")" => "a component's connectors, equations", + "OM.explainIndexReduction(name, file)" => "the states chosen, and why", + "OM.isTunable(name, [\"k\"])" => "whether parameters are tunable", + "OM.pristineParameters(name)" => "the parameters simulations start from", + "OM.eventCount(name)" => "the events of a compiled model", + "OM.writeModelToFile(name, file, out)" => "the generated Julia code", + "OM.listAvailableModels()" => "the models compiled in this session", + ], + "Scripts" => [ + "OM.runScript(\"script.mos\")" => "an OpenModelica .mos script", + ], + "Settings and debugging" => [ + "...; mode = OM.MTK_MODE" => "backend: IMTK_MODE (default), MTK, DE", + "OM.DEFAULT_MSL_VERSION" => "the MSL used without MSL_Version: 4.1.0", + "OM.clearCaches!()" => "forget compiled models and caches", + "OM.setDebug(frontend = true, backend = true)" => "debug logging; setDebug() ends it", + "...; warnMissingStartValues = true" => "warn of start values left at 0.0", + "OM.ModelicaAssertionError" => "thrown by a failed Modelica assert", + ], +] + +const HELP_EXAMPLES = """ +# The models are in OM.jl's examples/Libraries; include("run.jl") there runs all of them. +ex(file) = joinpath(pkgdir(OM), "examples", "Libraries", file) + +# A model of the MSL by name: the bundled MSL 4.1.0, or 3.2.3 with MSL_Version +sol = OM.simulate("Modelica.Blocks.Examples.PID_Controller"; stopTime = 4.0) +OM.OMBackend.getVariableValues(sol, "inertia1.w") +sol = OM.simulate("Modelica.Blocks.Examples.PID_Controller"; MSL_Version = "MSL:3.2.3", + stopTime = 4.0) + +# Your own model in a file, using the MSL: MSL 4.1.0 comes with it; MSL = true picks the version +sol = OM.simulate("HeatedMass", ex("HeatedMass.mo"); stopTime = 1000.0) +sol = OM.simulate("HeatedMass", ex("HeatedMass.mo"); MSL = true, MSL_Version = "MSL:3.2.3", + stopTime = 1000.0) + +# A library model with other parameters: MoreTorque extends PID_Controller(PI(yMax = 20)) +sol = OM.simulate("MoreTorque", ex("MoreTorque.mo"); stopTime = 4.0) + +# A library of your own in one file, used by a model in another file +sol = OM.simulate("TankLevel", ex("TankLevel.mo"); libraries = [ex("Controllers.mo")], + stopTime = 30.0) + +# A library of your own as a folder with package.mo, using the MSL +sol = OM.simulate("Heating.Room"; libraries = [ex("Heating")], stopTime = 21600.0) + +# An installed library, with the libraries it uses +OM.installLibrary("Buildings"; version = "13.0.0") # once +key = OM.loadInstalledLibrary("Buildings"; version = "13.0.0") +sol = OM.simulate("Buildings.Controls.OBC.CDL.Reals.Validation.Add"; libraries = [key]) + +# Parameters changed without translating again +name = "Modelica.Mechanics.Rotational.Examples.First" +OM.translate(name; tunableParameters = ["damping"]) +sols = [OM.resimulate(name; parameters = Dict("damping" => d)) for d in (2.0, 10.0, 50.0)] + +# An OpenModelica .mos script: loadModel(Modelica, {"4.1.0"}), loadFile, simulate, val +OM.runScript(ex("HeatedMass.mos")).values[end] # its last statement's value: val(mass.T, 1000) +""" + +""" + help(io::IO = stdout) + help(topic::Symbol, io::IO = stdout) + +Print the main OM.jl functions by task (`help()`), or worked examples (`help(:examples)`): +simulating models of the bundled MSL, of your own libraries and of installed ones, on the models in +OM.jl's `examples/Libraries`. The documentation is at https://jkrt.github.io/OM.jl/dev/. +""" +function help(io::IO = stdout)::Nothing + printstyled(io, "OM.jl\n"; bold = true) + local width::Int = maximum(length(first(entry)) for (_, entries) in HELP_SECTIONS for entry in entries) + for (title::String, entries::Vector{Pair{String, String}}) in HELP_SECTIONS + println(io) + printstyled(io, " ", title, ":\n"; color = :cyan) + for (call::String, what::String) in entries + println(io, " ", isempty(call) ? what : rpad(call, width) * " " * what) + end + end + println(io) + println(io, "Examples: OM.help(:examples). Documentation: ", DOCUMENTATION_URL) + return nothing +end + +function help(topic::Symbol, io::IO = stdout)::Nothing + topic === :examples || error("OM.help has no topic :$(topic): use OM.help() or OM.help(:examples).") + printstyled(io, "OM.jl examples\n\n"; bold = true) + print(io, HELP_EXAMPLES) + println(io) + println(io, "More: the Examples and Libraries pages of ", DOCUMENTATION_URL) + return nothing +end diff --git a/test/Models/MSL/MSL4Units.mo b/test/Models/MSL/MSL4Units.mo new file mode 100644 index 0000000..020432d --- /dev/null +++ b/test/Models/MSL/MSL4Units.mo @@ -0,0 +1,5 @@ +model MSL4Units "uses Modelica.Units, which MSL 3.2.3 does not have: T(t) = 290 + 10 exp(-0.1 t)" + Modelica.Units.SI.Temperature T(start = 300, fixed = true); +equation + der(T) = -0.1 * (T - 290); +end MSL4Units; diff --git a/test/helpTests.jl b/test/helpTests.jl new file mode 100644 index 0000000..bd5b3c0 --- /dev/null +++ b/test/helpTests.jl @@ -0,0 +1,26 @@ +#= OM.help() and OM.help(:examples) name only what OM.jl has: its functions, and the files of + examples/Libraries. =# +@testset "help:" begin + local overview = sprint(OM.help) + local examples = sprint(io -> OM.help(:examples, io)) + for text in (overview, examples) + for m in eachmatch(r"\bOM\.([A-Za-z_][A-Za-z0-9_!]*)", text) + m.captures[1] == "jl" && continue # "OM.jl" + @test isdefined(OM, Symbol(m.captures[1])) + end + end + local exampleDir = joinpath(@__DIR__, "..", "examples", "Libraries") + for m in eachmatch(r"ex\(\"([^\"]+)\"\)", examples) + @test ispath(joinpath(exampleDir, m.captures[1])) + end + @test_throws ErrorException OM.help(:nope) +end + +#= MSL 4.1.0 is the default MSL_Version: Modelica.Units and the Polyphase package exist only in MSL 4. =# +@testset "MSL 4.1.0 by default:" begin + @test OM.DEFAULT_MSL_VERSION == "MSL:4.1.0" + local sol = OM.simulate("MSL4Units", "./Models/MSL/MSL4Units.mo"; MSL = true, stopTime = 1.0) + @test isapprox(OMBackend.getVariableValues(sol, "T")[end], 290 + 10 * exp(-0.1); atol = 1e-3) + @test OM.exportModelica("MSL4Units", "./Models/MSL/MSL4Units.mo"; MSL = true) isa String + @test OM.flatten("Modelica.Electrical.Polyphase.Examples.TransformerYY") isa Tuple +end diff --git a/test/libraries.jl b/test/libraries.jl index d183ab2..8d3f32e 100644 --- a/test/libraries.jl +++ b/test/libraries.jl @@ -16,6 +16,7 @@ end runModelMTK("ElectricalComponentTestMSL.SimpleCircuit" ,"./Models/MSL/ElectricalComponentTest.mo" ;MSL = true, + MSL_Version = "MSL:3.2.3", # SineVoltage(freqHz): MSL 3.2.3's name (4.x: f) timeSpan=(0.0, 1.0)) true end diff --git a/test/runtests.jl b/test/runtests.jl index af4d73a..fdcc631 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -34,6 +34,7 @@ ingroup(g) = TEST_GROUP == "all" || TEST_GROUP == g include("simCodeCheckTests.jl") include("translateReuseTests.jl") include("componentInterfaceTests.jl") + include("helpTests.jl") end ingroup("core") && @testset "Libraries And Language Extensions:" begin #= Translate and run some "advanced" models. Does not check the results =# diff --git a/test/testUtils.jl b/test/testUtils.jl index 1dcd9c2..c5220cd 100644 --- a/test/testUtils.jl +++ b/test/testUtils.jl @@ -95,24 +95,27 @@ end ``` runModelMTK(model, file; MSL = false, + MSL_Version = OM.DEFAULT_MSL_VERSION, timeSpan = (0.0, 1.0)) ``` Given a model and a file run the model in the file. -If MSL is true we use the MSL. Timespan is used to set the time. +If MSL is true we use the MSL (`MSL_Version`). Timespan is used to set the time. """ function runModelMTK(model, file; MSL = false, + MSL_Version = OM.DEFAULT_MSL_VERSION, timeSpan = (0.0, 1.0), solver = OMBackend.defaultSolver(), kwargs...) @info "Translating : " model - OM.translate(model, file; MSL = MSL) + OM.translate(model, file; MSL = MSL, MSL_Version = MSL_Version) @info "Simulating:" return OM.simulate(model, file; startTime = first(timeSpan), stopTime = last(timeSpan), MSL = MSL, + MSL_Version = MSL_Version, mode = OMBackend.MTK_MODE, solver = solver, kwargs...) end