Skip to content
Closed
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.9.1"
version = "0.9.2"
authors = ["John Tinnerholm <john.tinnerholm@liu.se>"]

[deps]
Expand Down
49 changes: 44 additions & 5 deletions src/OM.jl
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ function unloadLibrary(key::String)::Vector{String}
delete!(TRANSLATED_WITH, name)
delete!(LIBRARIES_AT_TRANSLATE, name)
delete!(SCALARIZED_AT_TRANSLATE, name)
delete!(SETTINGS_AT_TRANSLATE, name)
push!(forgotten, name)
end
@info "Unloaded $(join(removed, ", "))" * (isempty(forgotten) ? "" : "; forgot the models $(join(sort!(forgotten), ", "))")
Expand Down Expand Up @@ -703,16 +704,48 @@ function _recordLoadedLibraries(modelName::String)::Nothing
end

"""
_reusableTranslation(internalName, scalarize, MSL_Version, libraries) -> Bool
The settings of a translation that a simulate by name takes as well (`translationSettings`): the observed filter's
patterns, and the elimination of non-dynamic variables as its options' reachability and patterns; `nothing` for no
filter, no elimination.
"""
const TranslationSettings = Tuple{Union{Nothing, Vector{String}}, Union{Nothing, Tuple{Bool, Vector{String}, Vector{String}}}}

"""
translationSettings(observedFilter, eliminateNonDynamic) -> TranslationSettings

The settings `observedFilter` and `eliminateNonDynamic` as a translation records them and a simulate by name compares
them (`_reusableTranslation`): a filter's patterns as strings, the elimination as its options (`true` the default
options, `false` none).
"""
function translationSettings(observedFilter::Union{Nothing, Vector{String}, Vector{Regex}},
eliminateNonDynamic::Union{Nothing, Bool, EliminationOptions})::TranslationSettings
local patterns::Union{Nothing, Vector{String}} = observedFilter === nothing ? nothing :
String[p isa Regex ? p.pattern : p for p in observedFilter]
local options::Union{Nothing, EliminationOptions} = eliminateNonDynamic === true ? EliminationOptions() :
eliminateNonDynamic isa EliminationOptions ? eliminateNonDynamic : nothing
local elimination::Union{Nothing, Tuple{Bool, Vector{String}, Vector{String}}} = options === nothing ? nothing :
(options.reachability, String[r.pattern for r in options.patterns], String[r.pattern for r in options.keepPatterns])
return (patterns, elimination)
end

#= The settings each model was last translated with that a simulate takes as well (by canonical name): a simulate by
name with others translates again. They were ignored: the coverage runner's reference filter, passed to its
simulate after the translate of its backend phase, never applied. =#
const SETTINGS_AT_TRANSLATE = Dict{String, TranslationSettings}()

"""
_reusableTranslation(internalName, scalarize, MSL_Version, libraries, settings) -> Bool

Whether a simulate by name can use the model's translation from earlier in the session: it was translated (or
taken by the array path), with the same scalarize setting, and with the same MSL version, libraries and loaded
libraries where its translate by name recorded them.
taken by the array path), with the same scalarize setting, the same observed filter and elimination (`settings`,
`translationSettings`), and with the same MSL version, libraries and loaded libraries where its translate by name
recorded them.
"""
function _reusableTranslation(internalName::String, scalarize::Bool, MSL_Version::Union{Nothing, String},
libraries::Vector{String})::Bool
libraries::Vector{String}, settings::TranslationSettings)::Bool
(haskey(OMBackend.COMPILED_MODELS_MTK, internalName) || internalName in OMBackend.ARRAY_ODE_MODELS) || return false
get(SCALARIZED_AT_TRANSLATE, internalName, true) == scalarize || return false
get(SETTINGS_AT_TRANSLATE, internalName, settings) == settings || return false
local key::Tuple{Union{Nothing, String}, Vector{String}, Vector{String}} = (MSL_Version, libraries, loadedLibraries())
return get(LIBRARIES_AT_TRANSLATE, internalName, key) == key
end
Expand Down Expand Up @@ -868,6 +901,9 @@ Defaults to `MSL=true`.
is already compiled (default `false`). Useful when code generation logic has
changed and the cached compiled model is stale.

A translation of the model from earlier in the session is reused when it had the same `scalarize`, MSL version,
libraries, `observedFilter` and `eliminateNonDynamic`; else the model is translated again.

# Example
```julia
sol = OM.simulate("Modelica.Mechanics.MultiBody.Examples.Elementary.Pendulum"; stopTime=1.0)
Expand Down Expand Up @@ -901,7 +937,8 @@ function simulate(modelName::String;
kwargs...)
return _withSimulationSettings(directRHS, startTime) do
internalName = OMBackend.canonicalName(modelName)
alreadyCompiled = _reusableTranslation(internalName, scalarize, _mslVersion(MSL_Version), libraries)
alreadyCompiled = _reusableTranslation(internalName, scalarize, _mslVersion(MSL_Version), libraries,
translationSettings(observedFilter, eliminateNonDynamic))
local rebuilt = false
if (!alreadyCompiled || overwriteCache) && (MSL || !isempty(libraries))
rebuilt = _freshBuildAtTranslate!(modelName, mode, overwriteCache)
Expand Down Expand Up @@ -1081,6 +1118,7 @@ function translate(modelName::String,
libraries = libraries,
scalarize = !keep)
SCALARIZED_AT_TRANSLATE[OMBackend.canonicalName(modelName)] = scalarize
SETTINGS_AT_TRANSLATE[OMBackend.canonicalName(modelName)] = translationSettings(observedFilter, eliminateNonDynamic)
delete!(LIBRARIES_AT_TRANSLATE, OMBackend.canonicalName(modelName))
_recordLoadedLibraries(modelName)
if keep
Expand Down Expand Up @@ -1165,6 +1203,7 @@ function translate(modelName::String;
(fm, cache) = flatten(modelName; MSL_Version = MSL_Version, libraries = libraries,
scalarize = !keep)
SCALARIZED_AT_TRANSLATE[OMBackend.canonicalName(modelName)] = scalarize
SETTINGS_AT_TRANSLATE[OMBackend.canonicalName(modelName)] = translationSettings(observedFilter, eliminateNonDynamic)
LIBRARIES_AT_TRANSLATE[OMBackend.canonicalName(modelName)] = (_mslVersion(MSL_Version), copy(libraries),
loadedLibraries())
_recordLoadedLibraries(modelName)
Expand Down
11 changes: 11 additions & 0 deletions test/Models/BuildingsRepro.mo
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,17 @@ package BuildingsRepro
Real y = getTimeTableValue(tab, 1, time, 1e60, 1e60);
end TableInit3;

model TableAtFailedBuild "TableInit3 with an initial equation no start satisfies: the build fails after its module made the table"
parameter Real table[2, 2] = [0, 0; 1, 2];
parameter TimeTable41 tab = TimeTable41("NoName", "NoName", table, 0.0, {2}, 1, 2, 0.0, 3, false, ",", 0);
Real y = getTimeTableValue(tab, 1, time, 1e60, 1e60);
Real x(start = 1);
initial equation
x * x = -1;
equation
der(x) = y - x;
end TableAtFailedBuild;

model ExternalObjectVariable "An external object declared without parameter (Buildings: Spawn adapters, schedules, plotters)"
parameter Real table[2, 2] = [0, 0; 1, 2];
TimeTable41 tab = TimeTable41("NoName", "NoName", table, 0.0, {2}, 1, 2, 0.0, 3, false, ",", 0);
Expand Down
13 changes: 13 additions & 0 deletions test/buildingsReproTests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,19 @@ end
@test !haskey(CG.EXTERNAL_OBJECTS, cname)
end

#= A failed build freed its tables (forgetBuild) while its module stayed evaluated: the module's own simulate read
the freed table and failed on that ("... was freed with the build", or rows not finite) instead of the build's
error (MSL FullRobot with an observed filter). The module is evaluated again first. =#
@testset "A failed build's tables: the simulate fails on the build's error" begin
for scalarize in (false, true)
local err = try brSimulate("TableAtFailedBuild"; stopTime = 1.0, scalarize = scalarize); nothing catch e; e end
@test err isa Exception
local msg = err === nothing ? "" : sprint(showerror, err)
@test occursin("DAE init", msg)
@test !occursin("freed", msg)
end
end

#= Two models' tables of one name in one process: each reads its own. The generated functions
differed only in the table object, which printed without its pointer, so the global cache
of RuntimeGeneratedFunctions gave the second model the first one's (0.05 for 1.0). =#
Expand Down
46 changes: 38 additions & 8 deletions test/translateReuseTests.jl
Original file line number Diff line number Diff line change
@@ -1,32 +1,62 @@
#= A simulate by name reuses the session's translation only for the same MSL version, libraries and loaded
libraries (OM._reusableTranslation): a build of MSL 3.2.3's SignConvention answered for MSL 4.1.0's. A translate
of a file records none, so its model is still reused by name. =#
of a file records none, so its model is still reused by name. Nor for another observed filter or elimination:
they were ignored (the coverage runner's reference filter never applied). =#
using Test
import OM
import OMBackend

@testset "Translation reuse by library" begin
local name = "TranslateReuse_Model"
local defaults = OM.translationSettings(nothing, true)
try
OMBackend.COMPILED_MODELS_MTK[name] = (Expr(:block), false, UInt64(0))
OM.SCALARIZED_AT_TRANSLATE[name] = false
OM.LIBRARIES_AT_TRANSLATE[name] = ("MSL:3.2.3", String[], OM.loadedLibraries())
@test OM._reusableTranslation(name, false, "MSL:3.2.3", String[])
@test !OM._reusableTranslation(name, false, "MSL:4.1.0", String[])
@test !OM._reusableTranslation(name, false, "MSL:3.2.3", ["Buildings"])
@test !OM._reusableTranslation(name, true, "MSL:3.2.3", String[])
@test OM._reusableTranslation(name, false, "MSL:3.2.3", String[], defaults)
@test !OM._reusableTranslation(name, false, "MSL:4.1.0", String[], defaults)
@test !OM._reusableTranslation(name, false, "MSL:3.2.3", ["Buildings"], defaults)
@test !OM._reusableTranslation(name, true, "MSL:3.2.3", String[], defaults)
#= translated while another library was loaded =#
OM.LIBRARIES_AT_TRANSLATE[name] = ("MSL:3.2.3", String[], vcat(OM.loadedLibraries(), "Lib_1_0_0"))
@test !OM._reusableTranslation(name, false, "MSL:3.2.3", String[])
@test !OM._reusableTranslation(name, false, "MSL:3.2.3", String[], defaults)
#= translated from a file: no record, any library =#
delete!(OM.LIBRARIES_AT_TRANSLATE, name)
@test OM._reusableTranslation(name, false, "MSL:4.1.0", String[])
@test OM._reusableTranslation(name, false, "MSL:4.1.0", String[], defaults)
#= not translated =#
delete!(OMBackend.COMPILED_MODELS_MTK, name)
@test !OM._reusableTranslation(name, false, "MSL:3.2.3", String[])
@test !OM._reusableTranslation(name, false, "MSL:3.2.3", String[], defaults)
finally
delete!(OMBackend.COMPILED_MODELS_MTK, name)
delete!(OM.SCALARIZED_AT_TRANSLATE, name)
delete!(OM.LIBRARIES_AT_TRANSLATE, name)
end
end

@testset "Translation reuse by observed filter and elimination" begin
local name = "TranslateReuse_Model"
local reusable(f::Union{Nothing, Vector{String}, Vector{Regex}}, e::Union{Nothing, Bool, OM.EliminationOptions})::Bool =
OM._reusableTranslation(name, false, "MSL:3.2.3", String[], OM.translationSettings(f, e))
try
OMBackend.COMPILED_MODELS_MTK[name] = (Expr(:block), false, UInt64(0))
OM.SCALARIZED_AT_TRANSLATE[name] = false
OM.SETTINGS_AT_TRANSLATE[name] = OM.translationSettings(nothing, true)
@test reusable(nothing, true)
@test reusable(nothing, OM.EliminationOptions())
@test !reusable(["^y\$"], true)
@test !reusable(String[], true)
@test !reusable(nothing, false)
@test !reusable(nothing, OM.EliminationOptions(; keepPatterns = [r"^y$"]))
OM.SETTINGS_AT_TRANSLATE[name] = OM.translationSettings(["^y\$"], false)
@test reusable([r"^y$"], nothing)
@test !reusable(["^x\$"], false)
finally
delete!(OMBackend.COMPILED_MODELS_MTK, name)
delete!(OM.SCALARIZED_AT_TRANSLATE, name)
delete!(OM.SETTINGS_AT_TRANSLATE, name)
end
#= a translate records its settings =#
local file = joinpath(@__DIR__, "Models", "HelloWorld.mo")
OM.translate("HelloWorld", file; observedFilter = ["^x\$"])
@test OM.SETTINGS_AT_TRANSLATE[OMBackend.canonicalName("HelloWorld")] == OM.translationSettings(["^x\$"], true)
end
Loading