From dc16f48c678f53e0a74ba0c5bd70937df9a4ebe9 Mon Sep 17 00:00:00 2001 From: ThummeTo <83663542+ThummeTo@users.noreply.github.com> Date: Mon, 17 Jun 2024 21:04:14 +0200 Subject: [PATCH] v1.0.0 (#111) * WIP * preparation for FMIBase.jl * bug fixes, preparations for FMI3 and FMIBase * minor change * Julia 1.9+ extension system * getValue bug fix (strings) * Julia 1.9+ extensions --- .github/workflows/Eval.yml | 2 +- Project.toml | 20 +- README.md | 3 +- ext/FMIZooExt.jl | 15 + src/FMI2/c.jl | 366 ++++----- src/FMI2/convert.jl | 448 ---------- src/FMI2/ext.jl | 1418 +------------------------------- src/FMI2/fmu_to_md.jl | 198 ----- src/FMI2/int.jl | 340 ++++++-- src/FMI2/md.jl | 1218 +++------------------------ src/FMI2/prep.jl | 299 +------ src/FMI3/c.jl | 297 +++---- src/FMI3/convert.jl | 91 -- src/FMI3/ext.jl | 1351 +----------------------------- src/FMI3/fmu_to_md.jl | 59 -- src/FMI3/int.jl | 599 ++++++++++---- src/FMI3/md.jl | 623 +++----------- src/FMI3/prep.jl | 237 ++++++ src/FMIImport.jl | 172 +--- src/binary.jl | 121 +++ src/convert.jl | 175 ++++ src/info.jl | 143 ++++ src/jacobian.jl | 332 ++++++++ src/md_parse.jl | 91 ++ src/parse.jl | 105 --- src/zip.jl | 92 +++ test/FMI2/externalLogging.jl | 7 +- test/FMI2/getter_setter.jl | 9 +- test/FMI2/logging.jl | 10 +- test/FMI2/model_description.jl | 111 ++- test/FMI2/state.jl | 11 +- test/FMI3/dir_ders.jl | 49 -- test/FMI3/getter_setter.jl | 8 +- test/FMI3/logging.jl | 6 +- test/FMI3/model_description.jl | 44 +- test/FMI3/state.jl | 10 +- test/{FMI2 => }/eval.jl | 28 +- test/runtests.jl | 10 +- 38 files changed, 2740 insertions(+), 6378 deletions(-) create mode 100644 ext/FMIZooExt.jl delete mode 100644 src/FMI2/convert.jl delete mode 100644 src/FMI2/fmu_to_md.jl delete mode 100644 src/FMI3/convert.jl delete mode 100644 src/FMI3/fmu_to_md.jl create mode 100644 src/FMI3/prep.jl create mode 100644 src/binary.jl create mode 100644 src/convert.jl create mode 100644 src/info.jl create mode 100644 src/jacobian.jl create mode 100644 src/md_parse.jl delete mode 100644 src/parse.jl create mode 100644 src/zip.jl delete mode 100644 test/FMI3/dir_ders.jl rename test/{FMI2 => }/eval.jl (78%) diff --git a/.github/workflows/Eval.yml b/.github/workflows/Eval.yml index 0de4c32..a160936 100644 --- a/.github/workflows/Eval.yml +++ b/.github/workflows/Eval.yml @@ -55,4 +55,4 @@ jobs: import Pkg; Pkg.instantiate(); Pkg.add(path="./PkgEval.jl")' - julia --project=. ./test/FMI2/eval.jl + julia --project=. ./test/eval.jl diff --git a/Project.toml b/Project.toml index 38a83e3..5154a61 100644 --- a/Project.toml +++ b/Project.toml @@ -1,21 +1,27 @@ name = "FMIImport" uuid = "9fcbc62e-52a0-44e9-a616-1359a0008194" authors = ["TT ", "LM ", "JK "] -version = "0.16.4" +version = "1.0.0" [deps] Downloads = "f43a241f-c20a-4ad4-852c-f6b1247861c6" -EzXML = "8f5d6c58-4d21-5cfd-889c-e3ad7ee6a615" -FMICore = "8af89139-c281-408e-bce2-3005eb87462f" +FMIBase = "900ee838-d029-460e-b485-d98a826ceef2" Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb" +PackageExtensionCompat = "65ce6f38-6b18-4e1d-a461-8949797d7930" RelocatableFolders = "05181044-ff0b-4ac5-8273-598c1e38db00" -ZipFile = "a5390f91-8eb1-5f08-bee0-b1d1ffed6cea" +Requires = "ae029012-a4dd-5104-9daa-d747884805df" + +[weakdeps] +FMIZoo = "724179cf-c260-40a9-bd27-cccc6fe2f195" + +[extensions] +FMIZooExt = ["FMIZoo"] [compat] Downloads = "1" -EzXML = "1.1.0" -FMICore = "0.20.0" +FMIBase = "1.0.0" Libdl = "1" +PackageExtensionCompat = "1.0.0" RelocatableFolders = "1" -ZipFile = "0.10.0" +Requires = "1.3.0" julia = "1.6" diff --git a/README.md b/README.md index 85e45ff..3f0b14f 100644 --- a/README.md +++ b/README.md @@ -35,10 +35,11 @@ To keep dependencies nice and clean, the original package [*FMI.jl*](https://git - [*FMI.jl*](https://github.com/ThummeTo/FMI.jl): High level loading, manipulating, saving or building entire FMUs from scratch - [*FMIImport.jl*](https://github.com/ThummeTo/FMIImport.jl): Importing FMUs into Julia - [*FMIExport.jl*](https://github.com/ThummeTo/FMIExport.jl): Exporting stand-alone FMUs from Julia Code +- [*FMIBase.jl*](https://github.com/ThummeTo/FMIBase.jl): Common concepts for import and export of FMUs - [*FMICore.jl*](https://github.com/ThummeTo/FMICore.jl): C-code wrapper for the FMI-standard - [*FMISensitivity.jl*](https://github.com/ThummeTo/FMISensitivity.jl): Static and dynamic sensitivities over FMUs - [*FMIBuild.jl*](https://github.com/ThummeTo/FMIBuild.jl): Compiler/Compilation dependencies for FMIExport.jl -- [*FMIFlux.jl*](https://github.com/ThummeTo/FMIFlux.jl): Machine Learning with FMUs (differentiation over FMUs) +- [*FMIFlux.jl*](https://github.com/ThummeTo/FMIFlux.jl): Machine Learning with FMUs - [*FMIZoo.jl*](https://github.com/ThummeTo/FMIZoo.jl): A collection of testing and example FMUs ## What Platforms are supported? diff --git a/ext/FMIZooExt.jl b/ext/FMIZooExt.jl new file mode 100644 index 0000000..c31ac04 --- /dev/null +++ b/ext/FMIZooExt.jl @@ -0,0 +1,15 @@ +# +# Copyright (c) 2021 Frederic Bruder, Tobias Thummerer, Lars Mikelsons +# Licensed under the MIT license. See LICENSE file in the project root for details. +# + +module FMIZooExt + +using FMIImport, FMIZoo + +function FMIImport.loadFMU(modelName::AbstractString, tool::AbstractString, version::AbstractString, fmiversion::AbstractString="2.0"; kwargs...) + fname = get_model_filename(modelName, tool, version, fmiversion) + return FMIImport.loadFMU(fname; kwargs...) +end + +end # FMIZooExt diff --git a/src/FMI2/c.jl b/src/FMI2/c.jl index 474ab61..c1932ef 100644 --- a/src/FMI2/c.jl +++ b/src/FMI2/c.jl @@ -7,19 +7,7 @@ # - default implementations for the `fmi2CallbackFunctions` # - julia-implementaions of the functions inside the FMI-specification # Any c-function `f(c::fmi2Component, args...)` in the spec is implemented as `f(c::FMU2Component, args...)`. -# Any c-function `f(args...)` without a leading `fmi2Component`-arguemnt is already implented as `f(c_ptr, args...)` in FMICore, where `c_ptr` is a pointer to the c-function (inside the DLL). - -# already defined in FMICore.jl: -# - fmi2Instantiate - -import FMICore: fmi2Instantiate, fmi2FreeInstance!, fmi2GetTypesPlatform, fmi2GetVersion -import FMICore: fmi2SetDebugLogging, fmi2SetupExperiment, fmi2EnterInitializationMode, fmi2ExitInitializationMode, fmi2Terminate, fmi2Reset -import FMICore: fmi2GetReal!, fmi2SetReal, fmi2GetInteger!, fmi2SetInteger, fmi2GetBoolean!, fmi2SetBoolean, fmi2GetString!, fmi2SetString -import FMICore: fmi2GetFMUstate!, fmi2SetFMUstate, fmi2FreeFMUstate!, fmi2SerializedFMUstateSize!, fmi2SerializeFMUstate!, fmi2DeSerializeFMUstate! -import FMICore: fmi2GetDirectionalDerivative!, fmi2SetRealInputDerivatives, fmi2GetRealOutputDerivatives! -import FMICore: fmi2DoStep, fmi2CancelStep, fmi2GetStatus!, fmi2GetRealStatus!, fmi2GetIntegerStatus!, fmi2GetBooleanStatus!, fmi2GetStringStatus! -import FMICore: fmi2SetTime, fmi2SetContinuousStates, fmi2EnterEventMode, fmi2NewDiscreteStates!, fmi2EnterContinuousTimeMode, fmi2CompletedIntegratorStep! -import FMICore: fmi2GetDerivatives!, fmi2GetEventIndicators!, fmi2GetContinuousStates!, fmi2GetNominalsOfContinuousStates! +# Any c-function `f(args...)` without a leading `fmi2Component`-argument is already implented as `f(c_ptr, args...)` in FMICore, where `c_ptr` is a pointer to the c-function (inside the DLL). """ Source: FMISpec2.0.2[p.21]: 2.1.5 Creation, Destruction and Logging of FMU Instances @@ -52,13 +40,6 @@ function fmi2CallbackLogger(_componentEnvironment::Ptr{FMU2ComponentEnvironment} return nothing end -# (cfmi2CallbackLogger, fmi2CallbackLogger) = Cfunction{ fmi2ComponentEnvironment, Ptr{Cchar}, Cuint, Ptr{Cchar}, Tuple{Ptr{Cchar}, Vararg} }() do componentEnvironment::fmi2ComponentEnvironment, instanceName::Ptr{Cchar}, status::Cuint, category::Ptr{Cchar}, message::Tuple{Ptr{Cchar}, Vararg} -# printf(message) -# nothing -# end - - - """ Source: FMISpec2.0.2[p.21-22]: 2.1.5 Creation, Destruction and Logging of FMU Instances @@ -93,61 +74,7 @@ end # Common function for ModelExchange & CoSimulation -""" - fmi2FreeInstance!(c::FMU2Component; popComponent::Bool = true) - -Disposes the given instance, unloads the loaded model, and frees all the allocated memory and other resources that have been allocated by the functions of the FMU interface. -If a null pointer is provided for “c”, the function call is ignored (does not have an effect). - -Removes the component from the FMUs component list. - -# Arguments -- `c::FMU2Component`: Mutable struct represents an instantiated instance of an FMU in the FMI 2.0.2 Standard. - -# Returns -- nothing - -# Source -- FMISpec2.0.2 Link: [https://fmi-standard.org/](https://fmi-standard.org/) -- FMISpec2.0.2[p.22]: 2.1.5 Creation, Destruction and Logging of FMU Instances -- FMISpec2.0.2[p.16]: 2.1.2 Platform Dependent Definitions -See Also [`fmi2FreeInstance!`](@ref). -""" -lk_fmi2FreeInstance = ReentrantLock() -function fmi2FreeInstance!(c::FMU2Component; popComponent::Bool=true, doccall::Bool=true) - - global lk_fmi2FreeInstance - - compAddr = c.compAddr - - # invalidate all active snapshots - while length(c.snapshots) > 0 - FMICore.freeSnapshot!(c.snapshots[end]) - end - - @assert c.threadid == Threads.threadid() "Thread #$(Threads.threadid()) tried to free component with address $(c.compAddr), but doesn't own it.\nThe component is owned by thread $(c.threadid)" - - if popComponent - lock(lk_fmi2FreeInstance) do - ind = findall(x -> x.compAddr == compAddr, c.fmu.components) - @assert length(ind) == 1 "fmi2FreeInstance!(...): Freeing $(length(ind)) instances with one call, this is not allowed. Target address `$(compAddr)` was found $(length(ind)) times at indicies $(ind)." - deleteat!(c.fmu.components, ind) - - for key in keys(c.fmu.threadComponents) - if !isnothing(c.fmu.threadComponents[key]) && c.fmu.threadComponents[key].compAddr == compAddr - c.fmu.threadComponents[key] = nothing - end - end - end - end - - if doccall - fmi2FreeInstance!(c.fmu.cFreeInstance, compAddr) - end - - nothing -end - +import FMIBase.FMICore: fmi2GetTypesPlatform """ fmi2GetTypesPlatform(fmu::FMU2) @@ -165,37 +92,15 @@ The standard header file, as documented in this specification, has fmi2TypesPlat - FMISpec2.0.2[p.22]: 2.1.4 Inquire Platform and Version Number of Header Files - FMISpec2.0.2[p.16]: 2.1.2 Platform Dependent Definitions """ -function fmi2GetTypesPlatform(fmu::FMU2) +function FMICore.fmi2GetTypesPlatform(fmu::FMU2) typesPlatform = fmi2GetTypesPlatform(fmu.cGetTypesPlatform) unsafe_string(typesPlatform) end -# special case - - - -""" - fmi2GetTypesPlatform(c::FMU2Component) - -Returns the string to uniquely identify the “fmi2TypesPlatform.h” header file used for compilation of the functions of the FMU. -The standard header file, as documented in this specification, has fmi2TypesPlatform set to “default” (so this function usually returns “default”). - -# Arguments -- `c::FMU2Component`: Mutable struct represents an instantiated instance of an FMU in the FMI 2.0.2 Standard. - -# Returns -- Returns the string to uniquely identify the “fmi2TypesPlatform.h” header file used for compilation of the functions of the FMU. - -# Source -- FMISpec2.0.2 Link: [https://fmi-standard.org/](https://fmi-standard.org/) -- FMISpec2.0.2[p.22]: 2.1.4 Inquire Platform and Version Number of Header Files -- FMISpec2.0.2[p.16]: 2.1.2 Platform Dependent Definitions -""" -function fmi2GetTypesPlatform(c::FMU2Component) - fmi2GetTypesPlatform(c.fmu) -end +FMICore.fmi2GetTypesPlatform(c::FMU2Component) = fmi2GetTypesPlatform(c.fmu) +import FMIBase.FMICore: fmi2GetVersion """ fmi2GetVersion(fmu::FMU2) @@ -213,33 +118,13 @@ Returns the version of the “fmi2Functions.h” header file which was used to c - FMISpec2.0.2[p.22]: 2.1.4 Inquire Platform and Version Number of Header Files - FMISpec2.0.2[p.16]: 2.1.2 Platform Dependent Definitions """ -function fmi2GetVersion(fmu::FMU2) +function FMICore.fmi2GetVersion(fmu::FMU2) fmi2Version = fmi2GetVersion(fmu.cGetVersion) unsafe_string(fmi2Version) end -# special case -""" - fmi2GetVersion(c::FMU2Component) - -Returns the version of the “fmi2Functions.h” header file which was used to compile the functions of the FMU. - -# Arguments -- `c::FMU2Component`: Mutable struct represents an instantiated instance of an FMU in the FMI 2.0.2 Standard. - -# Returns -- Returns a string from the address of a C-style (NUL-terminated) string. The string represents the version of the “fmi2Functions.h” header file which was used to compile the functions of the FMU. The function returns “fmiVersion” which is defined in this header file. The standard header file as documented in this specification has version “2.0” - - -# Source -- FMISpec2.0.2 Link: [https://fmi-standard.org/](https://fmi-standard.org/) -- FMISpec2.0.2[p.22]: 2.1.4 Inquire Platform and Version Number of Header Files -- FMISpec2.0.2[p.16]: 2.1.2 Platform Dependent Definitions -""" -function fmi2GetVersion(c::FMU2Component) - fmi2GetVersion(c.fmu) -end +FMICore.fmi2GetVersion(c::FMU2Component) = fmi2GetVersion(c.fmu) # helper function checkStatus(c::FMU2Component, status::fmi2Status) @@ -256,6 +141,7 @@ function checkStatus(c::FMU2Component, status::fmi2Status) end end +import FMIBase.FMICore: fmi2SetDebugLogging """ fmi2SetDebugLogging(c::FMU2Component, logginOn::fmi2Boolean, nCategories::Unsigned, categories::Ptr{Nothing}) @@ -283,13 +169,14 @@ More detailed: - FMISpec2.0.2[p.22]: 2.1.5 Creation, Destruction and Logging of FMU Instances See also [`fmi2SetDebugLogging`](@ref). """ -function fmi2SetDebugLogging(c::FMU2Component, logginOn::fmi2Boolean, nCategories::Unsigned, categories::Ptr{Nothing}) +function FMICore.fmi2SetDebugLogging(c::FMU2Component, logginOn::fmi2Boolean, nCategories::Unsigned, categories::Ptr{Nothing}) - status = fmi2SetDebugLogging(c.fmu.cSetDebugLogging, c.compAddr, logginOn, nCategories, categories) + status = fmi2SetDebugLogging(c.fmu.cSetDebugLogging, c.addr, logginOn, nCategories, categories) checkStatus(c, status) return status end +import FMIBase.FMICore: fmi2SetupExperiment """ fmi2SetupExperiment(c::FMU2Component, toleranceDefined::fmi2Boolean, tolerance::fmi2Real, startTime::fmi2Real, stopTimeDefined::fmi2Boolean, stopTime::fmi2Real) @@ -323,7 +210,7 @@ More detailed: See also [`fmi2SetupExperiment`](@ref). """ -function fmi2SetupExperiment(c::FMU2Component, +function FMICore.fmi2SetupExperiment(c::FMU2Component, toleranceDefined::fmi2Boolean, tolerance::fmi2Real, startTime::fmi2Real, @@ -344,7 +231,7 @@ function fmi2SetupExperiment(c::FMU2Component, end status = fmi2SetupExperiment(c.fmu.cSetupExperiment, - c.compAddr, toleranceDefined, tolerance, startTime, stopTimeDefined, stopTime) + c.addr, toleranceDefined, tolerance, startTime, stopTimeDefined, stopTime) checkStatus(c, status) # remain in status on success, nothing to do here @@ -352,6 +239,7 @@ function fmi2SetupExperiment(c::FMU2Component, return status end +import FMIBase.FMICore: fmi2EnterInitializationMode """ fmi2EnterInitializationMode(c::FMU2Component) @@ -378,12 +266,12 @@ More detailed: See also [`fmi2EnterInitializationMode`](@ref). """ -function fmi2EnterInitializationMode(c::FMU2Component) +function FMICore.fmi2EnterInitializationMode(c::FMU2Component) if c.state != fmi2ComponentStateInstantiated @warn "fmi2EnterInitializationMode(...): Needs to be called in state `fmi2ComponentStateInstantiated`." end - status = fmi2EnterInitializationMode(c.fmu.cEnterInitializationMode, c.compAddr) + status = fmi2EnterInitializationMode(c.fmu.cEnterInitializationMode, c.addr) checkStatus(c, status) if status == fmi2StatusOK c.state = fmi2ComponentStateInitializationMode @@ -391,6 +279,7 @@ function fmi2EnterInitializationMode(c::FMU2Component) return status end +import FMIBase.FMICore: fmi2ExitInitializationMode """ fmi2ExitInitializationMode(c::FMU2Component) @@ -415,13 +304,13 @@ More detailed: - FMISpec2.0.2[p.22]: 2.1.6 Initialization, Termination, and Resetting an FMU See also [`fmi2EnterInitializationMode`](@ref). """ -function fmi2ExitInitializationMode(c::FMU2Component) +function FMICore.fmi2ExitInitializationMode(c::FMU2Component) if c.state != fmi2ComponentStateInitializationMode @warn "fmi2ExitInitializationMode(...): Needs to be called in state `fmi2ComponentStateInitializationMode`." end - status = fmi2ExitInitializationMode(c.fmu.cExitInitializationMode, c.compAddr) + status = fmi2ExitInitializationMode(c.fmu.cExitInitializationMode, c.addr) checkStatus(c, status) if status == fmi2StatusOK c.state = fmi2ComponentStateEventMode @@ -429,6 +318,7 @@ function fmi2ExitInitializationMode(c::FMU2Component) return status end +import FMIBase.FMICore: fmi2Terminate """ fmi2Terminate(c::FMU2Component; soft::Bool=false) @@ -457,7 +347,7 @@ More detailed: - FMISpec2.0.2[p.22]: 2.1.6 Initialization, Termination, and Resetting an FMU See also [`fmi2Terminate`](@ref). """ -function fmi2Terminate(c::FMU2Component; soft::Bool=false) +function FMICore.fmi2Terminate(c::FMU2Component; soft::Bool=false) if c.state != fmi2ComponentStateContinuousTimeMode && c.state != fmi2ComponentStateEventMode if soft return fmi2StatusOK @@ -466,7 +356,7 @@ function fmi2Terminate(c::FMU2Component; soft::Bool=false) end end - status = fmi2Terminate(c.fmu.cTerminate, c.compAddr) + status = fmi2Terminate(c.fmu.cTerminate, c.addr) checkStatus(c, status) if status == fmi2StatusOK c.state = fmi2ComponentStateTerminated @@ -474,6 +364,7 @@ function fmi2Terminate(c::FMU2Component; soft::Bool=false) return status end +import FMIBase.FMICore: fmi2Reset """ fmi2Reset(c::FMU2Component; soft::Bool=false) @@ -502,7 +393,7 @@ More detailed: - FMISpec2.0.3[p.22]: 2.1.6 Initialization, Termination, and Resetting an FMU See also [`fmi2Terminate`](@ref). """ -function fmi2Reset(c::FMU2Component; soft::Bool=false) +function FMICore.fmi2Reset(c::FMU2Component; soft::Bool=false) # according to FMISpec2.0.3[p.90], fmi2Reset can be called almost always, except before # instantiation and after a fatal error. if c.state == fmi2ComponentStateFatal @@ -515,18 +406,18 @@ function fmi2Reset(c::FMU2Component; soft::Bool=false) end if c.fmu.cReset == C_NULL - fmi2FreeInstance!(c.fmu.cFreeInstance, c.compAddr) - compAddr = fmi2Instantiate(c.fmu.cInstantiate, pointer(c.fmu.instanceName), c.fmu.type, pointer(c.fmu.modelDescription.guid), pointer(c.fmu.fmuResourceLocation), Ptr{fmi2CallbackFunctions}(pointer_from_objref(c.callbackFunctions)), fmi2Boolean(false), fmi2Boolean(false)) + fmi2FreeInstance!(c.fmu.cFreeInstance, c.addr) + addr = fmi2Instantiate(c.fmu.cInstantiate, pointer(c.fmu.instanceName), c.fmu.type, pointer(c.fmu.modelDescription.guid), pointer(c.fmu.fmuResourceLocation), Ptr{fmi2CallbackFunctions}(pointer_from_objref(c.callbackFunctions)), fmi2Boolean(false), fmi2Boolean(false)) - if compAddr == Ptr{Cvoid}(C_NULL) + if addr == Ptr{Cvoid}(C_NULL) @error "fmi2Reset(...): Reinstantiation failed!" return fmi2StatusError end - c.compAddr = compAddr + c.addr = addr return fmi2StatusOK else - status = fmi2Reset(c.fmu.cReset, c.compAddr) + status = fmi2Reset(c.fmu.cReset, c.addr) checkStatus(c, status) if status == fmi2StatusOK c.state = fmi2ComponentStateInstantiated @@ -535,6 +426,7 @@ function fmi2Reset(c::FMU2Component; soft::Bool=false) end end +import FMIBase.FMICore: fmi2GetReal! """ fmi2GetReal!(c::FMU2Component, vr::AbstractArray{fmi2ValueReference}, nvr::Csize_t, value::AbstractArray{fmi2Real}) @@ -563,15 +455,15 @@ More detailed: See also [`fmi2GetReal!`](@ref). """ -function fmi2GetReal!(c::FMU2Component, vr::AbstractArray{fmi2ValueReference}, nvr::Csize_t, value::AbstractArray{fmi2Real}) +function FMICore.fmi2GetReal!(c::FMU2Component, vr::AbstractArray{fmi2ValueReference}, nvr::Csize_t, value::AbstractArray{fmi2Real}) status = fmi2GetReal!(c.fmu.cGetReal, - c.compAddr, vr, nvr, value) + c.addr, vr, nvr, value) checkStatus(c, status) return status end - +import FMIBase.FMICore: fmi2SetReal """ fmi2SetReal(c::FMU2Component, vr::AbstractArray{fmi2ValueReference}, nvr::Csize_t, value::AbstractArray{fmi2Real}) @@ -598,14 +490,14 @@ More detailed: See also [`fmi2GetReal`](@ref). """ -function fmi2SetReal(c::FMU2Component, +function FMICore.fmi2SetReal(c::FMU2Component, vr::AbstractArray{fmi2ValueReference}, nvr::Csize_t, value::AbstractArray{fmi2Real}; track::Bool=true) status = fmi2SetReal(c.fmu.cSetReal, - c.compAddr, vr, nvr, value) + c.addr, vr, nvr, value) checkStatus(c, status) if track && status == fmi2StatusOK @@ -630,6 +522,7 @@ function fmi2SetReal(c::FMU2Component, return status end +import FMIBase.FMICore: fmi2GetInteger! """ fmi2GetInteger!(c::FMU2Component, vr::AbstractArray{fmi2ValueReference}, nvr::Csize_t, value::AbstractArray{fmi2Integer}) @@ -662,15 +555,15 @@ More detailed: See also [`fmi2GetInteger!`](@ref). """ -function fmi2GetInteger!(c::FMU2Component, vr::AbstractArray{fmi2ValueReference}, nvr::Csize_t, value::AbstractArray{fmi2Integer}) +function FMICore.fmi2GetInteger!(c::FMU2Component, vr::AbstractArray{fmi2ValueReference}, nvr::Csize_t, value::AbstractArray{fmi2Integer}) status = fmi2GetInteger!(c.fmu.cGetInteger, - c.compAddr, vr, nvr, value) + c.addr, vr, nvr, value) checkStatus(c, status) return status end - +import FMIBase.FMICore: fmi2SetInteger """ fmi2SetInteger(c::FMU2Component, vr::AbstractArray{fmi2ValueReference}, nvr::Csize_t, value::AbstractArray{fmi2Integer}) @@ -700,15 +593,15 @@ More detailed: See also [`fmi2GetInteger!`](@ref). """ -function fmi2SetInteger(c::FMU2Component, vr::AbstractArray{fmi2ValueReference}, nvr::Csize_t, value::AbstractArray{fmi2Integer}) +function FMICore.fmi2SetInteger(c::FMU2Component, vr::AbstractArray{fmi2ValueReference}, nvr::Csize_t, value::AbstractArray{fmi2Integer}) status = fmi2SetInteger(c.fmu.cSetInteger, - c.compAddr, vr, nvr, value) + c.addr, vr, nvr, value) checkStatus(c, status) return status end - +import FMIBase.FMICore: fmi2GetBoolean! """ fmi2GetBoolean!(c::FMU2Component, vr::AbstractArray{fmi2ValueReference}, nvr::Csize_t, value::AbstractArray{fmi2Boolean}) @@ -740,15 +633,15 @@ More detailed: See also [`fmi2GetBoolean!`](@ref). """ -function fmi2GetBoolean!(c::FMU2Component, vr::AbstractArray{fmi2ValueReference}, nvr::Csize_t, value::AbstractArray{fmi2Boolean}) +function FMICore.fmi2GetBoolean!(c::FMU2Component, vr::AbstractArray{fmi2ValueReference}, nvr::Csize_t, value::AbstractArray{fmi2Boolean}) status = fmi2GetBoolean!(c.fmu.cGetBoolean, - c.compAddr, vr, nvr, value) + c.addr, vr, nvr, value) checkStatus(c, status) return status end - +import FMIBase.FMICore: fmi2SetBoolean """ fmi2SetBoolean(c::FMU2Component, vr::AbstractArray{fmi2ValueReference}, nvr::Csize_t, value::AbstractArray{fmi2Boolean}) @@ -776,15 +669,15 @@ More detailed: - FMISpec2.0.2[p.18]: 2.1.3 Status Returned by Functions See also [`fmi2GetBoolean`](@ref). """ -function fmi2SetBoolean(c::FMU2Component, vr::AbstractArray{fmi2ValueReference}, nvr::Csize_t, value::AbstractArray{fmi2Boolean}) +function FMICore.fmi2SetBoolean(c::FMU2Component, vr::AbstractArray{fmi2ValueReference}, nvr::Csize_t, value::AbstractArray{fmi2Boolean}) status = fmi2SetBoolean(c.fmu.cSetBoolean, - c.compAddr, vr, nvr, value) + c.addr, vr, nvr, value) checkStatus(c, status) return status end - +import FMIBase.FMICore: fmi2GetString! """ fmi2GetString!(c::FMU2Component, vr::AbstractArray{fmi2ValueReference}, nvr::Csize_t, value::Union{AbstractArray{Ptr{Cchar}}, AbstractArray{Ptr{UInt8}}}) @@ -816,15 +709,15 @@ More detailed: - FMISpec2.0.2[p.24]: 2.1.7 Getting and Setting Variable Values See also [`fmi2GetString!`](@ref). """ -function fmi2GetString!(c::FMU2Component, vr::AbstractArray{fmi2ValueReference}, nvr::Csize_t, value::Union{AbstractArray{Ptr{Cchar}}, AbstractArray{Ptr{UInt8}}}) +function FMICore.fmi2GetString!(c::FMU2Component, vr::AbstractArray{fmi2ValueReference}, nvr::Csize_t, value::Union{AbstractArray{Ptr{Cchar}}, AbstractArray{Ptr{UInt8}}}) status = fmi2GetString!(c.fmu.cGetString, - c.compAddr, vr, nvr, value) + c.addr, vr, nvr, value) checkStatus(c, status) return status end - +import FMIBase.FMICore: fmi2SetString """ fmi2SetString(c::FMU2Component, vr::AbstractArray{fmi2ValueReference}, nvr::Csize_t, value::Union{AbstractArray{Ptr{Cchar}}, AbstractArray{Ptr{UInt8}}}) @@ -854,15 +747,15 @@ More detailed: - FMISpec2.0.2[p.24]: 2.1.7 Getting and Setting Variable Values See also [`fmi2GetString!`](@ref). """ -function fmi2SetString(c::FMU2Component, vr::AbstractArray{fmi2ValueReference}, nvr::Csize_t, value::Union{AbstractArray{Ptr{Cchar}}, AbstractArray{Ptr{UInt8}}}) +function FMICore.fmi2SetString(c::FMU2Component, vr::AbstractArray{fmi2ValueReference}, nvr::Csize_t, value::Union{AbstractArray{Ptr{Cchar}}, AbstractArray{Ptr{UInt8}}}) status = fmi2SetString(c.fmu.cSetString, - c.compAddr, vr, nvr, value) + c.addr, vr, nvr, value) checkStatus(c, status) return status end - +import FMIBase.FMICore: fmi2GetFMUstate! """ fmi2GetFMUstate!(c::FMU2Component, FMUstate::Ref{fmi2FMUstate}) @@ -889,14 +782,15 @@ More detailed: - FMISpec2.0.2[p.24]: 2.1.7 Getting and Setting Variable Values See also [`fmi2GetFMUstate!`](@ref). """ -function fmi2GetFMUstate!(c::FMU2Component, FMUstate::Ref{fmi2FMUstate}) +function FMICore.fmi2GetFMUstate!(c::FMU2Component, FMUstate::Ref{fmi2FMUstate}) status = fmi2GetFMUstate!(c.fmu.cGetFMUstate, - c.compAddr, FMUstate) + c.addr, FMUstate) checkStatus(c, status) return status end +import FMIBase.FMICore: fmi2SetFMUstate """ fmi2SetFMUstate(c::FMU2Component, FMUstate::fmi2FMUstate) @@ -928,16 +822,17 @@ More detailed: See also [`fmi2GetFMUstate`](@ref). """ -function fmi2SetFMUstate(c::FMU2Component, FMUstate::fmi2FMUstate) +function FMICore.fmi2SetFMUstate(c::FMU2Component, FMUstate::fmi2FMUstate) status = fmi2SetFMUstate(c.fmu.cSetFMUstate, - c.compAddr, FMUstate) + c.addr, FMUstate) checkStatus(c, status) return status end +import FMIBase.FMICore: fmi2FreeFMUstate """ - fmi2FreeFMUstate!(c::FMU2Component, FMUstate::Ref{fmi2FMUstate}) + fmi2FreeFMUstate(c::FMU2Component, FMUstate::Ref{fmi2FMUstate}) Frees all memory and other resources allocated with the fmi2GetFMUstate call for this FMUstate. @@ -962,14 +857,15 @@ More detailed: See also [`fmi2FreeFMUstate!`](@ref). """ -function fmi2FreeFMUstate!(c::FMU2Component, FMUstate::Ref{fmi2FMUstate}) +function FMICore.fmi2FreeFMUstate(c::FMU2Component, FMUstate::Ref{fmi2FMUstate}) - status = fmi2FreeFMUstate!(c.fmu.cFreeFMUstate, - c.compAddr, FMUstate) + status = fmi2FreeFMUstate(c.fmu.cFreeFMUstate, + c.addr, FMUstate) checkStatus(c, status) return status end +import FMIBase.FMICore: fmi2SerializedFMUstateSize! """ fmi2SerializedFMUstateSize!(c::FMU2Component, FMUstate::fmi2FMUstate, size::Ref{Csize_t}) @@ -996,14 +892,15 @@ More detailed: See also [`fmi2SerializedFMUstateSize!`](@ref). """ -function fmi2SerializedFMUstateSize!(c::FMU2Component, FMUstate::fmi2FMUstate, size::Ref{Csize_t}) +function FMICore.fmi2SerializedFMUstateSize!(c::FMU2Component, FMUstate::fmi2FMUstate, size::Ref{Csize_t}) status = fmi2SerializedFMUstateSize!(c.fmu.cSerializedFMUstateSize, - c.compAddr, FMUstate, size) + c.addr, FMUstate, size) checkStatus(c, status) return status end +import FMIBase.FMICore: fmi2SerializeFMUstate! """ fmi2SerializeFMUstate!(c::FMU2Component, FMUstate::fmi2FMUstate, serialzedState::AbstractArray{fmi2Byte}, size::Csize_t) @@ -1033,15 +930,15 @@ More detailed: See also [`fmi2SerializeFMUstate`](@ref). """ -function fmi2SerializeFMUstate!(c::FMU2Component, FMUstate::fmi2FMUstate, serialzedState::AbstractArray{fmi2Byte}, size::Csize_t) +function FMICore.fmi2SerializeFMUstate!(c::FMU2Component, FMUstate::fmi2FMUstate, serialzedState::AbstractArray{fmi2Byte}, size::Csize_t) status = fmi2SerializeFMUstate!(c.fmu.cSerializeFMUstate, - c.compAddr, FMUstate, serialzedState, size) + c.addr, FMUstate, serialzedState, size) checkStatus(c, status) return status end - +import FMIBase.FMICore: fmi2DeSerializeFMUstate! """ fmi2DeSerializeFMUstate!(c::FMU2Component, serializedState::AbstractArray{fmi2Byte}, size::Csize_t, FMUstate::Ref{fmi2FMUstate}) @@ -1073,15 +970,15 @@ More detailed: See also [`fmi2DeSerializeFMUstate!`](@ref). """ -function fmi2DeSerializeFMUstate!(c::FMU2Component, serializedState::AbstractArray{fmi2Byte}, size::Csize_t, FMUstate::Ref{fmi2FMUstate}) +function FMICore.fmi2DeSerializeFMUstate!(c::FMU2Component, serializedState::AbstractArray{fmi2Byte}, size::Csize_t, FMUstate::Ref{fmi2FMUstate}) status = fmi2DeSerializeFMUstate!(c.fmu.cDeSerializeFMUstate, - c.compAddr, serializedState, size, FMUstate) + c.addr, serializedState, size, FMUstate) checkStatus(c, status) return status end - +import FMIBase.FMICore: fmi2GetDirectionalDerivative! """ fmi2GetDirectionalDerivative!(c::FMU2Component, vUnknown_ref::AbstractArray{fmi2ValueReference}, @@ -1134,23 +1031,22 @@ More detailed: - FMISpec2.0.2[p.25]: 2.1.9 Getting Partial Derivatives See also [`fmi2GetDirectionalDerivative!`](@ref). """ -function fmi2GetDirectionalDerivative!(c::FMU2Component, +function FMICore.fmi2GetDirectionalDerivative!(c::FMU2Component, vUnknown_ref::AbstractArray{fmi2ValueReference}, nUnknown::Csize_t, vKnown_ref::AbstractArray{fmi2ValueReference}, nKnown::Csize_t, dvKnown::AbstractArray{fmi2Real}, dvUnknown::AbstractArray{fmi2Real}) - @assert fmi2ProvidesDirectionalDerivative(c.fmu) ["fmi2GetDirectionalDerivative!(...): This FMU does not support build-in directional derivatives!"] + + @assert providesDirectionalDerivatives(c.fmu) ["fmi2GetDirectionalDerivative!(...): This FMU does not support build-in directional derivatives!"] status = fmi2GetDirectionalDerivative!(c.fmu.cGetDirectionalDerivative, - c.compAddr, vUnknown_ref, nUnknown, vKnown_ref, nKnown, dvKnown, dvUnknown) + c.addr, vUnknown_ref, nUnknown, vKnown_ref, nKnown, dvKnown, dvUnknown) checkStatus(c, status) return status end - -# for AD primitives -function fmi2GetDirectionalDerivative!(c::FMU2Component, +function FMICore.fmi2GetDirectionalDerivative!(c::FMU2Component, vUnknown_ref::AbstractArray{fmi2ValueReference}, nUnknown::Csize_t, vKnown_ref::AbstractArray{fmi2ValueReference}, @@ -1167,6 +1063,8 @@ function fmi2GetDirectionalDerivative!(c::FMU2Component, end # Functions specificly for isCoSimulation + +import FMIBase.FMICore: fmi2SetRealInputDerivatives """ fmi2SetRealInputDerivatives(c::FMU2Component, vr::AbstractArray{fmi2ValueReference}, @@ -1201,15 +1099,15 @@ More detailed: See also [`fmi2SetRealInputDerivatives`](@ref). """ -function fmi2SetRealInputDerivatives(c::FMU2Component, vr::Array{fmi2ValueReference}, nvr::Csize_t, order::Array{fmi2Integer}, value::Array{fmi2Real}) +function FMICore.fmi2SetRealInputDerivatives(c::FMU2Component, vr::Array{fmi2ValueReference}, nvr::Csize_t, order::Array{fmi2Integer}, value::Array{fmi2Real}) status = fmi2SetRealInputDerivatives(c.fmu.cSetRealInputDerivatives, - c.compAddr, vr, nvr, order, value) + c.addr, vr, nvr, order, value) checkStatus(c, status) return status end - +import FMIBase.FMICore: fmi2GetRealOutputDerivatives! """ fmi2GetRealOutputDerivatives!(c::FMU2Component, vr::AbstractArray{fmi2ValueReference}, @@ -1241,15 +1139,15 @@ More detailed: - FMISpec2.0.2[p.18]: 2.1.3 Status Returned by Functions - FMISpec2.0.2[p.104]: 4.2.1 Transfer of Input / Output Values and Parameters """ -function fmi2GetRealOutputDerivatives!(c::FMU2Component, vr::Array{fmi2ValueReference}, nvr::Csize_t, order::Array{fmi2Integer}, value::Array{fmi2Real}) +function FMICore.fmi2GetRealOutputDerivatives!(c::FMU2Component, vr::Array{fmi2ValueReference}, nvr::Csize_t, order::Array{fmi2Integer}, value::Array{fmi2Real}) status = fmi2GetRealOutputDerivatives!(c.fmu.cGetRealOutputDerivatives, - c.compAddr, vr, nvr, order, value) + c.addr, vr, nvr, order, value) checkStatus(c, status) return status end - +import FMIBase.FMICore: fmi2DoStep """ fmi2DoStep(c::FMU2Component, currentCommunicationPoint::fmi2Real, @@ -1281,15 +1179,16 @@ More detailed: - FMISpec2.0.2[p.104]: 4.2.2 Computation See also [`fmi2DoStep`](@ref). """ -function fmi2DoStep(c::FMU2Component, currentCommunicationPoint::fmi2Real, communicationStepSize::fmi2Real, noSetFMUStatePriorToCurrentPoint::fmi2Boolean) +function FMICore.fmi2DoStep(c::FMU2Component, currentCommunicationPoint::fmi2Real, communicationStepSize::fmi2Real, noSetFMUStatePriorToCurrentPoint::fmi2Boolean) @assert c.fmu.cDoStep != C_NULL ["fmi2DoStep(...): This FMU does not support fmi2DoStep, probably it's a ME-FMU with no CS-support?"] status = fmi2DoStep(c.fmu.cDoStep, - c.compAddr, currentCommunicationPoint, communicationStepSize, noSetFMUStatePriorToCurrentPoint) + c.addr, currentCommunicationPoint, communicationStepSize, noSetFMUStatePriorToCurrentPoint) checkStatus(c, status) return status end +import FMIBase.FMICore: fmi2CancelStep """ fmi2CancelStep(c::FMU2Component) @@ -1315,13 +1214,14 @@ More detailed: - FMISpec2.0.2[p.104]: 4.2.2 Computation See also [`fmi2DoStep`](@ref). """ -function fmi2CancelStep(c::FMU2Component) +function FMICore.fmi2CancelStep(c::FMU2Component) - status = fmi2CancelStep(c.fmu.cCancelStep, c.compAddr) + status = fmi2CancelStep(c.fmu.cCancelStep, c.addr) checkStatus(c, status) return status end +import FMIBase.FMICore: fmi2GetStatus! """ fmi2GetStatus!(c::FMU2Component, s::fmi2StatusKind, @@ -1355,7 +1255,7 @@ More detailed: - FMISpec2.0.2[p.106]: 4.2.3 Retrieving Status Information from the Slave See also [`fmi2GetStatus!`](@ref). """ -function fmi2GetStatus!(c::FMU2Component, s::fmi2StatusKind, value) +function FMICore.fmi2GetStatus!(c::FMU2Component, s::fmi2StatusKind, value) rtype = nothing if s == fmi2Terminated rtype = fmi2Boolean @@ -1367,12 +1267,13 @@ function fmi2GetStatus!(c::FMU2Component, s::fmi2StatusKind, value) status = fmi2Error if rtype == fmi2Boolean status = fmi2GetStatus!(c.fmu.cGetRealStatus, - c.compAddr, s, Ref(value)) + c.addr, s, Ref(value)) checkStatus(c, status) end return status end +import FMIBase.FMICore: fmi2GetRealStatus! """ fmi2GetRealStatus!(c::FMU2Component, s::fmi2StatusKind, @@ -1406,14 +1307,15 @@ More detailed: - FMISpec2.0.2[p.106]: 4.2.3 Retrieving Status Information from the Slave See also [`fmi2GetRealStatus!`](@ref). """ -function fmi2GetRealStatus!(c::FMU2Component, s::fmi2StatusKind, value::fmi2Real) +function FMICore.fmi2GetRealStatus!(c::FMU2Component, s::fmi2StatusKind, value::fmi2Real) status = fmi2GetRealStatus!(c.fmu.cGetRealStatus, - c.compAddr, s, Ref(value)) + c.addr, s, Ref(value)) checkStatus(c, status) return status end +import FMIBase.FMICore: fmi2GetIntegerStatus! """ fmi2GetIntegerStatus!(c::FMU2Component, s::fmi2StatusKind, @@ -1447,14 +1349,15 @@ More detailed: - FMISpec2.0.2[p.106]: 4.2.3 Retrieving Status Information from the Slave See also [`fmi2GetIntegerStatus!`](@ref). """ -function fmi2GetIntegerStatus!(c::FMU2Component, s::fmi2StatusKind, value::fmi2Integer) +function FMICore.fmi2GetIntegerStatus!(c::FMU2Component, s::fmi2StatusKind, value::fmi2Integer) status = fmi2GetIntegerStatus!(c.fmu.cGetIntegerStatus, - c.compAddr, s, Ref(value)) + c.addr, s, Ref(value)) checkStatus(c, status) return status end +import FMIBase.FMICore: fmi2GetBooleanStatus! """ fmi2GetBooleanStatus!(c::FMU2Component, s::fmi2StatusKind, @@ -1488,14 +1391,15 @@ More detailed: - FMISpec2.0.2[p.106]: 4.2.3 Retrieving Status Information from the Slave See also [`fmi2GetBooleanStatus!`](@ref). """ -function fmi2GetBooleanStatus!(c::FMU2Component, s::fmi2StatusKind, value::fmi2Boolean) +function FMICore.fmi2GetBooleanStatus!(c::FMU2Component, s::fmi2StatusKind, value::fmi2Boolean) status = fmi2GetBooleanStatus!(c.fmu.cGetBooleanStatus, - c.compAddr, s, Ref(value)) + c.addr, s, Ref(value)) checkStatus(c, status) return status end +import FMIBase.FMICore: fmi2GetStringStatus! """ fmi2GetStringStatus!(c::FMU2Component, s::fmi2StatusKind, @@ -1529,15 +1433,17 @@ More detailed: - FMISpec2.0.2[p.106]: 4.2.3 Retrieving Status Information from the Slave See also [`fmi2GetStringStatus!`](@ref). """ -function fmi2GetStringStatus!(c::FMU2Component, s::fmi2StatusKind, value::fmi2String) +function FMICore.fmi2GetStringStatus!(c::FMU2Component, s::fmi2StatusKind, value::fmi2String) status = fmi2GetStringStatus!(c.fmu.cGetStringStatus, - c.compAddr, s, Ref(value)) + c.addr, s, Ref(value)) checkStatus(c, status) return status end # Model Exchange specific Functions + +import FMIBase.FMICore: fmi2SetTime """ fmi2SetTime(c::FMU2Component, time::fmi2Real; @@ -1574,7 +1480,7 @@ More detailed: - FMISpec2.0.2[p.83]: 3.2.1 Providing Independent Variables and Re-initialization of Caching See also [`fmi2SetTime`](@ref). """ -function fmi2SetTime(c::FMU2Component, time::fmi2Real; soft::Bool=false, track::Bool=true, force::Bool=c.force, time_shift::Bool=c.fmu.executionConfig.autoTimeShift) +function FMICore.fmi2SetTime(c::FMU2Component, time::fmi2Real; soft::Bool=false, track::Bool=true, force::Bool=c.force, time_shift::Bool=c.fmu.executionConfig.autoTimeShift) # ToDo: Double-check this in the spec. # discrete = (c.fmu.hasStateEvents == true || c.fmu.hasTimeEvents == true) @@ -1597,7 +1503,7 @@ function fmi2SetTime(c::FMU2Component, time::fmi2Real; soft::Bool=false, track:: end end - status = fmi2SetTime(c.fmu.cSetTime, c.compAddr, time) + status = fmi2SetTime(c.fmu.cSetTime, c.addr, time) checkStatus(c, status) if track @@ -1613,6 +1519,7 @@ function fmi2SetTime(c::FMU2Component, time::fmi2Real; soft::Bool=false, track:: return status end +import FMIBase.FMICore: fmi2SetContinuousStates """ fmi2SetContinuousStates(c::FMU2Component, x::AbstractArray{fmi2Real}, @@ -1643,7 +1550,7 @@ More detailed: See also [`fmi2SetContinuousStates`](@ref). """ -function fmi2SetContinuousStates(c::FMU2Component, +function FMICore.fmi2SetContinuousStates(c::FMU2Component, x::AbstractArray{fmi2Real}, nx::Csize_t; track::Bool=true, @@ -1655,7 +1562,7 @@ function fmi2SetContinuousStates(c::FMU2Component, end end - status = fmi2SetContinuousStates(c.fmu.cSetContinuousStates, c.compAddr, x, nx) + status = fmi2SetContinuousStates(c.fmu.cSetContinuousStates, c.addr, x, nx) checkStatus(c, status) if track @@ -1671,6 +1578,7 @@ function fmi2SetContinuousStates(c::FMU2Component, return status end +import FMIBase.FMICore: fmi2EnterEventMode """ fmi2EnterEventMode(c::FMU2Component; soft::Bool=false) @@ -1699,7 +1607,7 @@ More detailed: - FMISpec2.0.2[p.83]: 3.2.2 Evaluation of Model Equations See also [`fmi2EnterEventMode`](@ref). """ -function fmi2EnterEventMode(c::FMU2Component; soft::Bool=false) +function FMICore.fmi2EnterEventMode(c::FMU2Component; soft::Bool=false) if c.state != fmi2ComponentStateContinuousTimeMode if soft @@ -1710,7 +1618,7 @@ function fmi2EnterEventMode(c::FMU2Component; soft::Bool=false) end status = fmi2EnterEventMode(c.fmu.cEnterEventMode, - c.compAddr) + c.addr) checkStatus(c, status) if status == fmi2StatusOK c.state = fmi2ComponentStateEventMode @@ -1752,14 +1660,14 @@ More detailed: - FMISpec2.0.2[p.83]: 3.2.2 Evaluation of Model Equations See also [`fmi2NewDiscreteStates`](@ref). """ -function fmi2NewDiscreteStates!(c::FMU2Component, eventInfo::fmi2EventInfo) +function FMICore.fmi2NewDiscreteStates!(c::FMU2Component, eventInfo::fmi2EventInfo) if c.state != fmi2ComponentStateEventMode @warn "fmi2NewDiscreteStates(...): Needs to be called in state `fmi2ComponentStateEventMode` [$(fmi2ComponentStateEventMode)], is in [$(c.state)]." end status = fmi2NewDiscreteStates!(c.fmu.cNewDiscreteStates, - c.compAddr, Ptr{fmi2EventInfo}(pointer_from_objref(eventInfo)) ) + c.addr, Ptr{fmi2EventInfo}(pointer_from_objref(eventInfo)) ) if eventInfo.nextEventTimeDefined == fmi2True eventInfo.nextEventTime -= c.t_offset @@ -1770,6 +1678,7 @@ function fmi2NewDiscreteStates!(c::FMU2Component, eventInfo::fmi2EventInfo) return status end +import FMIBase.FMICore: fmi2EnterContinuousTimeMode """ fmi2EnterContinuousTimeMode(c::FMU2Component; soft::Bool=false) @@ -1801,7 +1710,7 @@ More detailed: - FMISpec2.0.2[p.83]: 3.2.2 Evaluation of Model Equations See also [`fmi2EnterContinuousTimeMode`](@ref). """ -function fmi2EnterContinuousTimeMode(c::FMU2Component; soft::Bool=false) +function FMICore.fmi2EnterContinuousTimeMode(c::FMU2Component; soft::Bool=false) if c.state != fmi2ComponentStateEventMode if soft @@ -1812,7 +1721,7 @@ function fmi2EnterContinuousTimeMode(c::FMU2Component; soft::Bool=false) end status = fmi2EnterContinuousTimeMode(c.fmu.cEnterContinuousTimeMode, - c.compAddr) + c.addr) checkStatus(c, status) if status == fmi2StatusOK c.state = fmi2ComponentStateContinuousTimeMode @@ -1820,11 +1729,12 @@ function fmi2EnterContinuousTimeMode(c::FMU2Component; soft::Bool=false) return status end +import FMIBase.FMICore: fmi2CompletedIntegratorStep! """ fmi2CompletedIntegratorStep!(c::FMU2Component, noSetFMUStatePriorToCurrentPoint::fmi2Boolean, - enterEventMode::Ref{fmi2Boolean}, - terminateSimulation::Ref{fmi2Boolean}) + enterEventMode::Ptr{fmi2Boolean}, + terminateSimulation::Ptr{fmi2Boolean}) This function must be called by the environment after every completed step of the integrator provided the capability flag completedIntegratorStepNotNeeded = false. @@ -1849,17 +1759,18 @@ More detailed: - FMISpec2.0.2[p.83]: 3.2.2 Evaluation of Model Equations See also [`fmi2CompletedIntegratorStep!`](@ref). """ -function fmi2CompletedIntegratorStep!(c::FMU2Component, +function FMICore.fmi2CompletedIntegratorStep!(c::FMU2Component, noSetFMUStatePriorToCurrentPoint::fmi2Boolean, enterEventMode::Ptr{fmi2Boolean}, terminateSimulation::Ptr{fmi2Boolean}) status = fmi2CompletedIntegratorStep!(c.fmu.cCompletedIntegratorStep, - c.compAddr, noSetFMUStatePriorToCurrentPoint, enterEventMode, terminateSimulation) + c.addr, noSetFMUStatePriorToCurrentPoint, enterEventMode, terminateSimulation) checkStatus(c, status) return status end +import FMIBase.FMICore: fmi2GetDerivatives! """ fmi2GetDerivatives!(c::FMU2Component, derivatives::AbstractArray{fmi2Real}, @@ -1890,17 +1801,18 @@ More detailed: See also [`fmi2GetDerivatives!`](@ref). """ -function fmi2GetDerivatives!(c::FMU2Component, +function FMICore.fmi2GetDerivatives!(c::FMU2Component, derivatives::AbstractArray{fmi2Real}, nx::Csize_t) status = fmi2GetDerivatives!(c.fmu.cGetDerivatives, - c.compAddr, derivatives, nx) + c.addr, derivatives, nx) checkStatus(c, status) return status end +import FMIBase.FMICore: fmi2GetEventIndicators! """ fmi2GetEventIndicators!(c::FMU2Component, eventIndicators::AbstractArray{fmi2Real}, ni::Csize_t) @@ -1927,14 +1839,15 @@ More detailed: - FMISpec2.0.2[p.83]: 3.2.2 Evaluation of Model Equations See also [`fmi2GetEventIndicators!`](@ref). """ -function fmi2GetEventIndicators!(c::FMU2Component, eventIndicators::AbstractArray{fmi2Real}, ni::Csize_t) +function FMICore.fmi2GetEventIndicators!(c::FMU2Component, eventIndicators::AbstractArray{fmi2Real}, ni::Csize_t) status = fmi2GetEventIndicators!(c.fmu.cGetEventIndicators, - c.compAddr, eventIndicators, ni) + c.addr, eventIndicators, ni) checkStatus(c, status) return status end +import FMIBase.FMICore: fmi2GetContinuousStates! """ fmi2GetContinuousStates!(c::FMU2Component, x::AbstractArray{fmi2Real}, @@ -1963,16 +1876,17 @@ More detailed: - FMISpec2.0.2[p.83]: 3.2.2 Evaluation of Model Equations See also [`fmi2GetEventIndicators!`](@ref). """ -function fmi2GetContinuousStates!(c::FMU2Component, +function FMICore.fmi2GetContinuousStates!(c::FMU2Component, x::AbstractArray{fmi2Real}, nx::Csize_t) status = fmi2GetContinuousStates!(c.fmu.cGetContinuousStates, - c.compAddr, x, nx) + c.addr, x, nx) checkStatus(c, status) return status end +import FMIBase.FMICore: fmi2GetNominalsOfContinuousStates! """ fmi2GetNominalsOfContinuousStates!(c::FMU2Component, x_nominal::AbstractArray{fmi2Real}, nx::Csize_t) @@ -1999,10 +1913,10 @@ More detailed: - FMISpec2.0.2[p.83]: 3.2.2 Evaluation of Model Equations See also [`fmi2GetEventIndicators!`](@ref). """ -function fmi2GetNominalsOfContinuousStates!(c::FMU2Component, x_nominal::AbstractArray{fmi2Real}, nx::Csize_t) +function FMICore.fmi2GetNominalsOfContinuousStates!(c::FMU2Component, x_nominal::AbstractArray{fmi2Real}, nx::Csize_t) status = fmi2GetNominalsOfContinuousStates!(c.fmu.cGetNominalsOfContinuousStates, - c.compAddr, x_nominal, nx) + c.addr, x_nominal, nx) checkStatus(c, status) return status -end +end \ No newline at end of file diff --git a/src/FMI2/convert.jl b/src/FMI2/convert.jl deleted file mode 100644 index 5a8a30a..0000000 --- a/src/FMI2/convert.jl +++ /dev/null @@ -1,448 +0,0 @@ -# -# Copyright (c) 2021 Tobias Thummerer, Lars Mikelsons, Josef Kircher -# Licensed under the MIT license. See LICENSE file in the project root for details. -# - -# ToDo: Fix this: import SciMLSensitivity.ForwardDiff - -# Receives one or an array of value references in an arbitrary format (see fmi2ValueReferenceFormat) and converts it into an Array{fmi2ValueReference} (if not already). -prepareValueReference(md::fmi2ModelDescription, vr::AbstractVector{fmi2ValueReference}) = vr -prepareValueReference(md::fmi2ModelDescription, vr::fmi2ValueReference) = [vr] -prepareValueReference(md::fmi2ModelDescription, vr::String) = [fmi2StringToValueReference(md, vr)] -prepareValueReference(md::fmi2ModelDescription, vr::AbstractVector{String}) = fmi2StringToValueReference(md, vr) -prepareValueReference(md::fmi2ModelDescription, vr::AbstractVector{<:Integer}) = fmi2ValueReference.(vr) -prepareValueReference(md::fmi2ModelDescription, vr::Integer) = [fmi2ValueReference(vr)] -prepareValueReference(md::fmi2ModelDescription, vr::Nothing) = fmi2ValueReference[] -function prepareValueReference(md::fmi2ModelDescription, vr::Symbol) - if vr == :states - return md.stateValueReferences - elseif vr == :derivatives - return md.derivativeValueReferences - elseif vr == :inputs - return md.inputValueReferences - elseif vr == :outputs - return md.outputValueReferences - elseif vr == :all - return md.valueReferences - elseif vr == :none - return Array{fmi2ValueReference,1}() - else - @assert false "Unknwon symbol `$vr`, can't convert to value reference." - end -end -function prepareValueReference(fmu::FMU2, vr::fmi2ValueReferenceFormat) - prepareValueReference(fmu.modelDescription, vr) -end -function prepareValueReference(comp::FMU2Component, vr::fmi2ValueReferenceFormat) - prepareValueReference(comp.fmu.modelDescription, vr) -end - -""" - fmi2StringToValueReference(md::fmi2ModelDescription, names::AbstractArray{String}) - -Returns an array of ValueReferences coresponding to the variable names. - -# Arguments -- `md::fmi2ModelDescription`: Argument `md` stores all static information related to an FMU. Especially, the FMU variables and their attributes such as name, unit, default initial value, etc.. -- `names::AbstractArray{String}`: Argument `names` contains a list of Strings. For each string ("variable name"), the corresponding value reference is searched in the given modelDescription. - -# Returns -- `vr:Array{fmi2ValueReference}`: Return `vr` is an array of `ValueReference` coresponding to the variable names. - -# Source -- FMISpec2.0.2 Link: [https://fmi-standard.org/](https://fmi-standard.org/) -- FMISpec2.0.2[p.16]: 2.1.2 Platform Dependent Definitions -See also [`fmi2StringToValueReference`](@ref). -""" -function fmi2StringToValueReference(md::fmi2ModelDescription, names::AbstractArray{String}) - # vr = Array{fmi2ValueReference}(undef,0) - # for name in names - # reference = fmi2StringToValueReference(md, name) - # if reference == nothing - # @warn "Value reference for variable '$name' not found, skipping." - # else - # push!(vr, reference) - # end - # end - # vr - return broadcast(fmi2StringToValueReference, (md,), names) -end - -""" - fmi2ModelVariablesForValueReference(md::fmi2ModelDescription, vr::fmi2ValueReference) - -Returns the model variable(s) fitting the value reference. - -# Arguments -- `md::fmi2ModelDescription`: Argument `md` stores all static information related to an FMU. Especially, the FMU variables and their attributes such as name, unit, default initial value, etc.. -- `vr::fmi2ValueReference`: Argument `vr` contains a value of type`fmi2ValueReference` which are identifiers of a variable value of the model. - -# Returns -- `ar::Array{fmi2ScalarVariable}`: Return `ar` is an array of `fmi2ScalarVariable` containing the modelVariables with the identical fmi2ValueReference to the input variable vr. - -# Source -- FMISpec2.0.2 Link: [https://fmi-standard.org/](https://fmi-standard.org/) -- FMISpec2.0.2[p.16]: 2.1.2 Platform Dependent Definitions -See also [`fmi2ModelVariablesForValueReference`](@ref). -""" -function fmi2ModelVariablesForValueReference(md::fmi2ModelDescription, vr::fmi2ValueReference) - ar = [] - for modelVariable in md.modelVariables - if modelVariable.valueReference == vr - push!(ar, modelVariable) - end - end - ar -end - -""" - fmi2DataTypeForValueReference(md::fmi2ModelDescription, vr::fmi2ValueReference) -Returns the fmi2DataType (`fmi2Real`, `fmi2Integer`, `fmi2Boolean`, `fmi2String`) for a given Valuereference `vr` of a given FMU-ModelDescription `md` -""" -function fmi2DataTypeForValueReference(md::fmi2ModelDescription, vr::fmi2ValueReference) - mv = fmi2ModelVariablesForValueReference(md, vr)[1] - if !isnothing(mv.Real) - return fmi2Real - elseif !isnothing(mv.Integer) || !isnothing(mv.Enumeration) - return fmi2Integer - elseif !isnothing(mv.Boolean) - return fmi2Boolean - elseif !isnothing(mv.String) - return fmi2String - else - @assert false "fmi2TypeForValueReference(...): Unknown data type for value reference `$(vr)`." - end - return nothing -end - -""" - fmi2StringToValueReference(md::fmi2ModelDescription, name::String) - -Returns the ValueReference or an array of ValueReferences coresponding to the variable names. - -# Arguments -- `md::fmi2ModelDescription`: Argument `md` stores all static information related to an FMU. Especially, the FMU variables and their attributes such as name, unit, default initial value, etc.. -- `name::String`: Argument `names` contains a String or a list of Strings. For each string ("variable name"), the corresponding value reference is searched in the given modelDescription. - -# Returns -- `reference::md.stringValueReferences`: Return `references` is an array of `ValueReference` coresponding to the variable name. - - -# Source -- FMISpec2.0.2 Link: [https://fmi-standard.org/](https://fmi-standard.org/) -- FMISpec2.0.2[p.16]: 2.1.2 Platform Dependent Definitions -See also [`fmi2StringToValueReference`](@ref) -""" -function fmi2StringToValueReference(md::fmi2ModelDescription, name::String) - reference = nothing - if haskey(md.stringValueReferences, name) - reference = md.stringValueReferences[name] - else - @warn "No variable named '$name' found." - end - reference -end - -""" - fmi2StringToValueReference(fmu::FMU2, name::Union{String, AbstractArray{String}}) - -Returns the ValueReference or an array of ValueReferences coresponding to the variable names. - -# Arguments -- `fmu::FMU2`: Mutable struct representing a FMU and all it instantiated instances in the FMI 2.0.2 Standard. -- `name::Union{String, AbstractArray{String}}`: Argument `names` contains a Strings or AbstractArray{String}. For that, the corresponding value reference is searched in the given modelDescription. - -# Returns -For input parameter `name::Sting`: -- `reference::md.stringValueReferences`: Return `references` is an array of `ValueReference` coresponding to the variable name. -For input parameter `name::AbstractArray{String}` -- `ar::Array{fmi2ScalarVariable}`: Return `ar` is an array of `fmi2ScalarVariable` containing the modelVariables with the identical fmi2ValueReference to the input variable vr. - -# Source -- FMISpec2.0.2 Link: [https://fmi-standard.org/](https://fmi-standard.org/) -- FMISpec2.0.2[p.16]: 2.1.2 Platform Dependent Definitions -See also [`fmi2StringToValueReference`](@ref) -""" -function fmi2StringToValueReference(fmu::FMU2, name::Union{String, AbstractArray{String}}) - fmi2StringToValueReference(fmu.modelDescription, name) -end - -""" - fmi2ValueReferenceToString(md::fmi2ModelDescription, reference::fmi2ValueReference) - -# Arguments -- `md::fmi2ModelDescription`: Argument `md` stores all static information related to an FMU. Especially, the FMU variables and their attributes such as name, unit, default initial value, etc.. -- `reference::fmi2ValueReference`: The argument `references` is a variable of the type `ValueReference`. - -# Return -- `md.stringValueReferences::Dict{String, fmi2ValueReference}`: Returns a dictionary `md.stringValueReferences` that constructs a hash table with keys of type String and values of type fmi2ValueReference. - -# Source -- FMISpec2.0.2 Link: [https://fmi-standard.org/](https://fmi-standard.org/) -- FMISpec2.0.2[p.22]: 2.1.2 Platform Dependent Definitions (fmi2TypesPlatform.h) -""" -function fmi2ValueReferenceToString(md::fmi2ModelDescription, reference::fmi2ValueReference) - [k for (k,v) in md.stringValueReferences if v == reference] -end - -""" - fmi2ValueReferenceToString(md::fmi2ModelDescription, reference::Int64) - -# Arguments -- `md::fmi2ModelDescription`: Argument `md` stores all static information related to an FMU. Especially, the FMU variables and their attributes such as name, unit, default initial value, etc.. -- `reference::Int64`: Argument `references` is a variable of the type `Int64`. - -# Return -- `md.stringValueReferences::Dict{String, fmi2ValueReference}`: Returns a dictionary `md.stringValueReferences` that constructs a hash table with keys of type String and values of type fmi2ValueReference. - -# Source -- FMISpec2.0.2 Link: [https://fmi-standard.org/](https://fmi-standard.org/) -- FMISpec2.0.2[p.22]: 2.1.2 Platform Dependent Definitions (fmi2TypesPlatform.h) -""" -function fmi2ValueReferenceToString(md::fmi2ModelDescription, reference::Int64) - fmi2ValueReferenceToString(md, fmi2ValueReference(reference)) -end - -""" - fmi2ValueReferenceToString(fmu::FMU2, reference::Union{fmi2ValueReference, Int64}) - -# Arguments -- `fmu::FMU2`: Mutable struct representing a FMU and all it instantiated instances in the FMI 2.0.2 Standard. -- `reference::Union{fmi2ValueReference, Int64}`: Argument `references` of the type `fmi2ValueReference` or `Int64`. - -# Return -- `md.stringValueReferences::Dict{String, fmi2ValueReference}`: Returns a dictionary `md.stringValueReferences` that constructs a hash table with keys of type String and values of type fmi2ValueReference. - -# Source -- FMISpec2.0.2 Link: [https://fmi-standard.org/](https://fmi-standard.org/) -- FMISpec2.0.2[p.22]: 2.1.2 Platform Dependent Definitions (fmi2TypesPlatform.h) -""" -function fmi2ValueReferenceToString(fmu::FMU2, reference::Union{fmi2ValueReference, Int64}) - fmi2ValueReferenceToString(fmu.modelDescription, reference) -end - - -""" - fmi2GetSolutionState(solution::FMU2Solution, vr::fmi2ValueReferenceFormat; isIndex::Bool=false) - -Returns the Solution state. - -# Arguments -- `solution::FMU2Solution`: Struct contains information about the solution `value`, `success`, `state` and `events` of a specific FMU. -- `vr::fmi2ValueReferenceFormat`: wildcards for how a user can pass a fmi[X]ValueReference (default = md.valueReferences) -More detailed: `fmi2ValueReferenceFormat = Union{Nothing, String, Array{String,1}, fmi2ValueReference, Array{fmi2ValueReference,1}, Int64, Array{Int64,1}, Symbol}` -- `isIndex::Bool=false`: Argument `isIndex` exists to check if `vr` ist the spezific solution element ("index") that equals the given fmi2ValueReferenceFormat - -# Return -- If he length of the given referencees equals 1, each element u in the collection `solution.states.u`, it is selecting the element at the index represented by indices[1] and returns it. - Thus, the collect() function is taking the generator expression and returning an array of the selected elements. -- If more than one reference is given, the same process takes place as before. The difference is that now more than one indice is accessed. - -# Source -- FMISpec2.0.2 Link: [https://fmi-standard.org/](https://fmi-standard.org/) -- FMISpec2.0.2[p.22]: 2.1.2 Platform Dependent Definitions (fmi2TypesPlatform.h) -""" -function fmi2GetSolutionState(solution::FMU2Solution, vrs::fmi2ValueReferenceFormat; isIndex::Bool=false) - - indices = [] - - if isIndex - if length(vrs) == 1 - indices = [vrs] - else - indices = vrs - end - else - ignore_derivatives() do - vrs = prepareValueReference(solution.component.fmu, vrs) - - if !isnothing(solution.states) - for vr in vrs - found = false - for i in 1:length(solution.component.fmu.modelDescription.stateValueReferences) - if solution.component.fmu.modelDescription.stateValueReferences[i] == vr - push!(indices, i) - found = true - break - end - end - @assert found "Couldn't find the index for value reference `$(vr)`! This is probaly because this value reference does not belong to a system state." - end - end - - end # ignore_derivatives - end - - # found something - if length(indices) == length(vrs) - - if length(vrs) == 1 # single value - return collect(u[indices[1]] for u in solution.states.u) - - else # multi value - return collect(collect(u[indices[i]] for u in solution.states.u) for i in 1:length(indices)) - - end - end - - return nothing -end - -""" - fmi2GetSolutionDerivative(solution::FMU2Solution, vr::fmi2ValueReferenceFormat; isIndex::Bool=false) - -Returns the Solution values. - -# Arguments -- `solution::FMU2Solution`: Struct contains information about the solution `value`, `success`, `state` and `events` of a specific FMU. -- `vr::fmi2ValueReferenceFormat`: wildcards for how a user can pass a fmi[X]ValueReference (default = md.valueReferences) -More detailed: `fmi2ValueReferenceFormat = Union{Nothing, String, Array{String,1}, fmi2ValueReference, Array{fmi2ValueReference,1}, Int64, Array{Int64,1}, Symbol}` -- `isIndex::Bool=false`: Argument `isIndex` exists to check if `vr` ist the spezific solution element ("index") that equals the given fmi2ValueReferenceFormat - -# Return -- If the length of the given referencees equals 1, each element `myt` in the collection `solution.states.t` is selecting the derivative of the solution states represented by indices[1] in respect to time, at time `myt` and returns its it. - Thus, the collect() function is taking the generator expression and returning an array of the selected derivatives. -- If more than one reference is given, the same process takes place as before. The difference is that now more than one indice is accessed. - -# Source -- FMISpec2.0.2 Link: [https://fmi-standard.org/](https://fmi-standard.org/) -- FMISpec2.0.2[p.22]: 2.1.2 Platform Dependent Definitions (fmi2TypesPlatform.h) -""" -function fmi2GetSolutionDerivative(solution::FMU2Solution, vrs::fmi2ValueReferenceFormat; isIndex::Bool=false, order::Integer=1) - indices = [] - - if isIndex - if length(vrs) == 1 - indices = [vrs] - else - indices = vrs - end - else - ignore_derivatives() do - vrs = prepareValueReference(solution.component.fmu, vrs) - - if !isnothing(solution.states) - for vr in vrs - found = false - for i in 1:length(solution.component.fmu.modelDescription.stateValueReferences) - if solution.component.fmu.modelDescription.stateValueReferences[i] == vr - push!(indices, i) - found = true - break - end - end - @assert found "Couldn't find the index for value reference `$(vr)`! This is probaly because this value reference does not belong to a system state." - end - end - - end # ignore_derivatives - end - - # found something - if length(indices) == length(vrs) - - if length(vrs) == 1 # single value - return collect(solution.states(t, Val{order})[indices[1]] for t in solution.states.t) - - else # multi value - return collect(collect(solution.states(t, Val{order})[indices[i]] for t in solution.states.t) for i in 1:length(indices)) - end - end - - return nothing -end - -""" - fmi2GetSolutionValue(solution::FMU2Solution, vr::fmi2ValueReferenceFormat; isIndex::Bool=false) - -Returns the Solution values. - -# Arguments -- `solution::FMU2Solution`: Struct contains information about the solution `value`, `success`, `state` and `events` of a specific FMU. -- `vr::fmi2ValueReferenceFormat`: wildcards for how a user can pass a fmi[X]ValueReference (default = md.valueReferences) -More detailed: `fmi2ValueReferenceFormat = Union{Nothing, String, Array{String,1}, fmi2ValueReference, Array{fmi2ValueReference,1}, Int64, Array{Int64,1}, Symbol}` -- `isIndex::Bool=false`: Argument `isIndex` exists to check if `vr` ist the spezific solution element ("index") that equals the given fmi2ValueReferenceFormat - -# Return -- If he length of the given referencees equals 1, each element u in the collection `solution.values.saveval` is selecting the element at the index represented by indices[1] and returns it. - Thus, the collect() function is taking the generator expression and returning an array of the selected elements. -- If more than one reference is given, the same process takes place as before. The difference is that now more than one indice is accessed. - - -# Source -- FMISpec2.0.2 Link: [https://fmi-standard.org/](https://fmi-standard.org/) -- FMISpec2.0.2[p.22]: 2.1.2 Platform Dependent Definitions (fmi2TypesPlatform.h) -""" -function fmi2GetSolutionValue(solution::FMU2Solution, vrs::fmi2ValueReferenceFormat; isIndex::Bool=false) - - indices = [] - - if isIndex - if length(vrs) == 1 - indices = [vrs] - else - indices = vrs - end - else - ignore_derivatives() do - vrs = prepareValueReference(solution.component.fmu, vrs) - - if !isnothing(solution.values) - for vr in vrs - found = false - for i in 1:length(solution.valueReferences) - if solution.valueReferences[i] == vr - push!(indices, i) - found = true - break - end - end - @assert found "Couldn't find the index for value reference `$(vr)`! This is probaly because this value reference does not exist for this system." - end - end - - end # ignore_derivatives - end - - # found something - if length(indices) == length(vrs) - - if length(vrs) == 1 # single value - return collect(u[indices[1]] for u in solution.values.saveval) - - else # multi value - return collect(collect(u[indices[i]] for u in solution.values.saveval) for i in 1:length(indices)) - - end - end - - return nothing -end - -""" - fmi2GetSolutionTime(solution::FMU2Solution) - -Returns the Solution time. - -# Arguments -- `solution::FMU2Solution`: Struct contains information about the solution `value`, `success`, `state` and `events` of a specific FMU. - -# Return -- `solution.states.t::tType`: `solution.state` is a struct `ODESolution` with attribute t. `t` is the time points corresponding to the saved values of the ODE solution. -- `solution.values.t::tType`: `solution.value` is a struct `ODESolution` with attribute t.`t` the time points corresponding to the saved values of the ODE solution. -- If no solution time is found `nothing` is returned. - -#Source -- using OrdinaryDiffEq: [ODESolution](https://github.com/SciML/SciMLBase.jl/blob/b10025c579bcdecb94b659aa3723fdd023096197/src/solutions/ode_solutions.jl) (SciML/SciMLBase.jl) -- FMISpec2.0.2 Link: [https://fmi-standard.org/](https://fmi-standard.org/) -- FMISpec2.0.2[p.22]: 2.1.2 Platform Dependent Definitions (fmi2TypesPlatform.h) -""" -function fmi2GetSolutionTime(solution::FMU2Solution) - if solution.states !== nothing - return solution.states.t - elseif solution.values !== nothing - return solution.values.t - else - return nothing - end -end diff --git a/src/FMI2/ext.jl b/src/FMI2/ext.jl index ebcfbfe..87c4b49 100644 --- a/src/FMI2/ext.jl +++ b/src/FMI2/ext.jl @@ -3,113 +3,12 @@ # Licensed under the MIT license. See LICENSE file in the project root for details. # -# What is included in the file `FMI2_ext.jl` (external/additional functions)? -# - new functions, that are useful, but not part of the FMI-spec (example: `fmi2Load`, `fmi2SampleJacobian`) - using Libdl -using ZipFile -import Downloads const CB_LIB_PATH = @path joinpath(dirname(@__FILE__), "callbackFunctions", "binaries") """ - fmi2Unzip(pathToFMU::String; unpackPath=nothing, cleanup=true) - -Create a copy of the .fmu file as a .zip folder and unzips it. -Returns the paths to the zipped and unzipped folders. - -# Arguments -- `pathToFMU::String`: The folder path to the .zip folder. - -# Keywords -- `unpackPath=nothing`: Via optional argument ```unpackPath```, a path to unpack the FMU can be specified (default: system temporary directory). -- `cleanup=true`: The cleanup option controls whether the temporary directory is automatically deleted when the process exits. - -# Returns -- `unzippedAbsPath::String`: Contains the Path to the uzipped Folder. -- `zipAbsPath::String`: Contains the Path to the zipped Folder. - -See also [`mktempdir`](https://docs.julialang.org/en/v1/base/file/#Base.Filesystem.mktempdir-Tuple{AbstractString}). -""" -function fmi2Unzip(pathToFMU::String; unpackPath=nothing, cleanup=true) - - fileNameExt = basename(pathToFMU) - (fileName, fileExt) = splitext(fileNameExt) - - if unpackPath == nothing - # cleanup=true leads to issues with automatic testing on linux server. - unpackPath = mktempdir(; prefix="fmijl_", cleanup=cleanup) - end - - zipPath = joinpath(unpackPath, fileName * ".zip") - unzippedPath = joinpath(unpackPath, fileName) - - # only copy ZIP if not already there - if !isfile(zipPath) - cp(pathToFMU, zipPath; force=true) - end - - @assert isfile(zipPath) ["fmi2Unzip(...): ZIP-Archive couldn't be copied to `$zipPath`."] - - zipAbsPath = isabspath(zipPath) ? zipPath : joinpath(pwd(), zipPath) - unzippedAbsPath = isabspath(unzippedPath) ? unzippedPath : joinpath(pwd(), unzippedPath) - - @assert isfile(zipAbsPath) ["fmi2Unzip(...): Can't deploy ZIP-Archive at `$(zipAbsPath)`."] - - numFiles = 0 - - # only unzip if not already done - if !isdir(unzippedAbsPath) - mkpath(unzippedAbsPath) - - zarchive = ZipFile.Reader(zipAbsPath) - for f in zarchive.files - fileAbsPath = normpath(joinpath(unzippedAbsPath, f.name)) - - if endswith(f.name,"/") || endswith(f.name,"\\") - mkpath(fileAbsPath) # mkdir(fileAbsPath) - - @assert isdir(fileAbsPath) ["fmi2Unzip(...): Can't create directory `$(f.name)` at `$(fileAbsPath)`."] - else - # create directory if not forced by zip file folder - mkpath(dirname(fileAbsPath)) - - numBytes = write(fileAbsPath, read(f)) - - if numBytes == 0 - @debug "fmi2Unzip(...): Written file `$(f.name)`, but file is empty." - end - - @assert isfile(fileAbsPath) ["fmi2Unzip(...): Can't unzip file `$(f.name)` at `$(fileAbsPath)`."] - numFiles += 1 - end - end - close(zarchive) - end - - @assert isdir(unzippedAbsPath) ["fmi2Unzip(...): ZIP-Archive couldn't be unzipped at `$(unzippedPath)`."] - @debug "fmi2Unzip(...): Successfully unzipped $numFiles files at `$unzippedAbsPath`." - - (unzippedAbsPath, zipAbsPath) -end - -# Checks with dlsym for available function in library. -# Prints an info text and returns C_NULL if not (soft-check). -# TODO used in FMI3_ext.jl too other spot to put it? -function dlsym_opt(libHandle, symbol) - addr = dlsym(libHandle, symbol; throw_error=false) - if addr == nothing - logWarning(fmu, "This FMU does not support function '$symbol'.") - addr = Ptr{Cvoid}(C_NULL) - end - addr -end - -""" - fmi2Load(pathToFMU::String; - unpackPath=nothing, - type=nothing, - cleanup=true) + createFMU2 Sets the properties of the fmu by reading the modelDescription.xml. Retrieves all the pointers of binary functions. @@ -124,13 +23,8 @@ Retrieves all the pointers of binary functions. # Returns - Returns the instance of the FMU struct. - -# Source -- FMISpec2.0.2 Link: [https://fmi-standard.org/](https://fmi-standard.org/) -- FMISpec2.0.2: 2.2.7 Definition of Model Variables (ModelVariables) - """ -function fmi2Load(pathToFMU::String; unpackPath::Union{String, Nothing}=nothing, type::Union{Symbol, fmi2Type, Nothing}=nothing, cleanup::Bool=true, logLevel::Union{FMULogLevel, Symbol}=FMULogLevelWarn) +function createFMU2(fmuPath, fmuZipPath; type::Union{Symbol, fmi2Type, Nothing}=nothing, logLevel::Union{FMULogLevel, Symbol}=FMULogLevelWarn) # Create uninitialized FMU if isa(logLevel, Symbol) @@ -144,20 +38,14 @@ function fmi2Load(pathToFMU::String; unpackPath::Union{String, Nothing}=nothing, @assert false "Unknown logLevel symbol: `$(logLevel)`, supported are `:info`, `:warn` and `:error`." end end - fmu = FMU2(logLevel) - - if startswith(pathToFMU, "http") - logInfo(fmu, "Downloading FMU from `$(pathToFMU)`.") - pathToFMU = Downloads.download(pathToFMU) - end - pathToFMU = normpath(pathToFMU) + fmu = FMU2(logLevel) # set paths for fmu handling - (fmu.path, fmu.zipPath) = fmi2Unzip(pathToFMU; unpackPath=unpackPath, cleanup=cleanup) + fmu.path = fmuPath + fmu.zipPath = fmuZipPath # set paths for modelExchangeScripting and binary - tmpName = splitpath(fmu.path) pathToModelDescription = joinpath(fmu.path, "modelDescription.xml") # parse modelDescription.xml @@ -178,14 +66,14 @@ function fmi2Load(pathToFMU::String; unpackPath::Union{String, Nothing}=nothing, end else # type==nothing - if fmi2IsCoSimulation(fmu.modelDescription) && fmi2IsModelExchange(fmu.modelDescription) + if isCoSimulation(fmu.modelDescription) && isModelExchange(fmu.modelDescription) fmu.type = fmi2TypeCoSimulation - logInfo(fmu, "fmi2Load(...): FMU supports both CS and ME, using CS as default if nothing specified.") + logInfo(fmu, "createFMU2(...): FMU supports both CS and ME, using CS as default if nothing specified.") - elseif fmi2IsCoSimulation(fmu.modelDescription) + elseif isCoSimulation(fmu.modelDescription) fmu.type = fmi2TypeCoSimulation - elseif fmi2IsModelExchange(fmu.modelDescription) + elseif isModelExchange(fmu.modelDescription) fmu.type = fmi2TypeModelExchange else @@ -193,7 +81,7 @@ function fmi2Load(pathToFMU::String; unpackPath::Union{String, Nothing}=nothing, end end - fmuName = fmi2GetModelIdentifier(fmu.modelDescription; type=fmu.type) # tmpName[length(tmpName)] + fmuName = getModelIdentifier(fmu.modelDescription; type=fmu.type) # tmpName[length(tmpName)] directoryBinary = "" pathToBinary = "" @@ -204,7 +92,7 @@ function fmi2Load(pathToFMU::String; unpackPath::Union{String, Nothing}=nothing, osStr = "" juliaArch = Sys.WORD_SIZE - @assert (juliaArch == 64 || juliaArch == 32) "fmi2Load(...): Unknown Julia Architecture with $(juliaArch)-bit, must be 64- or 32-bit." + @assert (juliaArch == 64 || juliaArch == 32) "createFMU2(...): Unknown Julia Architecture with $(juliaArch)-bit, must be 64- or 32-bit." if Sys.iswindows() if juliaArch == 64 @@ -231,10 +119,10 @@ function fmi2Load(pathToFMU::String; unpackPath::Union{String, Nothing}=nothing, osStr = "Mac" fmuExt = "dylib" else - @assert false "fmi2Load(...): Unsupported target platform. Supporting Windows, Linux and Mac. Please open an issue if you want to use another OS/architecture." + @assert false "createFMU2(...): Unsupported target platform. Supporting Windows, Linux and Mac. Please open an issue if you want to use another OS/architecture." end - @assert (length(directories) > 0) "fmi2Load(...): Unsupported architecture. Supporting Julia for Windows (64- and 32-bit), Linux (64-bit) and Mac (64-bit). Please open an issue if you want to use another architecture." + @assert (length(directories) > 0) "createFMU2(...): Unsupported architecture. Supporting Julia for Windows (64- and 32-bit), Linux (64-bit) and Mac (64-bit). Please open an issue if you want to use another architecture." for directory in directories directoryBinary = joinpath(fmu.path, directory) if isdir(directoryBinary) @@ -242,26 +130,26 @@ function fmi2Load(pathToFMU::String; unpackPath::Union{String, Nothing}=nothing, break end end - @assert isfile(pathToBinary) "fmi2Load(...): Target platform is $(osStr), but can't find valid FMU binary at `$(pathToBinary)` for path `$(fmu.path)`." + @assert isfile(pathToBinary) "createFMU2(...): Target platform is $(osStr), but can't find valid FMU binary at `$(pathToBinary)` for path `$(fmu.path)`." # make URI ressource location tmpResourceLocation = string("file:///", fmu.path) tmpResourceLocation = joinpath(tmpResourceLocation, "resources") fmu.fmuResourceLocation = replace(tmpResourceLocation, "\\" => "/") # URIs.escapeuri(tmpResourceLocation) - logInfo(fmu, "fmi2Load(...): FMU resources location is `$(fmu.fmuResourceLocation)`") + logInfo(fmu, "createFMU2(...): FMU resources location is `$(fmu.fmuResourceLocation)`") fmu.binaryPath = pathToBinary - loadBinary(fmu) + loadPointers(fmu) return fmu end """ - loadBinary(fmu::FMU2) + loadPointers(fmu::FMU2) load pointers to `fmu`\`s c functions from shared library handle (provided by `fmu.libHandle`) """ -function loadBinary(fmu::FMU2) +function loadPointers(fmu::FMU2) lastDirectory = pwd() cd(dirname(fmu.binaryPath)) @@ -290,24 +178,24 @@ function loadBinary(fmu::FMU2) fmu.cGetString = dlsym_opt(fmu.libHandle, :fmi2GetString) fmu.cSetString = dlsym_opt(fmu.libHandle, :fmi2SetString) - if fmi2CanGetSetState(fmu.modelDescription) + if canGetSetFMUState(fmu.modelDescription) fmu.cGetFMUstate = dlsym_opt(fmu.libHandle, :fmi2GetFMUstate) fmu.cSetFMUstate = dlsym_opt(fmu.libHandle, :fmi2SetFMUstate) fmu.cFreeFMUstate = dlsym_opt(fmu.libHandle, :fmi2FreeFMUstate) end - if fmi2CanSerializeFMUstate(fmu.modelDescription) + if canSerializeFMUState(fmu.modelDescription) fmu.cSerializedFMUstateSize = dlsym_opt(fmu.libHandle, :fmi2SerializedFMUstateSize) fmu.cSerializeFMUstate = dlsym_opt(fmu.libHandle, :fmi2SerializeFMUstate) fmu.cDeSerializeFMUstate = dlsym_opt(fmu.libHandle, :fmi2DeSerializeFMUstate) end - if fmi2ProvidesDirectionalDerivative(fmu.modelDescription) + if providesDirectionalDerivatives(fmu.modelDescription) fmu.cGetDirectionalDerivative = dlsym_opt(fmu.libHandle, :fmi2GetDirectionalDerivative) end # CS specific function calls - if fmi2IsCoSimulation(fmu.modelDescription) + if isCoSimulation(fmu.modelDescription) fmu.cSetRealInputDerivatives = dlsym(fmu.libHandle, :fmi2SetRealInputDerivatives) fmu.cGetRealOutputDerivatives = dlsym(fmu.libHandle, :fmi2GetRealOutputDerivatives) fmu.cDoStep = dlsym(fmu.libHandle, :fmi2DoStep) @@ -320,7 +208,7 @@ function loadBinary(fmu::FMU2) end # ME specific function calls - if fmi2IsModelExchange(fmu.modelDescription) + if isModelExchange(fmu.modelDescription) fmu.cEnterContinuousTimeMode = dlsym(fmu.libHandle, :fmi2EnterContinuousTimeMode) fmu.cGetContinuousStates = dlsym(fmu.libHandle, :fmi2GetContinuousStates) fmu.cGetDerivatives = dlsym(fmu.libHandle, :fmi2GetDerivatives) @@ -334,7 +222,7 @@ function loadBinary(fmu::FMU2) end end -function unloadBinary(fmu::FMU2) +function unloadPointers(fmu::FMU2) # retrieve functions fmu.cInstantiate = @cfunction(FMICore.unload_fmi2Instantiate, fmi2Component, (fmi2String, fmi2Type, fmi2String, fmi2String, Ptr{fmi2CallbackFunctions}, fmi2Boolean, fmi2Boolean)) @@ -387,7 +275,7 @@ function unloadBinary(fmu::FMU2) # end # ME specific function calls - if fmi2IsModelExchange(fmu.modelDescription) + if isModelExchange(fmu.modelDescription) fmu.cEnterContinuousTimeMode = @cfunction(FMICore.unload_fmi2EnterContinuousTimeMode, fmi2Status, (fmi2Component,)) fmu.cGetContinuousStates = @cfunction(FMICore.unload_fmi2GetContinuousStates, fmi2Status, (fmi2Component, Ptr{fmi2Real}, Csize_t)) fmu.cGetDerivatives = @cfunction(FMICore.unload_fmi2GetDerivatives, fmi2Status, (fmi2Component, Ptr{fmi2Real}, Csize_t)) @@ -399,1260 +287,4 @@ function unloadBinary(fmu::FMU2) fmu.cGetEventIndicators = @cfunction(FMICore.unload_fmi2GetEventIndicators, fmi2Status, (fmi2Component, Ptr{fmi2Real}, Csize_t)) fmu.cGetNominalsOfContinuousStates= @cfunction(FMICore.unload_fmi2GetNominalsOfContinuousStates, fmi2Status, (fmi2Component, Ptr{fmi2Real}, Csize_t)) end -end - -lk_fmi2Instantiate = ReentrantLock() -""" - fmi2Instantiate!(fmu::FMU2; - instanceName::String=fmu.modelName, - type::fmi2Type=fmu.type, - pushComponents::Bool = true, - visible::Bool = false, - loggingOn::Bool = fmu.executionConfig.loggingOn, - externalCallbacks::Bool = fmu.executionConfig.externalCallbacks, - logStatusOK::Bool=true, - logStatusWarning::Bool=true, - logStatusDiscard::Bool=true, - logStatusError::Bool=true, - logStatusFatal::Bool=true, - logStatusPending::Bool=true) - -Create a new instance of the given fmu, adds a logger if logginOn == true. -# Arguments -- `fmu::FMU2`: Mutable struct representing a FMU and all it instantiated instances in the FMI 2.0.2 Standard. - -# Keywords -- `instanceName::String=fmu.modelName`: Name of the instance -- `type::fmi2Type=fmu.type`: Defines whether a Co-Simulation or Model Exchange is present -- `pushComponents::Bool = true`: Defines if the fmu components should be pushed in the application. -- `visible::Bool = false` if the FMU should be started with graphic interface, if supported (default=`false`) -- `loggingOn::Bool = fmu.executionConfig.loggingOn` if the FMU should log and display function calls (default=`false`) -- `externalCallbacks::Bool = fmu.executionConfig.externalCallbacks` if an external shared library should be used for the fmi2CallbackFunctions, this may improve readability of logging messages (default=`false`) -- `logStatusOK::Bool=true` whether to log status of kind `fmi2OK` (default=`true`) -- `logStatusWarning::Bool=true` whether to log status of kind `fmi2Warning` (default=`true`) -- `logStatusDiscard::Bool=true` whether to log status of kind `fmi2Discard` (default=`true`) -- `logStatusError::Bool=true` whether to log status of kind `fmi2Error` (default=`true`) -- `logStatusFatal::Bool=true` whether to log status of kind `fmi2Fatal` (default=`true`) -- `logStatusPending::Bool=true` whether to log status of kind `fmi2Pending` (default=`true`) - -# Returns -- Returns the instance of a new FMU component. - -# Source -- FMISpec2.0.2 Link: [https://fmi-standard.org/](https://fmi-standard.org/) -- FMISpec2.0.2: 2.2.7 Definition of Model Variables (ModelVariables) - -See also [`fmi2Instantiate`](#@ref). -""" -function fmi2Instantiate!(fmu::FMU2; - instanceName::String=fmu.modelName, - type::fmi2Type=fmu.type, - pushComponents::Bool = true, - visible::Bool = false, - loggingOn::Bool = fmu.executionConfig.loggingOn, - externalCallbacks::Bool = fmu.executionConfig.externalCallbacks, - logStatusOK::Bool=true, - logStatusWarning::Bool=true, - logStatusDiscard::Bool=true, - logStatusError::Bool=true, - logStatusFatal::Bool=true, - logStatusPending::Bool=true) - - compEnv = FMU2ComponentEnvironment() - compEnv.logStatusOK = logStatusOK - compEnv.logStatusWarning = logStatusWarning - compEnv.logStatusDiscard = logStatusDiscard - compEnv.logStatusError = logStatusError - compEnv.logStatusFatal = logStatusFatal - compEnv.logStatusPending = logStatusPending - - ptrLogger = @cfunction(fmi2CallbackLogger, Cvoid, (Ptr{FMU2ComponentEnvironment}, Ptr{Cchar}, Cuint, Ptr{Cchar}, Ptr{Cchar})) - if externalCallbacks - if fmu.callbackLibHandle == C_NULL - @assert Sys.WORD_SIZE == 64 "`externalCallbacks=true` is only supported for 64-bit." - - cbLibPath = CB_LIB_PATH - if Sys.iswindows() - cbLibPath = joinpath(cbLibPath, "win64", "callbackFunctions.dll") - elseif Sys.islinux() - cbLibPath = joinpath(cbLibPath, "linux64", "libcallbackFunctions.so") - elseif Sys.isapple() - cbLibPath = joinpath(cbLibPath, "darwin64", "libcallbackFunctions.dylib") - else - @error "Unsupported OS" - end - - # check permission to execute the DLL - perm = filemode(cbLibPath) - permRWX = 16895 - if perm != permRWX - chmod(cbLibPath, permRWX; recursive=true) - end - - fmu.callbackLibHandle = dlopen(cbLibPath) - end - ptrLogger = dlsym(fmu.callbackLibHandle, :logger) - end - ptrAllocateMemory = @cfunction(fmi2CallbackAllocateMemory, Ptr{Cvoid}, (Csize_t, Csize_t)) - ptrFreeMemory = @cfunction(fmi2CallbackFreeMemory, Cvoid, (Ptr{Cvoid},)) - ptrStepFinished = C_NULL # ToDo - ptrComponentEnvironment = Ptr{FMU2ComponentEnvironment}(pointer_from_objref(compEnv)) - callbackFunctions = fmi2CallbackFunctions(ptrLogger, ptrAllocateMemory, ptrFreeMemory, ptrStepFinished, ptrComponentEnvironment) - - guidStr = "$(fmu.modelDescription.guid)" - - global lk_fmi2Instantiate - - lock(lk_fmi2Instantiate) do - - component = nothing - compAddr = fmi2Instantiate(fmu.cInstantiate, pointer(instanceName), type, pointer(guidStr), pointer(fmu.fmuResourceLocation), Ptr{fmi2CallbackFunctions}(pointer_from_objref(callbackFunctions)), fmi2Boolean(visible), fmi2Boolean(loggingOn)) - - if compAddr == Ptr{Cvoid}(C_NULL) - @error "fmi2Instantiate!(...): Instantiation failed, see error messages above.\nIf no error messages, enable FMU debug logging.\nIf logging is on and no messages are printed before this, the FMU might not log errors." - return nothing - end - - # check if address is already inside of the components (this may be in FMIExport.jl) - for c in fmu.components - if c.compAddr == compAddr - component = c - break - end - end - - if !isnothing(component) - logWarning(fmu, "fmi2Instantiate!(...): This component was already registered. This may be because you created the FMU by yourself with FMIExport.jl.") - else - component = FMU2Component(compAddr, fmu) - - component.callbackFunctions = callbackFunctions - component.instanceName = instanceName - component.type = type - - if pushComponents - push!(fmu.components, component) - end - end - - component.componentEnvironment = compEnv - component.loggingOn = loggingOn - component.visible = visible - - # Jacobians - - # smpFct = (mtx, ∂f_refs, ∂x_refs) -> fmi2SampleJacobian!(mtx, component, ∂f_refs, ∂x_refs) - # updFct = nothing - # if fmi2ProvidesDirectionalDerivative(fmu) - # updFct = (mtx, ∂f_refs, ∂x_refs) -> fmi2GetJacobian!(mtx, component, ∂f_refs, ∂x_refs) - # else - # updFct = smpFct - # end - - # component.∂ẋ_∂x = FMICore.FMU2Jacobian{fmi2Real, fmi2ValueReference}(component, updFct) - # component.∂ẋ_∂u = FMICore.FMU2Jacobian{fmi2Real, fmi2ValueReference}(component, updFct) - # component.∂ẋ_∂p = FMICore.FMU2Jacobian{fmi2Real, fmi2ValueReference}(component, updFct) - - # component.∂y_∂x = FMICore.FMU2Jacobian{fmi2Real, fmi2ValueReference}(component, updFct) - # component.∂y_∂u = FMICore.FMU2Jacobian{fmi2Real, fmi2ValueReference}(component, updFct) - # component.∂y_∂p = FMICore.FMU2Jacobian{fmi2Real, fmi2ValueReference}(component, updFct) - - # component.∂e_∂x = FMICore.FMU2Jacobian{fmi2Real, fmi2ValueReference}(component, smpFct) - # component.∂e_∂u = FMICore.FMU2Jacobian{fmi2Real, fmi2ValueReference}(component, smpFct) - # component.∂e_∂p = FMICore.FMU2Jacobian{fmi2Real, fmi2ValueReference}(component, smpFct) - - # register component for current thread - fmu.threadComponents[Threads.threadid()] = component - end - - return getCurrentComponent(fmu) -end - -""" - fmi2Reload(fmu::FMU2) - -Reloads the FMU-binary. This is useful, if the FMU does not support a clean reset implementation. - -# Arguments -- `fmu::FMU2`: Mutable struct representing a FMU and all it instantiated instances in the FMI 2.0.2 Standard. - -# Source -- FMISpec2.0.2 Link: [https://fmi-standard.org/](https://fmi-standard.org/) -- FMISpec2.0.2: 2.2.7 Definition of Model Variables (ModelVariables) -""" -function fmi2Reload(fmu::FMU2) - dlclose(fmu.libHandle) - loadBinary(fmu) -end - -""" - fmi2Unload(fmu::FMU2, cleanUp::Bool=true; secure_pointers::Bool=true) - -Unload a FMU. -Free the allocated memory, close the binaries and remove temporary zip and unziped FMU model description. - -# Arguments -- `fmu::FMU2`: Mutable struct representing a FMU and all it instantiated instances in the FMI 2.0.2 Standard. -- `cleanUp::Bool= true`: Defines if the file and directory should be deleted. - -# Keywords -- `secure_pointers=true` whether pointers to C-functions should be overwritten with dummies with Julia assertions, instead of pointing to dead memory (slower, but more user safe) -""" -function fmi2Unload(fmu::FMU2, cleanUp::Bool=true; secure_pointers::Bool=true) - - while length(fmu.components) > 0 - c = fmu.components[end] - - # release allocated memory for snapshots (they might be used elsewhere too) - # if !isnothing(c.solution) - # for iter in c.solution.snapshots - # t, snapshot = iter - # cleanup!(c, snapshot) - # end - # end - - fmi2FreeInstance!(c) - end - - # the components are removed from the component list via call to fmi2FreeInstance! - @assert length(fmu.components) == 0 "fmi2Unload(...): Failure during deleting components, $(length(fmu.components)) remaining in stack." - - if secure_pointers - unloadBinary(fmu) - end - - dlclose(fmu.libHandle) - - if cleanUp - try - rm(fmu.path; recursive = true, force = true) - rm(fmu.zipPath; recursive = true, force = true) - catch e - @warn "Cannot delete unpacked data on disc. Maybe some files are opened in another application." - end - end -end - -""" - fmi2SampleJacobian(c::FMU2Component, - vUnknown_ref::Union{AbstractArray{fmi2ValueReference}, Symbol}, - vKnown_ref::AbstractArray{fmi2ValueReference}, - steps::Union{AbstractArray{fmi2Real}, Nothing} = nothing) - -This function samples the directional derivative by manipulating corresponding values (central differences). - -Computes the directional derivatives of an FMU. An FMU has different modes and in every Mode an FMU might be described by different equations and different unknowns. The precise definitions are given in the mathematical descriptions of Model Exchange (section 3.1) and Co-Simulation (section 4.1). In every Mode, the general form of the FMU equations are: -𝐯_unknown = 𝐡(𝐯_known, 𝐯_rest) - -- `v_unknown`: vector of unknown Real variables computed in the actual Mode: - - Initialization Mode: unkowns kisted under `` that have type Real. - - Continuous-Time Mode (ModelExchange): The continuous-time outputs and state derivatives. (= the variables listed under `` with type Real and variability = `continuous` and the variables listed as state derivatives under `)`. - - Event Mode (ModelExchange): The same variables as in the Continuous-Time Mode and additionally variables under `` with type Real and variability = `discrete`. - - Step Mode (CoSimulation): The variables listed under `` with type Real and variability = `continuous` or `discrete`. If `` is present, also the variables listed here as state derivatives. -- `v_known`: Real input variables of function h that changes its value in the actual Mode. -- `v_rest`:Set of input variables of function h that either changes its value in the actual Mode but are non-Real variables, or do not change their values in this Mode, but change their values in other Modes. - -Computes a linear combination of the partial derivatives of h with respect to the selected input variables 𝐯_known: - - Δv_unknown = (δh / δv_known) Δv_known - -# Arguments -- `c::FMU2Component`: Mutable struct represents an instantiated instance of an FMU in the FMI 2.0.2 Standard. -- `vUnknown_ref::AbstractArray{fmi2ValueReference}`: Argument `vUnknown_ref` contains values of type`fmi2ValueReference` which are identifiers of a variable value of the model. `vUnknown_ref` can be equated with `v_unknown`(variable described above). -- `vKnown_ref::AbstractArray{fmi2ValueReference}`: Argument `vKnown_ref` contains values of type `fmi2ValueReference` which are identifiers of a variable value of the model.`vKnown_ref` can be equated with `v_known`(variable described above). -- `steps::Union{AbstractArray{fmi2Real}, Nothing} = nothing)`: If sampling is used, sampling step size can be set (for each direction individually) using optional argument `steps`. - -# Returns -- `dvUnkonwn::Array{fmi2Real}`: Argument `vUnknown_ref` contains values of type`fmi2ValueReference` which are identifiers of a variable value of the model. `vUnknown_ref` can be equated with `v_unknown`(see function fmi2GetDirectionalDerivative!). - -# Source -- FMISpec2.0.2 Link: [https://fmi-standard.org/](https://fmi-standard.org/) -- FMISpec2.0.2: 2.2.7 Definition of Model Variables (ModelVariables) - -See also [`fmi2GetDirectionalDerivative!`](@ref). -""" -function fmi2SampleJacobian(c::FMU2Component, - vUnknown_ref::AbstractArray{fmi2ValueReference}, - vKnown_ref::AbstractArray{fmi2ValueReference}, - steps::Union{AbstractArray{fmi2Real}, Nothing} = nothing) - - mtx = zeros(fmi2Real, length(vUnknown_ref), length(vKnown_ref)) - - fmi2SampleJacobian!(mtx, vUnknown_ref, vKnown_ref, steps) - - return mtx -end - -""" - function fmi2SampleJacobian!(mtx::Matrix{<:Real}, - c::FMU2Component, - vUnknown_ref::Union{AbstractArray{fmi2ValueReference}, Symbol}, - vKnown_ref::Union{AbstractArray{fmi2ValueReference}, Symbol}, - steps::Union{AbstractArray{fmi2Real}, Nothing} = nothing) - -This function samples the directional derivative by manipulating corresponding values (central differences) and saves in-place. - - -Computes the directional derivatives of an FMU. An FMU has different Modes and in every Mode an FMU might be described by different equations and different unknowns. The precise definitions are given in the mathematical descriptions of Model Exchange (section 3.1) and Co-Simulation (section 4.1). In every Mode, the general form of the FMU equations are: -𝐯_unknown = 𝐡(𝐯_known, 𝐯_rest) - -- `v_unknown`: vector of unknown Real variables computed in the actual Mode: - - Initialization Mode: unkowns kisted under `` that have type Real. - - Continuous-Time Mode (ModelExchange): The continuous-time outputs and state derivatives. (= the variables listed under `` with type Real and variability = `continuous` and the variables listed as state derivatives under `)`. - - Event Mode (ModelExchange): The same variables as in the Continuous-Time Mode and additionally variables under `` with type Real and variability = `discrete`. - - Step Mode (CoSimulation): The variables listed under `` with type Real and variability = `continuous` or `discrete`. If `` is present, also the variables listed here as state derivatives. -- `v_known`: Real input variables of function h that changes its value in the actual Mode. -- `v_rest`:Set of input variables of function h that either changes its value in the actual Mode but are non-Real variables, or do not change their values in this Mode, but change their values in other Modes - -Computes a linear combination of the partial derivatives of h with respect to the selected input variables 𝐯_known: - - Δv_unknown = (δh / δv_known) Δv_known - -# Arguments -- `mtx::Matrix{<:Real}`:Output matrix to store the Jacobian. Its dimensions must be compatible with the number of unknown and known value references. -- `c::FMU2Component`: Mutable struct represents an instantiated instance of an FMU in the FMI 2.0.2 Standard. -- `vUnknown_ref::AbstractArray{fmi2ValueReference}`: Argument `vUnknown_ref` contains values of type`fmi2ValueReference` which are identifiers of a variable value of the model. `vUnknown_ref` can be equated with `v_unknown`(variable described above). -- `vKnown_ref::AbstractArray{fmi2ValueReference}`: Argument `vKnown_ref` contains values of type `fmi2ValueReference` which are identifiers of a variable value of the model.`vKnown_ref` can be equated with `v_known`(variable described above). -- `dvUnknown::AbstractArray{fmi2Real}`: Stores the directional derivative vector values. -- `steps::Union{AbstractArray{fmi2Real}, Nothing} = nothing)`: Step size to be used for numerical differentiation. If nothing, a default value will be chosen automatically. - -# Returns -- `nothing` - -# Source -- FMISpec2.0.2 Link: [https://fmi-standard.org/](https://fmi-standard.org/) -- FMISpec2.0.2: 2.2.7 Definition of Model Variables (ModelVariables) - -See also [`fmi2GetDirectionalDerivative!`](@ref). -""" -function fmi2SampleJacobian!(mtx::Matrix{<:Real}, - c::FMU2Component, - vUnknown_ref::AbstractArray{fmi2ValueReference}, - vKnown_ref::AbstractArray{fmi2ValueReference}, - steps::Union{AbstractArray{fmi2Real}, Nothing} = nothing) - - step = 0.0 - - negValues = zeros(length(vUnknown_ref)) - posValues = zeros(length(vUnknown_ref)) - - for i in 1:length(vKnown_ref) - vKnown = vKnown_ref[i] - origValue = fmi2GetReal(c, vKnown) - - if steps === nothing - # smaller than 1e-6 leads to issues - step = max(2.0 * eps(Float32(origValue)), 1e-6) - else - step = steps[i] - end - - fmi2SetReal(c, vKnown, origValue - step; track=false) - fmi2GetReal!(c, vUnknown_ref, negValues) - - fmi2SetReal(c, vKnown, origValue + step; track=false) - fmi2GetReal!(c, vUnknown_ref, posValues) - - fmi2SetReal(c, vKnown, origValue; track=false) - - if length(vUnknown_ref) == 1 - mtx[1,i] = (posValues-negValues) ./ (step * 2.0) - else - mtx[:,i] = (posValues-negValues) ./ (step * 2.0) - end - end - - nothing -end - -function fmi2SampleJacobian!(mtx::Matrix{<:Real}, - c::FMU2Component, - vUnknown_ref::Symbol, - vKnown_ref::AbstractArray{fmi2ValueReference}, - steps::Union{AbstractArray{fmi2Real}, Nothing} = nothing) - - @assert vUnknown_ref == :indicators "vUnknown_ref::Symbol must be `:indicators`!" - - step = 0.0 - - len_vUnknown_ref = c.fmu.modelDescription.numberOfEventIndicators - - negValues = zeros(len_vUnknown_ref) - posValues = zeros(len_vUnknown_ref) - - for i in 1:length(vKnown_ref) - vKnown = vKnown_ref[i] - origValue = fmi2GetReal(c, vKnown) - - if steps === nothing - step = max(2.0 * eps(Float32(origValue)), 1e-12) - else - step = steps[i] - end - - fmi2SetReal(c, vKnown, origValue - step; track=false) - fmi2GetEventIndicators!(c, negValues) - - fmi2SetReal(c, vKnown, origValue + step; track=false) - fmi2GetEventIndicators!(c, posValues) - - fmi2SetReal(c, vKnown, origValue; track=false) - - if len_vUnknown_ref == 1 - mtx[1,i] = (posValues-negValues) ./ (step * 2.0) - else - mtx[:,i] = (posValues-negValues) ./ (step * 2.0) - end - end - - nothing -end - -function fmi2SampleJacobian!(mtx::Matrix{<:Real}, - c::FMU2Component, - vUnknown_ref::AbstractArray{fmi2ValueReference}, - vKnown_ref::Symbol, - steps::Union{AbstractArray{fmi2Real}, Nothing} = nothing) - - @assert vKnown_ref == :time "vKnown_ref::Symbol must be `:time`!" - - step = 0.0 - - negValues = zeros(length(vUnknown_ref)) - posValues = zeros(length(vUnknown_ref)) - - for i in 1:length(vKnown_ref) - vKnown = vKnown_ref[i] - origValue = fmi2GetReal(c, vKnown) - - if steps === nothing - step = max(2.0 * eps(Float32(origValue)), 1e-12) - else - step = steps[i] - end - - fmi2SetReal(c, vKnown, origValue - step; track=false) - fmi2GetEventIndicators!(c, negValues) - - fmi2SetReal(c, vKnown, origValue + step; track=false) - fmi2GetEventIndicators!(c, posValues) - - fmi2SetReal(c, vKnown, origValue; track=false) - - if length(vUnknown_ref) == 1 - mtx[1,i] = (posValues-negValues) ./ (step * 2.0) - else - mtx[:,i] = (posValues-negValues) ./ (step * 2.0) - end - end - - nothing -end - -""" - fmi2GetJacobian(comp::FMU2Component, - rdx::AbstractArray{fmi2ValueReference}, - rx::AbstractArray{fmi2ValueReference}; - steps::Union{AbstractArray{fmi2Real}, Nothing} = nothing) - -Builds the jacobian over the FMU `fmu` for FMU value references `rdx` and `rx`, so that the function returns the jacobian ∂rdx / ∂rx. - -If FMI built-in directional derivatives are supported, they are used. -As fallback, directional derivatives will be sampled with central differences. -For optimization, if the FMU's model description has the optional entry 'dependencies', only dependent variables are sampled/retrieved. This drastically boosts performance for systems with large variable count (like CFD). - -# Arguments -- `comp::FMU2Component`: Mutable struct represents an instantiated instance of an FMU in the FMI 2.0.2 Standard. -- `rdx::AbstractArray{fmi2ValueReference}`: Argument `vUnknown_ref` contains values of type`fmi2ValueReference` which are identifiers of a variable value of the model. -- `rx::AbstractArray{fmi2ValueReference}`: Argument `vKnown_ref` contains values of type `fmi2ValueReference` which are identifiers of a variable value of the model. - -# Keywords -- `steps::Union{AbstractArray{fmi2Real}, Nothing} = nothing)`: If sampling is used, sampling step size can be set (for each direction individually) using optional argument `steps`. - -# Returns -- `mat::Array{fmi2Real}`: Return `mat` contains the jacobian ∂rdx / ∂rx. - -# Source -- FMISpec2.0.2 Link: [https://fmi-standard.org/](https://fmi-standard.org/) -- FMISpec2.0.2: 2.2.7 Definition of Model Variables (ModelVariables) - -""" -function fmi2GetJacobian(comp::FMU2Component, - rdx::AbstractArray{fmi2ValueReference}, - rx::AbstractArray{fmi2ValueReference}; - steps::Union{AbstractArray{fmi2Real}, Nothing} = nothing) - mat = zeros(fmi2Real, length(rdx), length(rx)) - fmi2GetJacobian!(mat, comp, rdx, rx; steps=steps) - return mat -end - -""" - fmi2GetJacobian!(jac::AbstractMatrix{fmi2Real}, - comp::FMU2Component, - rdx::AbstractArray{fmi2ValueReference}, - rx::AbstractArray{fmi2ValueReference}; - steps::Union{AbstractArray{fmi2Real}, Nothing} = nothing) - -Fills the jacobian over the FMU `fmu` for FMU value references `rdx` and `rx`, so that the function stores the jacobian ∂rdx / ∂rx in an AbstractMatrix `jac`. - -If FMI built-in directional derivatives are supported, they are used. -As fallback, directional derivatives will be sampled with central differences. -For optimization, if the FMU's model description has the optional entry 'dependencies', only dependent variables are sampled/retrieved. This drastically boosts performance for systems with large variable count (like CFD). - -# Arguments -- `jac::AbstractMatrix{fmi2Real}`: A matrix that will hold the computed Jacobian matrix. -- `comp::FMU2Component`: Mutable struct represents an instantiated instance of an FMU in the FMI 2.0.2 Standard. -- `rdx::AbstractArray{fmi2ValueReference}`: Argument `vUnknown_ref` contains values of type`fmi2ValueReference` which are identifiers of a variable value of the model. -- `rx::AbstractArray{fmi2ValueReference}`: Argument `vKnown_ref` contains values of type `fmi2ValueReference` which are identifiers of a variable value of the model. - -# Keywords -- `steps::Union{AbstractArray{fmi2Real}, Nothing} = nothing)`: Step size to be used for numerical differentiation. If nothing, a default value will be chosen automatically. - -# Returns -- `nothing` - -# Source -- FMISpec2.0.2 Link: [https://fmi-standard.org/](https://fmi-standard.org/) -- FMISpec2.0.2: 2.2.7 Definition of Model Variables (ModelVariables) - -""" -function fmi2GetJacobian!(jac::AbstractMatrix{fmi2Real}, - comp::FMU2Component, - rdx::AbstractArray{fmi2ValueReference}, - rx::AbstractArray{fmi2ValueReference}; - steps::Union{AbstractArray{fmi2Real}, Nothing} = nothing) - - @assert size(jac) == (length(rdx), length(rx)) ["fmi2GetJacobian!: Dimension missmatch between `jac` $(size(jac)), `rdx` $(length(rdx)) and `rx` $(length(rx))."] - - if length(rdx) == 0 || length(rx) == 0 - jac = zeros(length(rdx), length(rx)) - return nothing - end - - # ToDo: Pick entries based on dependency matrix! - #depMtx = fmi2GetDependencies(fmu) - rdx_inds = collect(comp.fmu.modelDescription.valueReferenceIndicies[vr] for vr in rdx) - rx_inds = collect(comp.fmu.modelDescription.valueReferenceIndicies[vr] for vr in rx) - - for i in 1:length(rx) - - sensitive_rdx_inds = 1:length(rdx) - sensitive_rdx = rdx - - # sensitive_rdx_inds = Int64[] - # sensitive_rdx = fmi2ValueReference[] - - # for j in 1:length(rdx) - # if depMtx[rdx_inds[j], rx_inds[i]] != fmi2DependencyIndependent - # push!(sensitive_rdx_inds, j) - # push!(sensitive_rdx, rdx[j]) - # end - # end - - if length(sensitive_rdx) > 0 - - fmi2GetDirectionalDerivative!(comp, sensitive_rdx, [rx[i]], view(jac, sensitive_rdx_inds, i)) - - # jac[sensitive_rdx_inds, i] = fmi2GetDirectionalDerivative(comp, sensitive_rdx, [rx[i]]) - - end - end - - return nothing -end - -""" - fmi2GetFullJacobian(comp::FMU2Component, - rdx::AbstractArray{fmi2ValueReference}, - rx::AbstractArray{fmi2ValueReference}; - steps::Union{AbstractArray{fmi2Real}, Nothing} = nothing) - -Builds the jacobian over the FMU `fmu` for FMU value references `rdx` and `rx`, so that the function returns the jacobian ∂rdx / ∂rx. - -If FMI built-in directional derivatives are supported, they are used. -As fallback, directional derivatives will be sampled with central differences. -No performance optimization, for an optimized version use `fmi2GetJacobian`. - - -# Arguments -- `comp::FMU2Component`: Mutable struct represents an instantiated instance of an FMU in the FMI 2.0.2 Standard. -- `rdx::AbstractArray{fmi2ValueReference}`: Argument `vUnknown_ref` contains values of type`fmi2ValueReference` which are identifiers of a variable value of the model. -- `rx::AbstractArray{fmi2ValueReference}`: Argument `vKnown_ref` contains values of type `fmi2ValueReference` which are identifiers of a variable value of the model. - -# Keywords -- `steps::Union{AbstractArray{fmi2Real}, Nothing} = nothing)`: If sampling is used, sampling step size can be set (for each direction individually) using optional argument `steps`. - -# Returns -- `mat::Array{fmi2Real}`: Return `mat` contains the jacobian ∂rdx / ∂rx. - -# Source -- FMISpec2.0.2 Link: [https://fmi-standard.org/](https://fmi-standard.org/) -- FMISpec2.0.2: 2.2.7 Definition of Model Variables (ModelVariables) - -See also [`fmi2GetFullJacobian!`](@ref) -""" -function fmi2GetFullJacobian(comp::FMU2Component, - rdx::AbstractArray{fmi2ValueReference}, - rx::AbstractArray{fmi2ValueReference}; - steps::Union{AbstractArray{fmi2Real}, Nothing} = nothing) - mat = zeros(fmi2Real, length(rdx), length(rx)) - fmi2GetFullJacobian!(mat, comp, rdx, rx; steps=steps) - return mat -end - -""" - - - fmi2GetFullJacobian!(jac::AbstractMatrix{fmi2Real}, - comp::FMU2Component, - rdx::AbstractArray{fmi2ValueReference}, - rx::AbstractArray{fmi2ValueReference}; - steps::Union{AbstractArray{fmi2Real}, Nothing} = nothing) - -Fills the jacobian over the FMU `fmu` for FMU value references `rdx` and `rx`, so that the function returns the jacobian ∂rdx / ∂rx. - -If FMI built-in directional derivatives are supported, they are used. -As fallback, directional derivatives will be sampled with central differences. -No performance optimization, for an optimized version use `fmi2GetJacobian!`. - -# Arguments -- `jac::AbstractMatrix{fmi2Real}`: Stores the the jacobian ∂rdx / ∂rx. -- `comp::FMU2Component`: Mutable struct represents an instantiated instance of an FMU in the FMI 2.0.2 Standard. -- `rdx::AbstractArray{fmi2ValueReference}`: Argument `vUnknown_ref` contains values of type`fmi2ValueReference` which are identifiers of a variable value of the model. -- `rx::AbstractArray{fmi2ValueReference}`: Argument `vKnown_ref` contains values of type `fmi2ValueReference` which are identifiers of a variable value of the model. - -# Keywords -- `steps::Union{AbstractArray{fmi2Real}, Nothing} = nothing)`: Step size to be used for numerical differentiation. -If nothing, a default value will be chosen automatically. - -# Returns -- `nothing` -""" -function fmi2GetFullJacobian!(jac::AbstractMatrix{fmi2Real}, - comp::FMU2Component, - rdx::AbstractArray{fmi2ValueReference}, - rx::AbstractArray{fmi2ValueReference}; - steps::Union{AbstractArray{fmi2Real}, Nothing} = nothing) - @assert size(jac) == (length(rdx),length(rx)) "fmi2GetFullJacobian!: Dimension missmatch between `jac` $(size(jac)), `rdx` ($length(rdx)) and `rx` ($length(rx))." - - @warn "`fmi2GetFullJacobian!` is for benchmarking only, please use `fmi2GetJacobian`." - - if length(rdx) == 0 || length(rx) == 0 - jac = zeros(length(rdx), length(rx)) - return nothing - end - - if fmi2ProvidesDirectionalDerivative(comp.fmu) - for i in 1:length(rx) - jac[:,i] = fmi2GetDirectionalDerivative(comp, rdx, [rx[i]]) - end - else - jac = fmi2SampleJacobian(comp, rdx, rx) - end - - return nothing -end - -""" - fmi2Get!(comp::FMU2Component, vrs::fmi2ValueReferenceFormat, dstArray::AbstractArray) - -Stores the specific value of `fmi2ScalarVariable` containing the modelVariables with the identical fmi2ValueReference and returns an array that indicates the Status. - -# Arguments -- `comp::FMU2Component`: Mutable struct represents an instantiated instance of an FMU in the FMI 2.0.2 Standard. -- `vrs::fmi2ValueReferenceFormat`: wildcards for how a user can pass a fmi[X]ValueReference -More detailed: `fmi2ValueReferenceFormat = Union{Nothing, String, Array{String,1}, fmi2ValueReference, Array{fmi2ValueReference,1}, Int64, Array{Int64,1}, Symbol}` -- `dstArray::AbstractArray`: Stores the specific value of `fmi2ScalarVariable` containing the modelVariables with the identical fmi2ValueReference to the input variable vr (vr = vrs[i]). `dstArray` has the same length as `vrs`. - -# Returns -- `retcodes::Array{fmi2Status}`: Returns an array of length length(vrs) with Type `fmi2Status`. Type `fmi2Status` is an enumeration and indicates the success of the function call. -More detailed: - - `fmi2OK`: all well - - `fmi2Warning`: things are not quite right, but the computation can continue - - `fmi2Discard`: if the slave computed successfully only a subinterval of the communication step - - `fmi2Error`: the communication step could not be carried out at all - - `fmi2Fatal`: if an error occurred which corrupted the FMU irreparably - - `fmi2Pending`: this status is returned if the slave executes the function asynchronously - -# Source -- FMISpec2.0.2 Link: [https://fmi-standard.org/](https://fmi-standard.org/) -- FMISpec2.0.2[p.23]: 2.1.6 Initialization, Termination, and Resetting an FMU -- FMISpec2.0.2[p.18]: 2.1.3 Status Returned by Functions - -""" -function fmi2Get!(comp::FMU2Component, vrs::fmi2ValueReferenceFormat, dstArray::AbstractArray) - vrs = prepareValueReference(comp, vrs) - - @assert length(vrs) == length(dstArray) "fmi2Get!(...): Number of value references doesn't match number of `dstArray` elements." - - retcodes = zeros(fmi2Status, length(vrs)) # fmi2StatusOK - - for i in 1:length(vrs) - vr = vrs[i] - mv = fmi2ModelVariablesForValueReference(comp.fmu.modelDescription, vr) - mv = mv[1] - - if mv.Real != nothing - #@assert isa(dstArray[i], Real) "fmi2Get!(...): Unknown data type for value reference `$(vr)` at index $(i), should be `Real`, is `$(typeof(dstArray[i]))`." - dstArray[i] = fmi2GetReal(comp, vr) - elseif mv.Integer != nothing - #@assert isa(dstArray[i], Union{Real, Integer}) "fmi2Get!(...): Unknown data type for value reference `$(vr)` at index $(i), should be `Integer`, is `$(typeof(dstArray[i]))`." - dstArray[i] = fmi2GetInteger(comp, vr) - elseif mv.Boolean != nothing - #@assert isa(dstArray[i], Union{Real, Bool}) "fmi2Get!(...): Unknown data type for value reference `$(vr)` at index $(i), should be `Bool`, is `$(typeof(dstArray[i]))`." - dstArray[i] = fmi2GetBoolean(comp, vr) - elseif mv.String != nothing - #@assert isa(dstArray[i], String) "fmi2Get!(...): Unknown data type for value reference `$(vr)` at index $(i), should be `String`, is `$(typeof(dstArray[i]))`." - dstArray[i] = fmi2GetString(comp, vr) - elseif mv.Enumeration != nothing - @warn "fmi2Get!(...): Currently not implemented for fmi2Enum." - else - @assert isa(dstArray[i], Real) "fmi2Get!(...): Unknown data type for value reference `$(vr)` at index $(i), is `$(mv.datatype.datatype)`." - end - end - - return retcodes -end - -""" - fmi2Get(comp::FMU2Component, vrs::fmi2ValueReferenceFormat) - - -Returns the specific value of `fmi2ScalarVariable` containing the modelVariables with the identical fmi2ValueReference in an array. - -# Arguments -- `comp::FMU2Component`: Mutable struct represents an instantiated instance of an FMU in the FMI 2.0.2 Standard. -- `vrs::fmi2ValueReferenceFormat`: wildcards for how a user can pass a fmi[X]ValueReference -More detailed: `fmi2ValueReferenceFormat = Union{Nothing, String, Array{String,1}, fmi2ValueReference, Array{fmi2ValueReference,1}, Int64, Array{Int64,1}, Symbol}` - -# Returns -- `dstArray::Array{Any,1}(undef, length(vrs))`: Stores the specific value of `fmi2ScalarVariable` containing the modelVariables with the identical fmi2ValueReference to the input variable vr (vr = vrs[i]). `dstArray` is a 1-Dimensional Array that has the same length as `vrs`. - -# Source -- FMISpec2.0.2 Link: [https://fmi-standard.org/](https://fmi-standard.org/) -- FMISpec2.0.2[p.23]: 2.1.6 Initialization, Termination, and Resetting an FMU -- FMISpec2.0.2[p.18]: 2.1.3 Status Returned by Functions -""" -function fmi2Get(comp::FMU2Component, vrs::fmi2ValueReferenceFormat) - vrs = prepareValueReference(comp, vrs) - dstArray = Array{Any,1}(undef, length(vrs)) - fmi2Get!(comp, vrs, dstArray) - - if length(dstArray) == 1 - return dstArray[1] - else - return dstArray - end -end - - -""" - fmi2Set(comp::FMU2Component, - vrs::fmi2ValueReferenceFormat, - srcArray::AbstractArray; - filter=nothing) - -Stores the specific value of `fmi2ScalarVariable` containing the modelVariables with the identical fmi2ValueReference and returns an array that indicates the Status. - -# Arguments -- `comp::FMU2Component`: Mutable struct represents an instantiated instance of an FMU in the FMI 2.0.2 Standard. -- `vrs::fmi2ValueReferenceFormat`: wildcards for how a user can pass a fmi[X]ValueReference -More detailed: `fmi2ValueReferenceFormat = Union{Nothing, String, Array{String,1}, fmi2ValueReference, Array{fmi2ValueReference,1}, Int64, Array{Int64,1}, Symbol}` -- `srcArray::AbstractArray`: Stores the specific value of `fmi2ScalarVariable` containing the modelVariables with the identical fmi2ValueReference to the input variable vr (vr = vrs[i]). `srcArray` has the same length as `vrs`. - -# Keywords -- `filter=nothing`: It is applied to each ModelVariable to determine if it should be updated. - -# Returns -- `retcodes::Array{fmi2Status}`: Returns an array of length length(vrs) with Type `fmi2Status`. Type `fmi2Status` is an enumeration and indicates the success of the function call. -More detailed: - - `fmi2OK`: all well - - `fmi2Warning`: things are not quite right, but the computation can continue - - `fmi2Discard`: if the slave computed successfully only a subinterval of the communication step - - `fmi2Error`: the communication step could not be carried out at all - - `fmi2Fatal`: if an error occurred which corrupted the FMU irreparably - - `fmi2Pending`: this status is returned if the slave executes the function asynchronously - -# Source -- FMISpec2.0.2 Link: [https://fmi-standard.org/](https://fmi-standard.org/) -- FMISpec2.0.2[p.23]: 2.1.6 Initialization, Termination, and Resetting an FMU -- FMISpec2.0.2[p.18]: 2.1.3 Status Returned by Functions -""" -function fmi2Set(comp::FMU2Component, vrs::fmi2ValueReferenceFormat, srcArray::AbstractArray; filter=nothing) - vrs = prepareValueReference(comp, vrs) - - @assert length(vrs) == length(srcArray) "fmi2Set(...): Number of value references [$(length(vrs))] doesn't match number of `srcArray` elements [$(length(srcArray))]." - - retcodes = zeros(fmi2Status, length(vrs)) # fmi2StatusOK - - for i in 1:length(vrs) - vr = vrs[i] - mv = fmi2ModelVariablesForValueReference(comp.fmu.modelDescription, vr) - mv = mv[1] - - if filter === nothing || filter(mv) - - if mv.Real != nothing - @assert isa(srcArray[i], Real) "fmi2Set(...): Unknown data type for value reference `$(vr)` at index $(i), should be `Real`, is `$(typeof(srcArray[i]))`." - retcodes[i] = fmi2SetReal(comp, vr, srcArray[i]) - elseif mv.Integer != nothing - @assert isa(srcArray[i], Union{Real, Integer}) "fmi2Set(...): Unknown data type for value reference `$(vr)` at index $(i), should be `Integer`, is `$(typeof(srcArray[i]))`." - retcodes[i] = fmi2SetInteger(comp, vr, Integer(srcArray[i])) - elseif mv.Boolean != nothing - @assert isa(srcArray[i], Union{Real, Bool}) "fmi2Set(...): Unknown data type for value reference `$(vr)` at index $(i), should be `Bool`, is `$(typeof(srcArray[i]))`." - retcodes[i] = fmi2SetBoolean(comp, vr, Bool(srcArray[i])) - elseif mv.String != nothing - @assert isa(srcArray[i], String) "fmi2Set(...): Unknown data type for value reference `$(vr)` at index $(i), should be `String`, is `$(typeof(srcArray[i]))`." - retcodes[i] = fmi2SetString(comp, vr, srcArray[i]) - elseif mv.Enumeration != nothing - @assert isa(srcArray[i], Union{Real, Integer}) "fmi2Set(...): Unknown data type for value reference `$(vr)` at index $(i), should be `Enumeration` (`Integer`), is `$(typeof(srcArray[i]))`." - retcodes[i] = fmi2SetInteger(comp, vr, Integer(srcArray[i])) - else - @assert false "fmi2Set(...): Unknown data type for value reference `$(vr)` at index $(i), is `$(mv.datatype.datatype)`." - end - - end - end - - return retcodes -end - -function fmi2Set(comp::FMU2Component, vrs::fmi2ValueReferenceFormat, src; filter=nothing) - fmi2Set(comp, vrs, [src]; filter=filter) -end - -""" - fmi2GetStartValue(md::fmi2ModelDescription, vrs::fmi2ValueReferenceFormat = md.valueReferences) - -Returns the start/default value for a given value reference. - -# Arguments -- `md::fmi2ModelDescription`: Struct which provides the static information of ModelVariables. -- `vrs::fmi2ValueReferenceFormat = md.valueReferences`: wildcards for how a user can pass a fmi[X]ValueReference (default = md.valueReferences) -More detailed: `fmi2ValueReferenceFormat = Union{Nothing, String, Array{String,1}, fmi2ValueReference, Array{fmi2ValueReference,1}, Int64, Array{Int64,1}, Symbol}` - -# Returns -- `starts::Array{fmi2ValueReferenceFormat}`: start/default value for a given value reference - -# Source -- FMISpec2.0.2 Link: [https://fmi-standard.org/](https://fmi-standard.org/) -- FMISpec2.0.2: 2.2.7 Definition of Model Variables (ModelVariables) -""" -function fmi2GetStartValue(md::fmi2ModelDescription, vrs::fmi2ValueReferenceFormat = md.valueReferences) - - vrs = prepareValueReference(md, vrs) - - starts = [] - - for vr in vrs - mvs = fmi2ModelVariablesForValueReference(md, vr) - - if length(mvs) == 0 - @warn "fmi2GetStartValue(...): Found no model variable with value reference $(vr)." - end - - push!(starts, fmi2GetStartValue(mvs[1]) ) - end - - if length(vrs) == 1 - return starts[1] - else - return starts - end -end - -""" - fmi2GetStartValue(fmu::FMU2, vrs::fmi2ValueReferenceFormat = fmu.modelDescription.valueReferences) - -Returns the start/default value for a given value reference. - -# Arguments -- `fmu::FMU2`: Mutable struct representing a FMU and all it instantiated instances in the FMI 2.0.2 Standard. -- `vrs::fmi2ValueReferenceFormat = fmu.modelDescription.valueReferences`: wildcards for how a user can pass a fmi[X]ValueReference (default = md.valueReferences) -More detailed: `fmi2ValueReferenceFormat = Union{Nothing, String, Array{String,1}, fmi2ValueReference, Array{fmi2ValueReference,1}, Int64, Array{Int64,1}, Symbol}` - -# Returns -- `starts::fmi2ValueReferenceFormat`: start/default value for a given value reference - -# Source -- FMISpec2.0.2 Link: [https://fmi-standard.org/](https://fmi-standard.org/) -- FMISpec2.0.2: 2.2.7 Definition of Model Variables (ModelVariables) -""" -function fmi2GetStartValue(fmu::FMU2, vrs::fmi2ValueReferenceFormat = fmu.modelDescription.valueReferences) - fmi2GetStartValue(fmu.modelDescription, vrs) -end - -""" - fmi2GetStartValue(c::FMU2Component, vrs::fmi2ValueReferenceFormat = c.fmu.modelDescription.valueReferences) - -Returns the start/default value for a given value reference. - -# Arguments -- `c::FMU2Component`: Mutable struct represents an instantiated instance of an FMU in the FMI 2.0.2 Standard. -- `vrs::fmi2ValueReferenceFormat = c.fmu.modelDescription.valueReferences`: wildcards for how a user can pass a fmi[X]ValueReference (default = md.valueReferences) -More detailed: `fmi2ValueReferenceFormat = Union{Nothing, String, Array{String,1}, fmi2ValueReference, Array{fmi2ValueReference,1}, Int64, Array{Int64,1}, Symbol}` - -# Returns -- `starts::fmi2ValueReferenceFormat`: start/default value for a given value reference - -# Source -- FMISpec2.0.2 Link: [https://fmi-standard.org/](https://fmi-standard.org/) -- FMISpec2.0.2: 2.2.7 Definition of Model Variables (ModelVariables) -""" -function fmi2GetStartValue(c::FMU2Component, vrs::fmi2ValueReferenceFormat = c.fmu.modelDescription.valueReferences) - - vrs = prepareValueReference(c, vrs) - - starts = [] - - for vr in vrs - mvs = fmi2ModelVariablesForValueReference(c.fmu.modelDescription, vr) - - if length(mvs) == 0 - @warn "fmi2GetStartValue(...): Found no model variable with value reference $(vr)." - end - - if mvs[1].Real != nothing - push!(starts, mvs[1].Real.start) - elseif mvs[1].Integer != nothing - push!(starts, mvs[1].Integer.start) - elseif mvs[1].Boolean != nothing - push!(starts, mvs[1].Boolean.start) - elseif mvs[1].String != nothing - push!(starts, mvs[1].String.start) - elseif mvs[1].Enumeration != nothing - push!(starts, mvs[1].Enumeration.start) - else - @assert false "fmi2GetStartValue(...): Value reference $(vr) has no data type." - end - end - - if length(vrs) == 1 - return starts[1] - else - return starts - end -end - -""" - fmi2GetStartValue(mv::fmi2ScalarVariable) - -Returns the start/default value for a given value reference. - -# Arguments -- `mv::fmi2ScalarVariable`: The “ModelVariables” element consists of an ordered set of “ScalarVariable” elements. A “ScalarVariable” represents a variable of primitive type, like a real or integer variable. - -# Returns -- `mv._Real.start`: start/default value for a given ScalarVariable. In this case representing a variable of primitive type Real. -- `mv._Integer.start`: start/default value for a given ScalarVariable. In this case representing a variable of primitive type Integer. -- `mv._Boolean.start`: start/default value for a given ScalarVariable. In this case representing a variable of primitive type Boolean. -- `mv._String.start`: start/default value for a given ScalarVariable. In this case representing a variable of primitive type String. -- `mv._Enumeration.start`: start/default value for a given ScalarVariable. In this case representing a variable of primitive type Enumeration. - - -# Source -- FMISpec2.0.2 Link: [https://fmi-standard.org/](https://fmi-standard.org/) -- FMISpec2.0.2: 2.2.7 Definition of Model Variables (ModelVariables) -""" -function fmi2GetStartValue(mv::fmi2ScalarVariable) - if mv.Real != nothing - return mv.Real.start - elseif mv.Integer != nothing - return mv.Integer.start - elseif mv.Boolean != nothing - return mv.Boolean.start - elseif mv.String != nothing - return mv.String.start - elseif mv.Enumeration != nothing - return mv.Enumeration.start - else - @assert false "fmi2GetStartValue(...): Variable $(mv) has no data type." - end -end - -""" - fmi2GetUnit(mv::fmi2ScalarVariable) - -Returns the `unit` entry (a string) of the corresponding model variable. - -# Arguments -- `fmi2GetStartValue(mv::fmi2ScalarVariable)`: The “ModelVariables” element consists of an ordered set of “ScalarVariable” elements. A “ScalarVariable” represents a variable of primitive type, like a real or integer variable. - -# Returns -- `mv.Real.unit`: Returns the `unit` entry of the corresponding ScalarVariable representing a variable of the primitive type Real. Otherwise `nothing` is returned. -# Source -- FMISpec2.0.2 Link: [https://fmi-standard.org/](https://fmi-standard.org/) -- FMISpec2.0.2: 2.2.7 Definition of Model Variables (ModelVariables) -""" -function fmi2GetUnit(mv::fmi2ScalarVariable) - if !isnothing(mv.Real) - return mv.Real.unit - else - return nothing - end -end - -""" - fmi2GetUnit(st::fmi2SimpleType) - -Returns the `unit` entry (a string) of the corresponding simple type `st` if it has the -attribute `Real` and `nothing` otherwise. - -# Source -- FMISpec2.0.3 Link: [https://fmi-standard.org/](https://fmi-standard.org/) -- FMISpec2.0.3: 2.2.3 Definition of Types (TypeDefinitions) -""" -function fmi2GetUnit(st::fmi2SimpleType) - if hasproperty(st, :Real) - return st.Real.unit - else - return nothing - end -end - -# ToDo: update Docu! -""" - fmi2GetUnit(md::fmi2ModelDescription, mv::fmi2ScalarVariable) - -Returns the `unit` of the corresponding model variable `mv` as a `fmi2Unit` if it is -defined in `md.unitDefinitions`. - -# Arguments -- `md::fmi2ModelDescription`: Struct which provides the static information of ModelVariables. -- `mv::fmi2ScalarVariable`: The “ModelVariables” element consists of an ordered set of “ScalarVariable” elements. A “ScalarVariable” represents a variable of primitive type, like a real or integer variable. - -# Source -- FMISpec2.0.2 Link: [https://fmi-standard.org/](https://fmi-standard.org/) -- FMISpec2.0.2: 2.2.7 Definition of Model Variables (ModelVariables) -""" -function fmi2GetUnit(md::fmi2ModelDescription, mv::Union{fmi2ScalarVariable, fmi2SimpleType}) # ToDo: Multiple Dispatch! - unit_str = fmi2GetUnit(mv) - if !isnothing(unit_str) - ui = findfirst(unit -> unit.name == unit_str, md.unitDefinitions) - if !isnothing(ui) - return md.unitDefinitions[ui] - end - end - return nothing -end - -""" - fmi2GetDeclaredType(md::fmi2ModelDescription, mv::fmi2ScalarVariable) - -Returns the `fmi2SimpleType` of the corresponding model variable `mv` as defined in -`md.typeDefinitions`. -If `mv` does not have a declared type, return `nothing`. -If `mv` has a declared type, but it is not found, issue a warning and return `nothing`. - -# Arguments -- `md::fmi2ModelDescription`: Struct which provides the static information of ModelVariables. -- `mv::fmi2ScalarVariable`: The “ModelVariables” element consists of an ordered set of “ScalarVariable” elements. A “ScalarVariable” represents a variable of primitive type, like a real or integer variable. - -# Source -- FMISpec2.0.3 Link: [https://fmi-standard.org/](https://fmi-standard.org/) -- FMISpec2.0.3: 2.2.7 Definition of Model Variables (ModelVariables) -""" -function fmi2GetDeclaredType(md::fmi2ModelDescription, mv::fmi2ScalarVariable) - if isdefined(mv.attribute, :declaredType) - dt = mv.attribute.declaredType - if !isnothing(dt) - for simple_type in md.typeDefinitions - if dt == simple_type.name - return simple_type - end - end - @warn "`fmi2GetDeclaredType`: Could not find a type definition with name \"$(dt)\" in the `typeDefinitions` of $(md)." - end - end - return nothing -end - -# TODO with the new `fmi2SimpleType` definition this function is superfluous...remove? -""" - fmi2GetSimpleTypeAttributeStruct(st::fmi2SimpleType) - -Returns the attribute structure for the simple type `st`. -Depending on definition, this is either `st.Real`, `st.Integer`, `st.String`, -`st.Boolean` or `st.Enumeration`. - -# Arguments -- `st::fmi2SimpleType`: Struct which provides the information on custom SimpleTypes. - -# Source -- FMISpec2.0.3 Link: [https://fmi-standard.org/](https://fmi-standard.org/) -- FMISpec2.0.3[p.40]: 2.2.3 Definition of Types (TypeDefinitions) -""" -function fmi2GetSimpleTypeAttributeStruct(st::fmi2SimpleType) - return typeof(st.attribute) -end - -""" - fmi2GetInitial(mv::fmi2ScalarVariable) - -Returns the `inital` entry of the corresponding model variable. - -# Arguments -- `fmi2GetStartValue(mv::fmi2ScalarVariable)`: The “ModelVariables” element consists of an ordered set of “ScalarVariable” elements. A “ScalarVariable” represents a variable of primitive type, like a real or integer variable. - -# Returns -- `mv.Real.unit`: Returns the `inital` entry of the corresponding ScalarVariable representing a variable of the primitive type Real. Otherwise `nothing` is returned. - -# Source -- FMISpec2.0.2 Link: [https://fmi-standard.org/](https://fmi-standard.org/) -- FMISpec2.0.2: 2.2.7 Definition of Model Variables (ModelVariables) -""" -function fmi2GetInitial(mv::fmi2ScalarVariable) - return mv.initial -end - -""" - fmi2SampleJacobian(c::FMU2Component, - vUnknown_ref::Array{fmi2ValueReference}, - vKnown_ref::Array{fmi2ValueReference}, - steps::Array{fmi2Real} = ones(fmi2Real, length(vKnown_ref)).*1e-5) - -This function samples the directional derivative by manipulating corresponding values (central differences). - -# Arguments -- `str::fmi2Struct`: Representative for an FMU in the FMI 2.0.2 Standard. -More detailed: `fmi2Struct = Union{FMU2, FMU2Component}` -- `str::FMU2`: Mutable struct representing a FMU and all it instantiated instances in the FMI 2.0.2 Standard. -- `str::FMU2Component`: Mutable struct represents an instantiated instance of an FMU in the FMI 2.0.2 Standard. -- `vUnknown_ref::Array{fmi2ValueReference}`: Argument `vUnKnown_ref` contains values of type `fmi2ValueReference` which are identifiers of a variable value of the model.`vKnown_ref` is the Array of the vector values of Real input variables of function h that changes its value in the actual Mode. -- `vKnown_ref::Array{fmi2ValueReference}`: Argument `vKnown_ref` contains values of type `fmi2ValueReference` which are identifiers of a variable value of the model.`vKnown_ref` is the Array of the vector values of Real input variables of function h that changes its value in the actual Mode. -- `steps::Array{fmi2Real} = ones(fmi2Real, length(vKnown_ref)).*1e-5`: Predefined step size vector `steps`, where all entries have the value 1e-5. - -# Returns -- `dvUnknown::Arrya{fmi2Real}`: stores the samples of the directional derivative - -# Source -- FMISpec2.0.2 Link: [https://fmi-standard.org/](https://fmi-standard.org/) -- FMISpec2.0.2[p.16]: 2.1.2 Platform Dependent Definitions (fmi2TypesPlatform.h) -""" -function fmi2SampleJacobian(c::FMU2Component, - vUnknown_ref::Array{fmi2ValueReference}, - vKnown_ref::Array{fmi2ValueReference}, - steps::Array{fmi2Real} = ones(fmi2Real, length(vKnown_ref)).*1e-5) - - dvUnknown = zeros(fmi2Real, length(vUnknown_ref), length(vKnown_ref)) - - fmi2SampleJacobian!(c, vUnknown_ref, vKnown_ref, dvUnknown, steps) - - dvUnknown -end - -""" - fmi2SampleJacobian!(c::FMU2Component, - vUnknown_ref::Array{fmi2ValueReference}, - vKnown_ref::Array{fmi2ValueReference}, - dvUnknown::AbstractArray, - steps::Array{fmi2Real} = ones(fmi2Real, length(vKnown_ref)).*1e-5) - -This function samples the directional derivative by manipulating corresponding values (central differences) and saves in-place. - -# Arguments -- `str::fmi2Struct`: Representative for an FMU in the FMI 2.0.2 Standard. -More detailed: `fmi2Struct = Union{FMU2, FMU2Component}` - - `str::FMU2`: Mutable struct representing a FMU and all it instantiated instances in the FMI 2.0.2 Standard. - - `str::FMU2Component`: Mutable struct represents an instantiated instance of an FMU in the FMI 2.0.2 Standard. -- `vUnknown_ref::Array{fmi2ValueReference}`: Argument `vUnKnown_ref` contains values of type `fmi2ValueReference` which are identifiers of a variable value of the model.`vKnown_ref` is the Array of the vector values of Real input variables of function h that changes its value in the actual Mode. -- `vKnown_ref::Array{fmi2ValueReference}`: Argument `vKnown_ref` contains values of type `fmi2ValueReference` which are identifiers of a variable value of the model.`vKnown_ref` is the Array of the vector values of Real input variables of function h that changes its value in the actual Mode. -- `dvUnknown::AbstractArray`: stores the samples of the directional derivative -- `steps::Array{fmi2Real} = ones(fmi2Real, length(vKnown_ref)).*1e-5`: current time stepsize - -# Returns -- `nothing ` - -# Source -- FMISpec2.0.2 Link: [https://fmi-standard.org/](https://fmi-standard.org/) -- FMISpec2.0.2[p.16]: 2.1.2 Platform Dependent Definitions (fmi2TypesPlatform.h) - -""" -function fmi2SampleJacobian!(c::FMU2Component, - vUnknown_ref::Array{fmi2ValueReference}, - vKnown_ref::Array{fmi2ValueReference}, - dvUnknown::AbstractArray, - steps::Array{fmi2Real} = ones(fmi2Real, length(vKnown_ref)).*1e-5) - - for i in 1:length(vKnown_ref) - vKnown = vKnown_ref[i] - origValue = fmi2GetReal(c, vKnown) - - fmi2SetReal(c, vKnown, origValue - steps[i]*0.5) - negValues = fmi2GetReal(c, vUnknown_ref) - - fmi2SetReal(c, vKnown, origValue + steps[i]*0.5) - posValues = fmi2GetReal(c, vUnknown_ref) - - fmi2SetReal(c, vKnown, origValue) - - if length(vUnknown_ref) == 1 - dvUnknown[1,i] = (posValues-negValues) ./ steps[i] - else - dvUnknown[:,i] = (posValues-negValues) ./ steps[i] - end - end - - nothing -end - -""" - fmi2SetDiscreteStates(c::FMU2Component, - x::Union{AbstractArray{Float32},AbstractArray{Float64}}) - -Set a new (discrete) state vector and reinitialize chaching of variables that depend on states. - -# Arguments -[ToDo] -""" -function fmi2SetDiscreteStates(c::FMU2Component, xd::AbstractArray{Union{fmi2Real, fmi2Integer, fmi2Boolean}}) - - if length(c.fmu.modelDescription.discreteStateValueReferences) <= 0 - return fmi2StatusOK - end - - status = fmi2Set(c, c.fmu.modelDescription.discreteStateValueReferences, xd) - if status == fmi2StatusOK - fast_copy!(c, :x_d, xd) - end - return status -end - -""" - fmi2GetDiscreteStates!(c::FMU2Component, - x::Union{AbstractArray{Float32},AbstractArray{Float64}}) - -Set a new (discrete) state vector and reinitialize chaching of variables that depend on states. - -# Arguments -[ToDo] -""" -function fmi2GetDiscreteStates!(c::FMU2Component, xd::AbstractArray{Union{fmi2Real, fmi2Integer, fmi2Boolean}}) - - if length(c.fmu.modelDescription.discreteStateValueReferences) <= 0 - return fmi2StatusOK - end - - status = fmi2Get!(c, c.fmu.modelDescription.discreteStateValueReferences, xd) - if status == fmi2StatusOK - fast_copy!(c, :x_d, xd) - end - return status -end - -""" - fmi2GetDiscreteStates(c::FMU2Component, - x::Union{AbstractArray{Float32},AbstractArray{Float64}}) - -Set a new (discrete) state vector and reinitialize chaching of variables that depend on states. - -# Arguments -[ToDo] -""" -function fmi2GetDiscreteStates(c::FMU2Component) - - ndx = length(c.fmu.modelDescription.discreteStateValueReferences) - xd = Vector{Union{fmi2Real, fmi2Integer, fmi2Boolean}}() - fmi2GetDiscreteStates!(c, xd) - return xd end \ No newline at end of file diff --git a/src/FMI2/fmu_to_md.jl b/src/FMI2/fmu_to_md.jl deleted file mode 100644 index 39198dd..0000000 --- a/src/FMI2/fmu_to_md.jl +++ /dev/null @@ -1,198 +0,0 @@ -# -# Copyright (c) 2021 Tobias Thummerer, Lars Mikelsons, Josef Kircher -# Licensed under the MIT license. See LICENSE file in the project root for details. -# - -# What is included in the file `FMI2_fmu_to_md.jl` (FMU to model description)? -# - wrappers to call the model description functions from a FMU-instance [exported] - -""" - fmi2GetNumberOfStates(fmu::FMU2) - -Returns the number of states of the FMU. - -# Arguments -- `fmu::FMU2`: Mutable struct representing a FMU and all it instantiated instances in the FMI 2.0.2 Standard. - -# Returns -- Returns the length of the `fmu.modelDescription.valueReferences::Array{fmi2ValueReference}` corresponding to the number of states of the FMU. -""" -function fmi2GetNumberOfStates(fmu::FMU2) - fmi2GetNumberOfStates(fmu.modelDescription) -end - -""" - function fmi2GetModelName(fmu::FMU2) - -Returns the tag 'modelName' from the model description. - -# Arguments -- `fmu::FMU2`: Mutable struct representing a FMU and all it instantiated instances in the FMI 2.0.2 Standard. - -# Returns -- `fmu.modelDescription.modelName::String`: Returns the tag 'modelName' from the model description. - -""" -function fmi2GetModelName(fmu::FMU2) - fmi2GetModelName(fmu.modelDescription) -end - -""" - fmi2GetGUID(fmu::FMU2) - -Returns the tag 'guid' from the model description. - -# Arguments -- `fmu::FMU2`: Mutable struct representing a FMU and all it instantiated instances in the FMI 2.0.2 Standard. - -# Returns -- `fmu.modelDescription.guid::String`: Returns the tag 'guid' from the model description. - -""" -function fmi2GetGUID(fmu::FMU2) - fmi2GetGUID(fmu.modelDescription) -end - -""" - fmi2GetGenerationTool(fmu::FMU2) - -Returns the tag 'generationtool' from the model description. - -# Arguments -- `fmu::FMU2`: Mutable struct representing a FMU and all it instantiated instances in the FMI 2.0.2 Standard. - -# Returns -- `fmu.modelDescription.generationTool::Union{String, Nothing}`: Returns the tag 'generationtool' from the model description. - -""" -function fmi2GetGenerationTool(fmu::FMU2) - fmi2GetGenerationTool(fmu.modelDescription) -end - -""" - fmi2GetGenerationDateAndTime(fmu::FMU2) - -Returns the tag 'generationdateandtime' from the model description. - -# Arguments -- `fmu::FMU2`: Mutable struct representing a FMU and all it instantiated instances in the FMI 2.0.2 Standard. - -# Returns -- `fmu.modelDescription.generationDateAndTime::DateTime`: Returns the tag 'generationdateandtime' from the model description. - -""" -function fmi2GetGenerationDateAndTime(fmu::FMU2) - fmi2GetGenerationDateAndTime(fmu.modelDescription) -end - -""" - fmi2GetVariableNamingConvention(fmu::FMU2) - -Returns the tag 'varaiblenamingconvention' from the model description. - -# Arguments -- `fmu::FMU2`: Mutable struct representing a FMU and all it instantiated instances in the FMI 2.0.2 Standard. - -# Returns -- `fmu.modelDescription.variableNamingConvention::Union{fmi2VariableNamingConvention, Nothing}`: Returns the tag 'variableNamingConvention' from the model description. - -""" -function fmi2GetVariableNamingConvention(fmu::FMU2) - fmi2GetVariableNamingConvention(fmu.modelDescription) -end - -""" - fmi2GetNumberOfEventIndicators(fmu::FMU2) - -Returns the tag 'numberOfEventIndicators' from the model description. - -# Arguments -- `fmu::FMU2`: Mutable struct representing a FMU and all it instantiated instances in the FMI 2.0.2 Standard. - -# Returns -- `fmu.modelDescription.numberOfEventIndicators::Union{UInt, Nothing}`: Returns the tag 'numberOfEventIndicators' from the model description. - -""" -function fmi2GetNumberOfEventIndicators(fmu::FMU2) - fmi2GetNumberOfEventIndicators(fmu.modelDescription) -end - -""" - fmi2CanGetSetState(fmu::FMU2) - -Returns true, if the FMU supports the getting/setting of states - -# Arguments -- `fmu::FMU2`: Mutable struct representing a FMU and all it instantiated instances in the FMI 2.0.2 Standard. - -# Returns -- `::Bool`: Returns true, if the FMU supports the getting/setting of states. - -""" -function fmi2CanGetSetState(fmu::FMU2) - fmi2CanGetSetState(fmu.modelDescription) -end - -""" - fmi2CanSerializeFMUstate(fmu::FMU2) - -Returns true, if the FMU state can be serialized - -# Arguments -- `fmu::FMU2`: Mutable struct representing a FMU and all it instantiated instances in the FMI 2.0.2 Standard. - -# Returns -- `::Bool`: Returns true, if the FMU state can be serialized - -""" -function fmi2CanSerializeFMUstate(fmu::FMU2) - fmi2CanSerializeFMUstate(fmu.modelDescription) -end - -""" - fmi2ProvidesDirectionalDerivative(fmu::FMU2) - -Returns true, if the FMU provides directional derivatives - -# Arguments -- `fmu::FMU2`: Mutable struct representing a FMU and all it instantiated instances in the FMI 2.0.2 Standard. - -# Returns -- `::Bool`: Returns true, if the FMU provides directional derivatives - -""" -function fmi2ProvidesDirectionalDerivative(fmu::FMU2) - fmi2ProvidesDirectionalDerivative(fmu.modelDescription) -end - -""" - fmi2IsCoSimulation(fmu::FMU2) - -Returns true, if the FMU supports co simulation - -# Arguments -- `fmu::FMU2`: Mutable struct representing a FMU and all it instantiated instances in the FMI 2.0.2 Standard. - -# Returns -- `::Bool`: Returns true, if the FMU supports co simulation - -""" -function fmi2IsCoSimulation(fmu::FMU2) - fmi2IsCoSimulation(fmu.modelDescription) -end - -""" - fmi2IsModelExchange(fmu::FMU2) - -Returns true, if the FMU supports model exchange - -# Arguments -- `fmu::FMU2`: Mutable struct representing a FMU and all it instantiated instances in the FMI 2.0.2 Standard. - -# Returns -- `::Bool`: Returns true, if the FMU supports model exchange - -""" -function fmi2IsModelExchange(fmu::FMU2) - fmi2IsModelExchange(fmu.modelDescription) -end diff --git a/src/FMI2/int.jl b/src/FMI2/int.jl index 0549383..444914b 100644 --- a/src/FMI2/int.jl +++ b/src/FMI2/int.jl @@ -3,11 +3,232 @@ # Licensed under the MIT license. See LICENSE file in the project root for details. # -# What is included in the file `FMI2_int.jl` (internal functions)? -# - optional, more comfortable calls to the C-functions from the FMI-spec (example: `fmiGetReal!(c, v, a)` is bulky, `a = fmiGetReal(c, v)` is more user friendly) +lk_fmi2Instantiate = ReentrantLock() +""" + fmi2Instantiate!(fmu::FMU2; + instanceName::String=fmu.modelName, + type::fmi2Type=fmu.type, + pushComponents::Bool = true, + visible::Bool = false, + loggingOn::Bool = fmu.executionConfig.loggingOn, + externalCallbacks::Bool = fmu.executionConfig.externalCallbacks, + logStatusOK::Bool=true, + logStatusWarning::Bool=true, + logStatusDiscard::Bool=true, + logStatusError::Bool=true, + logStatusFatal::Bool=true, + logStatusPending::Bool=true) + +Create a new instance of the given fmu, adds a logger if logginOn == true. +# Arguments +- `fmu::FMU2`: Mutable struct representing a FMU and all it instantiated instances in the FMI 2.0.2 Standard. + +# Keywords +- `instanceName::String=fmu.modelName`: Name of the instance +- `type::fmi2Type=fmu.type`: Defines whether a Co-Simulation or Model Exchange is present +- `pushComponents::Bool = true`: Defines if the fmu components should be pushed in the application. +- `visible::Bool = false` if the FMU should be started with graphic interface, if supported (default=`false`) +- `loggingOn::Bool = fmu.executionConfig.loggingOn` if the FMU should log and display function calls (default=`false`) +- `externalCallbacks::Bool = fmu.executionConfig.externalCallbacks` if an external shared library should be used for the fmi2CallbackFunctions, this may improve readability of logging messages (default=`false`) +- `logStatusOK::Bool=true` whether to log status of kind `fmi2OK` (default=`true`) +- `logStatusWarning::Bool=true` whether to log status of kind `fmi2Warning` (default=`true`) +- `logStatusDiscard::Bool=true` whether to log status of kind `fmi2Discard` (default=`true`) +- `logStatusError::Bool=true` whether to log status of kind `fmi2Error` (default=`true`) +- `logStatusFatal::Bool=true` whether to log status of kind `fmi2Fatal` (default=`true`) +- `logStatusPending::Bool=true` whether to log status of kind `fmi2Pending` (default=`true`) + +# Returns +- Returns the instance of a new FMU component. + +# Source +- FMISpec2.0.2 Link: [https://fmi-standard.org/](https://fmi-standard.org/) +- FMISpec2.0.2: 2.2.7 Definition of Model Variables (ModelVariables) + +See also [`fmi2Instantiate`](#@ref). +""" +function fmi2Instantiate!(fmu::FMU2; + instanceName::String=fmu.modelName, + type::fmi2Type=fmu.type, + pushComponents::Bool = true, + visible::Bool = false, + loggingOn::Bool = fmu.executionConfig.loggingOn, + externalCallbacks::Bool = fmu.executionConfig.externalCallbacks, + logStatusOK::Bool=true, + logStatusWarning::Bool=true, + logStatusDiscard::Bool=true, + logStatusError::Bool=true, + logStatusFatal::Bool=true, + logStatusPending::Bool=true) + + compEnv = FMU2ComponentEnvironment() + compEnv.logStatusOK = logStatusOK + compEnv.logStatusWarning = logStatusWarning + compEnv.logStatusDiscard = logStatusDiscard + compEnv.logStatusError = logStatusError + compEnv.logStatusFatal = logStatusFatal + compEnv.logStatusPending = logStatusPending + + ptrLogger = @cfunction(fmi2CallbackLogger, Cvoid, (Ptr{FMU2ComponentEnvironment}, Ptr{Cchar}, Cuint, Ptr{Cchar}, Ptr{Cchar})) + if externalCallbacks + if fmu.callbackLibHandle == C_NULL + @assert Sys.WORD_SIZE == 64 "`externalCallbacks=true` is only supported for 64-bit." + + cbLibPath = CB_LIB_PATH + if Sys.iswindows() + cbLibPath = joinpath(cbLibPath, "win64", "callbackFunctions.dll") + elseif Sys.islinux() + cbLibPath = joinpath(cbLibPath, "linux64", "libcallbackFunctions.so") + elseif Sys.isapple() + cbLibPath = joinpath(cbLibPath, "darwin64", "libcallbackFunctions.dylib") + else + @error "Unsupported OS" + end + + # check permission to execute the DLL + perm = filemode(cbLibPath) + permRWX = 16895 + if perm != permRWX + chmod(cbLibPath, permRWX; recursive=true) + end + + fmu.callbackLibHandle = dlopen(cbLibPath) + end + ptrLogger = dlsym(fmu.callbackLibHandle, :logger) + end + ptrAllocateMemory = @cfunction(fmi2CallbackAllocateMemory, Ptr{Cvoid}, (Csize_t, Csize_t)) + ptrFreeMemory = @cfunction(fmi2CallbackFreeMemory, Cvoid, (Ptr{Cvoid},)) + ptrStepFinished = C_NULL # ToDo + ptrComponentEnvironment = Ptr{FMU2ComponentEnvironment}(pointer_from_objref(compEnv)) + callbackFunctions = fmi2CallbackFunctions(ptrLogger, ptrAllocateMemory, ptrFreeMemory, ptrStepFinished, ptrComponentEnvironment) + + guidStr = "$(fmu.modelDescription.guid)" + + global lk_fmi2Instantiate + + lock(lk_fmi2Instantiate) do + + component = nothing + addr = fmi2Instantiate(fmu.cInstantiate, pointer(instanceName), type, pointer(guidStr), pointer(fmu.fmuResourceLocation), Ptr{fmi2CallbackFunctions}(pointer_from_objref(callbackFunctions)), fmi2Boolean(visible), fmi2Boolean(loggingOn)) + + if addr == Ptr{Cvoid}(C_NULL) + @error "fmi2Instantiate!(...): Instantiation failed, see error messages above.\nIf no error messages, enable FMU debug logging.\nIf logging is on and no messages are printed before this, the FMU might not log errors." + return nothing + end + + # check if address is already inside of the components (this may be in FMIExport.jl) + for c in fmu.components + if c.addr == addr + component = c + break + end + end + + if !isnothing(component) + logWarning(fmu, "fmi2Instantiate!(...): This component was already registered. This may be because you created the FMU by yourself with FMIExport.jl.") + else + component = FMU2Component(addr, fmu) + + component.callbackFunctions = callbackFunctions + component.instanceName = instanceName + component.type = type + + if pushComponents + push!(fmu.components, component) + end + end + + component.componentEnvironment = compEnv + component.loggingOn = loggingOn + component.visible = visible + + # Jacobians + + # smpFct = (mtx, ∂f_refs, ∂x_refs) -> fmi2SampleJacobian!(mtx, component, ∂f_refs, ∂x_refs) + # updFct = nothing + # if fmi2ProvidesDirectionalDerivative(fmu) + # updFct = (mtx, ∂f_refs, ∂x_refs) -> fmi2GetJacobian!(mtx, component, ∂f_refs, ∂x_refs) + # else + # updFct = smpFct + # end + + # component.∂ẋ_∂x = FMICore.FMU2Jacobian{fmi2Real, fmi2ValueReference}(component, updFct) + # component.∂ẋ_∂u = FMICore.FMU2Jacobian{fmi2Real, fmi2ValueReference}(component, updFct) + # component.∂ẋ_∂p = FMICore.FMU2Jacobian{fmi2Real, fmi2ValueReference}(component, updFct) + + # component.∂y_∂x = FMICore.FMU2Jacobian{fmi2Real, fmi2ValueReference}(component, updFct) + # component.∂y_∂u = FMICore.FMU2Jacobian{fmi2Real, fmi2ValueReference}(component, updFct) + # component.∂y_∂p = FMICore.FMU2Jacobian{fmi2Real, fmi2ValueReference}(component, updFct) + + # component.∂e_∂x = FMICore.FMU2Jacobian{fmi2Real, fmi2ValueReference}(component, smpFct) + # component.∂e_∂u = FMICore.FMU2Jacobian{fmi2Real, fmi2ValueReference}(component, smpFct) + # component.∂e_∂p = FMICore.FMU2Jacobian{fmi2Real, fmi2ValueReference}(component, smpFct) + + # register component for current thread + fmu.threadInstances[Threads.threadid()] = component + end + + return getCurrentInstance(fmu) +end +# [NOTE] needs to be exported, because FMICore only exports `fmi2Instantiate` +export fmi2Instantiate! + +""" + fmi2FreeInstance!(c::FMU2Component; popComponent::Bool = true) + +Disposes the given instance, unloads the loaded model, and frees all the allocated memory and other resources that have been allocated by the functions of the FMU interface. +If a null pointer is provided for “c”, the function call is ignored (does not have an effect). + +Removes the component from the FMUs component list. + +# Arguments +- `c::FMU2Component`: Mutable struct represents an instantiated instance of an FMU in the FMI 2.0.2 Standard. + +# Returns +- nothing + +# Source +- FMISpec2.0.2 Link: [https://fmi-standard.org/](https://fmi-standard.org/) +- FMISpec2.0.2[p.22]: 2.1.5 Creation, Destruction and Logging of FMU Instances +- FMISpec2.0.2[p.16]: 2.1.2 Platform Dependent Definitions +See Also [`fmi2FreeInstance!`](@ref). +""" +lk_fmi2FreeInstance = ReentrantLock() +function fmi2FreeInstance!(c::FMU2Component; popComponent::Bool=true, doccall::Bool=true) + + global lk_fmi2FreeInstance + + addr = c.addr + + # invalidate all active snapshots + while length(c.snapshots) > 0 + freeSnapshot!(c.snapshots[end]) + end + + @assert c.threadid == Threads.threadid() "Thread #$(Threads.threadid()) tried to free component with address $(c.addr), but doesn't own it.\nThe component is owned by thread $(c.threadid)" + + if popComponent + lock(lk_fmi2FreeInstance) do + ind = findall(x -> x.addr == addr, c.fmu.components) + @assert length(ind) == 1 "fmi2FreeInstance!(...): Freeing $(length(ind)) instances with one call, this is not allowed. Target address `$(addr)` was found $(length(ind)) times at indicies $(ind)." + deleteat!(c.fmu.components, ind) + + for key in keys(c.fmu.threadInstances) + if !isnothing(c.fmu.threadInstances[key]) && c.fmu.threadInstances[key].addr == addr + c.fmu.threadInstances[key] = nothing + end + end + end + end + + if doccall + fmi2FreeInstance(c.fmu.cFreeInstance, addr) + end + + nothing +end +# [NOTE] needs to be exported, because FMICore only exports `fmi2FreeInstance` +export fmi2FreeInstance! -# Best practices: -# - no direct access on C-pointers (`compAddr`), use existing FMICore-functions """ fmi2SetDebugLogging(c::FMU2Component) @@ -80,7 +301,7 @@ function fmi2SetupExperiment(c::FMU2Component, tolerance::Union{Real, Nothing} = nothing) if startTime == nothing - startTime = fmi2GetDefaultStartTime(c.fmu.modelDescription) + startTime = getDefaultStartTime(c.fmu.modelDescription) if startTime == nothing startTime = 0.0 end @@ -88,12 +309,12 @@ function fmi2SetupExperiment(c::FMU2Component, # default stopTime is set automatically if doing nothing # if stopTime == nothing - # stopTime = fmi2GetDefaultStopTime(c.fmu.modelDescription) + # stopTime = getDefaultStopTime(c.fmu.modelDescription) # end # default tolerance is set automatically if doing nothing # if tolerance == nothing - # tolerance = fmi2GetDefaultTolerance(c.fmu.modelDescription) + # tolerance = getDefaultTolerance(c.fmu.modelDescription) # end c.t = startTime @@ -148,6 +369,8 @@ function fmi2GetReal(c::FMU2Component, vr::fmi2ValueReferenceFormat) return values end end +# [NOTE] needs to be exported, because FMICore only exports `fmi2GetReal` +export fmi2GetReal """ fmi2GetReal!(c::FMU2Component, vr::fmi2ValueReferenceFormat, values::AbstractArray{fmi2Real}) @@ -272,6 +495,8 @@ function fmi2GetInteger(c::FMU2Component, vr::fmi2ValueReferenceFormat) return values end end +# [NOTE] needs to be exported, because FMICore only exports `fmi2GetInteger` +export fmi2GetInteger """ fmi2GetInteger!(c::FMU2Component, vr::fmi2ValueReferenceFormat, values::AbstractArray{fmi2Integer}) @@ -390,6 +615,8 @@ function fmi2GetBoolean(c::FMU2Component, vr::fmi2ValueReferenceFormat) return values end end +# [NOTE] needs to be exported, because FMICore only exports `fmi2GetBoolean` +export fmi2GetBoolean """ fmi2GetBoolean!(c::FMU2Component, vr::fmi2ValueReferenceFormat, values::AbstractArray{fmi2Boolean}) @@ -509,6 +736,8 @@ function fmi2GetString(c::FMU2Component, vr::fmi2ValueReferenceFormat) return values end end +# [NOTE] needs to be exported, because FMICore only exports `fmi2GetString!` +export fmi2GetString """ fmi2GetString!(c::FMU2Component, vr::fmi2ValueReferenceFormat, values::AbstractArray{fmi2String}) @@ -622,6 +851,8 @@ function fmi2GetFMUstate(c::FMU2Component) state = stateRef[] state end +# [NOTE] needs to be exported, because FMICore only exports `fmi2GetFMUstate!` +export fmi2GetFMUstate """ fmi2FreeFMUstate!(c::FMU2Component, state::fmi2FMUstate) @@ -643,9 +874,9 @@ Free the memory for the allocated FMU state See also [`fmi2FreeFMUstate!`](@ref). """ -function fmi2FreeFMUstate!(c::FMU2Component, state::fmi2FMUstate) +function fmi2FreeFMUstate(c::FMU2Component, state::fmi2FMUstate) stateRef = Ref(state) - fmi2FreeFMUstate!(c, stateRef) + fmi2FreeFMUstate(c, stateRef) state = stateRef[] return nothing end @@ -676,6 +907,8 @@ function fmi2SerializedFMUstateSize(c::FMU2Component, state::fmi2FMUstate) fmi2SerializedFMUstateSize!(c, state, sizeRef) size = sizeRef[] end +# [NOTE] needs to be exported, because FMICore only exports `fmi2SerializedFMUstateSize!` +export fmi2SerializedFMUstateSize """ fmi2SerializeFMUstate(c::FMU2Component, state::fmi2FMUstate) @@ -707,6 +940,8 @@ function fmi2SerializeFMUstate(c::FMU2Component, state::fmi2FMUstate) @assert status == Int(fmi2StatusOK) ["Failed with status `$status`."] serializedState end +# [NOTE] needs to be exported, because FMICore only exports `fmi2SerializeFMUstate!` +export fmi2SerializeFMUstate """ fmi2DeSerializeFMUstate(c::FMU2Component, serializedState::AbstractArray{fmi2Byte}) @@ -738,6 +973,8 @@ function fmi2DeSerializeFMUstate(c::FMU2Component, serializedState::AbstractArra state = stateRef[] end +# [NOTE] needs to be exported, because FMICore only exports `fmi2DeSerializeFMUstate!` +export fmi2DeSerializeFMUstate """ fmi2GetDirectionalDerivative(c::FMU2Component, @@ -782,23 +1019,26 @@ See also [`fmi2GetDirectionalDerivative!`](@ref). function fmi2GetDirectionalDerivative(c::FMU2Component, vUnknown_ref::AbstractArray{fmi2ValueReference}, vKnown_ref::AbstractArray{fmi2ValueReference}, - dvKnown::Union{AbstractArray{fmi2Real}, Nothing} = nothing) + dvKnown::AbstractArray{fmi2Real}) nUnknown = Csize_t(length(vUnknown_ref)) dvUnknown = zeros(fmi2Real, nUnknown) - status = fmi2GetDirectionalDerivative!(c, vUnknown_ref, vKnown_ref, dvUnknown, dvKnown) + status = fmi2GetDirectionalDerivative!(c, vUnknown_ref, vKnown_ref, dvKnown, dvUnknown) @assert status == fmi2StatusOK ["Failed with status `$status`."] return dvUnknown end +fmi2GetDirectionalDerivative(c::FMU2Component, vUnknown_ref::fmi2ValueReference, vKnown_ref::fmi2ValueReference, dvKnown::fmi2Real) = fmi2GetDirectionalDerivative(c, [vUnknown_ref], [vKnown_ref], [dvKnown])[1] +# [NOTE] needs to be exported, because FMICore only exports `fmi2GetDirectionalDerivative!` +export fmi2GetDirectionalDerivative """ fmiGetDirectionalDerivative!(c::FMU2Component, vUnknown_ref::AbstractArray{fmi2ValueReference}, vKnown_ref::AbstractArray{fmi2ValueReference}, - dvUnknown::AbstractArray, - dvKnown::Union{Array{fmi2Real}, Nothing} = nothing) + dvKnown::Array{fmi2Real}, + dvUnknown::AbstractArray) Wrapper Function call to compute the partial derivative with respect to the variables `vKnown_ref`. @@ -846,68 +1086,17 @@ See also [`fmi2GetDirectionalDerivative!`](@ref). function fmi2GetDirectionalDerivative!(c::FMU2Component, vUnknown_ref::AbstractArray{fmi2ValueReference}, vKnown_ref::AbstractArray{fmi2ValueReference}, - dvUnknown::AbstractArray, # ToDo: Data-type - dvKnown::Union{AbstractArray{fmi2Real}, Nothing} = nothing) + dvKnown::AbstractArray{fmi2Real}, + dvUnknown::AbstractArray) nKnown = Csize_t(length(vKnown_ref)) nUnknown = Csize_t(length(vUnknown_ref)) - if dvKnown == nothing - dvKnown = ones(fmi2Real, nKnown) - end - status = fmi2GetDirectionalDerivative!(c, vUnknown_ref, nUnknown, vKnown_ref, nKnown, dvKnown, dvUnknown) return status end -""" - fmi2GetDirectionalDerivative(c::FMU2Component, - vUnknown_ref::fmi2ValueReference, - vKnown_ref::fmi2ValueReference, - dvKnown::fmi2Real = 1.0) - -Direct function call to compute the partial derivative with respect to `vKnown_ref`. - -Computes the directional derivatives of an FMU. An FMU has different Modes and in every Mode an FMU might be described by different equations and different unknowns.The -precise definitions are given in the mathematical descriptions of Model Exchange (section 3.1) and Co-Simulation (section 4.1). In every Mode, the general form of the FMU equations are: -𝐯_unknown = 𝐡(𝐯_known, 𝐯_rest) - -- `v_unknown`: vector of unknown Real variables computed in the actual Mode: - - Initialization Mode: unkowns kisted under `` that have type Real. - - Continuous-Time Mode (ModelExchange): The continuous-time outputs and state derivatives. (= the variables listed under `` with type Real and variability = `continuous` and the variables listed as state derivatives under `)`. - - Event Mode (ModelExchange): The same variables as in the Continuous-Time Mode and additionally variables under `` with type Real and variability = `discrete`. - - Step Mode (CoSimulation): The variables listed under `` with type Real and variability = `continuous` or `discrete`. If `` is present, also the variables listed here as state derivatives. -- `v_known`: Real input variables of function h that changes its value in the actual Mode. -- `v_rest`:Set of input variables of function h that either changes its value in the actual Mode but are non-Real variables, or do not change their values in this Mode, but change their values in other Modes - -Computes a linear combination of the partial derivatives of h with respect to the selected input variables 𝐯_known: - - Δv_unknown = (δh / δv_known) Δv_known - -# Arguments - - `c::FMU2Component`: Mutable struct represents an instantiated instance of an FMU in the FMI 2.0.2 Standard. - - `vUnknown_ref::fmi2ValueReference`: Argument `vUnknown_ref` contains a value of type`fmi2ValueReference` which is an identifier of a variable value of the model. `vUnknown_ref` can be equated with `v_unknown`(variable described above). - - `vKnown_ref::fmi2ValueReference`: Argument `vKnown_ref` contains a value of type`fmi2ValueReference` which is an identifier of a variable value of the model. `vKnown_ref` can be equated with `v_known`(variable described above). - - `dvKnown::fmi2Real = 1.0`: If no seed value is passed the value `dvKnown = 1.0` is used. Compute the partial derivative with respect to `vKnown_ref` with the value `dvKnown = 1.0`. # gehört das zu den v_rest values -# Returns -- `dvUnknown::Array{fmi2Real}`: Return `dvUnknown` contains the directional derivative vector values. - -# Source -- FMISpec2.0.2 Link: [https://fmi-standard.org/](https://fmi-standard.org/) -- FMISpec2.0.2[p.16]: 2.1.2 Platform Dependent Definitions (fmi2TypesPlatform.h) -- FMISpec2.0.2[p.16]: 2.1.3 Status Returned by Functions -- FMISpec2.0.2[p.25]: 2.1.9 Getting Partial Derivatives -See also [`fmi2GetDirectionalDerivative!`](@ref). -""" -function fmi2GetDirectionalDerivative(c::FMU2Component, - vUnknown_ref::fmi2ValueReference, - vKnown_ref::fmi2ValueReference, - dvKnown::fmi2Real = 1.0) - - fmi2GetDirectionalDerivative(c, [vUnknown_ref], [vKnown_ref], [dvKnown])[1] -end - # CoSimulation specific functions """ fmi2SetRealInputDerivatives(c::FMU2Component, @@ -988,6 +1177,8 @@ function fmi2GetRealOutputDerivatives(c::FMU2Component, vr::fmi2ValueReferenceFo return values end end +# [NOTE] needs to be exported, because FMICore only exports `fmi2GetRealOutputDerivatives!` +export fmi2GetRealOutputDerivatives """ fmi2DoStep(c::FMU2Component, @@ -1028,10 +1219,10 @@ function fmi2DoStep(c::FMU2Component, communicationStepSize::Union{Real, Nothing @assert c.type == fmi2TypeCoSimulation "`fmi2DoStep` only available for CS-FMUs." # skip `fmi2DoStep` if this is set (allows evaluation of a CS_NeuralFMUs at t_0) - if c.skipNextDoStep - c.skipNextDoStep = false - return fmi2StatusOK - end + # if c.skipNextDoStep + # c.skipNextDoStep = false + # return fmi2StatusOK + # end if currentCommunicationPoint == nothing currentCommunicationPoint = c.t @@ -1155,6 +1346,7 @@ function fmi2NewDiscreteStates(c::FMU2Component) fmi2NewDiscreteStates!(c, eventInfo) eventInfo end +export fmi2NewDiscreteStates """ fmiCompletedIntegratorStep(c::FMU2Component, noSetFMUStatePriorToCurrentPoint::fmi2Boolean) @@ -1190,8 +1382,9 @@ function fmi2CompletedIntegratorStep(c::FMU2Component, c._ptr_enterEventMode, c._ptr_terminateSimulation) - return (status, c._enterEventMode, c._terminateSimulation) + return (status, c.enterEventMode, c.terminateSimulation) end +export fmi2CompletedIntegratorStep """ fmi2GetDerivatives(c::FMU2Component) @@ -1217,6 +1410,7 @@ function fmi2GetDerivatives(c::FMU2Component) fmi2GetDerivatives!(c, derivatives) return derivatives end +export fmi2GetDerivatives """ fmi2GetDerivatives!(c::FMU2Component, derivatives::AbstractArray{fmi2Real}) @@ -1273,6 +1467,7 @@ function fmi2GetEventIndicators(c::FMU2Component) fmi2GetEventIndicators!(c, eventIndicators, ni) return eventIndicators end +export fmi2GetEventIndicators """ fmi2GetEventIndicators!(c::FMU2Component, eventIndicators::AbstractArray{fmi2Real}) @@ -1315,6 +1510,7 @@ function fmi2GetContinuousStates(c::FMU2Component) fmi2GetContinuousStates!(c, x, nx) x end +export fmi2GetContinuousStates """ fmi2GetNominalsOfContinuousStates(c::FMU2Component) @@ -1338,6 +1534,7 @@ function fmi2GetNominalsOfContinuousStates(c::FMU2Component) fmi2GetNominalsOfContinuousStates!(c, x, nx) x end +export fmi2GetNominalsOfContinuousStates """ ToDo @@ -1358,3 +1555,4 @@ function fmi2GetStatus(c::FMU2Component, s::fmi2StatusKind) status, value[1] end +export fmi2GetStatus diff --git a/src/FMI2/md.jl b/src/FMI2/md.jl index 8b067ac..f2d4e9e 100644 --- a/src/FMI2/md.jl +++ b/src/FMI2/md.jl @@ -9,12 +9,11 @@ # - [Sec. 2] functions to get values from the model description in the format `fmi2Get[value](md::fmi2ModelDescription)` [exported] # - [Sec. 3] additional functions to get useful information from the model description in the format `fmi2Get[value](md::fmi2ModelDescription)` [exported] -using FMICore: fmi2ModelDescriptionModelExchange, fmi2ModelDescriptionCoSimulation, fmi2ModelDescriptionDefaultExperiment, fmi2ModelDescriptionEnumerationItem -using FMICore: fmi2RealAttributesExt, fmi2BooleanAttributesExt, fmi2IntegerAttributesExt, fmi2StringAttributesExt, fmi2EnumerationAttributesExt -using FMICore: fmi2RealAttributes, fmi2BooleanAttributes, fmi2IntegerAttributes, fmi2StringAttributes, fmi2EnumerationAttributes -using FMICore: fmi2ModelDescriptionModelStructure -using FMICore: fmi2DependencyKind -using FMICore: FMU2 +using FMIBase.FMICore: fmi2ModelDescriptionModelExchange, fmi2ModelDescriptionCoSimulation, fmi2ModelDescriptionDefaultExperiment, fmi2ModelDescriptionEnumerationItem +using FMIBase.FMICore: fmi2RealAttributesExt, fmi2BooleanAttributesExt, fmi2IntegerAttributesExt, fmi2StringAttributesExt, fmi2EnumerationAttributesExt +using FMIBase.FMICore: fmi2RealAttributes, fmi2BooleanAttributes, fmi2IntegerAttributes, fmi2StringAttributes, fmi2EnumerationAttributes +using FMIBase.FMICore: fmi2ModelDescriptionModelStructure +using FMIBase.FMICore: fmi2DependencyKind ###################################### # [Sec. 1a] fmi2LoadModelDescription # @@ -31,9 +30,6 @@ Extract the FMU variables and meta data from the ModelDescription # Returns - `md::fmi2ModelDescription`: Retuns a struct which provides the static information of ModelVariables. - -# Source -- [EzXML.jl](https://juliaio.github.io/EzXML.jl/stable/) """ function fmi2LoadModelDescription(pathToModelDescription::String) md = fmi2ModelDescription() @@ -54,13 +50,13 @@ function fmi2LoadModelDescription(pathToModelDescription::String) md.guid = root["guid"] # optional - md.generationTool = parseNodeString(root, "generationTool"; onfail="[Unknown generation tool]") - md.generationDateAndTime = parseNodeString(root, "generationDateAndTime"; onfail="[Unknown generation date and time]") - variableNamingConventionStr = parseNodeString(root, "variableNamingConvention"; onfail="flat") + md.generationTool = parseNode(root, "generationTool", String; onfail="[Unknown generation tool]") + md.generationDateAndTime = parseNode(root, "generationDateAndTime", String; onfail="[Unknown generation date and time]") + variableNamingConventionStr = parseNode(root, "variableNamingConvention", String; onfail="flat") @assert (variableNamingConventionStr == "flat" || variableNamingConventionStr == "structured") ["fmi2ReadModelDescription(...): Unknown entry for `variableNamingConvention=$(variableNamingConventionStr)`."] md.variableNamingConvention = (variableNamingConventionStr == "flat" ? fmi2VariableNamingConventionFlat : fmi2VariableNamingConventionStructured) - md.numberOfEventIndicators = parseNodeInteger(root, "numberOfEventIndicators"; onfail=0) - md.description = parseNodeString(root, "description"; onfail="[Unknown Description]") + md.numberOfEventIndicators = parseNode(root, "numberOfEventIndicators", Int; onfail=0) + md.description = parseNode(root, "description", String; onfail="[Unknown Description]") # defaults md.modelExchange = nothing @@ -76,42 +72,42 @@ function fmi2LoadModelDescription(pathToModelDescription::String) if node.name == "CoSimulation" || node.name == "ModelExchange" if node.name == "CoSimulation" md.coSimulation = fmi2ModelDescriptionCoSimulation() - md.coSimulation.modelIdentifier = node["modelIdentifier"] - md.coSimulation.canHandleVariableCommunicationStepSize = parseNodeBoolean(node, "canHandleVariableCommunicationStepSize" ; onfail=false) - md.coSimulation.canInterpolateInputs = parseNodeBoolean(node, "canInterpolateInputs" ; onfail=false) - md.coSimulation.maxOutputDerivativeOrder = parseNodeInteger(node, "maxOutputDerivativeOrder" ; onfail=nothing) - md.coSimulation.canGetAndSetFMUstate = parseNodeBoolean(node, "canGetAndSetFMUstate" ; onfail=false) - md.coSimulation.canSerializeFMUstate = parseNodeBoolean(node, "canSerializeFMUstate" ; onfail=false) - md.coSimulation.providesDirectionalDerivative = parseNodeBoolean(node, "providesDirectionalDerivative" ; onfail=false) + md.coSimulation.modelIdentifier = parseNode(node, "modelIdentifier") + md.coSimulation.canHandleVariableCommunicationStepSize = parseNode(node, "canHandleVariableCommunicationStepSize", Bool; onfail=false) + md.coSimulation.canInterpolateInputs = parseNode(node, "canInterpolateInputs", Bool ; onfail=false) + md.coSimulation.maxOutputDerivativeOrder = parseNode(node, "maxOutputDerivativeOrder", Int ; onfail=nothing) + md.coSimulation.canGetAndSetFMUstate = parseNode(node, "canGetAndSetFMUstate", Bool ; onfail=false) + md.coSimulation.canSerializeFMUstate = parseNode(node, "canSerializeFMUstate", Bool ; onfail=false) + md.coSimulation.providesDirectionalDerivative = parseNode(node, "providesDirectionalDerivative", Bool ; onfail=false) end if node.name == "ModelExchange" md.modelExchange = fmi2ModelDescriptionModelExchange() - md.modelExchange.modelIdentifier = node["modelIdentifier"] - md.modelExchange.canGetAndSetFMUstate = parseNodeBoolean(node, "canGetAndSetFMUstate" ; onfail=false) - md.modelExchange.canSerializeFMUstate = parseNodeBoolean(node, "canSerializeFMUstate" ; onfail=false) - md.modelExchange.providesDirectionalDerivative = parseNodeBoolean(node, "providesDirectionalDerivative" ; onfail=false) + md.modelExchange.modelIdentifier = parseNode(node, "modelIdentifier") + md.modelExchange.canGetAndSetFMUstate = parseNode(node, "canGetAndSetFMUstate", Bool ; onfail=false) + md.modelExchange.canSerializeFMUstate = parseNode(node, "canSerializeFMUstate", Bool ; onfail=false) + md.modelExchange.providesDirectionalDerivative = parseNode(node, "providesDirectionalDerivative", Bool ; onfail=false) end elseif node.name == "TypeDefinitions" - md.typeDefinitions = parseTypeDefinitions(node, md) + md.typeDefinitions = parseTypeDefinitions(md, node) elseif node.name == "UnitDefinitions" - md.unitDefinitions = parseUnitDefinitions(node) + md.unitDefinitions = parseUnitDefinitions(md, node) elseif node.name == "ModelVariables" - md.modelVariables = parseModelVariables(node, md) + md.modelVariables = parseModelVariables(md, node) elseif node.name == "ModelStructure" md.modelStructure = fmi2ModelDescriptionModelStructure() for element in eachelement(node) if element.name == "Derivatives" - parseDerivatives(element, md) + parseDerivatives(md, element) elseif element.name == "InitialUnknowns" - parseInitialUnknowns(element, md) + parseInitialUnknowns(md, element) elseif element.name == "Outputs" - parseOutputs(element, md) + parseOutputs(md, element) else @warn "Unknown tag `$(element.name)` for node `ModelStructure`." end @@ -119,10 +115,10 @@ function fmi2LoadModelDescription(pathToModelDescription::String) elseif node.name == "DefaultExperiment" md.defaultExperiment = fmi2ModelDescriptionDefaultExperiment() - md.defaultExperiment.startTime = parseNodeReal(node, "startTime") - md.defaultExperiment.stopTime = parseNodeReal(node, "stopTime") - md.defaultExperiment.tolerance = parseNodeReal(node, "tolerance") - md.defaultExperiment.stepSize = parseNodeReal(node, "stepSize") + md.defaultExperiment.startTime = parseNode(node, "startTime", fmi2Real) + md.defaultExperiment.stopTime = parseNode(node, "stopTime", fmi2Real) + md.defaultExperiment.tolerance = parseNode(node, "tolerance", fmi2Real) + md.defaultExperiment.stepSize = parseNode(node, "stepSize", fmi2Real) end end @@ -140,39 +136,8 @@ end # [Sec. 1b] helpers for load function # ####################################### -# Returns the indices of the state derivatives. -function getDerivativeIndices(node::EzXML.Node) - indices = [] - for element in eachelement(node) - if element.name == "Derivatives" - for derivative in eachelement(element) - ind = parse(Int, derivative["index"]) - der = nothing - derKind = nothing - - if haskey(derivative, "dependencies") - der = split(derivative["dependencies"], " ") - - if der[1] == "" - der = fmi2Integer[] - else - der = collect(parse(fmi2Integer, e) for e in der) - end - end - - if haskey(derivative, "dependenciesKind") - derKind = split(derivative["dependenciesKind"], " ") - end - - push!(indices, (ind, der, derKind)) - end - end - end - sort!(indices, rev=true) -end - # helper function to parse variable or simple type attributes -function parseAttribute(defnode; ext::Bool=false) +function parseAttribute(md::fmi2ModelDescription, defnode; ext::Bool=false) typename = defnode.name typenode = nothing @@ -182,31 +147,31 @@ function parseAttribute(defnode; ext::Bool=false) if ext typenode = fmi2RealAttributesExt() - typenode.start = parseNodeReal(defnode, "start") - typenode.derivative = parseNodeUInt(defnode, "derivative") - typenode.reinit = parseNodeBoolean(defnode, "reinit") - typenode.declaredType = parseNodeString(defnode, "declaredType") + typenode.start = parseNode(defnode, "start", fmi2Real) + typenode.derivative = parseNode(defnode, "derivative", UInt) + typenode.reinit = parseNode(defnode, "reinit", Bool) + typenode.declaredType = parseNode(defnode, "declaredType", String) else typenode = fmi2RealAttributes() end - typenode.quantity = parseNodeString(defnode, "quantity") - typenode.unit = parseNodeString(defnode, "unit") - typenode.displayUnit = parseNodeString(defnode, "displayUnit") - typenode.relativeQuantity = parseNodeBoolean(defnode, "relativeQuantity") - typenode.min = parseNodeReal(defnode, "min") - typenode.max = parseNodeReal(defnode, "max") - typenode.nominal = parseNodeReal(defnode, "nominal") - typenode.unbounded = parseNodeBoolean(defnode, "unbounded") + typenode.quantity = parseNode(defnode, "quantity", String) + typenode.unit = parseNode(defnode, "unit", String) + typenode.displayUnit = parseNode(defnode, "displayUnit", String) + typenode.relativeQuantity = parseNode(defnode, "relativeQuantity", Bool) + typenode.min = parseNode(defnode, "min", fmi2Real) + typenode.max = parseNode(defnode, "max", fmi2Real) + typenode.nominal = parseNode(defnode, "nominal", fmi2Real) + typenode.unbounded = parseNode(defnode, "unbounded", Bool) elseif typename == "String" if ext typenode = fmi2StringAttributesExt() - typenode.start = parseNodeString(defnode, "start") - typenode.declaredType = parseNodeString(defnode, "declaredType") + typenode.start = parseNode(defnode, "start", String) + typenode.declaredType = parseNode(defnode, "declaredType", String) else typenode = fmi2StringAttributes() end @@ -216,8 +181,8 @@ function parseAttribute(defnode; ext::Bool=false) if ext typenode = fmi2BooleanAttributesExt() - typenode.start = parseNodeBoolean(defnode, "start") - typenode.declaredType = parseNodeString(defnode, "declaredType") + typenode.start = parseNode(defnode, "start", Bool) + typenode.declaredType = parseNode(defnode, "declaredType", String) else typenode = fmi2BooleanAttributes() end @@ -227,25 +192,25 @@ function parseAttribute(defnode; ext::Bool=false) if ext typenode = fmi2IntegerAttributesExt() - typenode.start = parseNodeInteger(defnode, "start") - typenode.declaredType = parseNodeString(defnode, "declaredType") + typenode.start = parseNode(defnode, "start", Int) + typenode.declaredType = parseNode(defnode, "declaredType", String) else typenode = fmi2IntegerAttributes() end - typenode.quantity = parseNodeString(defnode, "quantity") - typenode.min = parseNodeInteger(defnode, "min") - typenode.max = parseNodeInteger(defnode, "max") + typenode.quantity = parseNode(defnode, "quantity", String) + typenode.min = parseNode(defnode, "min", fmi2Integer) + typenode.max = parseNode(defnode, "max", fmi2Integer) elseif typename == "Enumeration" if ext typenode = fmi2EnumerationAttributesExt() - typenode.start = parseNodeInteger(defnode, "start") - typenode.min = parseNodeInteger(defnode, "min") - typenode.max = parseNodeInteger(defnode, "max") - typenode.declaredType = parseNodeString(defnode, "declaredType") + typenode.start = parseNode(defnode, "start", fmi2Integer) + typenode.min = parseNode(defnode, "min", fmi2Integer) + typenode.max = parseNode(defnode, "max", fmi2Integer) + typenode.declaredType = parseNode(defnode, "declaredType", String) else typenode = fmi2EnumerationAttributes() @@ -260,28 +225,28 @@ function parseAttribute(defnode; ext::Bool=false) # mandatory if haskey(itemNode, "name") - it.name = parseNodeString(itemNode, "name") + it.name = parseNode(itemNode, "name", String) else @warn "Enumeration item `$(itemNode.name)` is missing the `name` key. This is not allowed." end # mandatory if haskey(itemNode, "value") - it.value = parseNodeInteger(itemNode, "value") + it.value = parseNode(itemNode, "value", Int) else @warn "Enumeration item `$(itemNode.name)` is missing the `value` key. This is not allowed." end # optional if haskey(itemNode, "description") - it.description = parseNodeString(itemNode, "description") + it.description = parseNode(itemNode, "description", String) end push!(typenode, it) end end - typenode.quantity = parseNodeString(defnode, "quantity") + typenode.quantity = parseNode(defnode, "quantity", String) else @warn "Unknown data type `$(typename)`." typenode = nothing @@ -290,7 +255,7 @@ function parseAttribute(defnode; ext::Bool=false) end # Parses the model variables of the FMU model description. -function parseModelVariables(nodes::EzXML.Node, md::fmi2ModelDescription) +function parseModelVariables(md::fmi2ModelDescription, nodes::EzXML.Node) numberOfVariables = 0 for node in eachelement(nodes) numberOfVariables += 1 @@ -300,11 +265,11 @@ function parseModelVariables(nodes::EzXML.Node, md::fmi2ModelDescription) index = 1 for node in eachelement(nodes) name = node["name"] - valueReference = parse(fmi2ValueReference, node["valueReference"]) + valueReference = parseNode(node, "valueReference", fmi2ValueReference) causality = nothing if haskey(node, "causality") - causality = fmi2StringToCausality(node["causality"]) + causality = stringToCausality(md, node["causality"]) if causality == fmi2CausalityOutput push!(md.outputValueReferences, valueReference) @@ -317,12 +282,12 @@ function parseModelVariables(nodes::EzXML.Node, md::fmi2ModelDescription) variability = nothing if haskey(node, "variability") - variability = fmi2StringToVariability(node["variability"]) + variability = stringToVariability(md, node["variability"]) end initial = nothing if haskey(node, "initial") - initial = fmi2StringToInitial(node["initial"]) + initial = stringToInitial(md, node["initial"]) end scalarVariables[index] = fmi2ScalarVariable(name, valueReference, causality, variability, initial) @@ -331,11 +296,11 @@ function parseModelVariables(nodes::EzXML.Node, md::fmi2ModelDescription) push!(md.valueReferences, valueReference) end - scalarVariables[index].description = parseNodeString(node, "description") + scalarVariables[index].description = parseNode(node, "description", String) # type node defnode = node.firstelement - typenode = parseAttribute(defnode; ext=true) + typenode = parseAttribute(md, defnode; ext=true) if isa(typenode, fmi2RealAttributesExt) scalarVariables[index].Real = typenode elseif isa(typenode, fmi2StringAttributesExt) @@ -350,7 +315,7 @@ function parseModelVariables(nodes::EzXML.Node, md::fmi2ModelDescription) # generic attributes if !isnothing(typenode) - typenode.declaredType = parseNodeString(node.firstelement, "declaredType") + typenode.declaredType = parseNode(node.firstelement, "declaredType", String) end md.stringValueReferences[name] = valueReference @@ -362,7 +327,7 @@ function parseModelVariables(nodes::EzXML.Node, md::fmi2ModelDescription) end # Parses the model variables of the FMU model description. -function parseTypeDefinitions(nodes::EzXML.Node, md::fmi2ModelDescription) +function parseTypeDefinitions(md::fmi2ModelDescription, nodes::EzXML.Node) simpleTypes = Array{fmi2SimpleType, 1}() @@ -375,10 +340,10 @@ function parseTypeDefinitions(nodes::EzXML.Node, md::fmi2ModelDescription) # attribute node (mandatory) defnode = node.firstelement - simpleType.attribute = parseAttribute(defnode; ext=false) + simpleType.attribute = parseAttribute(md, defnode; ext=false) # optional - simpleType.description = parseNodeString(node, "description") + simpleType.description = parseNode(node, "description", String) push!(simpleTypes, simpleType) end @@ -387,9 +352,9 @@ function parseTypeDefinitions(nodes::EzXML.Node, md::fmi2ModelDescription) end # Parses the `ModelStructure.Derivatives` of the FMU model description. -function parseUnknown(node::EzXML.Node) +function parseUnknown(md::fmi2ModelDescription, node::EzXML.Node) if haskey(node, "index") - varDep = fmi2VariableDependency(parseInteger(node["index"])) + varDep = fmi2VariableDependency(parseNode(node, "index", Int)) if haskey(node, "dependencies") dependencies = node["dependencies"] @@ -406,7 +371,7 @@ function parseUnknown(node::EzXML.Node) if length(dependenciesKind) > 0 dependenciesKindSplit = split(dependenciesKind, " ") if length(dependenciesKindSplit) > 0 - varDep.dependenciesKind = collect(fmi2StringToDependencyKind(e) for e in dependenciesKindSplit) + varDep.dependenciesKind = collect(stringToDependencyKind(md, e) for e in dependenciesKindSplit) end end end @@ -424,13 +389,13 @@ function parseUnknown(node::EzXML.Node) end # ToDo: Comment -function parseDerivatives(nodes::EzXML.Node, md::fmi2ModelDescription) +function parseDerivatives(md::fmi2ModelDescription, nodes::EzXML.Node) @assert (nodes.name == "Derivatives") "Wrong element name." md.modelStructure.derivatives = [] for node in eachelement(nodes) if node.name == "Unknown" if haskey(node, "index") - varDep = parseUnknown(node) + varDep = parseUnknown(md, node) # find states and derivatives derSV = md.modelVariables[varDep.index] @@ -455,13 +420,13 @@ function parseDerivatives(nodes::EzXML.Node, md::fmi2ModelDescription) end # ToDo: Comment -function parseInitialUnknowns(nodes::EzXML.Node, md::fmi2ModelDescription) +function parseInitialUnknowns(md::fmi2ModelDescription, nodes::EzXML.Node) @assert (nodes.name == "InitialUnknowns") "Wrong element name." md.modelStructure.initialUnknowns = [] for node in eachelement(nodes) if node.name == "Unknown" if haskey(node, "index") - varDep = parseUnknown(node) + varDep = parseUnknown(md, node) push!(md.modelStructure.initialUnknowns, varDep) else @@ -474,13 +439,13 @@ function parseInitialUnknowns(nodes::EzXML.Node, md::fmi2ModelDescription) end # ToDo: Comment -function parseOutputs(nodes::EzXML.Node, md::fmi2ModelDescription) +function parseOutputs(md::fmi2ModelDescription, nodes::EzXML.Node) @assert (nodes.name == "Outputs") "Wrong element name." md.modelStructure.outputs = [] for node in eachelement(nodes) if node.name == "Unknown" if haskey(node, "index") - varDep = parseUnknown(node) + varDep = parseUnknown(md, node) # find outputs outVR = md.modelVariables[varDep.index].valueReference @@ -538,35 +503,27 @@ function fmi2SetDatatypeVariables(node::EzXML.Node, md::fmi2ModelDescription, sv if haskey(typenode, "start") if typename == "Real" - type.start = parse(fmi2Real, typenode["start"]) + type.start = parseNode(typenode, "start", fmi2Real) sv.Real.start = type.start elseif typename == "Integer" - type.start = parse(fmi2Integer, typenode["start"]) + type.start = parseNode(typenode, "start", fmi2Integer) elseif typename == "Boolean" - type.start = parseFMI2Boolean(typenode["start"]) + type.start = parseNodeBoolean(typenode, "start") elseif typename == "Enumeration" - type.start = parse(fmi2Integer, typenode["start"]) + type.start = parseNode(typenode, "start", fmi2Integer) elseif typename == "String" - type.start = typenode["start"] + type.start = parseNode(typenode, "start") else @warn "setDatatypeVariables(...) unimplemented start value type $typename" - type.start = typenode["start"] + type.start = parseNode(typenode, "start") end end if haskey(typenode, "min") && (type.datatype == fmi2Real || type.datatype == fmi2Integer || type.datatype == fmi2Enum) - if type.datatype == fmi2Real - type.min = parse(fmi2Real, typenode["min"]) - else - type.min = parse(fmi2Integer, typenode["min"]) - end + type.min = parseNode(typenode, "min", type.datatype) end if haskey(typenode, "max") && (type.datatype == fmi2Real || type.datatype == fmi2Integer || type.datatype == fmi2Enum) - if type.datatype == fmi2Real - type.max = parse(fmi2Real, typenode["max"]) - elseif type.datatype == fmi2Integer - type.max = parse(fmi2Integer, typenode["max"]) - end + type.max = parseNode(typenode, "max", type.datatype) end if haskey(typenode, "quantity") && (type.datatype == fmi2Real || type.datatype == fmi2Integer || type.datatype == fmi2Enum) type.quantity = typenode["quantity"] @@ -578,16 +535,16 @@ function fmi2SetDatatypeVariables(node::EzXML.Node, md::fmi2ModelDescription, sv type.displayUnit = typenode["displayUnit"] end if haskey(typenode, "relativeQuantity") && type.datatype == fmi2Real - type.relativeQuantity = convert(fmi2Boolean, parse(Bool, typenode["relativeQuantity"])) + type.relativeQuantity = parseNode(typenode, "relativeQuantity", fmi2Boolean) end if haskey(typenode, "nominal") && type.datatype == fmi2Real - type.nominal = parse(fmi2Real, typenode["nominal"]) + type.nominal = parseNode(typenode, "nominal", fmi2Real) end if haskey(typenode, "unbounded") && type.datatype == fmi2Real - type.unbounded = parse(fmi2Boolean, typenode["unbounded"]) + type.unbounded = parseNode(typenode, "unbounded", fmi2Boolean) end if haskey(typenode, "derivative") && type.datatype == fmi2Real - type.derivative = parse(fmi2Integer, typenode["derivative"]) + type.derivative = parseNode(typenode, "derivative", fmi2Integer) if typename == "Real" sv.Real.derivative = type.derivative end @@ -599,43 +556,43 @@ function fmi2SetDatatypeVariables(node::EzXML.Node, md::fmi2ModelDescription, sv end # helper to create a `FMICore.BaseUnit` from a XML-Tag like `` -function parseBaseUnit(node) +function parseBaseUnit(md::fmi2ModelDescription, node) @assert node.name == "BaseUnit" - unit = FMICore.BaseUnit() + unit = fmi2BaseUnit() for siUnit in FMICore.SI_UNITS siStr = String(siUnit) if haskey(node, siStr) - setfield!(unit, Symbol(siStr), parse(Int32, node[siStr])) + setfield!(unit, Symbol(siStr), parseNode(node, siStr, Int32)) end end if haskey(node, "factor") - setfield!(unit, :factor, parse(Float64, node["factor"])) + setfield!(unit, :factor, parseNode(node, "factor", Float64)) end if haskey(node, "offset") - setfield!(unit, :offset, parse(Float64, node["offset"])) + setfield!(unit, :offset, parseNode(node, "offset", Float64)) end return unit end # helper to create a `FMICore.DisplayUnit` from a XML-Tag like # `` -function parseDisplayUnits(node) +function parseDisplayUnits(::fmi2ModelDescription, node) @assert node.name == "DisplayUnit" - unit = FMICore.DisplayUnit(node["name"]) + unit = fmi2DisplayUnit(node["name"]) if haskey(node, "factor") - setfield!(unit, :factor, parse(Float64, node["factor"])) + setfield!(unit, :factor, parseNode(node, "factor", Float64)) end if haskey(node, "offset") - setfield!(unit, :offset, parse(Float64, node["offset"])) + setfield!(unit, :offset, parseNode(node, "offset", Float64)) end return unit end -function parseUnitDefinitions(parentNode) +function parseUnitDefinitions(md::fmi2ModelDescription, parentNode) - units = Array{fmi2Unit,1}() + units = Vector{fmi2Unit}() for node in eachelement(parentNode) @@ -643,12 +600,12 @@ function parseUnitDefinitions(parentNode) for subNode = eachelement(node) if subNode.name == "BaseUnit" - unit.baseUnit = parseBaseUnit(subNode) + unit.baseUnit = parseBaseUnit(md, subNode) elseif subNode.name == "DisplayUnit" - displayUnits = parseDisplayUnits(subNode) + displayUnits = parseDisplayUnits(md, subNode) if isnothing(unit.displayUnits) - unit.displayUnits = Array{FMICore.DisplayUnit,1}() + unit.displayUnits = Vector{fmi2DisplayUnit}() else push!(unit.displayUnits, displayUnits) end @@ -661,129 +618,12 @@ function parseUnitDefinitions(parentNode) return units end -#= -Read all enumerations from the modeldescription and store them in a matrix. First entries are the enum names -------------------------------------------- -Example: -"enum1name" "value1" "value2" -"enum2name" "value1" "value2" -=# -function createEnum(node::EzXML.Node) - enum = 1 - idx = 1 - enumerations = [] - for simpleType in eachelement(node) - name = simpleType["name"] - for type in eachelement(simpleType) - if type.name == "Enumeration" - enum = [] - push!(enum, name) - for item in eachelement(type) - push!(enum, item["name"]) - end - push!(enumerations, enum) - end - end - end - enumerations -end - ################################ # [Sec. 2] get value functions # ################################ """ - fmi2GetDefaultStartTime(md::fmi2ModelDescription) - -Returns startTime from DefaultExperiment if defined else defaults to nothing. - -# Arguments -- `md::fmi2ModelDescription`: Struct which provides the static information of ModelVariables. - -# Returns -- `md.defaultExperiment.startTime::Union{Real,Nothing}`: Returns a real value `startTime` from the DefaultExperiment if defined else defaults to `nothing`. -""" -function fmi2GetDefaultStartTime(md::fmi2ModelDescription) - if md.defaultExperiment == nothing - return nothing - end - return md.defaultExperiment.startTime -end - -""" - fmi2GetDefaultStopTime(md::fmi2ModelDescription) - -Returns stopTime from DefaultExperiment if defined else defaults to nothing. - -# Arguments -- `md::fmi2ModelDescription`: Struct which provides the static information of ModelVariables. - -# Returns -- `md.defaultExperiment.stopTime::Union{Real,Nothing}`: Returns a real value `stopTime` from the DefaultExperiment if defined else defaults to `nothing`. - -""" -function fmi2GetDefaultStopTime(md::fmi2ModelDescription) - if md.defaultExperiment == nothing - return nothing - end - return md.defaultExperiment.stopTime -end - -""" - fmi2GetDefaultTolerance(md::fmi2ModelDescription) - -Returns tolerance from DefaultExperiment if defined else defaults to nothing. - -# Arguments -- `md::fmi2ModelDescription`: Struct which provides the static information of ModelVariables. - -# Returns -- `md.defaultExperiment.tolerance::Union{Real,Nothing}`: Returns a real value `tolerance` from the DefaultExperiment if defined else defaults to `nothing`. - -""" -function fmi2GetDefaultTolerance(md::fmi2ModelDescription) - if md.defaultExperiment == nothing - return nothing - end - return md.defaultExperiment.tolerance -end - -""" - fmi2GetDefaultStepSize(md::fmi2ModelDescription) - -Returns stepSize from DefaultExperiment if defined else defaults to nothing. - -# Arguments -- `md::fmi2ModelDescription`: Struct which provides the static information of ModelVariables. - -# Returns -- `md.defaultExperiment.stepSize::Union{Real,Nothing}`: Returns a real value `setpSize` from the DefaultExperiment if defined else defaults to `nothing`. - -""" -function fmi2GetDefaultStepSize(md::fmi2ModelDescription) - if md.defaultExperiment == nothing - return nothing - end - return md.defaultExperiment.stepSize -end - -""" - fmi2GetModelName(md::fmi2ModelDescription) -Returns the tag 'modelName' from the model description. - -# Arguments -- `md::fmi2ModelDescription`: Struct which provides the static information of ModelVariables. - -# Returns -- `md.modelName::String`: Returns the tag 'modelName' from the model description. - -""" -function fmi2GetModelName(md::fmi2ModelDescription)#, escape::Bool = true) - md.modelName -end - -""" - fmi2GetGUID(md::fmi2ModelDescription) + getGUID(md::fmi2ModelDescription) Returns the tag 'guid' from the model description. @@ -791,131 +631,21 @@ Returns the tag 'guid' from the model description. - `md::fmi2ModelDescription`: Struct which provides the static information of ModelVariables. # Returns -- `md.guid::String`: Returns the tag 'guid' from the model description. +- `guid::String`: Returns the tag 'guid' from the model description. """ -function fmi2GetGUID(md::fmi2ModelDescription) +function getGUID(md::fmi2ModelDescription) md.guid end - -""" - fmi2GetGenerationTool(md::fmi2ModelDescription) - -Returns the tag 'generationtool' from the model description. - -# Arguments -- `md::fmi2ModelDescription`: Struct which provides the static information of ModelVariables. - -# Returns -- `md.generationTool::Union{String, Nothing}`: Returns the tag 'generationtool' from the model description. - -""" -function fmi2GetGenerationTool(md::fmi2ModelDescription) - md.generationTool -end - -""" - fmi2GetGenerationDateAndTime(md::fmi2ModelDescription) - -Returns the tag 'generationdateandtime' from the model description. - -# Arguments -- `md::fmi2ModelDescription`: Struct which provides the static information of ModelVariables. - -# Returns -- `md.generationDateAndTime::DateTime`: Returns the tag 'generationdateandtime' from the model description. - -""" -function fmi2GetGenerationDateAndTime(md::fmi2ModelDescription) - md.generationDateAndTime -end - -""" - fmi2GetVariableNamingConvention(md::fmi2ModelDescription) - -Returns the tag 'varaiblenamingconvention' from the model description. - -# Arguments -- `md::fmi2ModelDescription`: Struct which provides the static information of ModelVariables. - -# Returns -- `md.variableNamingConvention::Union{fmi2VariableNamingConvention, Nothing}`: Returns the tag 'variableNamingConvention' from the model description. - -""" -function fmi2GetVariableNamingConvention(md::fmi2ModelDescription) - md.variableNamingConvention -end - -""" - fmi2GetNumberOfEventIndicators(md::fmi2ModelDescription) - -Returns the tag 'numberOfEventIndicators' from the model description. - -# Arguments -- `md::fmi2ModelDescription`: Struct which provides the static information of ModelVariables. - -# Returns -- `md.numberOfEventIndicators::Union{UInt, Nothing}`: Returns the tag 'numberOfEventIndicators' from the model description. - -""" -function fmi2GetNumberOfEventIndicators(md::fmi2ModelDescription) - md.numberOfEventIndicators -end - -""" - fmi2GetNumberOfStates(md::fmi2ModelDescription) - -Returns the number of states of the FMU. - -# Arguments -- `md::fmi2ModelDescription`: Struct which provides the static information of ModelVariables. - -# Returns -- Returns the length of the `md.valueReferences::Array{fmi2ValueReference}` corresponding to the number of states of the FMU. - -""" -function fmi2GetNumberOfStates(md::fmi2ModelDescription) - length(md.stateValueReferences) -end - -""" - fmi2IsCoSimulation(md::fmi2ModelDescription) - -Returns true, if the FMU supports co simulation - -# Arguments -- `md::fmi2ModelDescription`: Struct which provides the static information of ModelVariables. - -# Returns -- `::Bool`: Returns true, if the FMU supports co simulation - -""" -function fmi2IsCoSimulation(md::fmi2ModelDescription) - return (md.coSimulation != nothing) -end - -""" - fmi2IsModelExchange(md::fmi2ModelDescription) - -Returns true, if the FMU supports model exchange - -# Arguments -- `md::fmi2ModelDescription`: Struct which provides the static information of ModelVariables. - -# Returns -- `::Bool`: Returns true, if the FMU supports model exchange - -""" -function fmi2IsModelExchange(md::fmi2ModelDescription) - return (md.modelExchange != nothing) -end +getGUID(fmu::FMU) = getGUID(fmu.modelDescription) +export getGUID ################################## # [Sec. 3] information functions # ################################## """ - fmi2DependenciesSupported(md::fmi2ModelDescription) + isModelStructureAvailable(md::fmi2ModelDescription) Returns true if the FMU model description contains `dependency` information. @@ -926,16 +656,14 @@ Returns true if the FMU model description contains `dependency` information. - `::Bool`: Returns true, if the FMU model description contains `dependency` information. """ -function fmi2DependenciesSupported(md::fmi2ModelDescription) - if md.modelStructure === nothing - return false - end - - return true +function isModelStructureAvailable(md::fmi2ModelDescription) + return !isnothing(md.modelStructure) end +isModelStructureAvailable(fmu::FMU) = isModelStructureAvailable(fmu.modelDescription) +export isModelStructureAvailable """ - fmi2DerivativeDependenciesSupported(md::fmi2ModelDescription) + isModelStructureDerivativesAvailable(md::fmi2ModelDescription) Returns if the FMU model description contains `dependency` information for `derivatives`. @@ -946,717 +674,17 @@ Returns if the FMU model description contains `dependency` information for `deri - `::Bool`: Returns true, if the FMU model description contains `dependency` information for `derivatives`. """ -function fmi2DerivativeDependenciesSupported(md::fmi2ModelDescription) - if !fmi2DependenciesSupported(md) +function isModelStructureDerivativesAvailable(md::fmi2ModelDescription) + if !isModelStructureAvailable(md) return false end der = md.modelStructure.derivatives - if der === nothing || length(der) <= 0 + if isnothing(der) || length(der) <= 0 return false end return true end - -""" - fmi2GetModelIdentifier(md::fmi2ModelDescription; type=nothing) - -Returns the tag 'modelIdentifier' from CS or ME section. - -# Arguments -- `md::fmi2ModelDescription`: Struct which provides the static information of ModelVariables. - -# Keywords -- `type=nothing`: Defines whether a Co-Simulation or Model Exchange is present. (default = nothing) - -# Returns -- `md.modelExchange.modelIdentifier::String`: Returns the tag `modelIdentifier` from ModelExchange section. -- `md.coSimulation.modelIdentifier::String`: Returns the tag `modelIdentifier` from CoSimulation section. -""" -function fmi2GetModelIdentifier(md::fmi2ModelDescription; type=nothing) - - if type === nothing - if fmi2IsCoSimulation(md) - return md.coSimulation.modelIdentifier - elseif fmi2IsModelExchange(md) - return md.modelExchange.modelIdentifier - else - @assert false "fmi2GetModelName(...): FMU does not support ME or CS!" - end - elseif type == fmi2TypeCoSimulation - return md.coSimulation.modelIdentifier - elseif type == fmi2TypeModelExchange - return md.modelExchange.modelIdentifier - end -end - -""" - fmi2CanGetSetState(md::fmi2ModelDescription) - -Returns true, if the FMU supports the getting/setting of states - -# Arguments -- `md::fmi2ModelDescription`: Struct which provides the static information of ModelVariables. - -# Returns -- `::Bool`: Returns true, if the FMU supports the getting/setting of states. - -""" -function fmi2CanGetSetState(md::fmi2ModelDescription) - return (md.coSimulation != nothing && md.coSimulation.canGetAndSetFMUstate) || (md.modelExchange != nothing && md.modelExchange.canGetAndSetFMUstate) -end - -""" - fmi2CanSerializeFMUstate(md::fmi2ModelDescription) - -Returns true, if the FMU state can be serialized - -# Arguments -- `md::fmi2ModelDescription`: Struct which provides the static information of ModelVariables. - -# Returns -- `::Bool`: Returns true, if the FMU state can be serialized - -""" -function fmi2CanSerializeFMUstate(md::fmi2ModelDescription) - return (md.coSimulation != nothing && md.coSimulation.canSerializeFMUstate) || (md.modelExchange != nothing && md.modelExchange.canSerializeFMUstate) -end - -""" - fmi2ProvidesDirectionalDerivative(md::fmi2ModelDescription) - -Returns true, if the FMU provides directional derivatives - -# Arguments -- `md::fmi2ModelDescription`: Struct which provides the static information of ModelVariables. - -# Returns -- `::Bool`: Returns true, if the FMU provides directional derivatives - -""" -function fmi2ProvidesDirectionalDerivative(md::fmi2ModelDescription) - if md.coSimulation != nothing - return (md.coSimulation.providesDirectionalDerivative == true) - elseif md.modelExchange != nothing - return (md.modelExchange.providesDirectionalDerivative == true) - end - - return false -end - -""" - fmi2GetValueReferencesAndNames(md::fmi2ModelDescription; vrs=md.valueReferences) - -Returns a dictionary `Dict(fmi2ValueReference, Array{String})` of value references and their corresponding names. - -# Arguments -- `md::fmi2ModelDescription`: Struct which provides the static information of ModelVariables. - -# Keywords -- `vrs=md.valueReferences`: Additional attribute `valueReferences::Array{fmi2ValueReference}` of the Model Description that is a handle to a (base type) variable value. Handle and base type uniquely identify the value of a variable. (default = `md.valueReferences::Array{fmi2ValueReference}`) - -# Returns -- `dict::Dict{fmi2ValueReference, Array{String}}`: Returns a dictionary that constructs a hash table with keys of type fmi2ValueReference and values of type Array{String}. - -""" -function fmi2GetValueReferencesAndNames(md::fmi2ModelDescription; vrs=md.valueReferences) - dict = Dict{fmi2ValueReference, Array{String}}() - for vr in vrs - dict[vr] = fmi2ValueReferenceToString(md, vr) - end - return dict -end - -""" - fmi2GetValueReferencesAndNames(fmu::FMU2) - -Returns a dictionary `Dict(fmi2ValueReference, Array{String})` of value references and their corresponding names. - -# Arguments -- `fmu::FMU2`: Mutable struct representing a FMU and all it instantiated instances in the FMI 2.0.2 Standard. - -# Returns -- `dict::Dict{fmi2ValueReference, Array{String}}`: Returns a dictionary that constructs a hash table with keys of type fmi2ValueReference and values of type Array{String}. - -""" -function fmi2GetValueReferencesAndNames(fmu::FMU2) - fmi2GetValueReferencesAndNames(fmu.modelDescription) -end - -""" - fmi2GetNames(md::fmi2ModelDescription; vrs=md.valueReferences, mode=:first) - -Returns a array of names corresponding to value references `vrs`. - -# Arguments -- `md::fmi2ModelDescription`: Struct which provides the static information of ModelVariables. - -# Keywords -- `vrs=md.valueReferences`: Additional attribute `valueReferences::Array{fmi2ValueReference}` of the Model Description that is a handle to a (base type) variable value. Handle and base type uniquely identify the value of a variable. (default = `md.valueReferences::Array{fmi2ValueReference}`) -- `mode=:first`: If there are multiple names per value reference, availabel modes are `:first` (default, pick only the first one), `:group` (pick all and group them into an array) and `:flat` (pick all, but flat them out into a 1D-array together with all other names) -# Returns -- `names::Array{String}`: Returns a array of names corresponding to value references `vrs` - -""" -function fmi2GetNames(md::fmi2ModelDescription; vrs=md.valueReferences, mode=:first) - names = [] - for vr in vrs - ns = fmi2ValueReferenceToString(md, vr) - - if mode == :first - push!(names, ns[1]) - elseif mode == :group - push!(names, ns) - elseif mode == :flat - for n in ns - push!(names, n) - end - else - @assert false "fmi2GetNames(...) unknown mode `mode`, please choose between `:first`, `:group` and `:flat`." - end - end - return mode == :group ? [string.(name) for name in names] : string.(names) -end - -""" - fmi2GetNames(fmu::FMU2; vrs=md.valueReferences, mode=:first) - -Returns a array of names corresponding to value references `vrs`. - -# Arguments -- `fmu::FMU2`: Mutable struct representing a FMU and all it instantiated instances in the FMI 2.0.2 Standard. - -# Keywords -- `vrs=md.valueReferences`: Additional attribute `valueReferences::Array{fmi2ValueReference}` of the Model Description that is a handle to a (base type) variable value. Handle and base type uniquely identify the value of a variable. (default = `md.valueReferences::Array{fmi2ValueReference}`) -- `mode=:first`: If there are multiple names per value reference, availabel modes are `:first` (default, pick only the first one), `:group` (pick all and group them into an array) and `:flat` (pick all, but flat them out into a 1D-array together with all other names) -# Returns -- `names::Array{String}`: Returns a array of names corresponding to value references `vrs` - -""" -function fmi2GetNames(fmu::FMU2; kwargs...) - fmi2GetNames(fmu.modelDescription; kwargs...) -end - -""" - fmi2GetModelVariableIndices(md::fmi2ModelDescription; vrs=md.valueReferences) - -Returns a array of indices corresponding to value references `vrs` - -# Arguments -- `md::fmi2ModelDescription`: Struct which provides the static information of ModelVariables. - -# Keywords -- `vrs=md.valueReferences`: Additional attribute `valueReferences::Array{fmi2ValueReference}` of the Model Description that is a handle to a (base type) variable value. Handle and base type uniquely identify the value of a variable. (default = `md.valueReferences::Array{fmi2ValueReference}`) - -# Returns -- `names::Array{Integer}`: Returns a array of indices corresponding to value references `vrs` - -""" -function fmi2GetModelVariableIndices(md::fmi2ModelDescription; vrs=md.valueReferences) - indices = [] - - for i = 1:length(md.modelVariables) - if md.modelVariables[i].valueReference in vrs - push!(indices, i) - end - end - - return indices -end - -""" - fmi2GetInputValueReferencesAndNames(md::fmi2ModelDescription) - -Returns a dict with (vrs, names of inputs). - -# Arguments -- `md::fmi2ModelDescription`: Struct which provides the static information of ModelVariables. -- `fmu::FMU2`: Mutable struct representing a FMU and all it instantiated instances in the FMI 2.0.2 Standard. - - -# Returns -- `dict::Dict{fmi2ValueReference, Array{String}}`: Returns a dictionary that constructs a hash table with keys of type fmi2ValueReference and values of type Array{String}. So returns a dict with (vrs, names of inputs) - -""" -function fmi2GetInputValueReferencesAndNames(md::fmi2ModelDescription) - fmi2GetValueReferencesAndNames(md::fmi2ModelDescription; vrs=md.inputValueReferences) -end - -""" - fmi2GetInputValueReferencesAndNames(fmu::FMU2) - -Returns a dict with (vrs, names of inputs). - -# Arguments -- `fmu::FMU2`: Mutable struct representing a FMU and all it instantiated instances in the FMI 2.0.2 Standard. - - -# Returns -- `dict::Dict{fmi2ValueReference, Array{String}}`: Returns a dictionary that constructs a hash table with keys of type fmi2ValueReference and values of type Array{String}. So returns a dict with (vrs, names of inputs) - -""" -function fmi2GetInputValueReferencesAndNames(fmu::FMU2) - fmi2GetInputValueReferencesAndNames(fmu.modelDescription) -end - -""" - fmi2GetInputNames(md::fmi2ModelDescription; vrs=md.inputvalueReferences, mode=:first) - -Returns names of inputs. - - -# Arguments -- `md::fmi2ModelDescription`: Struct which provides the static information of ModelVariables. - -# Keywords -- `vrs=md.inputvalueReferences`: Additional attribute `inputvalueReferences::Array{fmi2ValueReference}` of the Model Description that is a handle to a (base type) variable value. Handle and base type uniquely identify the value of a variable. (default = `md.valueReferences::Array{fmi2ValueReference}`) -- `mode=:first`: If there are multiple names per value reference, availabel modes are `:first` (default, pick only the first one), `:group` (pick all and group them into an array) and `:flat` (pick all, but flat them out into a 1D-array together with all other names) -# Returns -- `names::Array{String}`: Returns a array of names corresponding to value references `vrs` - -""" -function fmi2GetInputNames(md::fmi2ModelDescription; kwargs...) - fmi2GetNames(md; vrs=md.inputValueReferences, kwargs...) -end - -""" - fmi2GetInputNames(fmu::FMU2; vrs=md.inputValueReferences, mode=:first) - -Returns names of inputs. - - -# Arguments -- `fmu::FMU2`: Mutable struct representing a FMU and all it instantiated instances in the FMI 2.0.2 Standard. - -# Keywords -- `vrs=md.inputvalueReferences`: Additional attribute `inputvalueReferences::Array{fmi2ValueReference}` of the Model Description that is a handle to a (base type) variable value. Handle and base type uniquely identify the value of a variable. (default = `md.valueReferences::Array{fmi2ValueReference}`) -- `mode=:first`: If there are multiple names per value reference, availabel modes are `:first` (default, pick only the first one), `:group` (pick all and group them into an array) and `:flat` (pick all, but flat them out into a 1D-array together with all other names) -# Returns -- `names::Array{String}`: Returns a array of names corresponding to value references `vrs` - -""" -function fmi2GetInputNames(fmu::FMU2; kwargs...) - fmi2GetInputNames(fmu.modelDescription; kwargs...) -end - -""" - fmi2GetOutputValueReferencesAndNames(md::fmi2ModelDescription) - -Returns a dictionary `Dict(fmi2ValueReference, Array{String})` of value references and their corresponding names. - -# Arguments -- `md::fmi2ModelDescription`: Struct which provides the static information of ModelVariables. - -# Keywords -- `vrs=md.outputvalueReferences`: Additional attribute `outputvalueReferences::Array{fmi2ValueReference}` of the Model Description that is a handle to a (base type) variable value. Handle and base type uniquely identify the value of a variable. (default = `md.outputvalueReferences::Array{fmi2ValueReference}`) - -# Returns -- `dict::Dict{fmi2ValueReference, Array{String}}`: Returns a dictionary that constructs a hash table with keys of type fmi2ValueReference and values of type Array{String}.So returns a dict with (vrs, names of outputs) - -""" -function fmi2GetOutputValueReferencesAndNames(md::fmi2ModelDescription) - fmi2GetValueReferencesAndNames(md::fmi2ModelDescription; vrs=md.outputValueReferences) -end - -""" - fmi2GetOutputValueReferencesAndNames(md::fmi2ModelDescription) - -Returns a dictionary `Dict(fmi2ValueReference, Array{String})` of value references and their corresponding names. - -# Arguments -- `fmu::FMU2`: Mutable struct representing a FMU and all it instantiated instances in the FMI 2.0.2 Standard. - -# Returns -- `dict::Dict{fmi2ValueReference, Array{String}}`: Returns a dictionary that constructs a hash table with keys of type fmi2ValueReference and values of type Array{String}.So returns a dict with (vrs, names of outputs) - -""" -function fmi2GetOutputValueReferencesAndNames(fmu::FMU2) - fmi2GetOutputValueReferencesAndNames(fmu.modelDescription) -end - -""" - fmi2GetOutputNames(md::fmi2ModelDescription; vrs=md.outputvalueReferences, mode=:first) - -Returns names of outputs. - -# Arguments -- `md::fmi2ModelDescription`: Struct which provides the static information of ModelVariables. - -# Keywords -- `vrs=md.outputvalueReferences`: Additional attribute `outputvalueReferences::Array{fmi2ValueReference}` of the Model Description that is a handle to a (base type) variable value. Handle and base type uniquely identify the value of a variable. (default = `md.outputvalueReferences::Array{fmi2ValueReference}`) -- `mode=:first`: If there are multiple names per value reference, availabel modes are `:first` (default, pick only the first one), `:group` (pick all and group them into an array) and `:flat` (pick all, but flat them out into a 1D-array together with all other names) -# Returns -- `names::Array{String}`: Returns a array of names corresponding to value references `vrs` - -""" -function fmi2GetOutputNames(md::fmi2ModelDescription; kwargs...) - fmi2GetNames(md; vrs=md.outputValueReferences, kwargs...) -end - -""" - fmi2GetOutputNames(fmu::FMU2; vrs=md.outputvalueReferences, mode=:first) - -Returns names of outputs. - -# Arguments -- `fmu::FMU2`: Mutable struct representing a FMU and all it instantiated instances in the FMI 2.0.2 Standard. - -# Keywords -- `vrs=md.outputvalueReferences`: Additional attribute `outputvalueReferences::Array{fmi2ValueReference}` of the Model Description that is a handle to a (base type) variable value. Handle and base type uniquely identify the value of a variable. (default = `md.outputvalueReferences::Array{fmi2ValueReference}`) -- `mode=:first`: If there are multiple names per value reference, availabel modes are `:first` (default, pick only the first one), `:group` (pick all and group them into an array) and `:flat` (pick all, but flat them out into a 1D-array together with all other names) -# Returns -- `names::Array{String}`: Returns a array of names corresponding to value references `vrs` - -""" -function fmi2GetOutputNames(fmu::FMU2; kwargs...) - fmi2GetOutputNames(fmu.modelDescription; kwargs...) -end - -""" - fmi2GetParameterValueReferencesAndNames(md::fmi2ModelDescription) - -Returns a dictionary `Dict(fmi2ValueReference, Array{String})` of parameterValueReferences and their corresponding names. - -# Arguments -- `md::fmi2ModelDescription`: Struct which provides the static information of ModelVariables. - -# Returns -- `dict::Dict{fmi2ValueReference, Array{String}}`: Returns a dictionary that constructs a hash table with keys of type fmi2ValueReference and values of type Array{String}. So returns a dict with (vrs, names of parameters). - -See also [`fmi2GetValueReferencesAndNames`](@ref). -""" -function fmi2GetParameterValueReferencesAndNames(md::fmi2ModelDescription) - fmi2GetValueReferencesAndNames(md::fmi2ModelDescription; vrs=md.parameterValueReferences) -end - -""" - fmi2GetParameterValueReferencesAndNames(fmu::FMU2) - -Returns a dictionary `Dict(fmi2ValueReference, Array{String})` of parameterValueReferences and their corresponding names. - -# Arguments -- `fmu::FMU2`: Mutable struct representing a FMU and all it instantiated instances in the FMI 2.0.2 Standard. - -# Returns -- `dict::Dict{fmi2ValueReference, Array{String}}`: Returns a dictionary that constructs a hash table with keys of type fmi2ValueReference and values of type Array{String}. So returns a dict with (vrs, names of parameters). - -See also [`fmi2GetValueReferencesAndNames`](@ref). -""" -function fmi2GetParameterValueReferencesAndNames(fmu::FMU2) - fmi2GetParameterValueReferencesAndNames(fmu.modelDescription) -end - -""" - fmi2GetParameterNames(md::fmi2ModelDescription; vrs=md.parameterValueReferences, mode=:first) - -Returns names of parameters. - -# Arguments -- `md::fmi2ModelDescription`: Struct which provides the static information of ModelVariables. - -# Keywords -- `vrs=md.parameterValueReferences`: Additional attribute `parameterValueReferences::Array{fmi2ValueReference}` of the Model Description that is a handle to a (base type) variable value. Handle and base type uniquely identify the value of a variable. (default = `md.parameterValueReferences::Array{fmi2ValueReference}`) -- `mode=:first`: If there are multiple names per value reference, availabel modes are `:first` (default, pick only the first one), `:group` (pick all and group them into an array) and `:flat` (pick all, but flat them out into a 1D-array together with all other names) -# Returns -- `names::Array{String}`: Returns a array of names corresponding to parameter value references `vrs` - - -""" -function fmi2GetParameterNames(md::fmi2ModelDescription; kwargs...) - fmi2GetNames(md; vrs=md.parameterValueReferences, kwargs...) -end - -""" - fmi2GetParameterNames(fmu::FMU2; vrs=md.parameterValueReferences, mode=:first) - -Returns names of parameters. - -# Arguments -- `fmu::FMU2`: Mutable struct representing a FMU and all it instantiated instances in the FMI 2.0.2 Standard. - -# Keywords -- `vrs=md.parameterValueReferences`: Additional attribute `parameterValueReferences::Array{fmi2ValueReference}` of the Model Description that is a handle to a (base type) variable value. Handle and base type uniquely identify the value of a variable. (default = `md.parameterValueReferences::Array{fmi2ValueReference}`) -- `mode=:first`: If there are multiple names per value reference, availabel modes are `:first` (default, pick only the first one), `:group` (pick all and group them into an array) and `:flat` (pick all, but flat them out into a 1D-array together with all other names) -# Returns -- `names::Array{String}`: Returns a array of names corresponding to parameter value references `vrs` - - -""" -function fmi2GetParameterNames(fmu::FMU2; kwargs...) - fmi2GetParameterNames(fmu.modelDescription; kwargs...) -end - -""" - fmi2GetStateValueReferencesAndNames(md::fmi2ModelDescription) - -Returns a dictionary `Dict(fmi2ValueReference, Array{String})` of state value references and their corresponding names. - -# Arguments -- `md::fmi2ModelDescription`: Struct which provides the static information of ModelVariables. - -# Returns -- `dict::Dict{fmi2ValueReference, Array{String}}`: Returns a dictionary that constructs a hash table with keys of type fmi2ValueReference and values of type Array{String}. So returns a dict with (vrs, names of states) - -""" -function fmi2GetStateValueReferencesAndNames(md::fmi2ModelDescription) - fmi2GetValueReferencesAndNames(md::fmi2ModelDescription; vrs=md.stateValueReferences) -end - -""" - fmi2GetStateValueReferencesAndNames(fmu::FMU2) - -Returns dict(vrs, names of states). - -Returns a dictionary `Dict(fmi2ValueReference, Array{String})` of state value references and their corresponding names. - -# Arguments -- `fmu::FMU2`: Mutable struct representing a FMU and all it instantiated instances in the FMI 2.0.2 Standard. - -# Returns -- `dict::Dict{fmi2ValueReference, Array{String}}`: Returns a dictionary that constructs a hash table with keys of type fmi2ValueReference and values of type Array{String}. So returns a dict with (vrs, names of states) - -""" -function fmi2GetStateValueReferencesAndNames(fmu::FMU2) - fmi2GetStateValueReferencesAndNames(fmu.modelDescription) -end - -""" - fmi2GetStateNames(fmu::FMU2; vrs=md.stateValueReferences, mode=:first) - -Returns names of states. - -# Arguments -- `md::fmi2ModelDescription`: Struct which provides the static information of ModelVariables. - -# Keywords -- `vrs=md.stateValueReferences`: Additional attribute `parameterValueReferences::Array{fmi2ValueReference}` of the Model Description that is a handle to a (base type) variable value. Handle and base type uniquely identify the value of a variable. (default = `md.stateValueReferences::Array{fmi2ValueReference}`) -- `mode=:first`: If there are multiple names per value reference, availabel modes are `:first` (default, pick only the first one), `:group` (pick all and group them into an array) and `:flat` (pick all, but flat them out into a 1D-array together with all other names) -# Returns -- `names::Array{String}`: Returns a array of names corresponding to parameter value references `vrs` - - -""" -function fmi2GetStateNames(md::fmi2ModelDescription; kwargs...) - fmi2GetNames(md; vrs=md.stateValueReferences, kwargs...) -end - -""" - fmi2GetStateNames(fmu::FMU2; vrs=md.stateValueReferences, mode=:first) - -Returns names of states. - -# Arguments -- `fmu::FMU2`: Mutable struct representing a FMU and all it instantiated instances in the FMI 2.0.2 Standard. - -# Keywords -- `vrs=md.stateValueReferences`: Additional attribute `parameterValueReferences::Array{fmi2ValueReference}` of the Model Description that is a handle to a (base type) variable value. Handle and base type uniquely identify the value of a variable. (default = `md.stateValueReferences::Array{fmi2ValueReference}`) -- `mode=:first`: If there are multiple names per value reference, availabel modes are `:first` (default, pick only the first one), `:group` (pick all and group them into an array) and `:flat` (pick all, but flat them out into a 1D-array together with all other names) -# Returns -- `names::Array{String}`: Returns a array of names corresponding to parameter value references `vrs` - - -""" -function fmi2GetStateNames(fmu::FMU2; kwargs...) - fmi2GetStateNames(fmu.modelDescription; kwargs...) -end - -""" - fmi2GetDerivateValueReferencesAndNames(md::fmi2ModelDescription) - -Returns a dictionary `Dict(fmi2ValueReference, Array{String})` of derivative value references and their corresponding names. - -# Arguments -- `md::fmi2ModelDescription`: Struct which provides the static information of ModelVariables. - -# Returns -- `dict::Dict{fmi2ValueReference, Array{String}}`: Returns a dictionary that constructs a hash table with keys of type fmi2ValueReference and values of type Array{String}. So returns a dict with (vrs, names of derivatives) -See also [`fmi2GetValueReferencesAndNames`](@ref) -""" -function fmi2GetDerivateValueReferencesAndNames(md::fmi2ModelDescription) - fmi2GetValueReferencesAndNames(md::fmi2ModelDescription; vrs=md.derivativeValueReferences) -end - -""" - fmi2GetDerivateValueReferencesAndNames(fmu::FMU2) - -Returns a dictionary `Dict(fmi2ValueReference, Array{String})` of derivative value references and their corresponding names. - -# Arguments -- `fmu::FMU2`: Mutable struct representing a FMU and all it instantiated instances in the FMI 2.0.2 Standard. - -# Returns -- `dict::Dict{fmi2ValueReference, Array{String}}`: Returns a dictionary that constructs a hash table with keys of type fmi2ValueReference and values of type Array{String}. So returns a dict with (vrs, names of derivatives) -See also [`fmi2GetValueReferencesAndNames`](@ref) -""" -function fmi2GetDerivateValueReferencesAndNames(fmu::FMU2) - fmi2GetDerivateValueReferencesAndNames(fmu.modelDescription) -end - -""" - fmi2GetDerivativeNames(md::fmi2ModelDescription; vrs=md.derivativeValueReferences, mode=:first) - -Returns names of derivatives. - -# Arguments -- `md::fmi2ModelDescription`: Struct which provides the static information of ModelVariables. - -# Keywords -- `vrs=md.derivativeValueReferences`: Additional attribute `derivativeValueReferences::Array{fmi2ValueReference}` of the Model Description that is a handle to a (base type) variable value. Handle and base type uniquely identify the value of a variable. (default = `md.derivativeValueReferences::Array{fmi2ValueReference}`) -- `mode=:first`: If there are multiple names per value reference, availabel modes are `:first` (default, pick only the first one), `:group` (pick all and group them into an array) and `:flat` (pick all, but flat them out into a 1D-array together with all other names) -# Returns -- `names::Array{String}`: Returns a array of names corresponding to parameter value references `vrs` - - -""" -function fmi2GetDerivativeNames(md::fmi2ModelDescription; kwargs...) - fmi2GetNames(md; vrs=md.derivativeValueReferences, kwargs...) -end - -""" - fmi2GetDerivativeNames(fmu::FMU2; vrs=md.derivativeValueReferences, mode=:first) - -Returns names of derivatives. - -# Arguments -- `fmu::FMU2`: Mutable struct representing a FMU and all it instantiated instances in the FMI 2.0.2 Standard. - -# Keywords -- `vrs=md.derivativeValueReferences`: Additional attribute `derivativeValueReferences::Array{fmi2ValueReference}` of the Model Description that is a handle to a (base type) variable value. Handle and base type uniquely identify the value of a variable. (default = `md.derivativeValueReferences::Array{fmi2ValueReference}`) -- `mode=:first`: If there are multiple names per value reference, availabel modes are `:first` (default, pick only the first one), `:group` (pick all and group them into an array) and `:flat` (pick all, but flat them out into a 1D-array together with all other names) -# Returns -- `names::Array{String}`: Returns a array of names corresponding to parameter value references `vrs` - - -""" -function fmi2GetDerivativeNames(fmu::FMU2; kwargs...) - fmi2GetDerivativeNames(fmu.modelDescription; kwargs...) -end - -""" - fmi2GetNamesAndDescriptions(md::fmi2ModelDescription) - -Returns a dictionary of variables with their descriptions. - -# Arguments -- `md::fmi2ModelDescription`: Struct which provides the static information of ModelVariables. - -# Returns -- `dict::Dict{String, String}`: Returns a dictionary that constructs a hash table with keys of type String and values of type String. So returns a dict with ( `md.modelVariables[i].name::String`, `md.modelVariables[i].description::Union{String, Nothing}`). (Creates a tuple (name, description) for each i in 1:length(md.modelVariables)) -""" -function fmi2GetNamesAndDescriptions(md::fmi2ModelDescription) - Dict(md.modelVariables[i].name => md.modelVariables[i].description for i = 1:length(md.modelVariables)) -end - -""" - fmi2GetNamesAndDescriptions(fmu::FMU2) - -Returns a dictionary of variables with their descriptions. - -# Arguments -- `fmu::FMU2`: Mutable struct representing a FMU and all it instantiated instances in the FMI 2.0.2 Standard. - -# Returns -- `dict::Dict{String, String}`: Returns a dictionary that constructs a hash table with keys of type String and values of type String. So returns a dict with ( `md.modelVariables[i].name::String`, `md.modelVariables[i].description::Union{String, Nothing}`). (Creates a tuple (name, description) for each i in 1:length(md.modelVariables)) -""" -function fmi2GetNamesAndDescriptions(fmu::FMU2) - fmi2GetNamesAndDescriptions(fmu.modelDescription) -end - -""" - fmi2GetNamesAndUnits(md::fmi2ModelDescription) - -Returns a dictionary of variables with their units. - -# Arguments -- `md::fmi2ModelDescription`: Struct which provides the static information of ModelVariables. - -# Returns -- `dict::Dict{String, String}`: Returns a dictionary that constructs a hash table with keys of type String and values of type String. So returns a dict with ( `md.modelVariables[i].name::String`, `md.modelVariables[i]._Real.unit::Union{String, Nothing}`). (Creates a tuple (name, unit) for each i in 1:length(md.modelVariables)) -See also [`fmi2GetUnit`](@ref). -""" -function fmi2GetNamesAndUnits(md::fmi2ModelDescription) - Dict(md.modelVariables[i].name => fmi2GetUnit(md.modelVariables[i]) for i = 1:length(md.modelVariables)) -end - -""" - fmi2GetNamesAndUnits(fmu::FMU2) - -Returns a dictionary of variables with their units. - -# Arguments -- `fmu::FMU2`: Mutable struct representing a FMU and all it instantiated instances in the FMI 2.0.2 Standard. - -# Returns -- `dict::Dict{String, String}`: Returns a dictionary that constructs a hash table with keys of type String and values of type String. So returns a dict with ( `md.modelVariables[i].name::String`, `md.modelVariables[i]._Real.unit::Union{String, Nothing}`). (Creates a tuple (name, unit) for each i in 1:length(md.modelVariables)) -See also [`fmi2GetUnit`](@ref). -""" -function fmi2GetNamesAndUnits(fmu::FMU2) - fmi2GetNamesAndUnits(fmu.modelDescription) -end - -""" - fmi2GetNamesAndInitials(md::fmi2ModelDescription) - -Returns a dictionary of variables with their initials. - -# Arguments -- `md::fmi2ModelDescription`: Struct which provides the static information of ModelVariables. - -# Returns -- `dict::Dict{String, Cuint}`: Returns a dictionary that constructs a hash table with keys of type String and values of type Cuint. So returns a dict with ( `md.modelVariables[i].name::String`, `md.modelVariables[i].inital::Union{fmi2Initial, Nothing}`). (Creates a tuple (name,initial) for each i in 1:length(md.modelVariables)) -See also [`fmi2GetInitial`](@ref). -""" -function fmi2GetNamesAndInitials(md::fmi2ModelDescription) - Dict(md.modelVariables[i].name => fmi2GetInitial(md.modelVariables[i]) for i = 1:length(md.modelVariables)) -end - -""" - fmi2GetNamesAndInitials(fmu::FMU2) - -Returns a dictionary of variables with their initials. - -# Arguments -- `fmu::FMU2`: Mutable struct representing a FMU and all it instantiated instances in the FMI 2.0.2 Standard. - -# Returns -- `dict::Dict{String, Cuint}`: Returns a dictionary that constructs a hash table with keys of type String and values of type Cuint. So returns a dict with ( `md.modelVariables[i].name::String`, `md.modelVariables[i].inital::Union{fmi2Initial, Nothing}`). (Creates a tuple (name,initial) for each i in 1:length(md.modelVariables)) -See also [`fmi2GetInitial`](@ref). -""" -function fmi2GetNamesAndInitials(fmu::FMU2) - fmi2GetNamesAndInitials(fmu.modelDescription) -end - -""" - fmi2GetInputNamesAndStarts(md::fmi2ModelDescription) - -Returns a dictionary of input variables with their starting values. - -# Arguments -- `md::fmi2ModelDescription`: Struct which provides the static information of ModelVariables. - -# Returns -- `dict::Dict{String, Array{fmi2ValueReferenceFormat}}`: Returns a dictionary that constructs a hash table with keys of type String and values of type fmi2ValueReferenceFormat. So returns a dict with ( `md.modelVariables[i].name::String`, `starts:: Array{fmi2ValueReferenceFormat}` ). (Creates a tuple (name, starts) for each i in inputIndices) -See also [`fmi2GetStartValue`](@ref). -""" -function fmi2GetInputNamesAndStarts(md::fmi2ModelDescription) - - inputIndices = fmi2GetModelVariableIndices(md; vrs=md.inputValueReferences) - Dict(md.modelVariables[i].name => fmi2GetStartValue(md.modelVariables[i]) for i in inputIndices) -end - -""" - fmi2GetInputNamesAndStarts(fmu::FMU2) - -Returns a dictionary of input variables with their starting values. - -# Arguments -- `fmu::FMU2`: Mutable struct representing a FMU and all it instantiated instances in the FMI 2.0.2 Standard. - -# Returns -- `dict::Dict{String, Array{fmi2ValueReferenceFormat}}`: Returns a dictionary that constructs a hash table with keys of type String and values of type fmi2ValueReferenceFormat. So returns a dict with ( `md.modelVariables[i].name::String`, `starts:: Array{fmi2ValueReferenceFormat}` ). (Creates a tuple (name, starts) for each i in inputIndices) -See also [`fmi2GetStartValue`](@ref). -""" -function fmi2GetInputNamesAndStarts(fmu::FMU2) - fmi2GetInputNamesAndStarts(fmu.modelDescription) -end +isModelStructureDerivativesAvailable(fmu::FMU) = isModelStructureDerivativesAvailable(fmu.modelDescription) +export isModelStructureDerivativesAvailable diff --git a/src/FMI2/prep.jl b/src/FMI2/prep.jl index 5a4a1de..d4fa03c 100644 --- a/src/FMI2/prep.jl +++ b/src/FMI2/prep.jl @@ -3,7 +3,8 @@ # Licensed under the MIT license. See LICENSE file in the project root for details. # -using FMICore: getAttributes, fmi2ScalarVariable +using FMIBase.FMICore: getAttributes, fmi2ScalarVariable +using FMIBase: handleEvents import FMIImport: fmi2VariabilityConstant, fmi2InitialApprox, fmi2InitialExact function setBeforeInitialization(mv::fmi2ScalarVariable) @@ -19,64 +20,44 @@ function setInInitialization(mv::fmi2ScalarVariable) return causality == fmi2CausalityInput || (causality != fmi2CausalityParameter && variability == fmi2VariabilityTunable) || (variability != fmi2VariabilityConstant && initial == fmi2InitialExact) end -function prepareSolveFMU(fmu::FMU2, - c::Union{Nothing, FMU2Component}, - type::fmi2Type, - instantiate::Union{Nothing, Bool}=nothing, - freeInstance::Union{Nothing, Bool}=nothing, - terminate::Union{Nothing, Bool}=nothing, - reset::Union{Nothing, Bool}=nothing, - setup::Union{Nothing, Bool}=nothing, +function prepareSolveFMU(fmu::FMU2, c::Union{Nothing, FMU2Component}, type::fmi2Type=fmu.type; + instantiate::Union{Nothing, Bool}=fmu.executionConfig.instantiate, + freeInstance::Union{Nothing, Bool}=fmu.executionConfig.freeInstance, + terminate::Union{Nothing, Bool}=fmu.executionConfig.terminate, + reset::Union{Nothing, Bool}=fmu.executionConfig.reset, + setup::Union{Nothing, Bool}=fmu.executionConfig.setup, parameters::Union{Dict{<:Any, <:Any}, Nothing}=nothing, t_start::Real=0.0, t_stop::Union{Real, Nothing}=nothing, - tolerance::Union{Real, Nothing}=nothing; + tolerance::Union{Real, Nothing}=nothing, x0::Union{AbstractArray{<:Real}, Nothing}=nothing, inputs::Union{Dict{<:Any, <:Any}, Nothing}=nothing, cleanup::Bool=false, - handleEvents=handleEvents) + handleEvents=handleEvents, + instantiateKwargs...) ignore_derivatives() do - if instantiate === nothing - instantiate = fmu.executionConfig.instantiate - end - - if freeInstance === nothing - freeInstance = fmu.executionConfig.freeInstance - end - - if terminate === nothing - terminate = fmu.executionConfig.terminate - end - - if reset === nothing - reset = fmu.executionConfig.reset - end - - if setup === nothing - setup = fmu.executionConfig.setup - end # instantiate (hard) if instantiate # remove old one if we missed it (callback) if cleanup && c != nothing - c = finishSolveFMU(fmu, c, freeInstance, terminate) + c = finishSolveFMU(fmu, c; freeInstance=freeInstance, terminate=terminate) end - c = fmi2Instantiate!(fmu; type=type) + c = fmi2Instantiate!(fmu; type=type, instantiateKwargs...) else # use existing instance if c === nothing - if hasCurrentComponent(fmu) - c = getCurrentComponent(fmu) + if hasCurrentInstance(fmu) + c = getCurrentInstance(fmu) else @warn "Found no FMU instance, but executionConfig doesn't force allocation. Allocating one.\nUse `fmi2Instantiate(fmu)` to prevent this message." - c = fmi2Instantiate!(fmu; type=type) + c = fmi2Instantiate!(fmu; type=type, instantiateKwargs...) end end end - @assert c != nothing "No FMU instance available, allocate one or use `fmu.executionConfig.instantiate=true`." + @assert !isnothing(c) "No FMU instance available, allocate one or use `fmu.executionConfig.instantiate=true`." # soft terminate (if necessary) # if terminate @@ -98,21 +79,21 @@ function prepareSolveFMU(fmu::FMU2, # parameters if parameters !== nothing - retcodes = fmi2Set(c, collect(keys(parameters)), collect(values(parameters)); filter=setBeforeInitialization) + retcodes = setValue(c, collect(keys(parameters)), collect(values(parameters)); filter=setBeforeInitialization) @assert all(retcodes .== fmi2StatusOK) "fmi2Simulate(...): Setting initial parameters failed with return code $(retcode)." end # inputs if inputs !== nothing - retcodes = fmi2Set(c, collect(keys(inputs)), collect(values(inputs)); filter=setBeforeInitialization) + retcodes = setValue(c, collect(keys(inputs)), collect(values(inputs)); filter=setBeforeInitialization) @assert all(retcodes .== fmi2StatusOK) "fmi2Simulate(...): Setting initial inputs failed with return code $(retcode)." end # start state - if x0 !== nothing + if !isnothing(x0) #retcode = fmi2SetContinuousStates(c, x0) #@assert retcode == fmi2StatusOK "fmi2Simulate(...): Setting initial state failed with return code $(retcode)." - retcodes = fmi2Set(c, fmu.modelDescription.stateValueReferences, x0; filter=setBeforeInitialization) + retcodes = setValue(c, fmu.modelDescription.stateValueReferences, x0; filter=setBeforeInitialization) @assert all(retcodes .== fmi2StatusOK) "fmi2Simulate(...): Setting initial states failed with return code $(retcode)." end @@ -124,21 +105,21 @@ function prepareSolveFMU(fmu::FMU2, # parameters if parameters !== nothing - retcodes = fmi2Set(c, collect(keys(parameters)), collect(values(parameters)); filter=setInInitialization) + retcodes = setValue(c, collect(keys(parameters)), collect(values(parameters)); filter=setInInitialization) @assert all(retcodes .== fmi2StatusOK) "fmi2Simulate(...): Setting initial parameters failed with return code $(retcodes)." end # inputs if inputs !== nothing - retcodes = fmi2Set(c, collect(keys(inputs)), collect(values(inputs)); filter=setInInitialization) + retcodes = setValue(c, collect(keys(inputs)), collect(values(inputs)); filter=setInInitialization) @assert all(retcodes .== fmi2StatusOK) "fmi2Simulate(...): Setting initial inputs failed with return code $(retcodes)." end # start state - if x0 !== nothing + if !isnothing(x0) #retcode = fmi2SetContinuousStates(c, x0) #@assert retcode == fmi2StatusOK "fmi2Simulate(...): Setting initial state failed with return code $(retcode)." - retcodes = fmi2Set(c, fmu.modelDescription.stateValueReferences, x0; filter=setInInitialization) + retcodes = setValue(c, fmu.modelDescription.stateValueReferences, x0; filter=setInInitialization) @assert all(retcodes .== fmi2StatusOK) "fmi2Simulate(...): Setting initial inputs failed with return code $(retcodes)." # safe start state in component @@ -152,11 +133,11 @@ function prepareSolveFMU(fmu::FMU2, end # allocate a solution object - c.solution = FMU2Solution(c) + c.solution = FMUSolution(c) # ME specific if type == fmi2TypeModelExchange - if x0 == nothing && !c.fmu.isZeroState + if isnothing(x0) && !c.fmu.isZeroState x0 = fmi2GetContinuousStates(c) end @@ -164,191 +145,30 @@ function prepareSolveFMU(fmu::FMU2, @debug "[NEW INST]" handleEvents(c) end + + c.fmu.hasStateEvents = (c.fmu.modelDescription.numberOfEventIndicators > 0) + c.fmu.hasTimeEvents = isTrue(c.eventInfo.nextEventTimeDefined) end end return c, x0 end - - -# Handles events and returns the values and nominals of the changed continuous states. -function handleEvents(c::FMU2Component) - - @assert c.state == fmi2ComponentStateEventMode "handleEvents(...): Must be in event mode!" - - # invalidate all cached jacobians/gradients - invalidate!(c.∂ẋ_∂x) - invalidate!(c.∂ẋ_∂u) - invalidate!(c.∂ẋ_∂p) - invalidate!(c.∂y_∂x) - invalidate!(c.∂y_∂u) - invalidate!(c.∂y_∂p) - invalidate!(c.∂e_∂x) - invalidate!(c.∂e_∂u) - invalidate!(c.∂e_∂p) - invalidate!(c.∂ẋ_∂t) - invalidate!(c.∂y_∂t) - invalidate!(c.∂e_∂t) - - #@debug "Handle Events..." - - # trigger the loop - c.eventInfo.newDiscreteStatesNeeded = fmi2True - - valuesOfContinuousStatesChanged = fmi2False - nominalsOfContinuousStatesChanged = fmi2False - nextEventTimeDefined = fmi2False - nextEventTime = 0.0 - - numCalls = 0 - while c.eventInfo.newDiscreteStatesNeeded == fmi2True - numCalls += 1 - fmi2NewDiscreteStates!(c, c.eventInfo) - - if c.eventInfo.valuesOfContinuousStatesChanged == fmi2True - valuesOfContinuousStatesChanged = fmi2True - end - - if c.eventInfo.nominalsOfContinuousStatesChanged == fmi2True - nominalsOfContinuousStatesChanged = fmi2True - end - - if c.eventInfo.nextEventTimeDefined == fmi2True - nextEventTimeDefined = fmi2True - nextEventTime = c.eventInfo.nextEventTime - end - - if c.eventInfo.terminateSimulation == fmi2True - @error "handleEvents(...): FMU throws `terminateSimulation`!" - end - - @assert numCalls <= c.fmu.executionConfig.maxNewDiscreteStateCalls "handleEvents(...): `fmi2NewDiscreteStates!` exceeded $(c.fmu.executionConfig.maxNewDiscreteStateCalls) calls, this may be an error in the FMU. If not, you can change the max value for this FMU in `fmu.executionConfig.maxNewDiscreteStateCalls`." +function prepareSolveFMU(fmu::FMU2, c::Union{Nothing, FMU2Component}, type::Symbol; kwargs...) + if type == :CS + return prepareSolveFMU(fmu, c, fmi2TypeCoSimulation; kwargs...) + elseif type == :ME + return prepareSolveFMU(fmu, c, fmi2TypeModelExchange; kwargs...) + elseif type == :SE + @assert false "FMU type `SE` is not supported in FMI2!" + else + @assert false "Unknwon FMU type `$(type)`" end - - c.eventInfo.valuesOfContinuousStatesChanged = valuesOfContinuousStatesChanged - c.eventInfo.nominalsOfContinuousStatesChanged = nominalsOfContinuousStatesChanged - c.eventInfo.nextEventTimeDefined = nextEventTimeDefined - c.eventInfo.nextEventTime = nextEventTime - - @assert fmi2EnterContinuousTimeMode(c) == fmi2StatusOK "FMU is not in state continuous time after event handling." - - return nothing end -function prepareSolveFMU(fmu::Vector{FMU2}, c::AbstractVector{Union{FMU2Component, Nothing}}, type::fmi2Type, instantiate::Union{Nothing, Bool}, freeInstance::Union{Nothing, Bool}, terminate::Union{Nothing, Bool}, reset::Union{Nothing, Bool}, setup::Union{Nothing, Bool}, parameters::Union{Vector{Union{Dict{<:Any, <:Any}, Nothing}}, Nothing}, t_start, t_stop, tolerance; - x0::Union{Vector{Union{Array{<:Real}, Nothing}}, Nothing}=nothing, initFct=nothing, cleanup::Bool=false, - handleEvents=handleEvents) - - ignore_derivatives() do - for i in 1:length(fmu) - - if instantiate === nothing - instantiate = fmu[i].executionConfig.instantiate - end - - if freeInstance === nothing - freeInstance = fmu[i].executionConfig.freeInstance - end - - if terminate === nothing - terminate = fmu[i].executionConfig.terminate - end - - if reset === nothing - reset = fmu[i].executionConfig.reset - end - - if setup === nothing - setup = fmu[i].executionConfig.setup - end - - # instantiate (hard) - if instantiate - # remove old one if we missed it (callback) - if cleanup && c[i] != nothing - c[i] = finishSolveFMU(fmu[i], c[i], freeInstance, terminate) - end - - c[i] = fmi2Instantiate!(fmu[i]; type=type) - @debug "[NEW INST]" - else - if c[i] === nothing - if length(fmu[i].components) > 0 - c[i] = getCurrentComponent(fmu[i]) - else - @warn "Found no FMU instance, but executionConfig doesn't force allocation. Allocating one. Use `fmi2Instantiate(fmu)` to prevent this message." - c[i] = fmi2Instantiate!(fmu[i]; type=type) - end - end - end - - # soft terminate (if necessary) - # if terminate - # retcode = fmi2Terminate(c[i]; soft=true) - # @assert retcode == fmi2StatusOK "fmi2Simulate(...): Termination failed with return code $(retcode)." - # end - - # soft reset (if necessary) - if reset - retcode = fmi2Reset(c[i]; soft=true) - @assert retcode == fmi2StatusOK "fmi2Simulate(...): Reset failed with return code $(retcode)." - end - - # enter setup (hard) - if setup - retcode = fmi2SetupExperiment(c[i], t_start, t_stop; tolerance=tolerance) - @assert retcode == fmi2StatusOK "fmi2Simulate(...): Setting up experiment failed with return code $(retcode)." - - retcode = fmi2EnterInitializationMode(c[i]) - @assert retcode == fmi2StatusOK "fmi2Simulate(...): Entering initialization mode failed with return code $(retcode)." - end - - if x0 !== nothing - if x0[i] !== nothing - retcode = fmi2SetContinuousStates(c[i], x0[i]) - @assert retcode == fmi2StatusOK "fmi2Simulate(...): Setting initial state failed with return code $(retcode)." - end - end - - if parameters !== nothing - if parameters[i] !== nothing - retcodes = fmi2Set(c[i], collect(keys(parameters[i])), collect(values(parameters[i])) ) - @assert all(retcodes .== fmi2StatusOK) "fmi2Simulate(...): Setting initial parameters failed with return code $(retcode)." - end - end - - if initFct !== nothing - initFct() - end - - # exit setup (hard) - if setup - retcode = fmi2ExitInitializationMode(c[i]) - @assert retcode == fmi2StatusOK "fmi2Simulate(...): Exiting initialization mode failed with return code $(retcode)." - end - - if type == fmi2TypeModelExchange - if x0 === nothing - if x0[i] === nothing - x0[i] = fmi2GetContinuousStates(c[i]) - end - end - - if instantiate || reset # we have a fresh instance - @debug "[NEW INST]" - handleEvents(c[i]) - end - end - - c[i].solution = FMU2Solution(c[i]) - end - - end # ignore_derivatives - - return c, x0 -end - -function finishSolveFMU(fmu::FMU2, c::FMU2Component, freeInstance::Union{Nothing, Bool}, terminate::Union{Nothing, Bool}; popComponent::Bool=true) +function finishSolveFMU(fmu::FMU2, c::FMU2Component; + freeInstance::Union{Nothing, Bool}=nothing, + terminate::Union{Nothing, Bool}=nothing, + popComponent::Bool=true) if isnothing(c) return @@ -376,38 +196,5 @@ function finishSolveFMU(fmu::FMU2, c::FMU2Component, freeInstance::Union{Nothing end end - return c -end - -function finishSolveFMU(fmu::Vector{FMU2}, c::AbstractVector{Union{FMU2Component, Nothing}}, freeInstance::Union{Nothing, Bool}, terminate::Union{Nothing, Bool}) - - ignore_derivatives() do - for i in 1:length(fmu) - if terminate === nothing - terminate = fmu[i].executionConfig.terminate - end - - if freeInstance === nothing - freeInstance = fmu[i].executionConfig.freeInstance - end - - if c[i] != nothing - - # soft terminate (if necessary) - if terminate - retcode = fmi2Terminate(c[i]; soft=true) - @assert retcode == fmi2StatusOK "fmi2Simulate(...): Termination failed with return code $(retcode)." - end - - if freeInstance - fmi2FreeInstance!(c[i]) - @debug "[RELEASED INST]" - end - c[i] = nothing - end - end - - end # ignore_derivatives - return c end \ No newline at end of file diff --git a/src/FMI3/c.jl b/src/FMI3/c.jl index a865a65..fc6eafa 100644 --- a/src/FMI3/c.jl +++ b/src/FMI3/c.jl @@ -10,21 +10,21 @@ # Any c-function `f(c::fmi3Instance, args...)` in the spec is implemented as `f(c::FMU3Instance, args...)`. # Any c-function `f(args...)` without a leading `fmi3Instance`-arguemnt is implented as `f(c_ptr, args...)` where `c_ptr` is a pointer to the c-function (inside the DLL). -import FMICore: fmi3InstantiateCoSimulation, fmi3InstantiateModelExchange, fmi3InstantiateScheduledExecution, fmi3FreeInstance!, fmi3GetVersion -import FMICore: fmi3SetDebugLogging, fmi3EnterInitializationMode, fmi3ExitInitializationMode, fmi3Terminate, fmi3Reset -import FMICore: fmi3GetFloat32!, fmi3SetFloat32, fmi3GetFloat64!, fmi3SetFloat64 -import FMICore: fmi3GetInt8!, fmi3SetInt8, fmi3GetInt16!, fmi3SetInt16,fmi3GetInt32!, fmi3SetInt32, fmi3GetInt64!, fmi3SetInt64 -import FMICore: fmi3GetUInt8!, fmi3SetUInt8, fmi3GetUInt16!, fmi3SetUInt16,fmi3GetUInt32!, fmi3SetUInt32, fmi3GetUInt64!, fmi3SetUInt64 -import FMICore: fmi3GetBoolean!, fmi3SetBoolean, fmi3GetString!, fmi3SetString, fmi3GetBinary!, fmi3SetBinary, fmi3GetClock!, fmi3SetClock -import FMICore: fmi3GetFMUState!, fmi3SetFMUState, fmi3FreeFMUState!, fmi3SerializedFMUStateSize!, fmi3SerializeFMUState!, fmi3DeSerializeFMUState! -import FMICore: fmi3SetIntervalDecimal, fmi3SetIntervalFraction, fmi3GetIntervalDecimal!, fmi3GetIntervalFraction!, fmi3GetShiftDecimal!, fmi3GetShiftFraction!, fmi3ActivateModelPartition -import FMICore: fmi3GetNumberOfVariableDependencies!, fmi3GetVariableDependencies! -import FMICore: fmi3GetDirectionalDerivative!, fmi3GetAdjointDerivative!, fmi3GetOutputDerivatives! -import FMICore: fmi3EnterConfigurationMode, fmi3ExitConfigurationMode -import FMICore: fmi3GetNumberOfContinuousStates!, fmi3GetNumberOfEventIndicators! -import FMICore: fmi3DoStep!, fmi3EnterStepMode -import FMICore: fmi3SetTime, fmi3SetContinuousStates, fmi3EnterEventMode, fmi3UpdateDiscreteStates, fmi3EnterContinuousTimeMode, fmi3CompletedIntegratorStep! -import FMICore: fmi3GetContinuousStateDerivatives!, fmi3GetEventIndicators!, fmi3GetContinuousStates!, fmi3GetNominalsOfContinuousStates!, fmi3EvaluateDiscreteStates +import FMIBase.FMICore: fmi3GetVersion +import FMIBase.FMICore: fmi3SetDebugLogging, fmi3EnterInitializationMode, fmi3ExitInitializationMode, fmi3Terminate, fmi3Reset +import FMIBase.FMICore: fmi3GetFloat32!, fmi3SetFloat32, fmi3GetFloat64!, fmi3SetFloat64 +import FMIBase.FMICore: fmi3GetInt8!, fmi3SetInt8, fmi3GetInt16!, fmi3SetInt16,fmi3GetInt32!, fmi3SetInt32, fmi3GetInt64!, fmi3SetInt64 +import FMIBase.FMICore: fmi3GetUInt8!, fmi3SetUInt8, fmi3GetUInt16!, fmi3SetUInt16,fmi3GetUInt32!, fmi3SetUInt32, fmi3GetUInt64!, fmi3SetUInt64 +import FMIBase.FMICore: fmi3GetBoolean!, fmi3SetBoolean, fmi3GetString!, fmi3SetString, fmi3GetBinary!, fmi3SetBinary, fmi3GetClock!, fmi3SetClock +import FMIBase.FMICore: fmi3GetFMUState!, fmi3SetFMUState, fmi3FreeFMUState, fmi3SerializedFMUStateSize!, fmi3SerializeFMUState!, fmi3DeSerializeFMUState! +import FMIBase.FMICore: fmi3SetIntervalDecimal, fmi3SetIntervalFraction, fmi3GetIntervalDecimal!, fmi3GetIntervalFraction!, fmi3GetShiftDecimal!, fmi3GetShiftFraction!, fmi3ActivateModelPartition +import FMIBase.FMICore: fmi3GetNumberOfVariableDependencies!, fmi3GetVariableDependencies! +import FMIBase.FMICore: fmi3GetDirectionalDerivative!, fmi3GetAdjointDerivative!, fmi3GetOutputDerivatives! +import FMIBase.FMICore: fmi3EnterConfigurationMode, fmi3ExitConfigurationMode +import FMIBase.FMICore: fmi3GetNumberOfContinuousStates!, fmi3GetNumberOfEventIndicators! +import FMIBase.FMICore: fmi3DoStep!, fmi3EnterStepMode +import FMIBase.FMICore: fmi3SetTime, fmi3SetContinuousStates, fmi3EnterEventMode, fmi3UpdateDiscreteStates, fmi3EnterContinuousTimeMode, fmi3CompletedIntegratorStep! +import FMIBase.FMICore: fmi3GetContinuousStateDerivatives!, fmi3GetEventIndicators!, fmi3GetContinuousStates!, fmi3GetNominalsOfContinuousStates!, fmi3EvaluateDiscreteStates """ @@ -49,23 +49,36 @@ Function that is called in the FMU, usually if an fmi3XXX function, does not beh - FMISpec3.0: 2.3.1. Super State: FMU State Setable """ function fmi3CallbackLogger(_instanceEnvironment::Ptr{FMU3InstanceEnvironment}, - _status::Cuint, + status::fmi3Status, _category::Ptr{Cchar}, _message::Ptr{Cchar}) message = unsafe_string(_message) category = unsafe_string(_category) - status = fmi3StatusToString(_status) instanceEnvironment = unsafe_load(_instanceEnvironment) - if status == fmi3StatusOK && instanceEnvironment.logStatusOK - @info "[$status][$category][$instanceName]: $message" - elseif (status == fmi3StatusWarning && instanceEnvironment.logStatusWarning) - @warn "[$status][$category][$instanceName]: $message" - elseif (status == fmi3StatusDiscard && instanceEnvironment.logStatusDiscard) || - (status == fmi3StatusError && instanceEnvironment.logStatusError) || - (status == fmi3StatusFatal && instanceEnvironment.logStatusFatal) - @error "[$status][$category][$instanceName]: $message" + if status == fmi3StatusOK + if instanceEnvironment.logStatusOK + @info "[$(status)][$(category)]: $(message)" + end + elseif status == fmi3StatusWarning + if instanceEnvironment.logStatusWarning + @warn "[$(status)][$(category)]: $(message)" + end + elseif status == fmi3StatusDiscard + if instanceEnvironment.logStatusDiscard + @error "[$(status)][$(category)]: $(message)" + end + elseif status == fmi3StatusError + if instanceEnvironment.logStatusError + @error "[$(status)][$(category)]: $(message)" + end + elseif status == fmi3StatusFatal + if instanceEnvironment.logStatusFatal + @error "[$(status)][$(category)]: $(message)" + end + else + @assert false "Unknown message received, status: $(status)" end return nothing @@ -114,7 +127,8 @@ function fmi3CallbackIntermediateUpdate(instanceEnvironment::Ptr{Cvoid}, canReturnEarly::fmi3Boolean, earlyReturnRequested::Ptr{fmi3Boolean}, earlyReturnTime::Ptr{fmi3Float64}) - @debug "To be implemented!" + + @debug "fmi3CallbackIntermediateUpdate be implemented!" # [ToDo] end """ @@ -138,41 +152,8 @@ A model partition of a Scheduled Execution FMU calls `fmi3CallbackClockUpdate` t - FMISpec3.0, Version D5ef1c1: 5.2.2. State: Clock Activation Mode """ function fmi3CallbackClockUpdate(_instanceEnvironment::Ptr{Cvoid}) - @debug "to be implemented!" -end - -""" - fmi3FreeInstance!(c::FMU3Instance; popInstance::Bool = true) - -Disposes the given instance, unloads the loaded model, and frees all the allocated memory and other resources that have been allocated by the functions of the FMU interface. -If a null pointer is provided for “c”, the function call is ignored (does not have an effect). - -Removes the component from the FMUs component list. - -# Arguments -- `c::FMU3Instance`: Argument `c` is a Mutable struct represents an instantiated instance of an FMU in the FMI 3.0 Standard. - -# Keywords -- `popInstance::Bool=true`: If the Keyword `popInstance = true` the freed instance is deleted - -# Returns -- nothing - -# Source -- FMISpec3.0 Link: [https://fmi-standard.org/](https://fmi-standard.org/) -- FMISpec3.0, Version D5ef1c1: 2.3.1. Super State: FMU State Setable -""" -function fmi3FreeInstance!(c::FMU3Instance; popInstance::Bool = true) - - if popInstance - ind = findall(x->x.compAddr==c.compAddr, c.fmu.instances) - @assert length(ind) == 1 "fmi3FreeInstance!(...): Freeing $(length(ind)) instances with one call, this is not allowed." - deleteat!(c.fmu.instances, ind) - end - fmi3FreeInstance!(c.fmu.cFreeInstance, c.compAddr) - - nothing + @debug " fmi3CallbackClockUpdateto be implemented!" # [ToDo] end """ @@ -256,7 +237,7 @@ More detailed: See also [`fmi3SetDebugLogging`](@ref). """ function fmi3SetDebugLogging(c::FMU3Instance, logginOn::fmi3Boolean, nCategories::UInt, categories::Ptr{Nothing}) - status = fmi3SetDebugLogging(c.fmu.cSetDebugLogging, c.compAddr, logginOn, nCategories, categories) + status = fmi3SetDebugLogging(c.fmu.cSetDebugLogging, c.addr, logginOn, nCategories, categories) checkStatus(c, status) return status end @@ -308,7 +289,7 @@ function fmi3EnterInitializationMode(c::FMU3Instance, toleranceDefined::fmi3Bool if c.state != fmi3InstanceStateInstantiated @warn "fmi3EnterInitializationMode(...): Needs to be called in state `fmi3IntanceStateInstantiated`." end - status = fmi3EnterInitializationMode(c.fmu.cEnterInitializationMode, c.compAddr, toleranceDefined, tolerance, startTime, stopTimeDefined, stopTime) + status = fmi3EnterInitializationMode(c.fmu.cEnterInitializationMode, c.addr, toleranceDefined, tolerance, startTime, stopTimeDefined, stopTime) checkStatus(c, status) if status == fmi3StatusOK c.state = fmi3InstanceStateInitializationMode @@ -347,7 +328,7 @@ function fmi3ExitInitializationMode(c::FMU3Instance) @warn "fmi3ExitInitializationMode(...): Needs to be called in state `fmi3InstanceStateInitializationMode`." end - status = fmi3ExitInitializationMode(c.fmu.cExitInitializationMode, c.compAddr) + status = fmi3ExitInitializationMode(c.fmu.cExitInitializationMode, c.addr) checkStatus(c, status) if status == fmi3StatusOK if c.type == fmi3TypeCoSimulation && !c.fmu.modelDescription.coSimulation.hasEventMode @@ -399,7 +380,7 @@ function fmi3Terminate(c::FMU3Instance; soft::Bool=false) end end - status = fmi3Terminate(c.fmu.cTerminate, c.compAddr) + status = fmi3Terminate(c.fmu.cTerminate, c.addr) checkStatus(c, status) if status == fmi3StatusOK c.state = fmi3InstanceStateTerminated @@ -446,24 +427,24 @@ function fmi3Reset(c::FMU3Instance; soft::Bool = false) end if c.fmu.cReset == C_NULL - fmi3FreeInstance!(c.fmu.cFreeInstance, c.compAddr) + fmi3FreeInstance!(c.fmu.cFreeInstance, c.addr) if fmi3IsCoSimulation(c.fmu) - compAddr = fmi3InstantiateCoSimulation!(c.fmu) + addr = fmi3InstantiateCoSimulation!(c.fmu) elseif fmi3IsModelExchange(c.fmu) - compAddr = fmi3InstantiateModelExchange!(c.fmu) + addr = fmi3InstantiateModelExchange!(c.fmu) elseif fmi3IsScheduledExecution(c.fmu) - compAddr = fmi3InstantiateScheduledExecution!(c.fmu) + addr = fmi3InstantiateScheduledExecution!(c.fmu) end - if compAddr == Ptr{Cvoid}(C_NULL) + if addr == Ptr{Cvoid}(C_NULL) @error "fmi3Reset(...): Reinstantiation failed!" return fmi3StatusError end - c.compAddr = compAddr + c.addr = addr return fmi3StatusOK else - status = fmi3Reset(c.fmu.cReset, c.compAddr) + status = fmi3Reset(c.fmu.cReset, c.addr) checkStatus(c, status) if status == fmi3StatusOK c.state = fmi3InstanceStateInstantiated @@ -504,7 +485,7 @@ See also [`fmi3GetFloat32!`](@ref). """ function fmi3GetFloat32!(c::FMU3Instance, vr::AbstractArray{fmi3ValueReference}, nvr::Csize_t, value::AbstractArray{fmi3Float32}, nvalue::Csize_t) status = fmi3GetFloat32!(c.fmu.cGetFloat32, - c.compAddr, vr, nvr, value, nvalue) + c.addr, vr, nvr, value, nvalue) checkStatus(c, status) return status end @@ -541,7 +522,7 @@ See also [`fmi3SetFloat32`](@ref). """ function fmi3SetFloat32(c::FMU3Instance, vr::AbstractArray{fmi3ValueReference}, nvr::Csize_t, value::AbstractArray{fmi3Float32}, nvalue::Csize_t) status = fmi3SetFloat32(c.fmu.cSetFloat32, - c.compAddr, vr, nvr, value, nvalue) + c.addr, vr, nvr, value, nvalue) checkStatus(c, status) return status end @@ -577,7 +558,7 @@ See also [`fmi3GetFloat64!`](@ref). """ function fmi3GetFloat64!(c::FMU3Instance, vr::AbstractArray{fmi3ValueReference}, nvr::Csize_t, value::AbstractArray{fmi3Float64}, nvalue::Csize_t) status = fmi3GetFloat64!(c.fmu.cGetFloat64, - c.compAddr, vr, nvr, value, nvalue) + c.addr, vr, nvr, value, nvalue) checkStatus(c, status) return status end @@ -612,10 +593,30 @@ More detailed: - FMISpec3.0: 2.2.6.2. Getting and Setting Variable Values See also [`fmi3SetFloat64`](@ref). """ -function fmi3SetFloat64(c::FMU3Instance, vr::AbstractArray{fmi3ValueReference}, nvr::Csize_t, value::AbstractArray{fmi3Float64}, nvalue::Csize_t) +function fmi3SetFloat64(c::FMU3Instance, vr::AbstractArray{fmi3ValueReference}, nvr::Csize_t, value::AbstractArray{fmi3Float64}, nvalue::Csize_t; track::Bool=true) status = fmi3SetFloat64(c.fmu.cSetFloat64, - c.compAddr, vr, nvr, value, nvalue) + c.addr, vr, nvr, value, nvalue) checkStatus(c, status) + + if track && status == fmi2StatusOK + check_invalidate!(vr, c.∂ẋ_∂x) + check_invalidate!(vr, c.∂ẋ_∂u) + check_invalidate!(vr, c.∂ẋ_∂p) + + check_invalidate!(vr, c.∂y_∂x) + check_invalidate!(vr, c.∂y_∂u) + check_invalidate!(vr, c.∂y_∂p) + + check_invalidate!(vr, c.∂e_∂x) + check_invalidate!(vr, c.∂e_∂u) + check_invalidate!(vr, c.∂e_∂p) + + # [NOTE] No need to check for: + # check_invalidate!(vr, c.∂ẋ_∂t) + # check_invalidate!(vr, c.∂y_∂t) + # check_invalidate!(vr, c.∂e_∂t) + end + return status end @@ -651,7 +652,7 @@ See also [`fmi3GetInt8!`](@ref). """ function fmi3GetInt8!(c::FMU3Instance, vr::AbstractArray{fmi3ValueReference}, nvr::Csize_t, value::AbstractArray{fmi3Int8}, nvalue::Csize_t) status = fmi3GetInt8!(c.fmu.cGetInt8, - c.compAddr, vr, nvr, value, nvalue) + c.addr, vr, nvr, value, nvalue) checkStatus(c, status) return status end @@ -686,7 +687,7 @@ More detailed: """ function fmi3SetInt8(c::FMU3Instance, vr::AbstractArray{fmi3ValueReference}, nvr::Csize_t, value::AbstractArray{fmi3Int8}, nvalue::Csize_t) status = fmi3SetInt8(c.fmu.cSetInt8, - c.compAddr, vr, nvr, value, nvalue) + c.addr, vr, nvr, value, nvalue) checkStatus(c, status) return status end @@ -723,7 +724,7 @@ See also [`fmi3GetUInt8!`](@ref). """ function fmi3GetUInt8!(c::FMU3Instance, vr::AbstractArray{fmi3ValueReference}, nvr::Csize_t, value::AbstractArray{fmi3UInt8}, nvalue::Csize_t) status = fmi3GetUInt8!(c.fmu.cGetUInt8, - c.compAddr, vr, nvr, value, nvalue) + c.addr, vr, nvr, value, nvalue) checkStatus(c, status) return status end @@ -759,7 +760,7 @@ See also [`fmi3SetUInt8`](@ref). """ function fmi3SetUInt8(c::FMU3Instance, vr::AbstractArray{fmi3ValueReference}, nvr::Csize_t, value::AbstractArray{fmi3UInt8}, nvalue::Csize_t) status = fmi3SetUInt8(c.fmu.cSetUInt8, - c.compAddr, vr, nvr, value, nvalue) + c.addr, vr, nvr, value, nvalue) checkStatus(c, status) return status end @@ -796,7 +797,7 @@ See also [`fmi3GetInt16!`](@ref). """ function fmi3GetInt16!(c::FMU3Instance, vr::AbstractArray{fmi3ValueReference}, nvr::Csize_t, value::AbstractArray{fmi3Int16}, nvalue::Csize_t) status = fmi3GetInt16!(c.fmu.cGetInt16, - c.compAddr, vr, nvr, value, nvalue) + c.addr, vr, nvr, value, nvalue) checkStatus(c, status) return status end @@ -832,7 +833,7 @@ See also [`fmi3SetInt16`](@ref). """ function fmi3SetInt16(c::FMU3Instance, vr::AbstractArray{fmi3ValueReference}, nvr::Csize_t, value::AbstractArray{fmi3Int16}, nvalue::Csize_t) status = fmi3SetInt16(c.fmu.cSetInt16, - c.compAddr, vr, nvr, value, nvalue) + c.addr, vr, nvr, value, nvalue) checkStatus(c, status) return status end @@ -869,7 +870,7 @@ See also [`fmi3GetUInt16!`](@ref). """ function fmi3GetUInt16!(c::FMU3Instance, vr::AbstractArray{fmi3ValueReference}, nvr::Csize_t, value::AbstractArray{fmi3UInt16}, nvalue::Csize_t) status = fmi3GetUInt16!(c.fmu.cGetUInt16, - c.compAddr, vr, nvr, value, nvalue) + c.addr, vr, nvr, value, nvalue) checkStatus(c, status) return status end @@ -904,7 +905,7 @@ More detailed: """ function fmi3SetUInt16(c::FMU3Instance, vr::AbstractArray{fmi3ValueReference}, nvr::Csize_t, value::AbstractArray{fmi3UInt16}, nvalue::Csize_t) status = fmi3SetUInt16(c.fmu.cSetUInt16, - c.compAddr, vr, nvr, value, nvalue) + c.addr, vr, nvr, value, nvalue) checkStatus(c, status) return status end @@ -940,7 +941,7 @@ See also [`fmi3GetInt32!`](@ref). """ function fmi3GetInt32!(c::FMU3Instance, vr::AbstractArray{fmi3ValueReference}, nvr::Csize_t, value::AbstractArray{fmi3Int32}, nvalue::Csize_t) status = fmi3GetInt32!(c.fmu.cGetInt32, - c.compAddr, vr, nvr, value, nvalue) + c.addr, vr, nvr, value, nvalue) checkStatus(c, status) return status end @@ -976,7 +977,7 @@ See also [`fmi3SetInt32`](@ref). """ function fmi3SetInt32(c::FMU3Instance, vr::AbstractArray{fmi3ValueReference}, nvr::Csize_t, value::AbstractArray{fmi3Int32}, nvalue::Csize_t) status = fmi3SetInt32(c.fmu.cSetInt32, - c.compAddr, vr, nvr, value, nvalue) + c.addr, vr, nvr, value, nvalue) checkStatus(c, status) return status end @@ -1013,7 +1014,7 @@ See also [`fmi3GetUInt32!`](@ref). """ function fmi3GetUInt32!(c::FMU3Instance, vr::AbstractArray{fmi3ValueReference}, nvr::Csize_t, value::AbstractArray{fmi3UInt32}, nvalue::Csize_t) status = fmi3GetUInt32!(c.fmu.cGetUInt32, - c.compAddr, vr, nvr, value, nvalue) + c.addr, vr, nvr, value, nvalue) checkStatus(c, status) return status end @@ -1049,7 +1050,7 @@ See also [`fmi3SetUInt32`](@ref). """ function fmi3SetUInt32(c::FMU3Instance, vr::AbstractArray{fmi3ValueReference}, nvr::Csize_t, value::AbstractArray{fmi3UInt32}, nvalue::Csize_t) status = fmi3SetUInt32(c.fmu.cSetUInt32, - c.compAddr, vr, nvr, value, nvalue) + c.addr, vr, nvr, value, nvalue) checkStatus(c, status) return status end @@ -1086,7 +1087,7 @@ See also [`fmi3GetInt64!`](@ref). """ function fmi3GetInt64!(c::FMU3Instance, vr::AbstractArray{fmi3ValueReference}, nvr::Csize_t, value::AbstractArray{fmi3Int64}, nvalue::Csize_t) status = fmi3GetInt64!(c.fmu.cGetInt64, - c.compAddr, vr, nvr, value, nvalue) + c.addr, vr, nvr, value, nvalue) checkStatus(c, status) return status end @@ -1122,7 +1123,7 @@ See also [`fmi3SetInt64`](@ref). """ function fmi3SetInt64(c::FMU3Instance, vr::AbstractArray{fmi3ValueReference}, nvr::Csize_t, value::AbstractArray{fmi3Int64}, nvalue::Csize_t) status = fmi3SetInt64(c.fmu.cSetInt64, - c.compAddr, vr, nvr, value, nvalue) + c.addr, vr, nvr, value, nvalue) checkStatus(c, status) return status end @@ -1159,7 +1160,7 @@ See also [`fmi3GetUInt64!`](@ref). """ function fmi3GetUInt64!(c::FMU3Instance, vr::AbstractArray{fmi3ValueReference}, nvr::Csize_t, value::AbstractArray{fmi3UInt64}, nvalue::Csize_t) status = fmi3GetUInt64!(c.fmu.cGetUInt64, - c.compAddr, vr, nvr, value, nvalue) + c.addr, vr, nvr, value, nvalue) checkStatus(c, status) return status end @@ -1195,7 +1196,7 @@ See also [`fmi3SetUInt64`](@ref). """ function fmi3SetUInt64(c::FMU3Instance, vr::AbstractArray{fmi3ValueReference}, nvr::Csize_t, value::AbstractArray{fmi3UInt64}, nvalue::Csize_t) status = fmi3SetUInt64(c.fmu.cSetUInt64, - c.compAddr, vr, nvr, value, nvalue) + c.addr, vr, nvr, value, nvalue) checkStatus(c, status) return status end @@ -1231,7 +1232,7 @@ See also [`fmi3GetBoolean!`](@ref). """ function fmi3GetBoolean!(c::FMU3Instance, vr::AbstractArray{fmi3ValueReference}, nvr::Csize_t, value::AbstractArray{fmi3Boolean}, nvalue::Csize_t) status = fmi3GetBoolean!(c.fmu.cGetBoolean, - c.compAddr, vr, nvr, value, nvalue) + c.addr, vr, nvr, value, nvalue) checkStatus(c, status) return status end @@ -1267,7 +1268,7 @@ See also [`fmi3SetBoolean`](@ref). """ function fmi3SetBoolean(c::FMU3Instance, vr::AbstractArray{fmi3ValueReference}, nvr::Csize_t, value::AbstractArray{fmi3Boolean}, nvalue::Csize_t) status = fmi3SetBoolean(c.fmu.cSetBoolean, - c.compAddr, vr, nvr, value, nvalue) + c.addr, vr, nvr, value, nvalue) checkStatus(c, status) return status end @@ -1304,7 +1305,7 @@ See also [`fmi3GetString!`](@ref). """ function fmi3GetString!(c::FMU3Instance, vr::AbstractArray{fmi3ValueReference}, nvr::Csize_t, value::Vector{Ptr{Cchar}}, nvalue::Csize_t) status = fmi3GetString!(c.fmu.cGetString, - c.compAddr, vr, nvr, value, nvalue) + c.addr, vr, nvr, value, nvalue) checkStatus(c, status) return status end @@ -1340,7 +1341,7 @@ See also [`fmi3SetString`](@ref). """ function fmi3SetString(c::FMU3Instance, vr::AbstractArray{fmi3ValueReference}, nvr::Csize_t, value::Union{AbstractArray{Ptr{Cchar}}, AbstractArray{Ptr{UInt8}}}, nvalue::Csize_t) status = fmi3SetString(c.fmu.cSetString, - c.compAddr, vr, nvr, value, nvalue) + c.addr, vr, nvr, value, nvalue) checkStatus(c, status) return status end @@ -1377,7 +1378,7 @@ See also [`fmi3GetBinary!`](@ref). """ function fmi3GetBinary!(c::FMU3Instance, vr::AbstractArray{fmi3ValueReference}, nvr::Csize_t, valueSizes::AbstractArray{Csize_t}, value::AbstractArray{fmi3Binary}, nvalue::Csize_t) status = fmi3GetBinary!(c.fmu.cGetBinary, - c.compAddr, vr, nvr, valueSizes, value, nvalue) + c.addr, vr, nvr, valueSizes, value, nvalue) checkStatus(c, status) return status end @@ -1414,7 +1415,7 @@ See also [`fmi3SetBinary`](@ref). """ function fmi3SetBinary(c::FMU3Instance, vr::AbstractArray{fmi3ValueReference}, nvr::Csize_t, valueSizes::AbstractArray{Csize_t}, value::AbstractArray{fmi3Binary}, nvalue::Csize_t) status = fmi3SetBinary(c.fmu.cSetBinary, - c.compAddr, vr, nvr, valueSizes, value, nvalue) + c.addr, vr, nvr, valueSizes, value, nvalue) checkStatus(c, status) return status end @@ -1451,7 +1452,7 @@ See also [`fmi3GetClock!`](@ref). """ function fmi3GetClock!(c::FMU3Instance, vr::AbstractArray{fmi3ValueReference}, nvr::Csize_t, value::AbstractArray{fmi3Clock}) status = fmi3GetClock!(c.fmu.cGetClock, - c.compAddr, vr, nvr, value) + c.addr, vr, nvr, value) checkStatus(c, status) return status end @@ -1487,7 +1488,7 @@ See also [`fmi3SetClock`](@ref). """ function fmi3SetClock(c::FMU3Instance, vr::AbstractArray{fmi3ValueReference}, nvr::Csize_t, value::AbstractArray{fmi3Clock}) status = fmi3SetClock(c.fmu.cSetClock, - c.compAddr, vr, nvr, value) + c.addr, vr, nvr, value) checkStatus(c, status) return status end @@ -1520,7 +1521,7 @@ See also [`fmi3GetFMUState!`](@ref). """ function fmi3GetFMUState!(c::FMU3Instance, FMUstate::Ref{fmi3FMUState}) status = fmi3GetFMUState!(c.fmu.cGetFMUState, - c.compAddr, FMUstate) + c.addr, FMUstate) checkStatus(c, status) return status end @@ -1553,14 +1554,14 @@ See also [`fmi3SetFMUState`](@ref). """ function fmi3SetFMUState(c::FMU3Instance, FMUstate::fmi3FMUState) status = fmi3SetFMUState(c.fmu.cSetFMUState, - c.compAddr, FMUstate) + c.addr, FMUstate) checkStatus(c, status) return status end """ - fmi3FreeFMUState!(c::FMU3Instance, FMUstate::Ref{fmi3FMUState}) + fmi3FreeFMUState(c::FMU3Instance, FMUstate::Ref{fmi3FMUState}) Frees all memory and other resources allocated with the `fmi3GetFMUstate` call for this FMUstate. @@ -1583,9 +1584,9 @@ More detailed: - FMISpec3.0: 2.2.4 Status Returned by Functions - FMISpec3.0: 2.2.6.4. Getting and Setting the Complete FMU State """ -function fmi3FreeFMUState!(c::FMU3Instance, FMUstate::Ref{fmi3FMUState}) - status = fmi3FreeFMUState!(c.fmu.cFreeFMUState, - c.compAddr, FMUstate) +function fmi3FreeFMUState(c::FMU3Instance, FMUstate::Ref{fmi3FMUState}) + status = fmi3FreeFMUState(c.fmu.cFreeFMUState, + c.addr, FMUstate) checkStatus(c, status) return status end @@ -1619,7 +1620,7 @@ See also [`fmi3SerializedFMUStateSize!`](@ref). """ function fmi3SerializedFMUStateSize!(c::FMU3Instance, FMUstate::fmi3FMUState, size::Ref{Csize_t}) status = fmi3SerializedFMUStateSize!(c.fmu.cSerializedFMUStateSize, - c.compAddr, FMUstate, size) + c.addr, FMUstate, size) checkStatus(c, status) return status end @@ -1654,7 +1655,7 @@ See also [`fmi3SerializeFMUState!`](@ref). """ function fmi3SerializeFMUState!(c::FMU3Instance, FMUstate::fmi3FMUState, serialzedState::AbstractArray{fmi3Byte}, size::Csize_t) status = fmi3SerializeFMUState!(c.fmu.cSerializeFMUState, - c.compAddr, FMUstate, serialzedState, size) + c.addr, FMUstate, serialzedState, size) checkStatus(c, status) return status end @@ -1689,7 +1690,7 @@ See also [`fmi3DeSerializeFMUState!`](@ref). """ function fmi3DeSerializeFMUState!(c::FMU3Instance, serialzedState::AbstractArray{fmi3Byte}, size::Csize_t, FMUstate::Ref{fmi3FMUState}) status = fmi3DeSerializeFMUState!(c.fmu.cDeSerializeFMUState, - c.compAddr, serialzedState, size, FMUstate) + c.addr, serialzedState, size, FMUstate) checkStatus(c, status) return status end @@ -1724,7 +1725,7 @@ See also [`fmi3SetIntervalDecimal`](@ref). """ function fmi3SetIntervalDecimal(c::FMU3Instance, vr::AbstractArray{fmi3ValueReference}, nvr::Csize_t, intervals::AbstractArray{fmi3Float64}) status = fmi3SetIntervalDecimal(c.fmu.cSetIntervalDecimal, - c.compAddr, vr, nvr, intervals) + c.addr, vr, nvr, intervals) checkStatus(c, status) return status end @@ -1760,7 +1761,7 @@ See also [`fmi3SetIntervalFraction`](@ref). """ function fmi3SetIntervalFraction(c::FMU3Instance, vr::AbstractArray{fmi3ValueReference}, nvr::Csize_t, intervalCounters::AbstractArray{fmi3UInt64}, resolutions::AbstractArray{fmi3UInt64}) status = fmi3SetIntervalFraction(c.fmu.cSetIntervalFraction, - c.compAddr, vr, nvr, intervalCounters, resolutions) + c.addr, vr, nvr, intervalCounters, resolutions) checkStatus(c, status) return status end @@ -1803,7 +1804,7 @@ See also [`fmi3GetIntervalDecimal!`](@ref). """ function fmi3GetIntervalDecimal!(c::FMU3Instance, vr::AbstractArray{fmi3ValueReference}, nvr::Csize_t, intervals::AbstractArray{fmi3Float64}, qualifiers::fmi3IntervalQualifier) status = fmi3GetIntervalDecimal!(c.fmu.cGetIntervalDecimal, - c.compAddr, vr, nvr, intervals, qualifiers) + c.addr, vr, nvr, intervals, qualifiers) checkStatus(c, status) return status end @@ -1847,7 +1848,7 @@ See also [`fmi3GetIntervalFraction!`](@ref). """ function fmi3GetIntervalFraction!(c::FMU3Instance, vr::AbstractArray{fmi3ValueReference}, nvr::Csize_t, intervalCounters::AbstractArray{fmi3UInt64}, resolutions::AbstractArray{fmi3UInt64}, qualifiers::fmi3IntervalQualifier) status = fmi3GetIntervalFraction!(c.fmu.cGetIntervalFraction, - c.compAddr, vr, nvr, intervalCounters, resolutions, qualifiers) + c.addr, vr, nvr, intervalCounters, resolutions, qualifiers) checkStatus(c, status) return status end @@ -1883,7 +1884,7 @@ See also [`fmi3GetShiftDecimal!`](@ref). """ function fmi3GetShiftDecimal!(c::FMU3Instance, vr::AbstractArray{fmi3ValueReference}, nvr::Csize_t, shifts::AbstractArray{fmi3Float64}) status = fmi3GetShiftDecimal!(c.fmu.cGetShiftDecimal, - c.compAddr, vr, nvr, shifts) + c.addr, vr, nvr, shifts) checkStatus(c, status) return status end @@ -1920,7 +1921,7 @@ See also [`fmi3GetShiftFraction!`](@ref). """ function fmi3GetShiftFraction!(c::FMU3Instance, vr::AbstractArray{fmi3ValueReference}, nvr::Csize_t, shiftCounters::AbstractArray{fmi3UInt64}, resolutions::AbstractArray{fmi3UInt64}) status = fmi3GetShiftFraction!(c.fmu.cGetShiftFraction, - c.compAddr, vr, nvr, shiftCounters, resolutions) + c.addr, vr, nvr, shiftCounters, resolutions) checkStatus(c, status) return status end @@ -1957,7 +1958,7 @@ See also [`fmi3ActivateModelPartition`](@ref). """ function fmi3ActivateModelPartition(c::FMU3Instance, vr::fmi3ValueReference, activationTime::AbstractArray{fmi3Float64}) status = fmi3ActivateModelPartition(c.fmu.cActivateModelPartition, - c.compAddr, vr, activationTime) + c.addr, vr, activationTime) checkStatus(c, status) return status end @@ -1993,7 +1994,7 @@ See also [`fmi3GetNumberOfVariableDependencies!`](@ref). """ function fmi3GetNumberOfVariableDependencies!(c::FMU3Instance, vr::fmi3ValueReference, nvr::Ref{Csize_t}) status = fmi3GetNumberOfVariableDependencies!(c.fmu.cGetNumberOfVariableDependencies, - c.compAddr, vr, nvr) + c.addr, vr, nvr) checkStatus(c, status) return status end @@ -2038,7 +2039,7 @@ See also [`fmi3GetVariableDependencies!`](@ref). function fmi3GetVariableDependencies!(c::FMU3Instance, vr::fmi3ValueReference, elementIndiceOfDependents::AbstractArray{Csize_t}, independents::AbstractArray{fmi3ValueReference}, elementIndiceOfInpendents::AbstractArray{Csize_t}, dependencyKind::AbstractArray{fmi3DependencyKind}, ndependencies::Csize_t) status = fmi3GetVariableDependencies!(c.fmu.cGetVariableDependencies, - c.compAddr, vr, elementIndiceOfDependents, independents, elementIndiceOfInpendents, dependencyKind, ndependencies) + c.addr, vr, elementIndiceOfDependents, independents, elementIndiceOfInpendents, dependencyKind, ndependencies) checkStatus(c, status) return status end @@ -2109,10 +2110,11 @@ function fmi3GetDirectionalDerivative!(c::FMU3Instance, nSeed::Csize_t, sensitivity::AbstractArray{fmi3Float64}, nSensitivity::Csize_t) - @assert fmi3ProvidesDirectionalDerivatives(c.fmu) ["fmi3GetDirectionalDerivative!(...): This FMU does not support build-in directional derivatives!"] + + @assert providesDirectionalDerivatives(c.fmu) ["fmi3GetDirectionalDerivative!(...): This FMU does not support build-in directional derivatives!"] status = fmi3GetDirectionalDerivative!(c.fmu.cGetDirectionalDerivative, - c.compAddr, unknowns, nUnknowns, knowns, nKnowns, seed, nSeed, sensitivity, nSensitivity) + c.addr, unknowns, nUnknowns, knowns, nKnowns, seed, nSeed, sensitivity, nSensitivity) checkStatus(c, status) return status @@ -2184,10 +2186,10 @@ function fmi3GetAdjointDerivative!(c::FMU3Instance, nSeed::Csize_t, sensitivity::AbstractArray{fmi3Float64}, nSensitivity::Csize_t) - @assert fmi3ProvidesAdjointDerivatives(c.fmu) ["fmi3GetAdjointDerivative!(...): This FMU does not support build-in adjoint derivatives!"] + @assert providesAdjointDerivatives(c.fmu) ["fmi3GetAdjointDerivative!(...): This FMU does not support build-in adjoint derivatives!"] status = fmi3GetAdjointDerivative!(c.fmu.cGetAdjointDerivative, - c.compAddr, unknowns, nUnknowns, knowns, nKnowns, seed, nSeed, sensitivity, nSensitivity) + c.addr, unknowns, nUnknowns, knowns, nKnowns, seed, nSeed, sensitivity, nSensitivity) checkStatus(c, status) return status @@ -2226,7 +2228,7 @@ See also [`fmi3GetOutputDerivatives!`](@ref). """ function fmi3GetOutputDerivatives!(c::FMU3Instance, vr::AbstractArray{fmi3ValueReference}, nValueReferences::Csize_t, order::AbstractArray{fmi3Int32}, values::AbstractArray{fmi3Float64}, nValues::Csize_t) status = fmi3GetOutputDerivatives!(c.fmu.cGetOutputDerivatives, - c.compAddr, vr, nValueReferences, order, values, nValues) + c.addr, vr, nValueReferences, order, values, nValues) checkStatus(c, status) return status end @@ -2270,7 +2272,7 @@ function fmi3EnterConfigurationMode(c::FMU3Instance; soft::Bool=false) end status = fmi3EnterConfigurationMode(c.fmu.cEnterConfigurationMode, - c.compAddr) + c.addr) checkStatus(c, status) if status == fmi3StatusOK if c.state == fmi3InstanceStateInstantiate @@ -2321,7 +2323,7 @@ function fmi3ExitConfigurationMode(c::FMU3Instance; soft::Bool = false) end status = fmi3ExitConfigurationMode(c.fmu.cExitConfigurationMode, - c.compAddr) + c.addr) checkStatus(c, status) if status == fmi3StatusOK if c.state == fmi3InstanceStateConfigurationMode @@ -2368,7 +2370,7 @@ See also [`fmi3GetNumberOfContinuousStates!`](@ref). """ function fmi3GetNumberOfContinuousStates!(c::FMU3Instance, nContinuousStates::Ref{Csize_t}) status = fmi3GetNumberOfContinuousStates!(c.fmu.cGetNumberOfContinuousStates, - c.compAddr, nContinuousStates) + c.addr, nContinuousStates) checkStatus(c, status) return status end @@ -2404,7 +2406,7 @@ See also [`fmi3GetNumberOfEventIndicators!`](@ref). """ function fmi3GetNumberOfEventIndicators!(c::FMU3Instance, nEventIndicators::Ref{Csize_t}) status = fmi3GetNumberOfEventIndicators!(c.fmu.cGetNumberOfEventIndicators, - c.compAddr, nEventIndicators) + c.addr, nEventIndicators) checkStatus(c, status) return status end @@ -2441,7 +2443,7 @@ See also [`fmi3GetContinuousStates!`](@ref). """ function fmi3GetContinuousStates!(c::FMU3Instance, nominals::AbstractArray{fmi3Float64}, nContinuousStates::Csize_t) status = fmi3GetContinuousStates!(c.fmu.cGetContinuousStates, - c.compAddr, nominals, nContinuousStates) + c.addr, nominals, nContinuousStates) checkStatus(c, status) return status end @@ -2479,7 +2481,7 @@ See also [`fmi3GetNominalsOfContinuousStates!`](@ref). """ function fmi3GetNominalsOfContinuousStates!(c::FMU3Instance, x_nominal::AbstractArray{fmi3Float64}, nx::Csize_t) status = fmi3GetNominalsOfContinuousStates!(c.fmu.cGetNominalsOfContinuousStates, - c.compAddr, x_nominal, nx) + c.addr, x_nominal, nx) checkStatus(c, status) return status end @@ -2514,7 +2516,7 @@ See also [`fmi3EvaluateDiscreteStates`](@ref). """ function fmi3EvaluateDiscreteStates(c::FMU3Instance) status = fmi3EvaluateDiscreteStates(c.fmu.cEvaluateDiscreteStates, - c.compAddr) + c.addr) checkStatus(c, status) return status end @@ -2526,7 +2528,6 @@ end This function is called to signal a converged solution at the current super-dense time instant. fmi3UpdateDiscreteStates must be called at least once per super-dense time instant. -# TODO Arguments # Arguments - `c::FMU3Instance`: Mutable struct represents an instantiated instance of an FMU in the FMI 3.0 Standard. - `discreteStatesNeedUpdate::Ref{fmi3Boolean}`: @@ -2556,7 +2557,7 @@ function fmi3UpdateDiscreteStates(c::FMU3Instance, discreteStatesNeedUpdate::Ref nominalsOfContinuousStatesChanged::Ref{fmi3Boolean}, valuesOfContinuousStatesChanged::Ref{fmi3Boolean}, nextEventTimeDefined::Ref{fmi3Boolean}, nextEventTime::Ref{fmi3Float64}) status = fmi3UpdateDiscreteStates(c.fmu.cUpdateDiscreteStates, - c.compAddr, discreteStatesNeedUpdate, terminateSimulation, nominalsOfContinuousStatesChanged, valuesOfContinuousStatesChanged, nextEventTimeDefined, nextEventTime) + c.addr, discreteStatesNeedUpdate, terminateSimulation, nominalsOfContinuousStatesChanged, valuesOfContinuousStatesChanged, nextEventTimeDefined, nextEventTime) checkStatus(c, status) return status end @@ -2601,7 +2602,7 @@ function fmi3EnterContinuousTimeMode(c::FMU3Instance; soft::Bool=false) end status = fmi3EnterContinuousTimeMode(c.fmu.cEnterContinuousTimeMode, - c.compAddr) + c.addr) checkStatus(c, status) if status == fmi3StatusOK c.state = fmi3InstanceStateContinuousTimeMode @@ -2648,7 +2649,7 @@ function fmi3EnterStepMode(c::FMU3Instance; soft::Bool = false) end status = fmi3EnterStepMode(c.fmu.cEnterStepMode, - c.compAddr) + c.addr) checkStatus(c, status) if status == fmi3StatusOK c.state = fmi3InstanceStateStepMode @@ -2684,14 +2685,22 @@ More detailed: See also [`fmi3SetTime`](@ref). """ -function fmi3SetTime(c::FMU3Instance, time::fmi3Float64) +function fmi3SetTime(c::FMU3Instance, time::fmi3Float64; track::Bool=true) status = fmi3SetTime(c.fmu.cSetTime, - c.compAddr, time + c.t_offset) + c.addr, time + c.t_offset) checkStatus(c, status) - if status == fmi3StatusOK - c.t = time + + if track + if isStatusOK(c, status) + c.t = time + + invalidate!(c.∂ẋ_∂t) + invalidate!(c.∂y_∂t) + invalidate!(c.∂e_∂t) + end end + return status end @@ -2731,7 +2740,7 @@ function fmi3SetContinuousStates(c::FMU3Instance, x::AbstractArray{fmi3Float64}, nx::Csize_t) status = fmi3SetContinuousStates(c.fmu.cSetContinuousStates, - c.compAddr, x, nx) + c.addr, x, nx) checkStatus(c, status) return status end @@ -2770,7 +2779,7 @@ function fmi3GetContinuousStateDerivatives!(c::FMU3Instance, derivatives::AbstractArray{fmi3Float64}, nx::Csize_t) status = fmi3GetContinuousStateDerivatives!(c.fmu.cGetContinuousStateDerivatives, - c.compAddr, derivatives, nx) + c.addr, derivatives, nx) checkStatus(c, status) return status end @@ -2805,7 +2814,7 @@ See also [`fmi3GetEventIndicators!`](@ref). """ function fmi3GetEventIndicators!(c::FMU3Instance, eventIndicators::AbstractArray{fmi3Float64}, ni::Csize_t) status = fmi3GetEventIndicators!(c.fmu.cGetEventIndicators, - c.compAddr, eventIndicators, ni) + c.addr, eventIndicators, ni) checkStatus(c, status) return status end @@ -2848,7 +2857,7 @@ function fmi3CompletedIntegratorStep!(c::FMU3Instance, enterEventMode::Ref{fmi3Boolean}, terminateSimulation::Ref{fmi3Boolean}) status = fmi3CompletedIntegratorStep!(c.fmu.cCompletedIntegratorStep, - c.compAddr, noSetFMUStatePriorToCurrentPoint, enterEventMode, terminateSimulation) + c.addr, noSetFMUStatePriorToCurrentPoint, enterEventMode, terminateSimulation) checkStatus(c, status) return status end @@ -2899,7 +2908,7 @@ function fmi3EnterEventMode(c::FMU3Instance, stepEvent::fmi3Boolean, stateEvent: end status = fmi3EnterEventMode(c.fmu.cEnterEventMode, - c.compAddr, stepEvent, stateEvent, rootsFound, nEventIndicators, timeEvent) + c.addr, stepEvent, stateEvent, rootsFound, nEventIndicators, timeEvent) checkStatus(c, status) if status == fmi3StatusOK c.state = fmi3InstanceStateEventMode @@ -2948,7 +2957,7 @@ function fmi3DoStep!(c::FMU3Instance, currentCommunicationPoint::fmi3Float64, co @assert c.fmu.cDoStep != C_NULL ["fmi3DoStep(...): This FMU does not support fmi3DoStep, probably it's a ME-FMU with no CS-support?"] status = fmi3DoStep!(c.fmu.cDoStep, - c.compAddr, currentCommunicationPoint, communicationStepSize, noSetFMUStatePriorToCurrentPoint, eventEncountered, terminateSimulation, earlyReturn, lastSuccessfulTime) + c.addr, currentCommunicationPoint, communicationStepSize, noSetFMUStatePriorToCurrentPoint, eventEncountered, terminateSimulation, earlyReturn, lastSuccessfulTime) checkStatus(c, status) return status end \ No newline at end of file diff --git a/src/FMI3/convert.jl b/src/FMI3/convert.jl deleted file mode 100644 index 2e3ae9b..0000000 --- a/src/FMI3/convert.jl +++ /dev/null @@ -1,91 +0,0 @@ -# -# Copyright (c) 2021 Tobias Thummerer, Lars Mikelsons, Josef Kircher -# Licensed under the MIT license. See LICENSE file in the project root for details. -# - -function prepareValueReference(md::fmi3ModelDescription, vr::fmi3ValueReferenceFormat) - tvr = typeof(vr) - if isa(vr, AbstractArray{fmi3ValueReference,1}) - return vr - elseif tvr == fmi3ValueReference - return [vr] - elseif tvr == String - return [fmi3StringToValueReference(md, vr)] - elseif isa(vr, AbstractArray{String,1}) - return fmi3StringToValueReference(md, vr) - elseif tvr == Int64 - return [fmi3ValueReference(vr)] - elseif isa(vr, AbstractArray{Int64,1}) - return fmi3ValueReference.(vr) - elseif tvr == Nothing - return Array{fmi3ValueReference,1}() - end - - @assert false "prepareValueReference(...): Unknown value reference structure `$tvr`." -end -function prepareValueReference(fmu::FMU3, vr::fmi3ValueReferenceFormat) - prepareValueReference(fmu.modelDescription, vr) -end -function prepareValueReference(comp::FMU3Instance, vr::fmi3ValueReferenceFormat) - prepareValueReference(comp.fmu.modelDescription, vr) -end - -""" -Returns an array of ValueReferences coresponding to the variable names. -""" -function fmi3StringToValueReference(md::fmi3ModelDescription, names::AbstractArray{String}) - vr = Array{fmi3ValueReference}(undef,0) - for name in names - reference = fmi3StringToValueReference(md, name) - if reference === nothing - @warn "Value reference for variable '$name' not found, skipping." - else - push!(vr, reference) - end - end - vr -end - -""" -Returns the model variable(s) fitting the value reference. -""" -function fmi3ModelVariablesForValueReference(md::fmi3ModelDescription, vr::fmi3ValueReference) - ar = [] - for modelVariable in md.modelVariables - if modelVariable.valueReference == vr - push!(ar, modelVariable) - end - end - ar -end - -""" -Returns the ValueReference coresponding to the variable name. -""" -function fmi3StringToValueReference(md::fmi3ModelDescription, name::String) - reference = nothing - if haskey(md.stringValueReferences, name) - reference = md.stringValueReferences[name] - else - @warn "No variable named '$name' found." - end - reference -end - -function fmi3StringToValueReference(fmu::FMU3, name::Union{String, AbstractArray{String}}) - fmi3StringToValueReference(fmu.modelDescription, name) -end - -""" -Returns an array of variable names matching a fmi3ValueReference. -""" -function fmi3ValueReferenceToString(md::fmi3ModelDescription, reference::fmi3ValueReference) - [k for (k,v) in md.stringValueReferences if v == reference] -end -function fmi3ValueReferenceToString(md::fmi3ModelDescription, reference::Int64) - fmi3ValueReferenceToString(md, fmi3ValueReference(reference)) -end - -function fmi3ValueReferenceToString(fmu::FMU3, reference::Union{fmi3ValueReference, Int64}) - fmi3ValueReferenceToString(fmu.modelDescription, reference) -end \ No newline at end of file diff --git a/src/FMI3/ext.jl b/src/FMI3/ext.jl index 8341e7e..e6dbe4c 100644 --- a/src/FMI3/ext.jl +++ b/src/FMI3/ext.jl @@ -4,97 +4,13 @@ # # What is included in the file `FMI3_ext.jl` (external/additional functions)? -# - new functions, that are useful, but not part of the FMI-spec (example: `fmi3Load`) +# - TODO using Libdl -using ZipFile -import Downloads """ - fmi3Unzip(pathToFMU::String; unpackPath=nothing, cleanup=true) - -Create a copy of the .fmu file as a .zip folder and unzips it. -Returns the paths to the zipped and unzipped folders. - -# Arguments -- `pathToFMU::String`: The folder path to the .zip folder. - -# Keywords -- `unpackPath=nothing`: Via optional argument ```unpackPath```, a path to unpack the FMU can be specified (default: system temporary directory). -- `cleanup=true`: The cleanup option controls whether the temporary directory is automatically deleted when the process exits. - -# Returns -- `unzippedAbsPath::String`: Contains the Path to the uzipped Folder. -- `zipAbsPath::String`: Contains the Path to the zipped Folder. - -See also [`mktempdir`](https://docs.julialang.org/en/v1/base/file/#Base.Filesystem.mktempdir-Tuple{AbstractString}). -""" -function fmi3Unzip(pathToFMU::String; unpackPath=nothing, cleanup=true) - - fileNameExt = basename(pathToFMU) - (fileName, fileExt) = splitext(fileNameExt) - - if unpackPath === nothing - # cleanup=true leads to issues with automatic testing on linux server. TODO - unpackPath = mktempdir(; prefix="fmijl_", cleanup=cleanup) - end - - zipPath = joinpath(unpackPath, fileName * ".zip") - unzippedPath = joinpath(unpackPath, fileName) - - # only copy ZIP if not already there - if !isfile(zipPath) - cp(pathToFMU, zipPath; force=true) - end - - @assert isfile(zipPath) ["fmi3Unzip(...): ZIP-Archive couldn't be copied to `$zipPath`."] - - zipAbsPath = isabspath(zipPath) ? zipPath : joinpath(pwd(), zipPath) - unzippedAbsPath = isabspath(unzippedPath) ? unzippedPath : joinpath(pwd(), unzippedPath) - - @assert isfile(zipAbsPath) ["fmi3Unzip(...): Can't deploy ZIP-Archive at `$(zipAbsPath)`."] - - numFiles = 0 - - # only unzip if not already done - if !isdir(unzippedAbsPath) - mkpath(unzippedAbsPath) - - zarchive = ZipFile.Reader(zipAbsPath) - for f in zarchive.files - fileAbsPath = normpath(joinpath(unzippedAbsPath, f.name)) - - if endswith(f.name,"/") || endswith(f.name,"\\") - mkpath(fileAbsPath) # mkdir(fileAbsPath) - - @assert isdir(fileAbsPath) ["fmi3Unzip(...): Can't create directory `$(f.name)` at `$(fileAbsPath)`."] - else - # create directory if not forced by zip file folder - mkpath(dirname(fileAbsPath)) - - numBytes = write(fileAbsPath, read(f)) - - if numBytes == 0 - @info "fmi3Unzip(...): Written file `$(f.name)`, but file is empty." - end - - @assert isfile(fileAbsPath) ["fmi3Unzip(...): Can't unzip file `$(f.name)` at `$(fileAbsPath)`."] - numFiles += 1 - end - end - close(zarchive) - end - - @assert isdir(unzippedAbsPath) ["fmi3Unzip(...): ZIP-Archive couldn't be unzipped at `$(unzippedPath)`."] - @info "fmi3Unzip(...): Successfully unzipped $numFiles files at `$unzippedAbsPath`." - - (unzippedAbsPath, zipAbsPath) -end - -""" - - fmi3Load(pathToFMU::String; unpackPath=nothing, type=nothing, cleanup=true) + createFMU3 Sets the properties of the fmu by reading the modelDescription.xml. Retrieves all the pointers of binary functions. @@ -116,46 +32,40 @@ Retrieves all the pointers of binary functions. See also . """ -function fmi3Load(pathToFMU::String; unpackPath=nothing, type=nothing, cleanup=true) +function createFMU3(fmuPath, fmuZipPath; type::Union{Symbol, Nothing}=nothing) # Create uninitialized FMU fmu = FMU3() - if startswith(pathToFMU, "http") - @info "Downloading FMU from `$(pathToFMU)`." - pathToFMU = download(pathToFMU) - end - - pathToFMU = normpath(pathToFMU) - # set paths for fmu handling - (fmu.path, fmu.zipPath) = fmi3Unzip(pathToFMU; unpackPath=unpackPath, cleanup=cleanup) # TODO + fmu.path = fmuPath + fmu.zipPath = fmuZipPath # set paths for modelExchangeScripting and binary - tmpName = splitpath(fmu.path) pathToModelDescription = joinpath(fmu.path, "modelDescription.xml") # parse modelDescription.xml fmu.modelDescription = fmi3LoadModelDescription(pathToModelDescription) # TODO Matrix mit Dimensions fmu.modelName = fmu.modelDescription.modelName + fmu.isZeroState = (length(fmu.modelDescription.stateValueReferences) == 0) # TODO special use case? not complete, some combinations are missing - if (fmi3IsCoSimulation(fmu.modelDescription) && fmi3IsModelExchange(fmu.modelDescription) && type==:CS) - fmu.type = fmi3TypeCoSimulation::fmi3Type - elseif (fmi3IsCoSimulation(fmu.modelDescription) && fmi3IsModelExchange(fmu.modelDescription) && type==:ME) - fmu.type = fmi3TypeModelExchange::fmi3Type - elseif fmi3IsScheduledExecution(fmu.modelDescription) && type==:SE - fmu.type = fmi3TypeScheduledExecution::fmi3Type - elseif fmi3IsCoSimulation(fmu.modelDescription) && (type===nothing || type==:CS) - fmu.type = fmi3TypeCoSimulation::fmi3Type - elseif fmi3IsModelExchange(fmu.modelDescription) && (type===nothing || type==:ME) - fmu.type = fmi3TypeModelExchange::fmi3Type - elseif fmi3IsScheduledExecution(fmu.modelDescription) && (type === nothing || type ==:SE) - fmu.type = fmi3TypeScheduledExecution::Fmi3Type + if isCoSimulation(fmu.modelDescription) && isModelExchange(fmu.modelDescription) && type==:CS + fmu.type = fmi3TypeCoSimulation + elseif isCoSimulation(fmu.modelDescription) && isModelExchange(fmu.modelDescription) && type==:ME + fmu.type = fmi3TypeModelExchange + elseif isScheduledExecution(fmu.modelDescription) && type==:SE + fmu.type = fmi3TypeScheduledExecution + elseif isCoSimulation(fmu.modelDescription) && (type===nothing || type==:CS) + fmu.type = fmi3TypeCoSimulation + elseif isModelExchange(fmu.modelDescription) && (type===nothing || type==:ME) + fmu.type = fmi3TypeModelExchange + elseif isScheduledExecution(fmu.modelDescription) && (type === nothing || type ==:SE) + fmu.type = fmi3TypeScheduledExecution else error(unknownFMUType) end - fmuName = fmi3GetModelIdentifier(fmu.modelDescription) # tmpName[length(tmpName)] TODO + fmuName = getModelIdentifier(fmu.modelDescription) # tmpName[length(tmpName)] TODO directoryBinary = "" pathToBinary = "" @@ -166,7 +76,7 @@ function fmi3Load(pathToFMU::String; unpackPath=nothing, type=nothing, cleanup=t osStr = "" juliaArch = Sys.WORD_SIZE - @assert (juliaArch == 64 || juliaArch == 32) "fmi3Load(...): Unknown Julia Architecture with $(juliaArch)-bit, must be 64- or 32-bit." + @assert (juliaArch == 64 || juliaArch == 32) "createFMU3(...): Unknown Julia Architecture with $(juliaArch)-bit, must be 64- or 32-bit." if Sys.iswindows() if juliaArch == 64 @@ -193,10 +103,10 @@ function fmi3Load(pathToFMU::String; unpackPath=nothing, type=nothing, cleanup=t osStr = "Mac" fmuExt = "dylib" else - @assert false "fmi3Load(...): Unsupported target platform. Supporting Windows, Linux and Mac. Please open an issue if you want to use another OS." + @assert false "createFMU3(...): Unsupported target platform. Supporting Windows, Linux and Mac. Please open an issue if you want to use another OS." end - @assert (length(directories) > 0) "fmi3Load(...): Unsupported architecture. Supporting Julia for Windows (64- and 32-bit), Linux (64-bit) and Mac (64-bit). Please open an issue if you want to use another architecture." + @assert (length(directories) > 0) "createFMU3(...): Unsupported architecture. Supporting Julia for Windows (64- and 32-bit), Linux (64-bit) and Mac (64-bit). Please open an issue if you want to use another architecture." for directory in directories directoryBinary = joinpath(fmu.path, directory) if isdir(directoryBinary) @@ -204,33 +114,31 @@ function fmi3Load(pathToFMU::String; unpackPath=nothing, type=nothing, cleanup=t break end end - @assert isfile(pathToBinary) "fmi3Load(...): Target platform is $(osStr), but can't find valid FMU binary at `$(pathToBinary)` for path `$(fmu.path)`." + @assert isfile(pathToBinary) "createFMU3(...): Target platform is $(osStr), but can't find valid FMU binary at `$(pathToBinary)` for path `$(fmu.path)`." # make URI ressource location tmpResourceLocation = string("file:///", fmu.path) tmpResourceLocation = joinpath(tmpResourceLocation, "resources") - fmu.fmuResourceLocation = replace(tmpResourceLocation, "\\" => "/") # URIs.escapeuri(tmpResourceLocation) + fmu.fmuResourceLocation = replace(tmpResourceLocation, "\\" => "/") - @info "fmi3Load(...): FMU resources location is `$(fmu.fmuResourceLocation)`" + @info "createFMU3(...): FMU resources location is `$(fmu.fmuResourceLocation)`" - if fmi3IsCoSimulation(fmu) && fmi3IsModelExchange(fmu) - @info "fmi3Load(...): FMU supports both CS and ME, using CS as default if nothing specified." # TODO ScheduledExecution + if isCoSimulation(fmu) && isModelExchange(fmu) + @info "createFMU3(...): FMU supports both CS and ME, using CS as default if nothing specified." # TODO ScheduledExecution end fmu.binaryPath = pathToBinary - loadBinary(fmu) + loadPointers(fmu) - # dependency matrix - # fmu.dependencies - - fmu + return fmu end """ - loadBinary(fmu::FMU3) + loadPointers(fmu::FMU3) + load pointers to `fmu`\`s c functions from shared library handle (provided by `fmu.libHandle`) """ -function loadBinary(fmu::FMU3) +function loadPointers(fmu::FMU3) lastDirectory = pwd() cd(dirname(fmu.binaryPath)) @@ -284,35 +192,35 @@ function loadBinary(fmu::FMU3) fmu.cGetBinary = dlsym_opt(fmu.libHandle, :fmi3GetBinary) fmu.cSetBinary = dlsym_opt(fmu.libHandle, :fmi3SetBinary) - if fmi3CanGetSetState(fmu) + if canGetSetFMUState(fmu) fmu.cGetFMUState = dlsym_opt(fmu.libHandle, :fmi3GetFMUState) fmu.cSetFMUState = dlsym_opt(fmu.libHandle, :fmi3SetFMUState) fmu.cFreeFMUState = dlsym_opt(fmu.libHandle, :fmi3FreeFMUState) end - if fmi3CanSerializeFMUState(fmu) + if canSerializeFMUState(fmu) fmu.cSerializedFMUStateSize = dlsym_opt(fmu.libHandle, :fmi3SerializedFMUStateSize) fmu.cSerializeFMUState = dlsym_opt(fmu.libHandle, :fmi3SerializeFMUState) fmu.cDeSerializeFMUState = dlsym_opt(fmu.libHandle, :fmi3DeserializeFMUState) end - if fmi3ProvidesDirectionalDerivatives(fmu) + if providesDirectionalDerivatives(fmu) fmu.cGetDirectionalDerivative = dlsym_opt(fmu.libHandle, :fmi3GetDirectionalDerivative) end - if fmi3ProvidesAdjointDerivatives(fmu) + if providesAdjointDerivatives(fmu) fmu.cGetAdjointDerivative = dlsym_opt(fmu.libHandle, :fmi3GetAdjointDerivative) end # CS specific function calls - if fmi3IsCoSimulation(fmu) + if isCoSimulation(fmu) fmu.cGetOutputDerivatives = dlsym(fmu.libHandle, :fmi3GetOutputDerivatives) fmu.cEnterStepMode = dlsym(fmu.libHandle, :fmi3EnterStepMode) fmu.cDoStep = dlsym(fmu.libHandle, :fmi3DoStep) end # ME specific function calls - if fmi3IsModelExchange(fmu) + if isModelExchange(fmu) fmu.cGetNumberOfContinuousStates = dlsym(fmu.libHandle, :fmi3GetNumberOfContinuousStates) fmu.cGetNumberOfEventIndicators = dlsym(fmu.libHandle, :fmi3GetNumberOfEventIndicators) fmu.cGetContinuousStates = dlsym(fmu.libHandle, :fmi3GetContinuousStates) @@ -327,7 +235,7 @@ function loadBinary(fmu::FMU3) fmu.cUpdateDiscreteStates = dlsym(fmu.libHandle, :fmi3UpdateDiscreteStates) end - if fmi3IsScheduledExecution(fmu) + if isScheduledExecution(fmu) fmu.cSetIntervalDecimal = dlsym(fmu.libHandle, :fmi3SetIntervalDecimal) fmu.cSetIntervalFraction = dlsym(fmu.libHandle, :fmi3SetIntervalFraction) fmu.cGetIntervalDecimal = dlsym(fmu.libHandle, :fmi3GetIntervalDecimal) @@ -338,1183 +246,6 @@ function loadBinary(fmu::FMU3) end end -""" - - fmi3InstantiateModelExchange!(fmu::FMU3; instanceName::String=fmu.modelName, type::fmi3Type=fmu.type, pushInstances::Bool = true, visible::Bool = false, loggingOn::Bool = fmu.executionConfig.loggingOn, externalCallbacks::Bool = fmu.executionConfig.externalCallbacks, - logStatusOK::Bool=true, logStatusWarning::Bool=true, logStatusDiscard::Bool=true, logStatusError::Bool=true, logStatusFatal::Bool=true) - -Create a new modelExchange instance of the given fmu, adds a logger if `logginOn == true`. - -# Arguments -- `fmu::FMU3`: Mutable struct representing a FMU and all it instantiated instances in the FMI 3.0 Standard. - -# Keywords -- `instanceName::String=fmu.modelName`: Name of the instance -- `type::fmi3Type=fmu.type`: Defines whether a Co-Simulation or Model Exchange is present -- `pushInstances::Bool = true`: Defines if the fmu instances should be pushed in the application. -- `visible::Bool = false` if the FMU should be started with graphic interface, if supported (default=`false`) -- `loggingOn::Bool = fmu.executionConfig.loggingOn` if the FMU should log and display function calls (default=`false`) -- `externalCallbacks::Bool = fmu.executionConfig.externalCallbacks` if an external shared library should be used for the fmi3CallbackFunctions, this may improve readability of logging messages (default=`false`) -- `logStatusOK::Bool=true` whether to log status of kind `fmi3OK` (default=`true`) -- `logStatusWarning::Bool=true` whether to log status of kind `fmi3Warning` (default=`true`) -- `logStatusDiscard::Bool=true` whether to log status of kind `fmi3Discard` (default=`true`) -- `logStatusError::Bool=true` whether to log status of kind `fmi3Error` (default=`true`) -- `logStatusFatal::Bool=true` whether to log status of kind `fmi3Fatal` (default=`true`) - -# Returns -- Returns the instance of a new FMU modelExchange instance. - -# Source -- FMISpec3.0 Link: [https://fmi-standard.org/](https://fmi-standard.org/) -- FMISpec3.0: 2.4.7 Model variables -- FMISpec3.0: 2.3.1. Super State: FMU State Setable - -See also [`fmi3InstantiateModelExchange`](#@ref). -""" -function fmi3InstantiateModelExchange!(fmu::FMU3; instanceName::String = fmu.modelName, type::fmi3Type = fmu.type, pushInstances::Bool = true, visible::Bool = false, loggingOn::Bool = fmu.executionConfig.loggingOn, externalCallBacks::Bool = fmu.executionConfig.externalCallbacks, - logStatusOK::Bool=true, logStatusWarning::Bool=true, logStatusDiscard::Bool=true, logStatusError::Bool=true, logStatusFatal::Bool=true) - - instEnv = FMU3InstanceEnvironment() - instEnv.logStatusOK = logStatusOK - instEnv.logStatusWarning = logStatusWarning - instEnv.logStatusDiscard = logStatusDiscard - instEnv.logStatusError = logStatusError - instEnv.logStatusFatal = logStatusFatal - - ptrLogger = @cfunction(fmi3CallbackLogger, Cvoid, (Ptr{FMU3InstanceEnvironment}, Cuint, Ptr{Cchar}, Ptr{Cchar})) - - if externalCallBacks - if fmu.callbackLibHandle == C_NULL - @assert Sys.WORD_SIZE == 64 "`externalCallbacks=true` is only supported for 64-bit." - - cbLibPath = joinpath(dirname(@__FILE__), "callbackFunctions", "binaries") - if Sys.iswindows() - cbLibPath = joinpath(cbLibPath, "win64", "callbackFunctions.dll") - elseif Sys.islinux() - cbLibPath = joinpath(cbLibPath, "linux64", "libcallbackFunctions.so") - elseif Sys.isapple() - cbLibPath = joinpath(cbLibPath, "darwin64", "libcallbackFunctions.dylib") - else - @error "Unsupported OS" - end - - # check permission to execute the DLL - perm = filemode(cbLibPath) - permRWX = 16895 - if perm != permRWX - chmod(cbLibPath, permRWX; recursive=true) - end - - fmu.callbackLibHandle = dlopen(cbLibPath) - end - ptrLogger = dlsym(fmu.callbackLibHandle, :logger) - end - ptrInstanceEnvironment = Ptr{Cvoid}(pointer_from_objref(instEnv)) - - instantiationTokenStr = "$(fmu.modelDescription.instantiationToken)" - - compAddr = fmi3InstantiateModelExchange(fmu.cInstantiateModelExchange, pointer(instanceName), pointer(instantiationTokenStr), pointer(fmu.fmuResourceLocation), fmi3Boolean(visible), fmi3Boolean(loggingOn), ptrInstanceEnvironment, ptrLogger) - - if compAddr == Ptr{Cvoid}(C_NULL) - @error "fmi3InstantiateModelExchange!(...): Instantiation failed!" - return nothing - end - - instance = nothing - - # check if address is already inside of the instance (this may be) - for c in fmu.instances - if c.compAddr == compAddr - instance = c - break - end - end - - if instance !== nothing - @info "fmi3InstantiateModelExchange!(...): This component was already registered. This may be because you created the FMU by yourself with FMIExport.jl." - else - - instance = FMU3Instance(compAddr, fmu) - instance.jacobianUpdate! = fmi3GetJacobian! - instance.instanceEnvironment = instEnv - instance.instanceName = instanceName - instance.z_prev = zeros(fmi3Float64, fmi3GetNumberOfEventIndicators(fmu.modelDescription)) - instance.rootsFound = zeros(fmi3Int32, fmi3GetNumberOfEventIndicators(fmu.modelDescription)) - instance.stateEvent = fmi3False - instance.timeEvent = fmi3False - instance.stepEvent = fmi3False - instance.type = fmi3TypeModelExchange - - if pushInstances - push!(fmu.instances, instance) - end - end - - instance -end - -""" - - fmi3InstantiateCoSimulation!(fmu::FMU3; instanceName::String=fmu.modelName, type::fmi3Type=fmu.type, pushInstances::Bool = true, visible::Bool = false, loggingOn::Bool = fmu.executionConfig.loggingOn, externalCallbacks::Bool = fmu.executionConfig.externalCallbacks, - eventModeUsed::Bool = false, ptrIntermediateUpdate=nothing, logStatusOK::Bool=true, logStatusWarning::Bool=true, logStatusDiscard::Bool=true, logStatusError::Bool=true, logStatusFatal::Bool=true) - -Create a new coSimulation instance of the given fmu, adds a logger if `logginOn == true`. - -# Arguments -- `fmu::FMU3`: Mutable struct representing a FMU and all it instantiated instances in the FMI 3.0 Standard. - -# Keywords -- `instanceName::String=fmu.modelName`: Name of the instance -- `type::fmi3Type=fmu.type`: Defines whether a Co-Simulation or Model Exchange is present -- `pushInstances::Bool = true`: Defines if the fmu instances should be pushed in the application. -- `visible::Bool = false` if the FMU should be started with graphic interface, if supported (default=`false`) -- `loggingOn::Bool = fmu.executionConfig.loggingOn` if the FMU should log and display function calls (default=`false`) -- `externalCallbacks::Bool = fmu.executionConfig.externalCallbacks` if an external shared library should be used for the fmi3CallbackFunctions, this may improve readability of logging messages (default=`false`) -- `eventModeUsed::Bool = false`: Defines if the FMU instance can use the event mode. (default=`false`) -- `ptrIntermediateUpdate=nothing`: Points to a function handling intermediate Updates (defalut=`nothing`) -- `logStatusOK::Bool=true` whether to log status of kind `fmi3OK` (default=`true`) -- `logStatusWarning::Bool=true` whether to log status of kind `fmi3Warning` (default=`true`) -- `logStatusDiscard::Bool=true` whether to log status of kind `fmi3Discard` (default=`true`) -- `logStatusError::Bool=true` whether to log status of kind `fmi3Error` (default=`true`) -- `logStatusFatal::Bool=true` whether to log status of kind `fmi3Fatal` (default=`true`) - -# Returns -- Returns the instance of a new FMU coSimulation instance. - -# Source -- FMISpec3.0 Link: [https://fmi-standard.org/](https://fmi-standard.org/) -- FMISpec3.0: 2.4.7 Model variables -- FMISpec3.0: 2.3.1. Super State: FMU State Setable - -See also [`fmi3InstantiateCoSimulation`](#@ref). -""" -function fmi3InstantiateCoSimulation!(fmu::FMU3; instanceName::String=fmu.modelName, type::fmi3Type=fmu.type, pushInstances::Bool = true, visible::Bool = false, loggingOn::Bool = fmu.executionConfig.loggingOn, externalCallbacks::Bool = fmu.executionConfig.externalCallbacks, - eventModeUsed::Bool = false, ptrIntermediateUpdate=nothing, logStatusOK::Bool=true, logStatusWarning::Bool=true, logStatusDiscard::Bool=true, logStatusError::Bool=true, logStatusFatal::Bool=true) - instEnv = FMU3InstanceEnvironment() - instEnv.logStatusOK = logStatusOK - instEnv.logStatusWarning = logStatusWarning - instEnv.logStatusDiscard = logStatusDiscard - instEnv.logStatusError = logStatusError - instEnv.logStatusFatal = logStatusFatal - - ptrLogger = @cfunction(fmi3CallbackLogger, Cvoid, (Ptr{FMU3InstanceEnvironment}, Cuint, Ptr{Cchar}, Ptr{Cchar})) - - if externalCallbacks - if fmu.callbackLibHandle == C_NULL - @assert Sys.WORD_SIZE == 64 "`externalCallbacks=true` is only supported for 64-bit." - - cbLibPath = joinpath(dirname(@__FILE__), "callbackFunctions", "binaries") - if Sys.iswindows() - cbLibPath = joinpath(cbLibPath, "win64", "callbackFunctions.dll") - elseif Sys.islinux() - cbLibPath = joinpath(cbLibPath, "linux64", "libcallbackFunctions.so") - elseif Sys.isapple() - cbLibPath = joinpath(cbLibPath, "darwin64", "libcallbackFunctions.dylib") - else - @error "Unsupported OS" - end - - # check permission to execute the DLL - perm = filemode(cbLibPath) - permRWX = 16895 - if perm != permRWX - chmod(cbLibPath, permRWX; recursive=true) - end - - fmu.callbackLibHandle = dlopen(cbLibPath) - end - ptrLogger = dlsym(fmu.callbackLibHandle, :logger) - end - - if ptrIntermediateUpdate === nothing - ptrIntermediateUpdate = @cfunction(fmi3CallbackIntermediateUpdate, Cvoid, (Ptr{Cvoid}, fmi3Float64, fmi3Boolean, fmi3Boolean, fmi3Boolean, fmi3Boolean, Ptr{fmi3Boolean}, Ptr{fmi3Float64})) - end - if fmu.modelDescription.coSimulation.hasEventMode !== nothing - mode = eventModeUsed - else - mode = false - end - ptrInstanceEnvironment = Ptr{Cvoid}(pointer_from_objref(instEnv)) - - instantiationTokenStr = "$(fmu.modelDescription.instantiationToken)" - - compAddr = fmi3InstantiateCoSimulation(fmu.cInstantiateCoSimulation, pointer(instanceName), pointer(instantiationTokenStr), pointer(fmu.fmuResourceLocation), fmi3Boolean(visible), fmi3Boolean(loggingOn), - fmi3Boolean(mode), fmi3Boolean(fmu.modelDescription.coSimulation.canReturnEarlyAfterIntermediateUpdate !== nothing), fmu.modelDescription.intermediateUpdateValueReferences, Csize_t(length(fmu.modelDescription.intermediateUpdateValueReferences)), ptrInstanceEnvironment, ptrLogger, ptrIntermediateUpdate) - - if compAddr == Ptr{Cvoid}(C_NULL) - @error "fmi3InstantiateCoSimulation!(...): Instantiation failed!" - return nothing - end - - instance = nothing - - # check if address is already inside of the instance (this may be) - for c in fmu.instances - if c.compAddr == compAddr - instance = c - break - end - end - - if instance !== nothing - @info "fmi3InstantiateCoSimulation!(...): This component was already registered. This may be because you created the FMU by yourself with FMIExport.jl." - else - instance = FMU3Instance(compAddr, fmu) - instance.jacobianUpdate! = fmi3GetJacobian! - instance.instanceEnvironment = instEnv - instance.instanceName = instanceName - instance.type = fmi3TypeCoSimulation - - if pushInstances - push!(fmu.instances, instance) - end - end - - instance -end - -# TODO not tested -""" - - fmi3InstantiateScheduledExecution!(fmu::FMU3; ptrlockPreemption::Ptr{Cvoid}, ptrunlockPreemption::Ptr{Cvoid}, instanceName::String=fmu.modelName, type::fmi3Type=fmu.type, pushInstances::Bool = true, visible::Bool = false, loggingOn::Bool = fmu.executionConfig.loggingOn, externalCallbacks::Bool = fmu.executionConfig.externalCallbacks, - logStatusOK::Bool=true, logStatusWarning::Bool=true, logStatusDiscard::Bool=true, logStatusError::Bool=true, logStatusFatal::Bool=true) - -Create a new ScheduledExecution instance of the given fmu, adds a logger if `logginOn == true`. - -# Arguments -- `fmu::FMU3`: Mutable struct representing a FMU and all it instantiated instances in the FMI 3.0 Standard. - -# Keywords -- `ptrlockPreemption::Ptr{Cvoid}`: Points to a function handling locking Preemption -- `ptrunlockPreemption::Ptr{Cvoid}`: Points to a function handling unlocking Preemption -- `instanceName::String=fmu.modelName`: Name of the instance -- `type::fmi3Type=fmu.type`: Defines whether a Co-Simulation or Model Exchange is present -- `pushInstances::Bool = true`: Defines if the fmu instances should be pushed in the application. -- `visible::Bool = false` if the FMU should be started with graphic interface, if supported (default=`false`) -- `loggingOn::Bool = fmu.executionConfig.loggingOn` if the FMU should log and display function calls (default=`false`) -- `externalCallbacks::Bool = fmu.executionConfig.externalCallbacks` if an external shared library should be used for the fmi3CallbackFunctions, this may improve readability of logging messages (default=`false`) -- `logStatusOK::Bool=true` whether to log status of kind `fmi3OK` (default=`true`) -- `logStatusWarning::Bool=true` whether to log status of kind `fmi3Warning` (default=`true`) -- `logStatusDiscard::Bool=true` whether to log status of kind `fmi3Discard` (default=`true`) -- `logStatusError::Bool=true` whether to log status of kind `fmi3Error` (default=`true`) -- `logStatusFatal::Bool=true` whether to log status of kind `fmi3Fatal` (default=`true`) - -# Returns -- Returns the instance of a new FMU ScheduledExecution instance. - -# Source -- FMISpec3.0 Link: [https://fmi-standard.org/](https://fmi-standard.org/) -- FMISpec3.0: 2.4.7 Model variables -- FMISpec3.0: 2.3.1. Super State: FMU State Setable - -See also [`fmi3InstantiateScheduledExecution`](#@ref). -""" -function fmi3InstantiateScheduledExecution!(fmu::FMU3; ptrlockPreemption::Ptr{Cvoid}, ptrunlockPreemption::Ptr{Cvoid}, instanceName::String=fmu.modelName, type::fmi3Type=fmu.type, pushInstances::Bool = true, visible::Bool = false, loggingOn::Bool = fmu.executionConfig.loggingOn, externalCallbacks::Bool = fmu.executionConfig.externalCallbacks, - logStatusOK::Bool=true, logStatusWarning::Bool=true, logStatusDiscard::Bool=true, logStatusError::Bool=true, logStatusFatal::Bool=true) - - instEnv = FMU3InstanceEnvironment() - instEnv.logStatusOK = logStatusOK - instEnv.logStatusWarning = logStatusWarning - instEnv.logStatusDiscard = logStatusDiscard - instEnv.logStatusError = logStatusError - instEnv.logStatusFatal = logStatusFatal - - ptrLogger = @cfunction(fmi3CallbackLogger, Cvoid, (Ptr{FMU3InstanceEnvironment}, Cuint, Ptr{Cchar}, Ptr{Cchar})) - if externalCallbacks - if fmu.callbackLibHandle == C_NULL - @assert Sys.WORD_SIZE == 64 "`externalCallbacks=true` is only supported for 64-bit." - - cbLibPath = joinpath(dirname(@__FILE__), "callbackFunctions", "binaries") - if Sys.iswindows() - cbLibPath = joinpath(cbLibPath, "win64", "callbackFunctions.dll") - elseif Sys.islinux() - cbLibPath = joinpath(cbLibPath, "linux64", "libcallbackFunctions.so") - elseif Sys.isapple() - cbLibPath = joinpath(cbLibPath, "darwin64", "libcallbackFunctions.dylib") - else - @error "Unsupported OS" - end - - # check permission to execute the DLL - perm = filemode(cbLibPath) - permRWX = 16895 - if perm != permRWX - chmod(cbLibPath, permRWX; recursive=true) - end - - fmu.callbackLibHandle = dlopen(cbLibPath) - end - ptrLogger = dlsym(fmu.callbackLibHandle, :logger) - end - ptrClockUpdate = @cfunction(fmi3CallbackClockUpdate, Cvoid, (Ptr{Cvoid}, )) - - ptrInstanceEnvironment = Ptr{FMU3InstanceEnvironment}(pointer_from_objref(instEnv)) - - instantiationTokenStr = "$(fmu.modelDescription.instantiationToken)" - - compAddr = fmi3InstantiateScheduledExecution(fmu.cInstantiateScheduledExecution, pointer(instanceName), pointer(instantiationTokenStr), pointer(fmu.fmuResourceLocation), fmi3Boolean(visible), fmi3Boolean(loggingOn), ptrInstanceEnvironment, ptrLogger, ptrClockUpdate, ptrlockPreemption, ptrunlockPreemption) - - if compAddr == Ptr{Cvoid}(C_NULL) - @error "fmi3InstantiateScheduledExecution!(...): Instantiation failed!" - return nothing - end - - instance = nothing - - # check if address is already inside of the instance (this may be) - for c in fmu.instances - if c.compAddr == compAddr - instance = c - break - end - end - - if instance !== nothing - @info "fmi3InstantiateScheduledExecution!(...): This component was already registered. This may be because you created the FMU by yourself with FMIExport.jl." - else - instance = FMU3Instance(compAddr, fmu) - instance.jacobianUpdate! = fmi3GetJacobian! - instance.instanceEnvironment = instEnv - instance.instanceName = instanceName - instance.type = fmi3TypeScheduledExecution - - if pushInstances - push!(fmu.instances, instance) - end - end - - instance -end - -""" - - fmi3Reload(fmu::FMU3) - -Reloads the FMU-binary. This is useful, if the FMU does not support a clean reset implementation. - -# Arguments -- `fmu::FMU3`: Mutable struct representing a FMU and all it instantiated instances in the FMI 3.0 Standard. - -# Source -- FMISpec3.0 Link: [https://fmi-standard.org/](https://fmi-standard.org/) -- FMISpec3.0: 2.4.7 Model Variables -""" -function fmi3Reload(fmu::FMU3) - dlclose(fmu.libHandle) - loadBinary(fmu) -end - -""" - - function fmi3Unload(fmu::FMU3, cleanUp::Bool = true) - -Unload a FMU. -Free the allocated memory, close the binaries and remove temporary zip and unziped FMU model description. - -# Arguments -- `fmu::FMU3`: Mutable struct representing a FMU and all it instantiated instances in the FMI 3.0 Standard. -- `cleanUp::Bool= true`: Defines if the file, link, or empty directory should be deleted. -""" -function fmi3Unload(fmu::FMU3, cleanUp::Bool = true) - - while length(fmu.instances) > 0 - fmi3FreeInstance!(fmu.instances[end]) - end - - dlclose(fmu.libHandle) - - # the instances are removed from the instances list via call to fmi3FreeInstance! - @assert length(fmu.instances) == 0 "fmi3Unload(...): Failure during deleting instances, $(length(fmu.instances)) remaining in stack." - - if cleanUp - try - rm(fmu.path; recursive = true, force = true) - rm(fmu.zipPath; recursive = true, force = true) - catch e - @warn "Cannot delete unpacked data on disc. Maybe some files are opened in another application." - end - end -end - -""" - fmi3SampleDirectionalDerivative(c::FMU3Instance, - vUnknown_ref::AbstractArray{fmi3ValueReference}, - vKnown_ref::AbstractArray{fmi3ValueReference}, - steps::Union{AbstractArray{fmi3Float64}, Nothing} = nothing) - -This function samples the directional derivative by manipulating corresponding values (central differences). - -Computes the directional derivatives of an FMU. An FMU has different Modes and in every Mode an FMU might be described by different equations and different unknowns. The precise definitions are given in the mathematical descriptions of Model Exchange (section 3.1) and Co-Simulation (section 4.1). In every Mode, the general form of the FMU equations are: -𝐯_unknown = 𝐡(𝐯_known, 𝐯_rest) - -- `v_unknown`: vector of unknown Real variables computed in the actual Mode: -- Initialization Mode: unkowns kisted under `` that have type Real. -- Continuous-Time Mode (ModelExchange): The continuous-time outputs and state derivatives. (= the variables listed under `` with type Real and variability = `continuous` and the variables listed as state derivatives under `)`. -- Event Mode (ModelExchange): The same variables as in the Continuous-Time Mode and additionally variables under `` with type Real and variability = `discrete`. -- Step Mode (CoSimulation): The variables listed under `` with type Real and variability = `continuous` or `discrete`. If `` is present, also the variables listed here as state derivatives. -- `v_known`: Real input variables of function h that changes its value in the actual Mode. -- `v_rest`:Set of input variables of function h that either changes its value in the actual Mode but are non-Real variables, or do not change their values in this Mode, but change their values in other Modes - -Computes a linear combination of the partial derivatives of h with respect to the selected input variables 𝐯_known: - -Δv_unknown = (δh / δv_known) Δv_known - -# Arguments -- `c::FMU3Instance`: Mutable struct represents an instantiated instance of an FMU in the FMI 3.0 Standard. -- `vUnknown_ref::AbstractArray{fmi3ValueReference}`: Argument `vUnknown_ref` contains values of type`fmi3ValueReference` which are identifiers of a variable value of the model. `vUnknown_ref` can be equated with `v_unknown`(variable described above). -- `vKnown_ref::AbstractArray{fmi3ValueReference}`: Argument `vKnown_ref` contains values of type `fmi3ValueReference` which are identifiers of a variable value of the model.`vKnown_ref` can be equated with `v_known`(variable described above). -- `steps::Union{AbstractArray{fmi3Float64}, Nothing} = nothing)`: If sampling is used, sampling step size can be set (for each direction individually) using optional argument `steps`. - -# Returns -- `dvUnkonwn::Array{fmi3Float64}`: Argument `vUnknown_ref` contains values of type`fmi3ValueReference` which are identifiers of a variable value of the model. `vUnknown_ref` can be equated with `v_unknown`(see function fmi3GetDirectionalDerivative!). - -# Source -- FMISpec3.0 Link: [https://fmi-standard.org/](https://fmi-standard.org/) -- FMISpec3.0: 2.4.7 Model Variables - -See also [`fmi3GetDirectionalDerivative!`](@ref) ,[`fmi3GetDirectionalDerivative`](@ref). -""" -function fmi3SampleDirectionalDerivative(c::fmi3Instance, - vUnknown_ref::AbstractArray{fmi3ValueReference}, - vKnown_ref::AbstractArray{fmi3ValueReference}, - steps::AbstractArray{fmi3Float64} = ones(fmi3Float64, length(vKnown_ref)).*DEFAULT_SAMPLE_STEP) - - dvUnknown = zeros(fmi3Float64, length(vUnknown_ref), length(vKnown_ref)) - - fmi3SampleDirectionalDerivative!(c, vUnknown_ref, vKnown_ref, dvUnknown, steps) - - dvUnknown -end - - -""" - fmi3SampleDirectionalDerivative!(c::FMU3Instance, - vUnknown_ref::AbstractArray{fmi3ValueReference}, - vKnown_ref::AbstractArray{fmi3ValueReference}, - steps::Union{AbstractArray{fmi3Float64}, Nothing} = nothing) - -This function samples the directional derivative by manipulating corresponding values (central differences). - -Computes the directional derivatives of an FMU. An FMU has different Modes and in every Mode an FMU might be described by different equations and different unknowns. The precise definitions are given in the mathematical descriptions of Model Exchange (section 3.1) and Co-Simulation (section 4.1). In every Mode, the general form of the FMU equations are: -𝐯_unknown = 𝐡(𝐯_known, 𝐯_rest) - -- `v_unknown`: vector of unknown Real variables computed in the actual Mode: -- Initialization Mode: unkowns kisted under `` that have type Real. -- Continuous-Time Mode (ModelExchange): The continuous-time outputs and state derivatives. (= the variables listed under `` with type Real and variability = `continuous` and the variables listed as state derivatives under `)`. -- Event Mode (ModelExchange): The same variables as in the Continuous-Time Mode and additionally variables under `` with type Real and variability = `discrete`. -- Step Mode (CoSimulation): The variables listed under `` with type Real and variability = `continuous` or `discrete`. If `` is present, also the variables listed here as state derivatives. -- `v_known`: Real input variables of function h that changes its value in the actual Mode. -- `v_rest`:Set of input variables of function h that either changes its value in the actual Mode but are non-Real variables, or do not change their values in this Mode, but change their values in other Modes - -Computes a linear combination of the partial derivatives of h with respect to the selected input variables 𝐯_known: - -Δv_unknown = (δh / δv_known) Δv_known - -# Arguments -- `c::FMU3Instance`: Mutable struct represents an instantiated instance of an FMU in the FMI 3.0 Standard. -- `vUnknown_ref::AbstractArray{fmi3ValueReference}`: Argument `vUnknown_ref` contains values of type`fmi3ValueReference` which are identifiers of a variable value of the model. `vUnknown_ref` can be equated with `v_unknown`(variable described above). -- `vKnown_ref::AbstractArray{fmi3ValueReference}`: Argument `vKnown_ref` contains values of type `fmi3ValueReference` which are identifiers of a variable value of the model.`vKnown_ref` can be equated with `v_known`(variable described above). -- `steps::Union{AbstractArray{fmi3Float64}, Nothing} = nothing)`: If sampling is used, sampling step size can be set (for each direction individually) using optional argument `steps`. - -# Returns -- `nothing` - -# Source -- FMISpec3.0 Link: [https://fmi-standard.org/](https://fmi-standard.org/) -- FMISpec3.0: 2.4.7 Model Variables - -See also [`fmi3GetDirectionalDerivative!`](@ref) ,[`fmi3GetDirectionalDerivative`](@ref). -""" -function fmi3SampleDirectionalDerivative!(c::fmi3Instance, - vUnknown_ref::AbstractArray{fmi3ValueReference}, - vKnown_ref::AbstractArray{fmi3ValueReference}, - dvUnknown::AbstractArray, - steps::AbstractArray{fmi3Float64} = ones(fmi3Float64, length(vKnown_ref)).*DEFAULT_SAMPLE_STEP) - - for i in 1:length(vKnown_ref) - vKnown = vKnown_ref[i] - origValue = fmi3GetFloat64(c, vKnown) - - if steps === nothing - # smaller than 1e-6 leads to issues - step = max(2.0 * eps(Float32(origValue)), 1e-6) - else - step = steps[i] - end - - fmi3SetFloat64(c, vKnown, origValue - step) - negValues = fmi3GetFloat64(c, vUnknown_ref) - - fmi3SetFloat64(c, vKnown, origValue + step) - posValues = fmi3GetFloat64(c, vUnknown_ref) - - fmi3SetFloat64(c, vKnown, origValue) - - if length(vUnknown_ref) == 1 - dvUnknown[1,i] = (posValues-negValues) ./ (step * 2.0) - else - dvUnknown[:,i] = (posValues-negValues) ./ (step * 2.0) - end - end - - nothing -end - -""" - - fmi3GetJacobian(inst::FMU3Instance, - rdx::AbstractArray{fmi3ValueReference}, - rx::AbstractArray{fmi3ValueReference}; - steps::Union{AbstractArray{fmi3Float64}, Nothing} = nothing) - -Builds the jacobian over the FMU `fmu` for FMU value references `rdx` and `rx`, so that the function returns the jacobian ∂rdx / ∂rx. - -If FMI built-in directional derivatives are supported, they are used. -As fallback, directional derivatives will be sampled with central differences. -For optimization, if the FMU's model description has the optional entry 'dependencies', only dependent variables are sampled/retrieved. This drastically boosts performance for systems with large variable count (like CFD). - -# Arguments -- `inst::FMU3Instance`: Mutable struct represents an instantiated instance of an FMU in the FMI 3.0 Standard. -- `rdx::AbstractArray{fmi3ValueReference}`: Argument `rdx` contains values of type`fmi3ValueReference` which are identifiers of a variable value of the model. -- `rx::AbstractArray{fmi3ValueReference}`: Argument `rx` contains values of type `fmi3ValueReference` which are identifiers of a variable value of the model. - -# Keywords -- `steps::Union{AbstractArray{fmi3Float64}, Nothing} = nothing)`: If sampling is used, sampling step size can be set (for each direction individually) using optional argument `steps`. - -# Returns -- `mat::Array{fmi3Float64}`: Return `mat` contains the jacobian ∂rdx / ∂rx. - -# Source -- FMISpec3.0 Link: [https://fmi-standard.org/](https://fmi-standard.org/) -- FMISpec3.0: 2.4.7 Model Variables -""" -function fmi3GetJacobian(inst::FMU3Instance, - rdx::AbstractArray{fmi3ValueReference}, - rx::AbstractArray{fmi3ValueReference}; - steps::AbstractArray{fmi3Float64} = ones(fmi3Float64, length(rdx)).*1e-5) - mat = zeros(fmi3Float64, length(rdx), length(rx)) - fmi3GetJacobian!(mat, inst, rdx, rx; steps=steps) - return mat -end - -""" - - function fmi3GetJacobian!(jac::AbstractMatrix{fmi3Float64}, - comp::FMU3Instance, - rdx::AbstractArray{fmi3ValueReference}, - rx::AbstractArray{fmi3ValueReference}; - steps::Union{AbstractArray{fmi3Float64}, Nothing} = nothing) - -Fills the jacobian over the FMU `fmu` for FMU value references `rdx` and `rx`, so that the function stores the jacobian ∂rdx / ∂rx in an AbstractMatrix `jac`. - -If FMI built-in directional derivatives are supported, they are used. -As fallback, directional derivatives will be sampled with central differences. -For optimization, if the FMU's model description has the optional entry 'dependencies', only dependent variables are sampled/retrieved. This drastically boosts performance for systems with large variable count (like CFD). - -# Arguments -- `jac::AbstractMatrix{fmi3Float64}`: Stores the the jacobian ∂rdx / ∂rx. -- `inst::FMU3Instance`: Mutable struct represents an instantiated instance of an FMU in the FMI 3.0 Standard. -- `rdx::AbstractArray{fmi3ValueReference}`: Argument `rdx` contains values of type`fmi3ValueReference` which are identifiers of a variable value of the model. -- `rx::AbstractArray{fmi3ValueReference}`: Argument `rx` contains values of type `fmi3ValueReference` which are identifiers of a variable value of the model. - -# Keywords -- `steps::Union{AbstractArray{fmi3Float64}, Nothing} = nothing)`: If sampling is used, sampling step size can be set (for each direction individually) using optional argument `steps`. - -# Returns -- `nothing` - -# Source -- FMISpec3.0 Link: [https://fmi-standard.org/](https://fmi-standard.org/) -- FMISpec3.0: 2.4.7 Model Variables -""" -function fmi3GetJacobian!(jac::Matrix{fmi3Float64}, - inst::FMU3Instance, - rdx::AbstractArray{fmi3ValueReference}, - rx::AbstractArray{fmi3ValueReference}; - steps::AbstractArray{fmi3Float64} = ones(fmi3Float64, length(rdx)).*1e-5) - - @assert size(jac) == (length(rdx), length(rx)) ["fmi3GetJacobian!: Dimension missmatch between `jac` $(size(jac)), `rdx` ($length(rdx)) and `rx` ($length(rx))."] - - if length(rdx) == 0 || length(rx) == 0 - jac = zeros(length(rdx), length(rx)) - return nothing - end - - ddsupported = fmi3ProvidesDirectionalDerivatives(inst.fmu) - - # ToDo: Pick entries based on dependency matrix! - #depMtx = fmi3GetDependencies(fmu) - rdx_inds = collect(inst.fmu.modelDescription.valueReferenceIndicies[vr] for vr in rdx) - rx_inds = collect(inst.fmu.modelDescription.valueReferenceIndicies[vr] for vr in rx) - - for i in 1:length(rx) - - sensitive_rdx_inds = 1:length(rdx) - sensitive_rdx = rdx - - # sensitive_rdx_inds = Int64[] - # sensitive_rdx = fmi3ValueReference[] - - # for j in 1:length(rdx) - # if depMtx[rdx_inds[j], rx_inds[i]] != fmi3DependencyIndependent - # push!(sensitive_rdx_inds, j) - # push!(sensitive_rdx, rdx[j]) - # end - # end - - if length(sensitive_rdx) > 0 - if ddsupported - # doesn't work because indexed-views can`t be passed by reference (to ccalls) - fmi3GetDirectionalDerivative!(inst, sensitive_rdx, [rx[i]], view(jac, sensitive_rdx_inds, i)) - # jac[sensitive_rdx_inds, i] = fmi3GetDirectionalDerivative!(inst, sensitive_rdx, [rx[i]]) - else - # doesn't work because indexed-views can`t be passed by reference (to ccalls) - # try - fmi3SampleDirectionalDerivative!(inst, sensitive_rdx, [rx[i]], view(jac, sensitive_rdx_inds, i)) # TODO not implemented - # catch e - # jac[sensitive_rdx_inds, i] = fmi3SampleDirectionalDerivative(inst, sensitive_rdx, [rx[i]], steps) - end - end - end - - return nothing -end - -""" - - fmi3GetFullJacobian(inst::FMU3Instance, - rdx::AbstractArray{fmi3ValueReference}, - rx::AbstractArray{fmi3ValueReference}; - steps::Union{AbstractArray{fmi3Float64}, Nothing} = nothing) - -Builds the jacobian over the FMU `fmu` for FMU value references `rdx` and `rx`, so that the function returns the jacobian ∂rdx / ∂rx. - -If FMI built-in directional derivatives are supported, they are used. -As fallback, directional derivatives will be sampled with central differences. -No performance optimization, for an optimized version use `fmi3GetJacobian`. - - -# Arguments -- `inst::FMU3Instance`: Mutable struct represents an instantiated instance of an FMU in the FMI 3.0 Standard. -- `rdx::AbstractArray{fmi3ValueReference}`: Argument `rdx` contains values of type`fmi3ValueReference` which are identifiers of a variable value of the model. -- `rx::AbstractArray{fmi3ValueReference}`: Argument `rx` contains values of type `fmi3ValueReference` which are identifiers of a variable value of the model. - -# Keywords -- `steps::Union{AbstractArray{fmi3Float64}, Nothing} = nothing)`: If sampling is used, sampling step size can be set (for each direction individually) using optional argument `steps`. - -# Returns -- `mat::Array{fmi3Float64}`: Return `mat` contains the jacobian ∂rdx / ∂rx. - -# Source -- FMISpec3.0 Link: [https://fmi-standard.org/](https://fmi-standard.org/) -- FMISpec3.0: 2.4.7 Model Variables - -See also [`fmi3GetFullJacobian!`](@ref) -""" -function fmi3GetFullJacobian(inst::FMU3Instance, - rdx::AbstractArray{fmi3ValueReference}, - rx::AbstractArray{fmi3ValueReference}; - steps::AbstractArray{fmi3Float64} = ones(fmi3Float64, length(rdx)).*1e-5) - mat = zeros(fmi3Float64, length(rdx), length(rx)) - fmi3GetFullJacobian!(mat, inst, rdx, rx; steps=steps) - return mat -end - -""" - - fmi3GetFullJacobian!(jac::Matrix{fmi3Float64}, - inst::FMU3Instance, - rdx::AbstractArray{fmi3ValueReference}, - rx::AbstractArray{fmi3ValueReference}; - steps::Union{AbstractArray{fmi3Float64}, Nothing} = nothing) - -Fills the jacobian over the FMU `fmu` for FMU value references `rdx` and `rx`, so that the function returns the jacobian ∂rdx / ∂rx. - -If FMI built-in directional derivatives are supported, they are used. -As fallback, directional derivatives will be sampled with central differences. -No performance optimization, for an optimized version use `fmi3GetJacobian`. - - -# Arguments -- `jac::AbstractMatrix{fmi3Float64}`: Stores the the jacobian ∂rdx / ∂rx. -- `inst::FMU3Instance`: Mutable struct represents an instantiated instance of an FMU in the FMI 3.0 Standard. -- `rdx::AbstractArray{fmi3ValueReference}`: Argument `rdx` contains values of type`fmi3ValueReference` which are identifiers of a variable value of the model. -- `rx::AbstractArray{fmi3ValueReference}`: Argument `rx` contains values of type `fmi3ValueReference` which are identifiers of a variable value of the model. - -# Keywords -- `steps::Union{AbstractArray{fmi3Float64}, Nothing} = nothing)`: If sampling is used, sampling step size can be set (for each direction individually) using optional argument `steps`. - -# Returns -- `nothing` -""" -function fmi3GetFullJacobian!(jac::Matrix{fmi3Float64}, - inst::FMU3Instance, - rdx::AbstractArray{fmi3ValueReference}, - rx::AbstractArray{fmi3ValueReference}; - steps::AbstractArray{fmi3Float64} = ones(fmi3Float64, length(rdx)).*1e-5) - @assert size(jac) == (length(rdx),length(rx)) "fmi3GetFullJacobian!: Dimension missmatch between `jac` $(size(jac)), `rdx` ($length(rdx)) and `rx` ($length(rx))." - - @warn "`fmi3GetFullJacobian!` is for benchmarking only, please use `fmi3GetJacobian`." - - if length(rdx) == 0 || length(rx) == 0 - jac = zeros(length(rdx), length(rx)) - return nothing - end - - if fmi3ProvidesDirectionalDerivative(inst.fmu) - for i in 1:length(rx) - jac[:,i] = fmi3GetDirectionalDerivative(inst, rdx, [rx[i]]) - end - else - jac = fmi3SampleDirectionalDerivative(inst, rdx, rx) # TODO not implemented - end - - return nothing -end - -""" - fmi3Get!(inst::FMU3Instance, vrs::fmi3ValueReferenceFormat, dstArray::AbstractArray) - -Stores the specific value of `fmi3Variable` containing the modelVariables with the identical fmi3ValueReference and returns an array that indicates the Status. - -# Arguments -- `inst::FMU3Instance`: Mutable struct represents an instantiated instance of an FMU in the FMI 3.0 Standard. -- `vrs::fmi3ValueReferenceFormat`: wildcards for how a user can pass a fmi[X]ValueReference -More detailed: `fmi3ValueReferenceFormat = Union{Nothing, String, Array{String,1}, fmi3ValueReference, Array{fmi3ValueReference,1}, Int64, Array{Int64,1}, Symbol}` -- `dstArray::AbstractArray`: Stores the specific value of `fmi3Variable` containing the modelVariables with the identical fmi3ValueReference to the input variable vr (vr = vrs[i]). `dstArray` has the same length as `vrs`. - -# Returns -- `retcodes::Array{fmi3Status}`: Returns an array of length length(vrs) with Type `fmi3Status`. Type `fmi3Status` is an enumeration and indicates the success of the function call. -More detailed: - - `fmi3OK`: all well - - `fmi3Warning`: things are not quite right, but the computation can continue - - `fmi3Discard`: if the slave computed successfully only a subinterval of the communication step - - `fmi3Error`: the communication step could not be carried out at all - - `fmi3Fatal`: if an error occurred which corrupted the FMU irreparably - -# Source -- FMISpec3.0 Link: [https://fmi-standard.org/](https://fmi-standard.org/) -- FMISpec3.0: 2.2.4 Status Returned by Functions -- FMISpec3.0: 2.2.6.2. Getting and Setting Variable Values -""" -function fmi3Get!(inst::FMU3Instance, vrs::fmi3ValueReferenceFormat, dstArray::Array) - vrs = prepareValueReference(inst, vrs) - - @assert length(vrs) == length(dstArray) "fmi3Get!(...): Number of value references doesn't match number of `dstArray` elements." - - retcodes = zeros(fmi3Status, length(vrs)) # fmi3StatusOK - - for i in 1:length(vrs) - vr = vrs[i] - mv = fmi3ModelVariablesForValueReference(inst.fmu.modelDescription, vr) - mv = mv[1] - # TODO change if dataytype is elimnated - if isa(mv, FMICore.fmi3VariableFloat32) - #@assert isa(dstArray[i], Real) "fmi3Get!(...): Unknown data type for value reference `$(vr)` at index $(i), should be `Real`, is `$(typeof(dstArray[i]))`." - dstArray[i] = fmi3GetFloat32(inst, vr) - elseif isa(mv, FMICore.fmi3VariableFloat64) - #@assert isa(dstArray[i], Union{Real, Integer}) "fmi3Get!(...): Unknown data type for value reference `$(vr)` at index $(i), should be `Integer`, is `$(typeof(dstArray[i]))`." - dstArray[i] = fmi3GetFloat64(inst, vr) - elseif isa(mv, FMICore.fmi3VariableInt8) - #@assert isa(dstArray[i], Union{Real, Integer}) "fmi3Get!(...): Unknown data type for value reference `$(vr)` at index $(i), should be `Integer`, is `$(typeof(dstArray[i]))`." - dstArray[i] = fmi3GetInt8(inst, vr) - elseif isa(mv, FMICore.fmi3VariableInt16) - #@assert isa(dstArray[i], Union{Real, Integer}) "fmi3Get!(...): Unknown data type for value reference `$(vr)` at index $(i), should be `Integer`, is `$(typeof(dstArray[i]))`." - dstArray[i] = fmi3GetInt16(inst, vr) - elseif isa(mv, FMICore.fmi3VariableInt32) - #@assert isa(dstArray[i], Union{Real, Integer}) "fmi3Get!(...): Unknown data type for value reference `$(vr)` at index $(i), should be `Integer`, is `$(typeof(dstArray[i]))`." - dstArray[i] = fmi3GetInt32(inst, vr) - elseif isa(mv, FMICore.fmi3VariableInt64) - #@assert isa(dstArray[i], Union{Real, Integer}) "fmi3Get!(...): Unknown data type for value reference `$(vr)` at index $(i), should be `Integer`, is `$(typeof(dstArray[i]))`." - dstArray[i] = fmi3GetInt64(inst, vr) - elseif isa(mv, FMICore.fmi3VariableUInt8) - #@assert isa(dstArray[i], Union{Real, Integer}) "fmi3Get!(...): Unknown data type for value reference `$(vr)` at index $(i), should be `Integer`, is `$(typeof(dstArray[i]))`." - dstArray[i] = fmi3GetUInt8(inst, vr) - elseif isa(mv, FMICore.fmi3VariableUInt16) - #@assert isa(dstArray[i], Union{Real, Integer}) "fmi3Get!(...): Unknown data type for value reference `$(vr)` at index $(i), should be `Integer`, is `$(typeof(dstArray[i]))`." - dstArray[i] = fmi3GetUInt16(inst, vr) - elseif isa(mv, FMICore.fmi3VariableUInt32) - #@assert isa(dstArray[i], Union{Real, Integer}) "fmi3Get!(...): Unknown data type for value reference `$(vr)` at index $(i), should be `Integer`, is `$(typeof(dstArray[i]))`." - dstArray[i] = fmi3GetUInt32(inst, vr) - elseif isa(mv, FMICore.fmi3VariableUInt64) - #@assert isa(dstArray[i], Union{Real, Integer}) "fmi3Get!(...): Unknown data type for value reference `$(vr)` at index $(i), should be `Integer`, is `$(typeof(dstArray[i]))`." - dstArray[i] = fmi3GetUInt64(inst, vr) - elseif isa(mv, FMICore.fmi3VariableBoolean) - #@assert isa(dstArray[i], Union{Real, Bool}) "fmi3Get!(...): Unknown data type for value reference `$(vr)` at index $(i), should be `Bool`, is `$(typeof(dstArray[i]))`." - dstArray[i] = fmi3GetBoolean(inst, vr) - elseif isa(mv, FMICore.fmi3VariableString) - #@assert isa(dstArray[i], String) "fmi3Get!(...): Unknown data type for value reference `$(vr)` at index $(i), should be `String`, is `$(typeof(dstArray[i]))`." - dstArray[i] = fmi3GetString(inst, vr) - elseif isa(mv, FMICore.fmi3VariableBinary) - #@assert isa(dstArray[i], String) "fmi3Get!(...): Unknown data type for value reference `$(vr)` at index $(i), should be `String`, is `$(typeof(dstArray[i]))`." - dstArray[i] = fmi3GetBinary(inst, vr) - elseif isa(mv, FMICore.fmi3VariableEnumeration) - @warn "fmi3Get!(...): Currently not implemented for fmi3Enum." - else - @assert isa(dstArray[i], Real) "fmi3Get!(...): Unknown data type for value reference `$(vr)` at index $(i), is `$(mv.datatype.datatype)`." - end - end - - return retcodes -end - -""" - - fmi3Get(inst::FMU3Instance, vrs::fmi3ValueReferenceFormat) - - -Returns the specific value of `fmi3Variable` containing the modelVariables with the identical fmi3ValueReference in an array. - -# Arguments -- `inst::FMU3Instance`: Mutable struct represents an instantiated instance of an FMU in the FMI 3.0 Standard. -- `vrs::fmi3ValueReferenceFormat`: wildcards for how a user can pass a fmi[X]ValueReference -More detailed: `fmi3ValueReferenceFormat = Union{Nothing, String, Array{String,1}, fmi3ValueReference, Array{fmi3ValueReference,1}, Int64, Array{Int64,1}, Symbol}` - -# Returns -- `dstArray::Array{Any,1}(undef, length(vrs))`: Stores the specific value of `fmi3Variable` containing the modelVariables with the identical fmi3ValueReference to the input variable vr (vr = vrs[i]). `dstArray` is a 1-Dimensional Array that has the same length as `vrs`. - -# Source -- FMISpec3.0 Link: [https://fmi-standard.org/](https://fmi-standard.org/) -- FMISpec3.0: 2.2.4 Status Returned by Functions -- FMISpec3.0: 2.2.6.2. Getting and Setting Variable Values -""" -function fmi3Get(inst::FMU3Instance, vrs::fmi3ValueReferenceFormat) - vrs = prepareValueReference(inst, vrs) - dstArray = Array{Any,1}(undef, length(vrs)) - fmi3Get!(inst, vrs, dstArray) - - if length(dstArray) == 1 - return dstArray[1] - else - return dstArray - end -end - -""" - fmi3Set(inst::FMU3Instance, vrs::fmi3ValueReferenceFormat, srcArray::AbstractArray; filter=nothing) - -Stores the specific value of `fmi3Variable` containing the modelVariables with the identical fmi3ValueReference and returns an array that indicates the Status. - -# Arguments -- `inst::FMU3Instance`: Mutable struct represents an instantiated instance of an FMU in the FMI 3.0 Standard. -- `vrs::fmi3ValueReferenceFormat`: wildcards for how a user can pass a fmi[X]ValueReference -More detailed: `fmi3ValueReferenceFormat = Union{Nothing, String, Array{String,1}, fmi3ValueReference, Array{fmi3ValueReference,1}, Int64, Array{Int64,1}, Symbol}` -- `srcArray::AbstractArray`: Stores the specific value of `fmi3Variable` containing the modelVariables with the identical fmi3ValueReference to the input variable vr (vr = vrs[i]). `srcArray` has the same length as `vrs`. - -# Keywords -- `filter=nothing`: whether the individual values of "fmi3Variable" are to be stored -# Returns -- `retcodes::Array{fmi3Status}`: Returns an array of length length(vrs) with Type `fmi3Status`. Type `fmi3Status` is an enumeration and indicates the success of the function call. -More detailed: - - `fmi3OK`: all well - - `fmi3Warning`: things are not quite right, but the computation can continue - - `fmi3Discard`: if the slave computed successfully only a subinterval of the communication step - - `fmi3Error`: the communication step could not be carried out at all - - `fmi3Fatal`: if an error occurred which corrupted the FMU irreparably - -# Source -- FMISpec3.0 Link: [https://fmi-standard.org/](https://fmi-standard.org/) -- FMISpec3.0: 2.2.4 Status Returned by Functions -- FMISpec3.0: 2.2.6.2. Getting and Setting Variable Values -""" -function fmi3Set(inst::FMU3Instance, vrs::fmi3ValueReferenceFormat, srcArray::Array) - vrs = prepareValueReference(inst, vrs) - - @assert length(vrs) == length(srcArray) "fmi3Set(...): Number of value references doesn't match number of `srcArray` elements." - - retcodes = zeros(fmi3Status, length(vrs)) # fmi3StatusOK - - for i in 1:length(vrs) - vr = vrs[i] - mv = fmi3ModelVariablesForValueReference(inst.fmu.modelDescription, vr) - mv = mv[1] - if isa(mv, FMICore.fmi3VariableFloat32) - #@assert isa(dstArray[i], Real) "fmi3Set!(...): Unknown data type for value reference `$(vr)` at index $(i), should be `Real`, is `$(typeof(dstArray[i]))`." - fmi3SetFloat32(inst, vr, srcArray[i]) - elseif isa(mv, FMICore.fmi3VariableFloat64) - #@assert isa(dstArray[i], Union{Real, Integer}) "fmi3Set!(...): Unknown data type for value reference `$(vr)` at index $(i), should be `Integer`, is `$(typeof(dstArray[i]))`." - fmi3SetFloat64(inst, vr, srcArray[i]) - elseif isa(mv, FMICore.fmi3VariableInt8) - #@assert isa(dstArray[i], Union{Real, Integer}) "fmi3Set!(...): Unknown data type for value reference `$(vr)` at index $(i), should be `Integer`, is `$(typeof(dstArray[i]))`." - fmi3SetInt8(inst, vr, Integer(srcArray[i])) - elseif isa(mv, FMICore.fmi3VariableInt16) - #@assert isa(dstArray[i], Union{Real, Integer}) "fmi3Set!(...): Unknown data type for value reference `$(vr)` at index $(i), should be `Integer`, is `$(typeof(dstArray[i]))`." - fmi3SetInt16(inst, vr, Integer(srcArray[i])) - elseif isa(mv, FMICore.fmi3VariableInt32) - #@assert isa(dstArray[i], Union{Real, Integer}) "fmi3Set!(...): Unknown data type for value reference `$(vr)` at index $(i), should be `Integer`, is `$(typeof(dstArray[i]))`." - fmi3SetInt32(inst, vr, Int32(srcArray[i])) - elseif isa(mv, FMICore.fmi3VariableInt64) - #@assert isa(dstArray[i], Union{Real, Integer}) "fmi3Set!(...): Unknown data type for value reference `$(vr)` at index $(i), should be `Integer`, is `$(typeof(dstArray[i]))`." - fmi3SetInt64(inst, vr, Integer(srcArray[i])) - elseif isa(mv, FMICore.fmi3VariableUInt8) - #@assert isa(dstArray[i], Union{Real, Integer}) "fmi3Set!(...): Unknown data type for value reference `$(vr)` at index $(i), should be `Integer`, is `$(typeof(dstArray[i]))`." - fmi3SetUInt8(inst, vr, Integer(srcArray[i])) - elseif isa(mv, FMICore.fmi3VariableUInt16) - #@assert isa(dstArray[i], Union{Real, Integer}) "fmi3Set!(...): Unknown data type for value reference `$(vr)` at index $(i), should be `Integer`, is `$(typeof(dstArray[i]))`." - fmi3SetUInt16(inst, vr, Integer(srcArray[i])) - elseif isa(mv, FMICore.fmi3VariableUInt32) - #@assert isa(dstArray[i], Union{Real, Integer}) "fmi3Set!(...): Unknown data type for value reference `$(vr)` at index $(i), should be `Integer`, is `$(typeof(dstArray[i]))`." - fmi3SetUInt32(inst, vr, Integer(srcArray[i])) - elseif isa(mv, FMICore.fmi3VariableUInt64) - #@assert isa(dstArray[i], Union{Real, Integer}) "fmi3Set!(...): Unknown data type for value reference `$(vr)` at index $(i), should be `Integer`, is `$(typeof(dstArray[i]))`." - fmi3SetUInt64(inst, vr, Integer(srcArray[i])) - elseif isa(mv, FMICore.fmi3VariableBoolean) - #@assert isa(dstArray[i], Union{Real, Bool}) "fmi3Set!(...): Unknown data type for value reference `$(vr)` at index $(i), should be `Bool`, is `$(typeof(dstArray[i]))`." - fmi3SetBoolean(inst, vr, Bool(srcArray[i])) - elseif isa(mv, FMICore.fmi3VariableString) - #@assert isa(dstArray[i], String) "fmi3Set!(...): Unknown data type for value reference `$(vr)` at index $(i), should be `String`, is `$(typeof(dstArray[i]))`." - fmi3SetString(inst, vr, srcArray[i]) - elseif isa(mv, FMICore.fmi3VariableBinary) - #@assert isa(dstArray[i], String) "fmi3Set!(...): Unknown data type for value reference `$(vr)` at index $(i), should be `String`, is `$(typeof(dstArray[i]))`." - fmi3SetBinary(inst, vr, Csize_t(length(srcArray[i])), pointer(srcArray[i])) # TODO fix this - elseif isa(mv, FMICore.fmi3VariableEnumeration) - @warn "fmi3Set!(...): Currently not implemented for fmi3Enum." - else - @assert isa(dstArray[i], Real) "fmi3Set!(...): Unknown data type for value reference `$(vr)` at index $(i), is `$(typeof(mv))`." - end - end - - return retcodes -end - -function fmi3Set(inst::FMU3Instance, vr::Union{fmi3ValueReference, String}, value) - vrs = prepareValueReference(inst, vr) - - ret = fmi3Set(inst, vrs, [value]) - - return ret[1] -end - -""" - - fmi3GetStartValue(md::fmi3ModelDescription, vrs::fmi3ValueReferenceFormat = md.valueReferences) - -Returns the start/default value for a given value reference. - -# Arguments -- `md::fmi3ModelDescription`: Struct which provides the static information of ModelVariables. -- `vrs::fmi3ValueReferenceFormat = md.valueReferences`: wildcards for how a user can pass a fmi[X]ValueReference (default = md.valueReferences) -More detailed: `fmi3ValueReferenceFormat = Union{Nothing, String, Array{String,1}, fmi3ValueReference, Array{fmi3ValueReference,1}, Int64, Array{Int64,1}, Symbol}` - -# Returns -- first optional function: `starts::Array{fmi3ValueReferenceFormat}`: start/default value for a given value reference -- second optional function:`starts::fmi3ValueReferenceFormat`: start/default value for a given value reference - -# Source -- FMISpec3.0 Link: [https://fmi-standard.org/](https://fmi-standard.org/) -- FMISpec3.0: 2.4.7 Model Variables -""" -function fmi3GetStartValue(md::fmi3ModelDescription, vrs::fmi3ValueReferenceFormat = md.valueReferences) - - vrs = prepareValueReference(md, vrs) - - starts = [] - - for vr in vrs - mvs = fmi3ModelVariablesForValueReference(md, vr) - - if length(mvs) == 0 - @warn "fmi3GetStartValue(...): Found no model variable with value reference $(vr)." - end - - push!(starts, fmi3GetStartValue(mvs[1]) ) - end - - if length(vrs) == 1 - return starts[1] - else - return starts - end -end - -""" - - fmi3GetStartValue(fmu::FMU3, vrs::fmi3ValueReferenceFormat = fmu.modelDescription.valueReferences) - -Returns the start/default value for a given value reference. - -# Arguments -- `fmu::FMU3`: Mutable struct representing a FMU and all it instantiated instances in the FMI 3.0 Standard. -- `vrs::fmi3ValueReferenceFormat = md.valueReferences`: wildcards for how a user can pass a fmi[X]ValueReference (default = md.valueReferences) -More detailed: `fmi3ValueReferenceFormat = Union{Nothing, String, Array{String,1}, fmi3ValueReference, Array{fmi3ValueReference,1}, Int64, Array{Int64,1}, Symbol}` - -# Returns -- first optional function: `starts::Array{fmi3ValueReferenceFormat}`: start/default value for a given value reference -- second optional function:`starts::fmi3ValueReferenceFormat`: start/default value for a given value reference - -# Source -- FMISpec3.0 Link: [https://fmi-standard.org/](https://fmi-standard.org/) -- FMISpec3.0: 2.4.7 Model Variables -""" -function fmi3GetStartValue(fmu::FMU3, vrs::fmi3ValueReferenceFormat = fmu.modelDescription.valueReferences) - fmi3GetStartValue(fmu.modelDescription, vrs) -end - -""" - - fmi3GetStartValue(c::FMU3Instance, vrs::fmi3ValueReferenceFormat = c.fmu.modelDescription.valueReferences) - -Returns the start/default value for a given value reference. - -# Arguments -- `c::FMU3Instance`: Mutable struct represents an instantiated instance of an FMU in the FMI 3.0 Standard. -- `vrs::fmi3ValueReferenceFormat = md.valueReferences`: wildcards for how a user can pass a fmi[X]ValueReference (default = md.valueReferences) -More detailed: `fmi3ValueReferenceFormat = Union{Nothing, String, Array{String,1}, fmi3ValueReference, Array{fmi3ValueReference,1}, Int64, Array{Int64,1}, Symbol}` - -# Returns -- first optional function: `starts::Array{fmi3ValueReferenceFormat}`: start/default value for a given value reference -- second optional function:`starts::fmi3ValueReferenceFormat`: start/default value for a given value reference - -# Source -- FMISpec3.0 Link: [https://fmi-standard.org/](https://fmi-standard.org/) -- FMISpec3.0: 2.4.7 Model Variables -""" -function fmi3GetStartValue(c::FMU3Instance, vrs::fmi3ValueReferenceFormat = c.fmu.modelDescription.valueReferences) - - vrs = prepareValueReference(c, vrs) - - starts = [] - - for vr in vrs - mvs = fmi3ModelVariablesForValueReference(c.fmu.modelDescription, vr) - - if length(mvs) == 0 - @warn "fmi3GetStartValue(...): Found no model variable with value reference $(vr)." - end - for mv in mvs - if hasproperty(mv, :start) - push!(starts, mv.start) - end - end - end - - if length(vrs) == 1 - return starts[1] - else - return starts - end -end - -""" - - fmi3GetStartValue(mv::fmi3Variable) - -Returns the start/default value for a given value reference. - -# Arguments -- `mv::fmi3Variable`: The “ModelVariables” element consists of an ordered set of "ModelVariable” elements. A “ModelVariable” represents a variable of primitive type, like a real or integer variable. -- `vrs::fmi3ValueReferenceFormat = md.valueReferences`: wildcards for how a user can pass a fmi[X]ValueReference (default = md.valueReferences) -More detailed: `fmi3ValueReferenceFormat = Union{Nothing, String, Array{String,1}, fmi3ValueReference, Array{fmi3ValueReference,1}, Int64, Array{Int64,1}, Symbol}` - -# Returns -- `mv._Real.start`: start/default value for a given ModelVariable. In this case representing a variable of primitive type Real. -- `mv._Integer.start`: start/default value for a given ModelVariable. In this case representing a variable of primitive type Integer. -- `mv._Boolean.start`: start/default value for a given ModelVariable. In this case representing a variable of primitive type Boolean. -- `mv._String.start`: start/default value for a given ModelVariable. In this case representing a variable of primitive type String. -- `mv._Enumeration.start`: start/default value for a given ModelVariable. In this case representing a variable of primitive type Enumeration. - - -# Source -- FMISpec3.0 Link: [https://fmi-standard.org/](https://fmi-standard.org/) -- FMISpec3.0: 2.4.7 Model Variables -""" - -function fmi3GetStartValue(mv::fmi3Variable) - if hasproperty(mv, :start) - return mv.start - end -end - -""" - function fmi3SampleDirectionalDerivative(c::FMU3Instance, - vUnknown_ref::Array{fmi3ValueReference}, - vKnown_ref::Array{fmi3ValueReference}, - steps::Array{fmi3Float64} = ones(fmi3Float64, length(vKnown_ref)).*1e-5) - -Wrapper for [`fmi3SampleDirectionalDerivative!`](@ref) with `dvUnknown` initialized with zeros - -Returning dvUnknown, modified by `fmi3SampleDirectionalDerivative!` call. -""" -function fmi3SampleDirectionalDerivative(c::FMU3Instance, - vUnknown_ref::Array{fmi3ValueReference}, - vKnown_ref::Array{fmi3ValueReference}, - steps::Array{fmi3Float64} = ones(fmi3Float64, length(vKnown_ref)).*1e-5) - - dvUnknown = zeros(fmi3Float64, length(vUnknown_ref), length(vKnown_ref)) - - fmi3SampleDirectionalDerivative!(c, vUnknown_ref, vKnown_ref, dvUnknown, steps) - - dvUnknown -end - -function fmi3SampleDirectionalDerivative!(c::FMU3Instance, - vUnknown_ref::Array{fmi3ValueReference}, - vKnown_ref::Array{fmi3ValueReference}, - dvUnknown::AbstractArray, - steps::Array{fmi3Float64} = ones(fmi3Float64, length(vKnown_ref)).*1e-5) - - for i in 1:length(vKnown_ref) - vKnown = vKnown_ref[i] - origValue = fmi3GetFloat64(c, vKnown) - - fmi3Set(c, vKnown, origValue - steps[i]*0.5) - negValues = fmi3GetFloat64(c, vUnknown_ref) - - fmi3Set(c, vKnown, origValue + steps[i]*0.5) - posValues = fmi3GetFloat64(c, vUnknown_ref) - - fmi3Set(c, vKnown, origValue) - - if length(vUnknown_ref) == 1 - dvUnknown[1,i] = (posValues-negValues) ./ steps[i] - else - dvUnknown[:,i] = (posValues-negValues) ./ steps[i] - end - end - - nothing -end - -""" - - fmi3GetUnit(mv::fmi3Variable) - -Returns the `unit` entry of the corresponding model variable. - -# Arguments -- `mv::fmi3Variable`: The “ModelVariables” element consists of an ordered set of “ModelVariable” elements. A “ModelVariable” represents a variable of primitive type, like a real or integer variable. - -# Returns -- `mv._Float.unit`: Returns the `unit` entry of the corresponding ScalarVariable representing a variable of the primitive type Real. Otherwise `nothing` is returned. -# Source -- FMISpec3.0 Link: [https://fmi-standard.org/](https://fmi-standard.org/) -- FMISpec3.0: 2.4.7 Model Variables - -""" -function fmi3GetUnit(mv::fmi3Variable) - if mv._Float !== nothing - return mv._Float.unit - else - return nothing - end -end - -""" - - fmi3GetInitial(mv::fmi3Variable) - -Returns the `inital` entry of the corresponding model variable. - -# Arguments -- `mv::fmi3Variable`: The “ModelVariables” element consists of an ordered set of “ModelVariable” elements. A “ModelVariable” represents a variable of primitive type, like a real or integer variable. - -# Returns -- `mv._Float.initial`: Returns the `inital` entry of the corresponding ModelVariable representing a variable of the primitive type Real. Otherwise `nothing` is returned. - -# Source -- FMISpec3.0 Link: [https://fmi-standard.org/](https://fmi-standard.org/) -- FMISpec3.0: 2.4.7 Model Variables -""" -function fmi3GetInitial(mv::fmi3Variable) - return mv.initial +function unloadPointers(fmu::FMU3) + # [TODO]: Write a macro for that. end \ No newline at end of file diff --git a/src/FMI3/fmu_to_md.jl b/src/FMI3/fmu_to_md.jl deleted file mode 100644 index bbcf7ac..0000000 --- a/src/FMI3/fmu_to_md.jl +++ /dev/null @@ -1,59 +0,0 @@ -# -# Copyright (c) 2021 Tobias Thummerer, Lars Mikelsons, Josef Kircher -# Licensed under the MIT license. See LICENSE file in the project root for details. -# - -# What is included in the file `FMI3_fmu_to_md.jl` (FMU to model description)? -# - wrappers to call the model description functions from a FMU-instance [exported] - -function fmi3GetModelName(fmu::FMU3) - fmi3GetModelName(fmu.modelDescription) -end - -function fmi3GetInstantiationToken(fmu::FMU3) - fmi3GetInstantiationToken(fmu.modelDescription) -end - -function fmi3GetGenerationTool(fmu::FMU3) - fmi3GetGenerationTool(fmu.modelDescription) -end - -function fmi3GetGenerationDateAndTime(fmu::FMU3) - fmi3GetGenerationDateAndTime(fmu.modelDescription) -end - -function fmi3GetVariableNamingConvention(fmu::FMU3) - fmi3GetVariableNamingConvention(fmu.modelDescription) -end - -function fmi3GetNumberOfEventIndicators(fmu::FMU3) - fmi3GetNumberOfEventIndicators(fmu.modelDescription) -end - -function fmi3CanGetSetState(fmu::FMU3) - fmi3CanGetSetState(fmu.modelDescription) -end - -function fmi3CanSerializeFMUState(fmu::FMU3) - fmi3CanSerializeFMUState(fmu.modelDescription) -end - -function fmi3ProvidesDirectionalDerivatives(fmu::FMU3) - fmi3ProvidesDirectionalDerivatives(fmu.modelDescription) -end - -function fmi3ProvidesAdjointDerivatives(fmu::FMU3) - fmi3ProvidesAdjointDerivatives(fmu.modelDescription) -end - -function fmi3IsCoSimulation(fmu::FMU3) - fmi3IsCoSimulation(fmu.modelDescription) -end - -function fmi3IsModelExchange(fmu::FMU3) - fmi3IsModelExchange(fmu.modelDescription) -end - -function fmi3IsScheduledExecution(fmu::FMU3) - fmi3IsScheduledExecution(fmu.modelDescription) -end diff --git a/src/FMI3/int.jl b/src/FMI3/int.jl index ed33faa..27fe49e 100644 --- a/src/FMI3/int.jl +++ b/src/FMI3/int.jl @@ -1,13 +1,330 @@ # -# Copyright (c) 2021 Tobias Thummerer, Lars Mikelsons, Josef Kircher +# Copyright (c) 2024 Tobias Thummerer, Lars Mikelsons # Licensed under the MIT license. See LICENSE file in the project root for details. # -# What is included in the file `FMI3_int.jl` (internal functions)? -# - optional, more comfortable calls to the C-functions from the FMI-spec (example: `fmiGetReal!(c, v, a)` is bulky, `a = fmiGetReal(c, v)` is more user friendly) +""" + + fmi3InstantiateModelExchange!(fmu::FMU3; instanceName::String=fmu.modelName, type::fmi3Type=fmu.type, pushInstances::Bool = true, visible::Bool = false, loggingOn::Bool = fmu.executionConfig.loggingOn, externalCallbacks::Bool = fmu.executionConfig.externalCallbacks, + logStatusOK::Bool=true, logStatusWarning::Bool=true, logStatusDiscard::Bool=true, logStatusError::Bool=true, logStatusFatal::Bool=true) + +Create a new modelExchange instance of the given fmu, adds a logger if `logginOn == true`. + +# Arguments +- `fmu::FMU3`: Mutable struct representing a FMU and all it instantiated instances in the FMI 3.0 Standard. + +# Keywords +- `instanceName::String=fmu.modelName`: Name of the instance +- `type::fmi3Type=fmu.type`: Defines whether a Co-Simulation or Model Exchange is present +- `pushInstances::Bool = true`: Defines if the fmu instances should be pushed in the application. +- `visible::Bool = false` if the FMU should be started with graphic interface, if supported (default=`false`) +- `loggingOn::Bool = fmu.executionConfig.loggingOn` if the FMU should log and display function calls (default=`false`) +- `externalCallbacks::Bool = fmu.executionConfig.externalCallbacks` if an external shared library should be used for the fmi3CallbackFunctions, this may improve readability of logging messages (default=`false`) +- `logStatusOK::Bool=true` whether to log status of kind `fmi3OK` (default=`true`) +- `logStatusWarning::Bool=true` whether to log status of kind `fmi3Warning` (default=`true`) +- `logStatusDiscard::Bool=true` whether to log status of kind `fmi3Discard` (default=`true`) +- `logStatusError::Bool=true` whether to log status of kind `fmi3Error` (default=`true`) +- `logStatusFatal::Bool=true` whether to log status of kind `fmi3Fatal` (default=`true`) + +# Returns +- Returns the instance of a new FMU modelExchange instance. + +# Source +- FMISpec3.0 Link: [https://fmi-standard.org/](https://fmi-standard.org/) +- FMISpec3.0: 2.4.7 Model variables +- FMISpec3.0: 2.3.1. Super State: FMU State Setable + +See also [`fmi3InstantiateModelExchange`](#@ref). +""" +function fmi3InstantiateModelExchange!(fmu::FMU3; instanceName::String = fmu.modelName, type::fmi3Type = fmu.type, pushInstances::Bool = true, visible::Bool = false, loggingOn::Bool = fmu.executionConfig.loggingOn, externalCallBacks::Bool = fmu.executionConfig.externalCallbacks, + logStatusOK::Bool=true, logStatusWarning::Bool=true, logStatusDiscard::Bool=true, logStatusError::Bool=true, logStatusFatal::Bool=true) + + instEnv = FMU3InstanceEnvironment() + instEnv.logStatusOK = logStatusOK + instEnv.logStatusWarning = logStatusWarning + instEnv.logStatusDiscard = logStatusDiscard + instEnv.logStatusError = logStatusError + instEnv.logStatusFatal = logStatusFatal + + ptrLogger = @cfunction(fmi3CallbackLogger, Cvoid, (Ptr{FMU3InstanceEnvironment}, Cuint, Ptr{Cchar}, Ptr{Cchar})) + + ptrInstanceEnvironment = Ptr{Cvoid}(pointer_from_objref(instEnv)) + + instantiationTokenStr = "$(fmu.modelDescription.instantiationToken)" + + addr = fmi3InstantiateModelExchange(fmu.cInstantiateModelExchange, pointer(instanceName), pointer(instantiationTokenStr), pointer(fmu.fmuResourceLocation), fmi3Boolean(visible), fmi3Boolean(loggingOn), ptrInstanceEnvironment, ptrLogger) + + if addr == Ptr{Cvoid}(C_NULL) + @error "fmi3InstantiateModelExchange!(...): Instantiation failed!" + return nothing + end + + instance = nothing + + # check if address is already inside of the instance (this may be) + for c in fmu.instances + if c.addr == addr + instance = c + break + end + end + + if instance !== nothing + @info "fmi3InstantiateModelExchange!(...): This component was already registered. This may be because you created the FMU by yourself with FMIExport.jl." + else + + instance = FMU3Instance(addr, fmu) + instance.instanceEnvironment = instEnv + instance.instanceName = instanceName + instance.z_prev = zeros(fmi3Float64, fmi3GetNumberOfEventIndicators(instance)) + instance.rootsFound = zeros(fmi3Int32, fmi3GetNumberOfEventIndicators(instance)) + instance.stateEvent = fmi3False + instance.timeEvent = fmi3False + instance.stepEvent = fmi3False + instance.type = fmi3TypeModelExchange + + if pushInstances + push!(fmu.instances, instance) + end + + fmu.threadInstances[Threads.threadid()] = instance + end + + return getCurrentInstance(fmu) +end +# [NOTE] needs to be exported, because FMICore only exports `fmi3InstantiateModelExchange` +export fmi3InstantiateModelExchange! + +""" + + fmi3InstantiateCoSimulation!(fmu::FMU3; instanceName::String=fmu.modelName, type::fmi3Type=fmu.type, pushInstances::Bool = true, visible::Bool = false, loggingOn::Bool = fmu.executionConfig.loggingOn, externalCallbacks::Bool = fmu.executionConfig.externalCallbacks, + eventModeUsed::Bool = false, ptrIntermediateUpdate=nothing, logStatusOK::Bool=true, logStatusWarning::Bool=true, logStatusDiscard::Bool=true, logStatusError::Bool=true, logStatusFatal::Bool=true) + +Create a new coSimulation instance of the given fmu, adds a logger if `logginOn == true`. + +# Arguments +- `fmu::FMU3`: Mutable struct representing a FMU and all it instantiated instances in the FMI 3.0 Standard. + +# Keywords +- `instanceName::String=fmu.modelName`: Name of the instance +- `type::fmi3Type=fmu.type`: Defines whether a Co-Simulation or Model Exchange is present +- `pushInstances::Bool = true`: Defines if the fmu instances should be pushed in the application. +- `visible::Bool = false` if the FMU should be started with graphic interface, if supported (default=`false`) +- `loggingOn::Bool = fmu.executionConfig.loggingOn` if the FMU should log and display function calls (default=`false`) +- `externalCallbacks::Bool = fmu.executionConfig.externalCallbacks` if an external shared library should be used for the fmi3CallbackFunctions, this may improve readability of logging messages (default=`false`) +- `eventModeUsed::Bool = false`: Defines if the FMU instance can use the event mode. (default=`false`) +- `ptrIntermediateUpdate=nothing`: Points to a function handling intermediate Updates (defalut=`nothing`) +- `logStatusOK::Bool=true` whether to log status of kind `fmi3OK` (default=`true`) +- `logStatusWarning::Bool=true` whether to log status of kind `fmi3Warning` (default=`true`) +- `logStatusDiscard::Bool=true` whether to log status of kind `fmi3Discard` (default=`true`) +- `logStatusError::Bool=true` whether to log status of kind `fmi3Error` (default=`true`) +- `logStatusFatal::Bool=true` whether to log status of kind `fmi3Fatal` (default=`true`) + +# Returns +- Returns the instance of a new FMU coSimulation instance. + +# Source +- FMISpec3.0 Link: [https://fmi-standard.org/](https://fmi-standard.org/) +- FMISpec3.0: 2.4.7 Model variables +- FMISpec3.0: 2.3.1. Super State: FMU State Setable + +See also [`fmi3InstantiateCoSimulation`](#@ref). +""" +function fmi3InstantiateCoSimulation!(fmu::FMU3; instanceName::String=fmu.modelName, type::fmi3Type=fmu.type, pushInstances::Bool = true, visible::Bool = false, loggingOn::Bool = fmu.executionConfig.loggingOn, externalCallbacks::Bool = fmu.executionConfig.externalCallbacks, + eventModeUsed::Bool = false, ptrIntermediateUpdate=nothing, logStatusOK::Bool=true, logStatusWarning::Bool=true, logStatusDiscard::Bool=true, logStatusError::Bool=true, logStatusFatal::Bool=true) + instEnv = FMU3InstanceEnvironment() + instEnv.logStatusOK = logStatusOK + instEnv.logStatusWarning = logStatusWarning + instEnv.logStatusDiscard = logStatusDiscard + instEnv.logStatusError = logStatusError + instEnv.logStatusFatal = logStatusFatal + + ptrLogger = @cfunction(fmi3CallbackLogger, Cvoid, (Ptr{FMU3InstanceEnvironment}, Cuint, Ptr{Cchar}, Ptr{Cchar})) + + if ptrIntermediateUpdate === nothing + ptrIntermediateUpdate = @cfunction(fmi3CallbackIntermediateUpdate, Cvoid, (Ptr{Cvoid}, fmi3Float64, fmi3Boolean, fmi3Boolean, fmi3Boolean, fmi3Boolean, Ptr{fmi3Boolean}, Ptr{fmi3Float64})) + end + if fmu.modelDescription.coSimulation.hasEventMode !== nothing + mode = eventModeUsed + else + mode = false + end + ptrInstanceEnvironment = Ptr{Cvoid}(pointer_from_objref(instEnv)) + + instantiationTokenStr = "$(fmu.modelDescription.instantiationToken)" + + addr = fmi3InstantiateCoSimulation(fmu.cInstantiateCoSimulation, pointer(instanceName), pointer(instantiationTokenStr), pointer(fmu.fmuResourceLocation), fmi3Boolean(visible), fmi3Boolean(loggingOn), + fmi3Boolean(mode), fmi3Boolean(fmu.modelDescription.coSimulation.canReturnEarlyAfterIntermediateUpdate !== nothing), fmu.modelDescription.intermediateUpdateValueReferences, Csize_t(length(fmu.modelDescription.intermediateUpdateValueReferences)), ptrInstanceEnvironment, ptrLogger, ptrIntermediateUpdate) + + if addr == Ptr{Cvoid}(C_NULL) + @error "fmi3InstantiateCoSimulation!(...): Instantiation failed!" + return nothing + end + + instance = nothing + + # check if address is already inside of the instance (this may be) + for c in fmu.instances + if c.addr == addr + instance = c + break + end + end + + if instance !== nothing + @info "fmi3InstantiateCoSimulation!(...): This component was already registered. This may be because you created the FMU by yourself with FMIExport.jl." + else + instance = FMU3Instance(addr, fmu) + instance.instanceEnvironment = instEnv + instance.instanceName = instanceName + instance.type = fmi3TypeCoSimulation + + if pushInstances + push!(fmu.instances, instance) + end + + fmu.threadInstances[Threads.threadid()] = instance + end + + return getCurrentInstance(fmu) +end +# [NOTE] needs to be exported, because FMICore only exports `fmi3InstantiateCoSimulation` +export fmi3InstantiateCoSimulation! + +# TODO not tested +""" + + fmi3InstantiateScheduledExecution!(fmu::FMU3; ptrlockPreemption::Ptr{Cvoid}, ptrunlockPreemption::Ptr{Cvoid}, instanceName::String=fmu.modelName, type::fmi3Type=fmu.type, pushInstances::Bool = true, visible::Bool = false, loggingOn::Bool = fmu.executionConfig.loggingOn, externalCallbacks::Bool = fmu.executionConfig.externalCallbacks, + logStatusOK::Bool=true, logStatusWarning::Bool=true, logStatusDiscard::Bool=true, logStatusError::Bool=true, logStatusFatal::Bool=true) + +Create a new ScheduledExecution instance of the given fmu, adds a logger if `logginOn == true`. + +# Arguments +- `fmu::FMU3`: Mutable struct representing a FMU and all it instantiated instances in the FMI 3.0 Standard. + +# Keywords +- `ptrlockPreemption::Ptr{Cvoid}`: Points to a function handling locking Preemption +- `ptrunlockPreemption::Ptr{Cvoid}`: Points to a function handling unlocking Preemption +- `instanceName::String=fmu.modelName`: Name of the instance +- `type::fmi3Type=fmu.type`: Defines whether a Co-Simulation or Model Exchange is present +- `pushInstances::Bool = true`: Defines if the fmu instances should be pushed in the application. +- `visible::Bool = false` if the FMU should be started with graphic interface, if supported (default=`false`) +- `loggingOn::Bool = fmu.executionConfig.loggingOn` if the FMU should log and display function calls (default=`false`) +- `externalCallbacks::Bool = fmu.executionConfig.externalCallbacks` if an external shared library should be used for the fmi3CallbackFunctions, this may improve readability of logging messages (default=`false`) +- `logStatusOK::Bool=true` whether to log status of kind `fmi3OK` (default=`true`) +- `logStatusWarning::Bool=true` whether to log status of kind `fmi3Warning` (default=`true`) +- `logStatusDiscard::Bool=true` whether to log status of kind `fmi3Discard` (default=`true`) +- `logStatusError::Bool=true` whether to log status of kind `fmi3Error` (default=`true`) +- `logStatusFatal::Bool=true` whether to log status of kind `fmi3Fatal` (default=`true`) + +# Returns +- Returns the instance of a new FMU ScheduledExecution instance. + +# Source +- FMISpec3.0 Link: [https://fmi-standard.org/](https://fmi-standard.org/) +- FMISpec3.0: 2.4.7 Model variables +- FMISpec3.0: 2.3.1. Super State: FMU State Setable + +See also [`fmi3InstantiateScheduledExecution`](#@ref). +""" +function fmi3InstantiateScheduledExecution!(fmu::FMU3; ptrlockPreemption::Ptr{Cvoid}, ptrunlockPreemption::Ptr{Cvoid}, instanceName::String=fmu.modelName, type::fmi3Type=fmu.type, pushInstances::Bool = true, visible::Bool = false, loggingOn::Bool = fmu.executionConfig.loggingOn, externalCallbacks::Bool = fmu.executionConfig.externalCallbacks, + logStatusOK::Bool=true, logStatusWarning::Bool=true, logStatusDiscard::Bool=true, logStatusError::Bool=true, logStatusFatal::Bool=true) + + instEnv = FMU3InstanceEnvironment() + instEnv.logStatusOK = logStatusOK + instEnv.logStatusWarning = logStatusWarning + instEnv.logStatusDiscard = logStatusDiscard + instEnv.logStatusError = logStatusError + instEnv.logStatusFatal = logStatusFatal + + ptrLogger = @cfunction(fmi3CallbackLogger, Cvoid, (Ptr{FMU3InstanceEnvironment}, Cuint, Ptr{Cchar}, Ptr{Cchar})) + ptrClockUpdate = @cfunction(fmi3CallbackClockUpdate, Cvoid, (Ptr{Cvoid}, )) + + ptrInstanceEnvironment = Ptr{FMU3InstanceEnvironment}(pointer_from_objref(instEnv)) + + instantiationTokenStr = "$(fmu.modelDescription.instantiationToken)" + + addr = fmi3InstantiateScheduledExecution(fmu.cInstantiateScheduledExecution, pointer(instanceName), pointer(instantiationTokenStr), pointer(fmu.fmuResourceLocation), fmi3Boolean(visible), fmi3Boolean(loggingOn), ptrInstanceEnvironment, ptrLogger, ptrClockUpdate, ptrlockPreemption, ptrunlockPreemption) + + if addr == Ptr{Cvoid}(C_NULL) + @error "fmi3InstantiateScheduledExecution!(...): Instantiation failed!" + return nothing + end + + instance = nothing + + # check if address is already inside of the instance (this may be) + for c in fmu.instances + if c.addr == addr + instance = c + break + end + end + + if instance !== nothing + @info "fmi3InstantiateScheduledExecution!(...): This component was already registered. This may be because you created the FMU by yourself with FMIExport.jl." + else + instance = FMU3Instance(addr, fmu) + instance.instanceEnvironment = instEnv + instance.instanceName = instanceName + instance.type = fmi3TypeScheduledExecution + + if pushInstances + push!(fmu.instances, instance) + end + + fmu.threadInstances[Threads.threadid()] = instance + end + + return getCurrentInstance(fmu) +end +# [NOTE] needs to be exported, because FMICore only exports `fmi3InstantiateScheduledExecution` +export fmi3InstantiateScheduledExecution! + +""" + + fmi3FreeInstance!(c::FMU3Instance; popInstance::Bool = true) + +Disposes the given instance, unloads the loaded model, and frees all the allocated memory and other resources that have been allocated by the functions of the FMU interface. +If a null pointer is provided for “c”, the function call is ignored (does not have an effect). + +Removes the component from the FMUs component list. + +# Arguments +- `c::FMU3Instance`: Argument `c` is a Mutable struct represents an instantiated instance of an FMU in the FMI 3.0 Standard. + +# Keywords +- `popInstance::Bool=true`: If the Keyword `popInstance = true` the freed instance is deleted + +# Returns +- nothing + +# Source +- FMISpec3.0 Link: [https://fmi-standard.org/](https://fmi-standard.org/) +- FMISpec3.0, Version D5ef1c1: 2.3.1. Super State: FMU State Setable +""" +function fmi3FreeInstance!(c::FMU3Instance; popInstance::Bool = true) + + addr = c.addr + + if popInstance + ind = findall(x->x.addr==c.addr, c.fmu.instances) + @assert length(ind) == 1 "fmi3FreeInstance!(...): Freeing $(length(ind)) instances with one call, this is not allowed." + deleteat!(c.fmu.instances, ind) + + for key in keys(c.fmu.threadInstances) + if !isnothing(c.fmu.threadInstances[key]) && c.fmu.threadInstances[key].addr == addr + c.fmu.threadInstances[key] = nothing + end + end + end + fmi3FreeInstance(c.fmu.cFreeInstance, c.addr) + + nothing +end +# [NOTE] needs to be exported, because FMICore only exports `fmi3FreeInstance` +export fmi3FreeInstance! -# Best practices: -# - no direct access on C-pointers (`compAddr`), use existing FMICore-functions """ @@ -76,7 +393,7 @@ function fmi3EnterInitializationMode(c::FMU3Instance, startTime::Union{Real, Not end if startTime === nothing - startTime = fmi3GetDefaultStartTime(c.fmu.modelDescription) + startTime = getDefaultStartTime(c.fmu.modelDescription) if startTime === nothing startTime = 0.0 end @@ -93,7 +410,7 @@ function fmi3EnterInitializationMode(c::FMU3Instance, startTime::Union{Real, Not stopTime = 0.0 # dummy value, will be ignored end - status = fmi3EnterInitializationMode(c.fmu.cEnterInitializationMode, c.compAddr, fmi3Boolean(toleranceDefined), fmi3Float64(tolerance), fmi3Float64(startTime), fmi3Boolean(stopTimeDefined), fmi3Float64(stopTime)) + status = fmi3EnterInitializationMode(c.fmu.cEnterInitializationMode, c.addr, fmi3Boolean(toleranceDefined), fmi3Float64(tolerance), fmi3Float64(startTime), fmi3Boolean(stopTimeDefined), fmi3Float64(stopTime)) checkStatus(c, status) if status == fmi3StatusOK c.state = fmi3InstanceStateInitializationMode @@ -136,6 +453,8 @@ function fmi3GetFloat32(c::FMU3Instance, vr::fmi3ValueReferenceFormat) return values end end +# [NOTE] needs to be exported, because FMICore only exports `fmi3GetFloat32!` +export fmi3GetFloat32 """ @@ -253,6 +572,8 @@ function fmi3GetFloat64(c::FMU3Instance, vr::fmi3ValueReferenceFormat) return values end end +# [NOTE] needs to be exported, because FMICore only exports `fmi3GetFloat64!` +export fmi3GetFloat64 """ @@ -326,14 +647,14 @@ More detailed: See also [`fmi3SetFloat64`](@ref). """ -function fmi3SetFloat64(c::FMU3Instance, vr::fmi3ValueReferenceFormat, values::Union{AbstractArray{fmi3Float64}, fmi3Float64}) +function fmi3SetFloat64(c::FMU3Instance, vr::fmi3ValueReferenceFormat, values::Union{AbstractArray{fmi3Float64}, fmi3Float64}; kwargs...) vr = prepareValueReference(c, vr) values = prepareValue(values) @assert length(vr) == length(values) "fmi3SetFloat64(...): `vr` and `values` need to be the same length." nvr = Csize_t(length(vr)) - fmi3SetFloat64(c, vr, nvr, values, nvr) + return fmi3SetFloat64(c, vr, nvr, values, nvr; kwargs...) end """ @@ -371,6 +692,8 @@ function fmi3GetInt8(c::FMU3Instance, vr::fmi3ValueReferenceFormat) return values end end +# [NOTE] needs to be exported, because FMICore only exports `fmi3GetInt8!` +export fmi3GetInt8 """ @@ -488,6 +811,8 @@ function fmi3GetUInt8(c::FMU3Instance, vr::fmi3ValueReferenceFormat) return values end end +# [NOTE] needs to be exported, because FMICore only exports `fmi3GetUInt8!` +export fmi3GetUInt8 """ @@ -605,6 +930,8 @@ function fmi3GetInt16(c::FMU3Instance, vr::fmi3ValueReferenceFormat) return values end end +# [NOTE] needs to be exported, because FMICore only exports `fmi3GetInt16!` +export fmi3GetInt16 """ @@ -722,6 +1049,8 @@ function fmi3GetUInt16(c::FMU3Instance, vr::fmi3ValueReferenceFormat) return values end end +# [NOTE] needs to be exported, because FMICore only exports `fmi3GetUInt16!` +export fmi3GetUInt16 """ @@ -839,6 +1168,8 @@ function fmi3GetInt32(c::FMU3Instance, vr::fmi3ValueReferenceFormat) return values end end +# [NOTE] needs to be exported, because FMICore only exports `fmi3GetInt32!` +export fmi3GetInt32 """ @@ -956,6 +1287,8 @@ function fmi3GetUInt32(c::FMU3Instance, vr::fmi3ValueReferenceFormat) return values end end +# [NOTE] needs to be exported, because FMICore only exports `fmi3GetUInt32!` +export fmi3GetUInt32 """ @@ -1073,6 +1406,9 @@ function fmi3GetInt64(c::FMU3Instance, vr::fmi3ValueReferenceFormat) return values end end +# [NOTE] needs to be exported, because FMICore only exports `fmi3GetInt64!` +export fmi3GetInt64 + """ @@ -1190,6 +1526,8 @@ function fmi3GetUInt64(c::FMU3Instance, vr::fmi3ValueReferenceFormat) return values end end +# [NOTE] needs to be exported, because FMICore only exports `fmi3GetUInt64!` +export fmi3GetUInt64 """ @@ -1307,6 +1645,8 @@ function fmi3GetBoolean(c::FMU3Instance, vr::fmi3ValueReferenceFormat) return values end end +# [NOTE] needs to be exported, because FMICore only exports `fmi3GetBoolean!` +export fmi3GetBoolean """ @@ -1427,6 +1767,8 @@ function fmi3GetString(c::FMU3Instance, vr::fmi3ValueReferenceFormat) return values end end +# [NOTE] needs to be exported, because FMICore only exports `fmi3GetString!` +export fmi3GetString """ @@ -1548,6 +1890,8 @@ function fmi3GetBinary(c::FMU3Instance, vr::fmi3ValueReferenceFormat) return values end end +# [NOTE] needs to be exported, because FMICore only exports `fmi3GetBinary!` +export fmi3GetBinary """ @@ -1666,6 +2010,8 @@ function fmi3GetClock(c::FMU3Instance, vr::fmi3ValueReferenceFormat) return values end end +# [NOTE] needs to be exported, because FMICore only exports `fmi3GetClock!` +export fmi3GetClock """ @@ -1774,6 +2120,8 @@ function fmi3GetFMUState(c::FMU3Instance) state = stateRef[] state end +# [NOTE] needs to be exported, because FMICore only exports `fmi3GetFMUState!` +export fmi3GetFMUState """ @@ -1793,9 +2141,9 @@ Free the allocated memory for the FMU state. - FMISpec3.0: 2.2.3 Platform Dependent Definitions - FMISpec3.0: 2.2.6.4. Getting and Setting the Complete FMU State """ -function fmi3FreeFMUState!(c::FMU3Instance, state::fmi3FMUState) +function fmi3FreeFMUState(c::FMU3Instance, state::fmi3FMUState) stateRef = Ref(state) - fmi3FreeFMUState!(c, stateRef) + fmi3FreeFMUState(c, stateRef) state = stateRef[] end @@ -1825,6 +2173,8 @@ function fmi3SerializedFMUStateSize(c::FMU3Instance, state::fmi3FMUState) fmi3SerializedFMUStateSize!(c, state, sizeRef) size = sizeRef[] end +# [NOTE] needs to be exported, because FMICore only exports `fmi3SerializedFMUStateSize!` +export fmi3SerializedFMUStateSize """ @@ -1853,6 +2203,8 @@ function fmi3SerializeFMUState(c::FMU3Instance, state::fmi3FMUState) @assert status == Int(fmi3StatusOK) ["Failed with status `$status`."] serializedState end +# [NOTE] needs to be exported, because FMICore only exports `fmi3SerializeFMUState!` +export fmi3SerializeFMUState """ @@ -1884,6 +2236,8 @@ function fmi3DeSerializeFMUState(c::FMU3Instance, serializedState::AbstractArray state = stateRef[] end +# [NOTE] needs to be exported, because FMICore only exports `fmi3DeSerializeFMUState!` +export fmi3DeSerializeFMUState """ @@ -1928,17 +2282,19 @@ See also [`fmi3GetDirectionalDerivative`](@ref). function fmi3GetDirectionalDerivative(c::FMU3Instance, unknowns::AbstractArray{fmi3ValueReference}, knowns::AbstractArray{fmi3ValueReference}, - seed::AbstractArray{fmi3Float64} = Array{fmi3Float64}([])) + seed::AbstractArray{fmi3Float64}) nUnknown = Csize_t(length(unknowns)) - sensitivity = zeros(fmi3Float64, nUnknown) - status = fmi3GetDirectionalDerivative!(c, unknowns, knowns, sensitivity, seed) - @assert status == Int(fmi3StatusOK) ["Failed with status `$status`."] + status = fmi3GetDirectionalDerivative!(c, unknowns, knowns, seed, sensitivity) + @assert isStatusOK(c, status) "Failed with status `$(status)`." return sensitivity end +fmi3GetDirectionalDerivative(c::FMU3Instance, unknown::fmi3ValueReference, known::fmi3ValueReference, seed::fmi3Float64) = fmi3GetDirectionalDerivative(c, [unknown], [known], [seed])[1] +# [NOTE] needs to be exported, because FMICore only exports `fmi3GetDirectionalDerivative!` +export fmi3GetDirectionalDerivative """ @@ -1992,15 +2348,11 @@ See also [`fmi3GetDirectionalDerivative!`](@ref). function fmi3GetDirectionalDerivative!(c::FMU3Instance, unknowns::AbstractArray{fmi3ValueReference}, knowns::AbstractArray{fmi3ValueReference}, - sensitivity::AbstractArray, - seed::Union{AbstractArray{fmi3Float64}, Nothing} = nothing) + seed::AbstractArray{fmi3Float64}, + sensitivity::AbstractArray{fmi3Float64}) - nKnowns = Csize_t(length(knowns)) nUnknowns = Csize_t(length(unknowns)) - - if seed === nothing - seed = ones(fmi3Float64, nKnowns) - end + nKnowns = Csize_t(length(knowns)) nSeed = Csize_t(length(seed)) nSensitivity = Csize_t(length(sensitivity)) @@ -2010,54 +2362,6 @@ function fmi3GetDirectionalDerivative!(c::FMU3Instance, return status end -""" - - fmi3GetDirectionalDerivative(c::FMU3Instance, - unknowns::AbstractArray{fmi3ValueReference}, - knowns::AbstractArray{fmi3ValueReference}, - seed::fmi3Float64) - -Wrapper Function call to compute the partial derivative with respect to the variables `unknowns`. - -Computes the directional derivatives of an FMU. An FMU has different Modes and in every Mode an FMU might be described by different equations and different unknowns. The precise definitions are given in the mathematical descriptions of Model Exchange (section 3) and Co-Simulation (section 4). In every Mode, the general form of the FMU equations are: -unknowns = 𝐡(knowns, rest) - -- `unknowns`: vector of unknown Real variables computed in the actual Mode: - - Initialization Mode: unkowns kisted under `` that have type Real. - - Continuous-Time Mode (ModelExchange): The continuous-time outputs and state derivatives. (= the variables listed under `` with type Real and variability = `continuous` and the variables listed as state derivatives under `)`. - - Event Mode (ModelExchange/CoSimulation): The same variables as in the Continuous-Time Mode and additionally variables under `` with type Real and variability = `discrete`. - - Step Mode (CoSimulation): The variables listed under `` with type Real and variability = `continuous` or `discrete`. If `` is present, also the variables listed here as state derivatives. -- `knowns`: Real input variables of function h that changes its value in the actual Mode. -- `rest`: Set of input variables of function h that either changes its value in the actual Mode but are non-Real variables, or do not change their values in this Mode, but change their values in other Modes - -Computes a linear combination of the partial derivatives of h with respect to the selected input variables 𝐯_known: - -Δunknowns = (δh / δknowns) Δknowns - -# Arguments -- `c::FMU3Instance` Mutable struct represents an instantiated instance of an FMU in the FMI 3.0 Standard. -- `unknowns::AbstracArray{fmi3ValueReference}`: Argument `unknowns` contains values of type`fmi3ValueReference` which are identifiers of a variable value of the model. `unknowns` can be equated with `unknowns`(variable described above). -- `knowns::AbstractArray{fmi3ValueReference}`: Argument `knowns` contains values of type `fmi3ValueReference` which are identifiers of a variable value of the model.`knowns` can be equated with `knowns`(variable described above). -- `seed::fmi3Float64 = 1.0`: If no seed value is passed the value `seed = 1.0` is used. Compute the partial derivative with respect to `knowns` with the value `seed = 1.0`. # gehört das zu den v_rest values - -# Returns -- `sensitivity::Array{fmi3Float64}`: Return `sensitivity` contains the directional derivative vector values. - -# Source -- FMISpec3.0 Link: [https://fmi-standard.org/](https://fmi-standard.org/) -- FMISpec3.0: 2.2.3 Platform Dependent Definitions -- FMISpec3.0: 2.2.11. Getting Partial Derivatives - -See also [`fmi3GetDirectionalDerivative`](@ref). -""" -function fmi3GetDirectionalDerivative(c::FMU3Instance, - unknown::fmi3ValueReference, - known::fmi3ValueReference, - seed::fmi3Float64 = 1.0) - - fmi3GetDirectionalDerivative(c, [unknown], [known], [seed])[1] -end - """ fmi3GetAdjointDerivative(c::FMU3Instance, @@ -2100,16 +2404,19 @@ See also [`fmi3GetAdjointDerivative`](@ref). function fmi3GetAdjointDerivative(c::FMU3Instance, unknowns::AbstractArray{fmi3ValueReference}, knowns::AbstractArray{fmi3ValueReference}, - seed::AbstractArray{fmi3Float64} = Array{fmi3Float64}([])) + seed::AbstractArray{fmi3Float64}) + nUnknown = Csize_t(length(unknowns)) - sensitivity = zeros(fmi3Float64, nUnknown) - status = fmi3GetAdjointDerivative!(c, unknowns, knowns, sensitivity, seed) + status = fmi3GetAdjointDerivative!(c, unknowns, knowns, seed, sensitivity) @assert status == Int(fmi3StatusOK) ["Failed with status `$status`."] return sensitivity end +fmi3GetAdjointDerivative(c::FMU3Instance, unknowns::fmi3ValueReference, knowns::fmi3ValueReference, seed::fmi3Float64) = fmi3GetAdjointDerivative(c, [unknowns], [knowns], [seed])[1] +# [NOTE] needs to be exported, because FMICore only exports `fmi3GetAdjointDerivative!` +export fmi3GetAdjointDerivative """ @@ -2164,69 +2471,17 @@ function fmi3GetAdjointDerivative!(c::FMU3Instance, unknowns::AbstractArray{fmi3ValueReference}, knowns::AbstractArray{fmi3ValueReference}, sensitivity::AbstractArray, - seed::Union{AbstractArray{fmi3Float64}, Nothing} = nothing) + seed::AbstractArray{fmi3Float64}) nKnowns = Csize_t(length(knowns)) nUnknowns = Csize_t(length(unknowns)) - if seed === nothing - seed = ones(fmi3Float64, nKnowns) - end - nSeed = Csize_t(length(seed)) nSensitivity = Csize_t(length(sensitivity)) status = fmi3GetAdjointDerivative!(c, unknowns, nUnknowns, knowns, nKnowns, seed, nSeed, sensitivity, nSensitivity) - status -end - -""" - - fmi3GetAdjointDerivative(c::FMU3Instance, - unknowns::AbstractArray{fmi3ValueReference}, - knowns::AbstractArray{fmi3ValueReference}, - seed::fmi3Float64) - -Wrapper Function call to compute the partial derivative with respect to the variables `unknowns`. - -Computes the adjoint derivatives of an FMU. An FMU has different Modes and in every Mode an FMU might be described by different equations and different unknowns. The precise definitions are given in the mathematical descriptions of Model Exchange (section 3) and Co-Simulation (section 4). In every Mode, the general form of the FMU equations are: -unknowns = 𝐡(knowns, rest) - -- `unknowns`: vector of unknown Real variables computed in the actual Mode: - - Initialization Mode: unkowns kisted under `` that have type Real. - - Continuous-Time Mode (ModelExchange): The continuous-time outputs and state derivatives. (= the variables listed under `` with type Real and variability = `continuous` and the variables listed as state derivatives under `)`. - - Event Mode (ModelExchange/CoSimulation): The same variables as in the Continuous-Time Mode and additionally variables under `` with type Real and variability = `discrete`. - - Step Mode (CoSimulation): The variables listed under `` with type Real and variability = `continuous` or `discrete`. If `` is present, also the variables listed here as state derivatives. -- `knowns`: Real input variables of function h that changes its value in the actual Mode. -- `rest`: Set of input variables of function h that either changes its value in the actual Mode but are non-Real variables, or do not change their values in this Mode, but change their values in other Modes - -Computes a linear combination of the partial derivatives of h with respect to the selected input variables 𝐯_known: - -Δunknowns = (δh / δknowns) Δknowns - -# Arguments -- `c::FMU3Instance` Mutable struct represents an instantiated instance of an FMU in the FMI 3.0 Standard. -- `unknowns::AbstracArray{fmi3ValueReference}`: Argument `unknowns` contains values of type`fmi3ValueReference` which are identifiers of a variable value of the model. `unknowns` can be equated with `unknowns`(variable described above). -- `knowns::AbstractArray{fmi3ValueReference}`: Argument `knowns` contains values of type `fmi3ValueReference` which are identifiers of a variable value of the model.`knowns` can be equated with `knowns`(variable described above). -- `seed::fmi3Float64 = 1.0`: If no seed value is passed the value `seed = 1.0` is used. Compute the partial derivative with respect to `knowns` with the value `seed = 1.0`. # gehört das zu den v_rest values - -# Returns -- `sensitivity::Array{fmi3Float64}`: Return `sensitivity` contains the directional derivative vector values. - -# Source -- FMISpec3.0 Link: [https://fmi-standard.org/](https://fmi-standard.org/) -- FMISpec3.0: 2.2.3 Platform Dependent Definitions -- FMISpec3.0: 2.2.11. Getting Partial Derivatives - -See also [`fmi3GetAdjointDerivative`](@ref). -""" -function fmi3GetAdjointDerivative(c::FMU3Instance, - unknowns::fmi3ValueReference, - knowns::fmi3ValueReference, - seed::fmi3Float64 = 1.0) - - fmi3GetAdjointDerivative(c, [unknowns], [knowns], [seed])[1] + return status end """ @@ -2263,6 +2518,8 @@ function fmi3GetOutputDerivatives(c::FMU3Instance, vr::fmi3ValueReferenceFormat, return values end end +# [NOTE] needs to be exported, because FMICore only exports `fmi3GetOutputDerivatives!` +export fmi3GetOutputDerivatives """ @@ -2288,10 +2545,12 @@ See also [`fmi3GetNumberOfContinuousStates`](@ref). function fmi3GetNumberOfContinuousStates(c::FMU3Instance) size = 0 sizeRef = Ref(Csize_t(size)) - fmi3GetNumberOfContinuousStates!(c, sizeRef) + fmi3GetNumberOfContinuousStates!(c, sizeRef) # [ToDo, Refactor] this needs to be inplace/non-allocating! size = sizeRef[] - Int32(size) + return Int32(size) end +# [NOTE] needs to be exported, because FMICore only exports `fmi3GetNumberOfContinuousStates!` +export fmi3GetNumberOfContinuousStates """ @@ -2317,10 +2576,12 @@ See also [`fmi3GetNumberOfEventIndicators`](@ref). function fmi3GetNumberOfEventIndicators(c::FMU3Instance) size = 0 sizeRef = Ref(Csize_t(size)) - fmi3GetNumberOfEventIndicators!(c, sizeRef) + fmi3GetNumberOfEventIndicators!(c, sizeRef) # [ToDo, Refactor] this needs to be inplace/non-allocating! size = sizeRef[] - Int32(size) + return Int32(size) end +# [NOTE] needs to be exported, because FMICore only exports `fmi3GetNumberOfEventIndicators!` +export fmi3GetNumberOfEventIndicators """ @@ -2350,10 +2611,12 @@ function fmi3GetNumberOfVariableDependencies(c::FMU3Instance, vr::Union{fmi3Valu end size = 0 sizeRef = Ref(Csize_t(size)) - fmi3GetNumberOfVariableDependencies!(c, vr, sizeRef) + fmi3GetNumberOfVariableDependencies!(c, vr, sizeRef) # [ToDo, Refactor] this needs to be inplace/non-allocating! size = sizeRef[] Int32(size) end +# [NOTE] needs to be exported, because FMICore only exports `fmi3GetNumberOfVariableDependencies!` +export fmi3GetNumberOfVariableDependencies """ @@ -2397,6 +2660,8 @@ function fmi3GetVariableDependencies(c::FMU3Instance, vr::Union{fmi3ValueReferen return elementIndiceOfDependents, independents, elementIndiceOfIndependents, dependencyKinds end +# [NOTE] needs to be exported, because FMICore only exports `fmi3GetVariableDependencies!` +export fmi3GetVariableDependencies """ @@ -2421,8 +2686,10 @@ function fmi3GetContinuousStates(c::FMU3Instance) nx = Csize_t(c.fmu.modelDescription.numberOfContinuousStates) x = zeros(fmi3Float64, nx) fmi3GetContinuousStates!(c, x, nx) - x + return x end +# [NOTE] needs to be exported, because FMICore only exports `fmi3GetContinuousStates!` +export fmi3GetContinuousStates """ @@ -2447,8 +2714,10 @@ function fmi3GetNominalsOfContinuousStates(c::FMU3Instance) nx = Csize_t(c.fmu.modelDescription.numberOfContinuousStates) x = zeros(fmi3Float64, nx) fmi3GetNominalsOfContinuousStates!(c, x, nx) - x + return x end +# [NOTE] needs to be exported, because FMICore only exports `fmi3GetNominalsOfContinuousStates!` +export fmi3GetNominalsOfContinuousStates """ @@ -2544,6 +2813,8 @@ function fmi3GetContinuousStateDerivatives(c::FMU3Instance) fmi3GetContinuousStateDerivatives!(c, derivatives) return derivatives end +# [NOTE] needs to be exported, because FMICore only exports `fmi3GetContinuousStateDerivatives!` +export fmi3GetContinuousStateDerivatives """ @@ -2584,11 +2855,11 @@ end fmi3UpdateDiscreteStates(c::FMU3Instance) This function is called to signal a converged solution at the current super-dense time instant. fmi3UpdateDiscreteStates must be called at least once per super-dense time instant. +Results are returned, use `fmi3UpdateDiscreteStates!` for the inplace variant. # Arguments - `c::FMU3Instance`: Mutable struct represents an instantiated instance of an FMU in the FMI 3.0 Standard. -# TODO returns # Returns - `discreteStatesNeedUpdate` - `terminateSimulation` @@ -2604,6 +2875,7 @@ This function is called to signal a converged solution at the current super-dens """ function fmi3UpdateDiscreteStates(c::FMU3Instance) + discreteStatesNeedUpdate = fmi3True terminateSimulation = fmi3True nominalsOfContinuousStatesChanged = fmi3True @@ -2617,7 +2889,7 @@ function fmi3UpdateDiscreteStates(c::FMU3Instance) refnETD = Ref(nextEventTimeDefined) refnET = Ref(nextEventTime) - fmi3UpdateDiscreteStates(c, refdS, reftS, refnOCS, refvOCS, refnETD, refnET) + fmi3UpdateDiscreteStates(c, refdS, reftS, refnOCS, refvOCS, refnETD, refnET) discreteStatesNeedUpdate = refdS[] terminateSimulation = reftS[] @@ -2629,6 +2901,36 @@ function fmi3UpdateDiscreteStates(c::FMU3Instance) discreteStatesNeedUpdate, terminateSimulation, nominalsOfContinuousStatesChanged, valuesOfContinuousStatesChanged, nextEventTimeDefined, nextEventTime end +""" + fmi3UpdateDiscreteStates!(c::FMU3Instance) + +This function is called to signal a converged solution at the current super-dense time instant. fmi3UpdateDiscreteStates must be called at least once per super-dense time instant. +Results are returned, use `fmi3UpdateDiscreteStates` for the out-of-place variant. + +# Arguments +- `c::FMU3Instance`: Mutable struct represents an instantiated instance of an FMU in the FMI 3.0 Standard. + +# Returns +- `fmi3Status` + +# Source +- FMISpec3.0 Link: [https://fmi-standard.org/](https://fmi-standard.org/) +- FMISpec3.0: 2.2.3 Platform Dependent Definitions +- FMISpec3.0: 2.3.5. State: Event Mode +""" +function fmi3UpdateDiscreteStates!(c::FMU3Instance) + + status = fmi3UpdateDiscreteStates(c, + c._ptr_discreteStatesNeedUpdate, + c._ptr_terminateSimulation, + c._ptr_nominalsOfContinuousStatesChanged, + c._ptr_valuesOfContinuousStatesChanged, + c._ptr_nextEventTimeDefined, + c._ptr_nextEventTime) + + return status +end + """ fmi3GetEventIndicators(c::FMU3Instance) @@ -2654,6 +2956,8 @@ function fmi3GetEventIndicators(c::FMU3Instance) fmi3GetEventIndicators!(c, eventIndicators, ni) return eventIndicators end +# [NOTE] needs to be exported, because FMICore only exports `fmi3GetEventIndicators!` +export fmi3GetEventIndicators """ @@ -2686,18 +2990,13 @@ See also [`fmi3CompletedIntegratorStep`](@ref). """ function fmi3CompletedIntegratorStep(c::FMU3Instance, noSetFMUStatePriorToCurrentPoint::fmi3Boolean) - enterEventMode = fmi3Boolean(true) - terminateSimulation = fmi3Boolean(true) - refEventMode = Ref(enterEventMode) - refterminateSimulation = Ref(terminateSimulation) - status = fmi3CompletedIntegratorStep!(c, - noSetFMUStatePriorToCurrentPoint, - refEventMode, - refterminateSimulation) - enterEventMode = refEventMode[] - terminateSimulation = refterminateSimulation[] - return (status, enterEventMode, terminateSimulation) + status = fmi3CompletedIntegratorStep!(c, + noSetFMUStatePriorToCurrentPoint, + c._ptr_enterEventMode, + c._ptr_terminateSimulation) + + return (status, c.enterEventMode, c.terminateSimulation) end """ @@ -2773,12 +3072,6 @@ See also [`fmi3DoStep!`](@ref). function fmi3DoStep!(c::FMU3Instance, currentCommunicationPoint::Union{Real, Nothing} = nothing, communicationStepSize::Union{Real, Nothing} = nothing, noSetFMUStatePriorToCurrentPoint::Bool = true, eventEncountered::fmi3Boolean = fmi3False, terminateSimulation::fmi3Boolean = fmi3False, earlyReturn::fmi3Boolean = fmi3False, lastSuccessfulTime::fmi3Float64 = 0.0) - # skip `fmi3DoStep` if this is set (allows evaluation of a CS_NeuralFMUs at t_0) - if c.skipNextDoStep - c.skipNextDoStep = false - return fmi3StatusOK - end - if currentCommunicationPoint === nothing currentCommunicationPoint = c.t end diff --git a/src/FMI3/md.jl b/src/FMI3/md.jl index d7bee73..5b7edfa 100644 --- a/src/FMI3/md.jl +++ b/src/FMI3/md.jl @@ -9,17 +9,16 @@ # - [Sec. 2] functions to get values from the model description in the format `fmi3Get[value](md::fmi3ModelDescription)` [exported] # - [Sec. 3] additional functions to get useful information from the model description in the format `fmi3Get[value](md::fmi3ModelDescription)` [exported] -using EzXML - - -using FMICore: fmi3ModelDescriptionModelExchange, fmi3ModelDescriptionCoSimulation, fmi3ModelDescriptionDefaultExperiment -using FMICore: fmi3VariableFloat32, fmi3VariableFloat64, fmi3VariableInt8, fmi3VariableUInt8, fmi3VariableInt16, fmi3VariableUInt16, fmi3VariableInt32, fmi3VariableUInt32, fmi3VariableInt64, fmi3VariableUInt64, fmi3VariableBoolean, fmi3VariableString, fmi3VariableBinary, fmi3VariableClock, fmi3VariableEnumeration -using FMICore: fmi3ModelDescriptionModelStructure -using FMICore: fmi3DependencyKind ###################################### # [Sec. 1a] fmi3LoadModelDescription # ###################################### +using FMIBase.FMICore: fmi3ModelDescriptionModelExchange, fmi3ModelDescriptionCoSimulation, fmi3ModelDescriptionScheduledExecution, fmi3ModelDescriptionDefaultExperiment +using FMIBase.FMICore: fmi3ModelDescriptionModelStructure, fmi3ModelDescriptionDefaultExperiment +using FMIBase.FMICore: fmi3VariableFloat32, fmi3VariableFloat64 +using FMIBase.FMICore: fmi3VariableInt8, fmi3VariableUInt8, fmi3VariableInt16, fmi3VariableUInt16, fmi3VariableInt32, fmi3VariableUInt32, fmi3VariableInt64, fmi3VariableUInt64 +using FMIBase.FMICore: fmi3VariableBoolean, fmi3VariableString, fmi3VariableBinary, fmi3VariableClock, fmi3VariableEnumeration + """ Extract the FMU variables and meta data from the ModelDescription """ @@ -49,12 +48,12 @@ function fmi3LoadModelDescription(pathToModellDescription::String) md.instantiationToken = root["instantiationToken"] # optional - md.generationTool = parseNodeString(root, "generationTool"; onfail="[Unknown generation tool]") - md.generationDateAndTime = parseNodeString(root, "generationDateAndTime"; onfail="[Unknown generation date and time]") - variableNamingConventionStr = parseNodeString(root, "variableNamingConvention"; onfail= "flat") + md.generationTool = parseNode(root, "generationTool", String; onfail="[Unknown generation tool]") + md.generationDateAndTime = parseNode(root, "generationDateAndTime", String; onfail="[Unknown generation date and time]") + variableNamingConventionStr = parseNode(root, "variableNamingConvention", String; onfail= "flat") @assert (variableNamingConventionStr == "flat" || variableNamingConventionStr == "structured") ["fmi3ReadModelDescription(...): Unknown entry for `variableNamingConvention=$(variableNamingConventionStr)`."] md.variableNamingConvention = (variableNamingConventionStr == "flat" ? fmi3VariableNamingConventionFlat : fmi3VariableNamingConventionStructured) - md.description = parseNodeString(root, "description"; onfail="[Unknown Description]") + md.description = parseNode(root, "description", String; onfail="[Unknown Description]") # defaults md.modelExchange = nothing @@ -70,57 +69,56 @@ function fmi3LoadModelDescription(pathToModellDescription::String) if node.name == "CoSimulation" md.coSimulation = fmi3ModelDescriptionCoSimulation() md.coSimulation.modelIdentifier = node["modelIdentifier"] - md.coSimulation.canHandleVariableCommunicationStepSize = parseNodeBoolean(node, "canHandleVariableCommunicationStepSize" ; onfail=false) - md.coSimulation.canInterpolateInputs = parseNodeBoolean(node, "canInterpolateInputs" ; onfail=false) - md.coSimulation.maxOutputDerivativeOrder = parseNodeInteger(node, "maxOutputDerivativeOrder" ; onfail=nothing) - md.coSimulation.canGetAndSetFMUstate = parseNodeBoolean(node, "canGetAndSetFMUState" ; onfail=false) - md.coSimulation.canSerializeFMUstate = parseNodeBoolean(node, "canSerializeFMUState" ; onfail=false) - md.coSimulation.providesDirectionalDerivatives = parseNodeBoolean(node, "providesDirectionalDerivatives" ; onfail=false) - md.coSimulation.providesAdjointDerivatives = parseNodeBoolean(node, "providesAdjointDerivatives" ; onfail=false) - md.coSimulation.hasEventMode = parseNodeBoolean(node, "hasEventMode" ; onfail=false) + md.coSimulation.canHandleVariableCommunicationStepSize = parseNode(node, "canHandleVariableCommunicationStepSize", Bool ; onfail=false) + md.coSimulation.canInterpolateInputs = parseNode(node, "canInterpolateInputs", Bool ; onfail=false) + md.coSimulation.maxOutputDerivativeOrder = parseNode(node, "maxOutputDerivativeOrder", Int ; onfail=nothing) + md.coSimulation.canGetAndSetFMUState = parseNode(node, "canGetAndSetFMUState", Bool ; onfail=false) + md.coSimulation.canSerializeFMUState = parseNode(node, "canSerializeFMUState", Bool ; onfail=false) + md.coSimulation.providesDirectionalDerivatives = parseNode(node, "providesDirectionalDerivatives", Bool ; onfail=false) + md.coSimulation.providesAdjointDerivatives = parseNode(node, "providesAdjointDerivatives", Bool ; onfail=false) + md.coSimulation.hasEventMode = parseNode(node, "hasEventMode", Bool ; onfail=false) elseif node.name == "ModelExchange" md.modelExchange = fmi3ModelDescriptionModelExchange() md.modelExchange.modelIdentifier = node["modelIdentifier"] - md.modelExchange.canGetAndSetFMUstate = parseNodeBoolean(node, "canGetAndSetFMUState" ; onfail=false) - md.modelExchange.canSerializeFMUstate = parseNodeBoolean(node, "canSerializeFMUState" ; onfail=false) - md.modelExchange.providesDirectionalDerivatives = parseNodeBoolean(node, "providesDirectionalDerivatives" ; onfail=false) - md.modelExchange.providesAdjointDerivatives = parseNodeBoolean(node, "providesAdjointDerivatives" ; onfail=false) + md.modelExchange.canGetAndSetFMUState = parseNode(node, "canGetAndSetFMUState", Bool ; onfail=false) + md.modelExchange.canSerializeFMUState = parseNode(node, "canSerializeFMUState", Bool ; onfail=false) + md.modelExchange.providesDirectionalDerivatives = parseNode(node, "providesDirectionalDerivatives", Bool ; onfail=false) + md.modelExchange.providesAdjointDerivatives = parseNode(node, "providesAdjointDerivatives", Bool ; onfail=false) elseif node.name == "ScheduledExecution" - md.scheduledExecution = FMICore.fmi3ModelDescriptionScheduledExecution() + md.scheduledExecution = fmi3ModelDescriptionScheduledExecution() md.scheduledExecution.modelIdentifier = node["modelIdentifier"] - md.scheduledExecution.needsExecutionTool = parseNodeBoolean(node, "needsExecutionTool" ; onfail=false) - md.scheduledExecution.canBeInstantiatedOnlyOncePerProcess = parseNodeBoolean(node, "canBeInstantiatedOnlyOncePerProcess" ; onfail=false) - md.scheduledExecution.canGetAndSetFMUstate = parseNodeBoolean(node, "canGetAndSetFMUState" ; onfail=false) - md.scheduledExecution.canSerializeFMUstate = parseNodeBoolean(node, "canSerializeFMUState" ; onfail=false) - md.scheduledExecution.providesDirectionalDerivatives = parseNodeBoolean(node, "providesDirectionalDerivatives" ; onfail=false) - md.scheduledExecution.providesAdjointDerivatives = parseNodeBoolean(node, "providesAdjointDerivatives" ; onfail=false) - md.scheduledExecution.providesPerElementDependencies = parseNodeBoolean(node, "providesPerElementDependencies" ; onfail=false) + md.scheduledExecution.needsExecutionTool = parseNode(node, "needsExecutionTool", Bool ; onfail=false) + md.scheduledExecution.canBeInstantiatedOnlyOncePerProcess = parseNode(node, "canBeInstantiatedOnlyOncePerProcess", Bool ; onfail=false) + md.scheduledExecution.canGetAndSetFMUState = parseNode(node, "canGetAndSetFMUState", Bool ; onfail=false) + md.scheduledExecution.canSerializeFMUState = parseNode(node, "canSerializeFMUState", Bool ; onfail=false) + md.scheduledExecution.providesDirectionalDerivatives = parseNode(node, "providesDirectionalDerivatives", Bool ; onfail=false) + md.scheduledExecution.providesAdjointDerivatives = parseNode(node, "providesAdjointDerivatives", Bool ; onfail=false) + md.scheduledExecution.providesPerElementDependencies = parseNode(node, "providesPerElementDependencies", Bool ; onfail=false) - elseif node.name == "TypeDefinitions" - #ToDo: md.enumerations = createEnum(node) - @warn "ToDo: FMU has TypeDefinitions, but this is not implemented yet." + elseif node.name ∈ ("TypeDefinitions", "LogCategories", "UnitDefinitions") + @warn "FMU has $(node.name), but parsing is not implemented yet." elseif node.name == "ModelVariables" - md.modelVariables = parseModelVariables(node, md) + md.modelVariables = parseModelVariables(md, node) elseif node.name == "ModelStructure" md.modelStructure = fmi3ModelDescriptionModelStructure() - parseModelStructure(node, md) + parseModelStructure(md, node) md.numberOfContinuousStates = length(md.stateValueReferences) elseif node.name == "DefaultExperiment" md.defaultExperiment = fmi3ModelDescriptionDefaultExperiment() - md.defaultExperiment.startTime = parseNodeReal(node, "startTime") - md.defaultExperiment.stopTime = parseNodeReal(node, "stopTime") - md.defaultExperiment.tolerance = parseNodeReal(node, "tolerance"; onfail = md.defaultExperiment.tolerance) - md.defaultExperiment.stepSize = parseNodeReal(node, "stepSize") + md.defaultExperiment.startTime = parseNode(node, "startTime", fmi3Float64) + md.defaultExperiment.stopTime = parseNode(node, "stopTime", fmi3Float64) + md.defaultExperiment.tolerance = parseNode(node, "tolerance", fmi3Float64; onfail = md.defaultExperiment.tolerance) + md.defaultExperiment.stepSize = parseNode(node, "stepSize", fmi3Float64) else - @warn "Unknwon node named `$(node.name)`" + @assert false "Unknwon node named `$(node.name)`, please open an issue on GitHub." end end @@ -132,7 +130,7 @@ function fmi3LoadModelDescription(pathToModellDescription::String) # check all intermediateUpdate variables for variable in md.modelVariables if hasproperty(variable, :intermediateUpdate) - if Bool(variable.intermediateUpdate) + if !isnothing(variable.intermediateUpdate) && Bool(variable.intermediateUpdate) push!(md.intermediateUpdateValueReferences, variable.valueReference) end end @@ -146,7 +144,7 @@ end ####################################### # Parses the model variables of the FMU model description. -function parseModelVariables(nodes::EzXML.Node, md::fmi3ModelDescription) +function parseModelVariables(md::fmi3ModelDescription, nodes::EzXML.Node) numberOfVariables = 0 for node in eachelement(nodes) numberOfVariables += 1 @@ -155,7 +153,7 @@ function parseModelVariables(nodes::EzXML.Node, md::fmi3ModelDescription) index = 1 for node in eachelement(nodes) name = node["name"] - valueReference = parse(fmi3ValueReference, (node["valueReference"])) + valueReference = parseNode(node, "valueReference", fmi3ValueReference) # type node typenode = nothing @@ -207,199 +205,117 @@ function parseModelVariables(nodes::EzXML.Node, md::fmi3ModelDescription) end if haskey(node, "causality") - causality = fmi3StringToCausality(node["causality"]) + causality = stringToCausality(md, node["causality"]) if causality == fmi3CausalityOutput push!(md.outputValueReferences, valueReference) elseif causality == fmi3CausalityInput push!(md.inputValueReferences, valueReference) + elseif causality == fmi3CausalityParameter + push!(md.parameterValueReferences, valueReference) end end if haskey(node, "variability") - variability = fmi3StringToVariability(node["variability"]) + variability = stringToVariability(md, parseNode(node, "variability", String)) end - - if haskey(node, "canHandleMultipleSetPerTimeInstant") - modelVariables[index].canHandleMultipleSetPerTimeInstant = fmi3parseBoolean(node["canHandleMultipleSetPerTimeInstant"]) - end - - if haskey(node, "annotations") - modelVariables[index].annotations = node["annotations"] - end - - if haskey(node, "clocks") - modelVariables[index].clocks = fmi3parseArrayValueReferences(node["clocks"]) - end - - if haskey(node, "intermediateUpdate") && typename != "Clock" && typename != "String" - modelVariables[index].intermediateUpdate = fmi3parseBoolean(node["intermediateUpdate"]) + modelVariables[index].canHandleMultipleSetPerTimeInstant = parseNode(node, "canHandleMultipleSetPerTimeInstant", Bool) + modelVariables[index].annotations = parseNode(node, "annotations", String) + modelVariables[index].clocks = parseArrayValueReferences(md, parseNode(node, "clocks", String)) + + if typename ∉ ("Clock", "String") + modelVariables[index].intermediateUpdate = parseNode(node, "intermediateUpdate", Bool) + else + @warn "Unsupported typename `$(typename)` for modelVariable attribute `intermediateUpdate`." end - if haskey(node, "previous") && typename != "Clock" && typename != "String" - modelVariables[index].previous = fmi3parseBoolean(node["previous"]) + if typename ∉ ("Clock", "String") + modelVariables[index].previous = parseNode(node, "previous", Bool) + else + @warn "Unsupported typename `$(typename)` for modelVariable attribute `previous`." end - if haskey(node, "initial") && typename != "Clock" && typename != "String" && typename != "Enumeration" - modelVariables[index].initial = fmi3StringToInitial(node["initial"]) - end - - if haskey(node, "quantity") && typename != "Clock" && typename != "String" && typename != "Binary" && typename != "Boolean" - modelVariables[index].quantity = node["quantity"] + if haskey(node, "initial") + if typename ∉ ("Clock", "String", "Enumeration") + modelVariables[index].initial = stringToInitial(md, parseNode(node, "initial", String)) + else + @warn "Unsupported typename `$(typename)` for modelVariable attribute `initial`." + end end - if haskey(node, "unit") && (typename == "Float32" || typename == "Float64") - modelVariables[index].unit = node["unit"] + if typename ∉ ("Clock", "String", "Binary", "Boolean") + modelVariables[index].quantity = parseNode(node, "quantity", String) + else + @warn "Unsupported typename `$(typename)` for modelVariable attribute `quantity`." end - - if haskey(node, "displayUnit") && (typename == "Float32" || typename == "Float64") - modelVariables[index].displayUnit = node["displayUnit"] + + if typename == "Float64" || typename == "Float32" + modelVariables[index].unit = parseNode(node, "unit", String) + modelVariables[index].displayUnit = parseNode(node, "displayUnit", String) end - if haskey(node, "declaredType") && typename != "String" - modelVariables[index].declaredType = node["declaredType"] + if typename != "String" + modelVariables[index].declaredType = parseNode(node, "declaredType", String) + else + @warn "Unsupported typename `$(typename)` for modelVariable attribute `declaredType`." end - if haskey(node, "min") && typename != "Clock" && typename != "String" && typename != "Binary" && typename != "Boolean" - if typename == "Float32" - modelVariables[index].min = parse(fmi3Float32, node["min"]) - elseif typename == "Float64" - modelVariables[index].min = parse(fmi3Float32, node["min"]) - elseif typename == "Int8" - modelVariables[index].min = parse(fmi3Int8, node["min"]) - elseif typename == "UInt8" - modelVariables[index].min = parse(fmi3UInt8, node["min"]) - elseif typename == "Int16" - modelVariables[index].min = parse(fmi3Int16, node["min"]) - elseif typename == "UInt16" - modelVariables[index].min = parse(fmi3UInt16, node["min"]) - elseif typename == "Int32" - modelVariables[index].min = parse(fmi3Int32, node["min"]) - elseif typename == "UInt32" - modelVariables[index].min = parse(fmi3UInt32, node["min"]) - elseif typename == "Int64" - modelVariables[index].min = parse(fmi3Int64, node["min"]) - elseif typename == "UInt64" - modelVariables[index].min = parse(fmi3UInt64, node["min"]) - end + if typename ∉ ("Clock", "String", "Binary", "Boolean") + modelVariables[index].min = parseNode(node, "min", stringToDataType(md, typename)) + else + @warn "Unsupported typename `$(typename)` for modelVariable attribute `min`." end - if haskey(node, "max") && typename != "Clock" && typename != "String" && typename != "Binary" && typename != "Boolean" - if typename == "Float32" - modelVariables[index].max = parse(fmi3Float32, node["max"]) - elseif typename == "Float64" - modelVariables[index].max = parse(fmi3Float32, node["max"]) - elseif typename == "Int8" - modelVariables[index].max = parse(fmi3Int8, node["max"]) - elseif typename == "UInt8" - modelVariables[index].max = parse(fmi3UInt8, node["max"]) - elseif typename == "Int16" - modelVariables[index].max = parse(fmi3Int16, node["max"]) - elseif typename == "UInt16" - modelVariables[index].max = parse(fmi3UInt16, node["max"]) - elseif typename == "Int32" - modelVariables[index].max = parse(fmi3Int32, node["max"]) - elseif typename == "UInt32" - modelVariables[index].max = parse(fmi3UInt32, node["max"]) - elseif typename == "Int64" - modelVariables[index].max = parse(fmi3Int64, node["max"]) - elseif typename == "UInt64" - modelVariables[index].max = parse(fmi3UInt64, node["max"]) - end + if typename ∉ ("Clock", "String", "Binary", "Boolean") + modelVariables[index].max = parseNode(node, "max", stringToDataType(md, typename)) + else + @warn "Unsupported typename `$(typename)` for modelVariable attribute `max`." end - if haskey(node, "nominal") && (typename == "Float32" || typename == "Float64") - modelVariables[index].nominal = parse(Real, node["nominal"]) + if typename == "Float64" || typename == "Float32" + modelVariables[index].nominal = parseNode(node, "nominal", stringToDataType(md, typename)) + modelVariables[index].unbounded = parseNode(node, "unbounded", stringToDataType(md, typename)) end - - if haskey(node, "unbounded") && (typename == "Float32" || typename == "Float64") - modelVariables[index].unbounded = fmi3parseBoolean(node["nominal"]) - end - if haskey(node, "start") && typename != "Binary" && typename != "Clock" - if node.firstelement !== nothing && node.firstelement.name == "Dimension" + + if typename ∉ ("Binary", "Clock") + if !isnothing(node.firstelement) && node.firstelement.name == "Dimension" substrings = split(node["start"], " ") - if typename == "Float32" - modelVariables[index].start = Array{fmi3Float32}(undef, 0) - for string in substrings - push!(modelVariables[index].start, parse(fmi3Float32, string)) - end - elseif typename == "Float64" - modelVariables[index].start = Array{fmi3Float64}(undef, 0) - for string in substrings - push!(modelVariables[index].start, parse(fmi3Float64, string)) - end - elseif typename == "Int32" - modelVariables[index].start = Array{fmi3Int32}(undef, 0) - for string in substrings - push!(modelVariables[index].start, parse(fmi3Int32, string)) - end - elseif typename == "UInt32" - modelVariables[index].start = Array{fmi3UInt32}(undef, 0) - for string in substrings - push!(modelVariables[index].start, parse(fmi3UInt32, string)) - end - elseif typename == "Int64" - modelVariables[index].start = Array{fmi3Int64}(undef, 0) - for string in substrings - push!(modelVariables[index].start, parse(fmi3Int64, string)) - end - elseif typename == "UInt64" - modelVariables[index].start = Array{fmi3UInt64}(undef, 0) - for string in substrings - push!(modelVariables[index].start, parse(fmi3UInt64, string)) - end - else - @warn "More array variable types not implemented yet!" + + T = stringToDataType(md, typename) + modelVariables[index].start = Array{T}(undef, 0) + for string in substrings + push!(modelVariables[index].start, parseType(string, T)) end else - if typename == "Float32" - modelVariables[index].start = parse(fmi3Float32, node["start"]) - elseif typename == "Float64" - modelVariables[index].start = parse(fmi3Float32, node["start"]) - elseif typename == "Int8" - modelVariables[index].start = parse(fmi3Int8, node["start"]) - elseif typename == "UInt8" - modelVariables[index].start = parse(fmi3UInt8, node["start"]) - elseif typename == "Int16" - modelVariables[index].start = parse(fmi3Int16, node["start"]) - elseif typename == "UInt16" - modelVariables[index].start = parse(fmi3UInt16, node["start"]) - elseif typename == "Int32" - modelVariables[index].start = parse(fmi3Int32, node["start"]) - elseif typename == "UInt32" - modelVariables[index].start = parse(fmi3UInt32, node["start"]) - elseif typename == "Int64" - modelVariables[index].start = parse(fmi3Int64, node["start"]) - elseif typename == "UInt64" - modelVariables[index].start = parse(fmi3UInt64, node["start"]) - elseif typename == "Boolean" - modelVariables[index].start = parseFMI3Boolean(node["start"]) - elseif typename == "Enum" + if typename == "Enum" for i in 1:length(md.enumerations) if modelVariables[index].declaredType == md.enumerations[i][1] # identify the enum by the name - modelVariables[index].start = md.enumerations[i][1 + parse(Int, node["start"])] # find the enum value and set it + modelVariables[index].start = md.enumerations[i][1 + parseNode(node, "start", Int)] # find the enum value and set it end end + else + modelVariables[index].start = parseNode(node, "start", stringToDataType(md, typename)) end end + else + @warn "Unsupported typename `$(typename)` for modelVariable attribute `start`." end - if haskey(node, "derivative") && (typename == "Float32" || typename == "Float64") - modelVariables[index].derivative = parse(fmi3ValueReference, node["derivative"]) + if typename == "Float64" || typename == "Float32" + modelVariables[index].derivative = parseNode(node, "derivative", fmi3ValueReference) + modelVariables[index].reinit = parseNode(node, "reinit", Bool) end - if haskey(node, "reinit") && (typename == "Float32" || typename == "Float64") - modelVariables[index].reinit = parseFMI3Boolean(node["reinit"]) - end - - if typename == "String" || typename == "Binary" + if typename == "String" for nod in eachelement(node) if nod.name == "Start" - if typename == "Binary" - modelVariables[index].start = pointer(nod["value"]) - elseif typename == "String" - modelVariables[index].start = nod["value"] - end + modelVariables[index].start = nod["value"] + end + end + elseif typename == "Binary" + for nod in eachelement(node) + if nod.name == "Start" + modelVariables[index].start = pointer(nod["value"]) end end end @@ -408,44 +324,44 @@ function parseModelVariables(nodes::EzXML.Node, md::fmi3ModelDescription) index += 1 end - modelVariables + return modelVariables end # Parses the model variables of the FMU model description. -function parseModelStructure(nodes::EzXML.Node, md::fmi3ModelDescription) - @assert (nodes.name == "ModelStructure") "Wrong section name." +function parseModelStructure(md::fmi3ModelDescription, nodes::EzXML.Node) + md.modelStructure.continuousStateDerivatives = [] md.modelStructure.initialUnknowns = [] md.modelStructure.eventIndicators = [] md.modelStructure.outputs = [] + for node in eachelement(nodes) if haskey(node, "valueReference") - varDep = parseDependencies(node) + varDep = parseDependencies(md, node) if node.name == "InitialUnknown" push!(md.modelStructure.initialUnknowns, varDep) elseif node.name == "EventIndicator" md.numberOfEventIndicators += 1 push!(md.modelStructure.eventIndicators) - # TODO parse valueReferences to another array + # [TODO] parse valueReferences to another array elseif node.name == "ContinuousStateDerivative" - # find states and derivatives^ - derSV = fmi3ModelVariablesForValueReference(md, fmi3ValueReference(fmi3parseInteger(node["valueReference"])))[1] - # derSV = md.modelVariables[varDep.index] - derVR = derSV.valueReference - stateVR = md.modelVariables[derSV.derivative].valueReference + # find states and derivatives + derValueRef = parseNode(node, "valueReference", fmi3ValueReference) + derVar = modelVariablesForValueReference(md, derValueRef)[1] + stateValueRef = modelVariablesForValueReference(md, derVar.derivative)[1].valueReference - if stateVR ∉ md.stateValueReferences - push!(md.stateValueReferences, stateVR) + if stateValueRef ∉ md.stateValueReferences + push!(md.stateValueReferences, stateValueRef) end - if derVR ∉ md.derivativeValueReferences - push!(md.derivativeValueReferences, derVR) + if derValueRef ∉ md.derivativeValueReferences + push!(md.derivativeValueReferences, derValueRef) end push!(md.modelStructure.continuousStateDerivatives, varDep) elseif node.name =="Output" # find outputs - outVR = fmi3ValueReference(fmi3parseInteger(node["valueReference"])) + outVR = parseNode(node, "valueReference", fmi3ValueReference) if outVR ∉ md.outputValueReferences push!(md.outputValueReferences, outVR) @@ -461,8 +377,8 @@ function parseModelStructure(nodes::EzXML.Node, md::fmi3ModelDescription) end end -function parseDependencies(node::EzXML.Node) - varDep = fmi3VariableDependency(fmi3ValueReference(fmi3parseInteger(node["valueReference"]))) +function parseDependencies(md::fmi3ModelDescription, node::EzXML.Node) + varDep = fmi3VariableDependency(parseNode(node, "valueReference", fmi3ValueReference)) if haskey(node, "dependencies") dependencies = node["dependencies"] @@ -479,7 +395,7 @@ function parseDependencies(node::EzXML.Node) if length(dependenciesKind) > 0 dependenciesKindSplit = split(dependenciesKind, " ") if length(dependenciesKindSplit) > 0 - varDep.dependenciesKind = collect(fmi3StringToDependencyKind(e) for e in dependenciesKindSplit) + varDep.dependenciesKind = collect(stringToDependencyKind(md, e) for e in dependenciesKindSplit) end end end @@ -493,13 +409,13 @@ function parseDependencies(node::EzXML.Node) return varDep end -function parseContinuousStateDerivative(nodes::EzXML.Node, md::fmi3ModelDescription) +function parseContinuousStateDerivative(md::fmi3ModelDescription, nodes::EzXML.Node) @assert (nodes.name == "ContinuousStateDerivative") "Wrong element name." md.modelStructure.derivatives = [] for node in eachelement(nodes) if node.name == "InitialUnknown" if haskey(node, "index") - varDep = parseUnknwon(node) + varDep = parseUnknwon(md, node) # find states and derivatives derSV = md.modelVariables[varDep.index] @@ -524,277 +440,4 @@ function parseContinuousStateDerivative(nodes::EzXML.Node, md::fmi3ModelDescript @warn "Unknown entry in `ModelStructure.Derivatives` named `$(node.name)`." end end -end - -# Parses a real value represented by a string. -function fmi3parseFloat(s::Union{String, SubString{String}}; onfail=nothing) - if onfail === nothing - return parse(fmi3Float64, s) - else - try - return parse(fmi3Float64, s) - catch - return onfail - end - end -end - -function fmi3parseNodeFloat(node, key; onfail=nothing) - if haskey(node, key) - return fmi3parseFloat(node[key]; onfail=onfail) - else - return onfail - end -end - -# Parses a Bool value represented by a string. -function fmi3parseBoolean(s::Union{String, SubString{String}}; onfail=nothing) - if s == "true" - return true - elseif s == "false" - return false - else - @assert onfail !== nothing ["parseBoolean(...) unknown boolean value '$s'."] - return onfail - end -end - -function fmi3parseNodeBoolean(node, key; onfail=nothing) - if haskey(node, key) - return fmi3parseBoolean(node[key]; onfail=onfail) - else - return onfail - end -end - -# Parses an Integer value represented by a string. -function fmi3parseInteger(s::Union{String, SubString{String}}; onfail=nothing) - if onfail === nothing - return parse(Int, s) - else - try - return parse(Int, s) - catch - return onfail - end - end -end - -function fmi3parseNodeInteger(node, key; onfail=nothing) - if haskey(node, key) - return fmi3parseInteger(node[key]; onfail=onfail) - else - return onfail - end -end - -function fmi3parseNodeString(node, key; onfail=nothing) - if haskey(node, key) - return node[key] - else - return onfail - end -end - -# Parses a fmi3Boolean value represented by a string. -function parseFMI3Boolean(s::Union{String, SubString{String}}) - if fmi3parseBoolean(s) - return fmi3True - else - return fmi3False - end -end - -# Parses a Bool value represented by a string. -function fmi3parseArrayValueReferences(s::Union{String, SubString{String}}) - references = Array{fmi3ValueReference}(undef, 0) - substrings = split(s, " ") - - for string in substrings - push!(references, parse(fmi3ValueReferenceFormat, string)) - end - - return references -end - -#= -Read all enumerations from the modeldescription and store them in a matrix. First entries are the enum names -------------------------------------------- -Example: -"enum1name" "value1" "value2" -"enum2name" "value1" "value2" -=# -# TODO unused -function fmi3createEnum(node::EzXML.Node) - enum = 1 - idx = 1 - enumerations = [] - for simpleType in eachelement(node) - name = simpleType["name"] - for type in eachelement(simpleType) - if type.name == "Enumeration" - enum = [] - push!(enum, name) - for item in eachelement(type) - push!(enum, item["name"]) - end - push!(enumerations, enum) - end - end - end - enumerations -end - -################################ -# [Sec. 2] get value functions # -################################ - -""" -Returns startTime from DefaultExperiment if defined else defaults to nothing. -""" -function fmi3GetDefaultStartTime(md::fmi3ModelDescription) - if md.defaultExperiment === nothing - return nothing - end - return md.defaultExperiment.startTime -end - -""" -Returns stopTime from DefaultExperiment if defined else defaults to nothing. -""" -function fmi3GetDefaultStopTime(md::fmi3ModelDescription) - if md.defaultExperiment === nothing - return nothing - end - return md.defaultExperiment.stopTime -end - -""" -Returns tolerance from DefaultExperiment if defined else defaults to nothing. -""" -function fmi3GetDefaultTolerance(md::fmi3ModelDescription) - if md.defaultExperiment === nothing - return nothing - end - return md.defaultExperiment.tolerance -end - -""" -Returns stepSize from DefaultExperiment if defined else defaults to nothing. -""" -function fmi3GetDefaultStepSize(md::fmi3ModelDescription) - if md.defaultExperiment === nothing - return nothing - end - return md.defaultExperiment.stepSize -end - -""" -Returns the tag 'modelName' from the model description. -""" -function fmi3GetModelName(md::fmi3ModelDescription)#, escape::Bool = true) - md.modelName -end - -""" -Returns the tag 'instantionToken' from the model description. -""" -function fmi3GetInstantiationToken(md::fmi3ModelDescription) - md.instantiationToken -end - -""" -Returns the tag 'generationtool' from the model description. -""" -function fmi3GetGenerationTool(md::fmi3ModelDescription) - md.generationTool -end - -""" -Returns the tag 'generationdateandtime' from the model description. -""" -function fmi3GetGenerationDateAndTime(md::fmi3ModelDescription) - md.generationDateAndTime -end - -""" -Returns the tag 'varaiblenamingconvention' from the model description. -""" -function fmi3GetVariableNamingConvention(md::fmi3ModelDescription) - md.variableNamingConvention -end - -""" -Returns the number of EventIndicators from the model description. -""" -function fmi3GetNumberOfEventIndicators(md::fmi3ModelDescription) - md.numberOfEventIndicators -end - -""" -Returns true, if the FMU supports co simulation -""" -function fmi3IsCoSimulation(md::fmi3ModelDescription) - return( md.coSimulation !== nothing) -end - -""" -Returns true, if the FMU supports model exchange -""" -function fmi3IsModelExchange(md::fmi3ModelDescription) - return( md.modelExchange !== nothing) -end -""" -Returns true, if the FMU supports scheduled execution -""" -function fmi3IsScheduledExecution(md::fmi3ModelDescription) - return( md.scheduledExecution !== nothing) -end - -################################## -# [Sec. 3] information functions # -################################## - -""" -Returns the tag 'modelIdentifier' from CS or ME section. -""" -function fmi3GetModelIdentifier(md::fmi3ModelDescription) - if fmi3IsCoSimulation(md) - return md.coSimulation.modelIdentifier - elseif fmi3IsModelExchange(md) - return md.modelExchange.modelIdentifier - else - @assert false "fmi3GetModelName(...): FMU does not support ME or CS!" - end -end - -""" -Returns true, if the FMU supports the getting/setting of states -""" -function fmi3CanGetSetState(md::fmi3ModelDescription) - return (md.coSimulation !== nothing && md.coSimulation.canGetAndSetFMUstate) || (md.modelExchange !== nothing && md.modelExchange.canGetAndSetFMUstate) - -end - -""" -Returns true, if the FMU state can be serialized -""" -function fmi3CanSerializeFMUState(md::fmi3ModelDescription) - return (md.coSimulation !== nothing && md.coSimulation.canSerializeFMUstate) || (md.modelExchange !== nothing && md.modelExchange.canSerializeFMUstate) - -end - -""" -Returns true, if the FMU provides directional derivatives -""" -function fmi3ProvidesDirectionalDerivatives(md::fmi3ModelDescription) - return (md.coSimulation !== nothing && md.coSimulation.providesDirectionalDerivatives) || (md.modelExchange !== nothing && md.modelExchange.providesDirectionalDerivatives) -end - -""" -Returns true, if the FMU provides adjoint derivatives -""" -function fmi3ProvidesAdjointDerivatives(md::fmi3ModelDescription) - return (md.coSimulation !== nothing && md.coSimulation.providesAdjointDerivatives) || (md.modelExchange !== nothing && md.modelExchange.providesAdjointDerivatives) - -end +end \ No newline at end of file diff --git a/src/FMI3/prep.jl b/src/FMI3/prep.jl new file mode 100644 index 0000000..f202922 --- /dev/null +++ b/src/FMI3/prep.jl @@ -0,0 +1,237 @@ +# +# Copyright (c) 2021 Tobias Thummerer, Lars Mikelsons, Josef Kircher +# Licensed under the MIT license. See LICENSE file in the project root for details. +# + +import FMIBase: isEventMode, isContinuousTimeMode, isTrue, isStatusOK +using FMIBase: handleEvents + +function setBeforeInitialization(mv::FMIImport.fmi3Variable) + return mv.variability != fmi3VariabilityConstant && mv.initial ∈ (fmi3InitialApprox, fmi3InitialExact) +end + +function setInInitialization(mv::FMIImport.fmi3Variable) + return mv.causality == fmi3CausalityInput || (mv.causality != fmi3CausalityParameter && mv.variability == fmi3VariabilityTunable) || (mv.variability != fmi3VariabilityConstant && mv.initial == fmi3InitialExact) +end + +function prepareSolveFMU(fmu::FMU3, c::Union{Nothing, FMU3Instance}, type::fmi3Type=fmu.type; + instantiate::Union{Nothing, Bool}=fmu.executionConfig.instantiate, + freeInstance::Union{Nothing, Bool}=fmu.executionConfig.freeInstance, + terminate::Union{Nothing, Bool}=fmu.executionConfig.terminate, + reset::Union{Nothing, Bool}=fmu.executionConfig.reset, + setup::Union{Nothing, Bool}=fmu.executionConfig.setup, + parameters::Union{Dict{<:Any, <:Any}, Nothing}=nothing, + t_start::Real=0.0, + t_stop::Union{Real, Nothing}=nothing, + tolerance::Union{Real, Nothing}=nothing, + x0::Union{AbstractArray{<:Real}, Nothing}=nothing, + inputs::Union{Dict{<:Any, <:Any}, Nothing}=nothing, + cleanup::Bool=false, + handleEvents=handleEvents, + instantiateKwargs...) + + ignore_derivatives() do + + c = nothing + + # instantiate (hard) + if instantiate + if type == fmi3TypeCoSimulation + c = fmi3InstantiateCoSimulation!(fmu; instantiateKwargs...) + elseif type == fmi3TypeModelExchange + c = fmi3InstantiateModelExchange!(fmu; instantiateKwargs...) + elseif type == fmi3TypeScheduledExecution + c = fmi3InstantiateScheduledExecution!(fmu; instantiateKwargs...) + else + @assert false "Unknwon fmi3Type `$(type)`" + end + else + if c === nothing + if length(fmu.instances) > 0 + c = fmu.instances[end] + else + @warn "Found no FMU instance, but executionConfig doesn't force allocation. Allocating one. Use `fmi3Instantiate[TYPE](fmu)` to prevent this message." + if type == fmi3TypeCoSimulation + c = fmi3InstantiateCoSimulation!(fmu; instantiateKwargs...) + elseif type == fmi3TypeModelExchange + c = fmi3InstantiateModelExchange!(fmu; instantiateKwargs...) + elseif type == fmi3TypeScheduledExecution + c = fmi3InstantiateScheduledExecution!(fmu; instantiateKwargs...) + else + @assert false "Unknwon FMU type `$(type)`." + end + end + end + end + + @assert !isnothing(c) "No FMU instance available, allocate one or use `fmu.executionConfig.instantiate=true`." + + # soft terminate (if necessary) + if terminate + retcode = fmi3Terminate(c; soft=true) + @assert retcode == fmi3StatusOK "fmi3Simulate(...): Termination failed with return code $(retcode)." + end + + # soft reset (if necessary) + if reset + retcode = fmi3Reset(c; soft=true) + @assert retcode == fmi3StatusOK "fmi3Simulate(...): Reset failed with return code $(retcode)." + end + + # setup experiment (hard) + # [Note] this part is handled by fmi3EnterInitializationMode + + # parameters + if !isnothing(parameters) + retcodes = setValue(c, collect(keys(parameters)), collect(values(parameters)); filter=setBeforeInitialization) + @assert all(retcodes .== fmi3StatusOK) "fmi3Simulate(...): Setting initial parameters failed with return code $(retcode)." + end + + # inputs + if !isnothing(inputs) + retcodes = setValue(c, collect(keys(inputs)), collect(values(inputs)); filter=setBeforeInitialization) + @assert all(retcodes .== fmi3StatusOK) "fmi3Simulate(...): Setting initial inputs failed with return code $(retcode)." + end + + # start state + if !isnothing(x0) + #retcode = fmi3SetContinuousStates(c, x0) + #@assert retcode == fmi3StatusOK "fmi3Simulate(...): Setting initial state failed with return code $(retcode)." + retcodes = setValue(c, fmu.modelDescription.stateValueReferences, x0; filter=setBeforeInitialization) + @assert all(retcodes .== fmi3StatusOK) "fmi3Simulate(...): Setting initial inputs failed with return code $(retcode)." + end + + # enter (hard) + if setup + retcode = fmi3EnterInitializationMode(c, t_start, t_stop; tolerance = tolerance) + @assert retcode == fmi3StatusOK "fmi3Simulate(...): Entering initialization mode failed with return code $(retcode)." + end + + # parameters + if parameters !== nothing + retcodes = setValue(c, collect(keys(parameters)), collect(values(parameters)); filter=setInInitialization) + @assert all(retcodes .== fmi3StatusOK) "fmi3Simulate(...): Setting initial parameters failed with return code $(retcode)." + end + + if inputs !== nothing + retcodes = setValue(c, collect(keys(inputs)), collect(values(inputs)); filter=setInInitialization) + @assert all(retcodes .== fmi3StatusOK) "fmi3Simulate(...): Setting initial inputs failed with return code $(retcode)." + end + + # start state + if x0 !== nothing + #retcode = fmi3SetContinuousStates(c, x0) + #@assert retcode == fmi3StatusOK "fmi3Simulate(...): Setting initial state failed with return code $(retcode)." + retcodes = setValue(c, fmu.modelDescription.stateValueReferences, x0; filter=setInInitialization) + @assert all(retcodes .== fmi3StatusOK) "fmi3Simulate(...): Setting initial inputs failed with return code $(retcode)." + end + + # exit setup (hard) + if setup + retcode = fmi3ExitInitializationMode(c) + @assert retcode == fmi3StatusOK "fmi3Simulate(...): Exiting initialization mode failed with return code $(retcode)." + end + + # allocate a solution object + c.solution = FMUSolution(c) + + # ME specific + if type == fmi3TypeModelExchange + if isnothing(x0) && !c.fmu.isZeroState + x0 = fmi3GetContinuousStates(c) + end + + if instantiate || reset # we have a fresh instance + @debug "[NEW INST]" + handleEvents(c) + end + + c.fmu.hasStateEvents = (c.fmu.modelDescription.numberOfEventIndicators > 0) + c.fmu.hasTimeEvents = isTrue(c.nextEventTimeDefined) + end + + end + + return c, x0 +end +function prepareSolveFMU(fmu::FMU3, c::Union{Nothing, FMU3Instance}, type::Symbol; kwargs...) + if type == :CS + return prepareSolveFMU(fmu, c, fmi3TypeCoSimulation; kwargs...) + elseif type == :ME + return prepareSolveFMU(fmu, c, fmi3TypeModelExchange; kwargs...) + elseif type == :SE + return prepareSolveFMU(fmu, c, fmi3TypeScheduledExecution; kwargs...) + else + @assert false "Unknwon FMU type `$(type)`" + end +end + +function finishSolveFMU(fmu::FMU3, c::FMU3Instance; + freeInstance::Union{Nothing, Bool}=nothing, + terminate::Union{Nothing, Bool}=nothing, + popComponent::Bool=true) + + if isnothing(c) + return + end + + ignore_derivatives() do + if c === nothing + return + end + + if terminate === nothing + terminate = fmu.executionConfig.terminate + end + + if freeInstance === nothing + freeInstance = fmu.executionConfig.freeInstance + end + + # soft terminate (if necessary) + if terminate + retcode = fmi3Terminate(c; soft=true) + @assert retcode == fmi3StatusOK "fmi3Simulate(...): Termination failed with return code $(retcode)." + end + + # freeInstance (hard) + if freeInstance + fmi3FreeInstance!(c) + end + end + + return c +end + +function finishSolveFMU(fmu::Vector{FMU2}, c::AbstractVector{Union{FMU2Component, Nothing}}, freeInstance::Union{Nothing, Bool}, terminate::Union{Nothing, Bool}) + + ignore_derivatives() do + for i in 1:length(fmu) + if terminate === nothing + terminate = fmu[i].executionConfig.terminate + end + + if freeInstance === nothing + freeInstance = fmu[i].executionConfig.freeInstance + end + + if c[i] != nothing + + # soft terminate (if necessary) + if terminate + retcode = fmi2Terminate(c[i]; soft=true) + @assert retcode == fmi2StatusOK "fmi2Simulate(...): Termination failed with return code $(retcode)." + end + + if freeInstance + fmi2FreeInstance!(c[i]) + @debug "[RELEASED INST]" + end + c[i] = nothing + end + end + + end # ignore_derivatives + + return c +end \ No newline at end of file diff --git a/src/FMIImport.jl b/src/FMIImport.jl index 5fd60a6..72a4ab6 100644 --- a/src/FMIImport.jl +++ b/src/FMIImport.jl @@ -5,170 +5,48 @@ module FMIImport -using FMICore -using FMICore: fmi2Component, fmi3Instance -using FMICore: fast_copy! +using FMIBase.Reexport +@reexport using FMIBase +@reexport using FMIBase.FMICore -using FMICore.Requires -import FMICore.ChainRulesCore: ignore_derivatives +using FMIBase +using FMIBase.FMICore +using FMIBase: fast_copy!, invalidate!, check_invalidate! +using FMIBase.Requires -using RelocatableFolders -using FMICore: invalidate!, check_invalidate! - -# functions that have (currently) no better place - -# Receives one or an array of values and converts it into an Array{typeof(value)} (if not already). -prepareValue(value) = [value] -prepareValue(value::AbstractVector) = value -export prepareValue, prepareValueReference +import FMIBase.ChainRulesCore: ignore_derivatives -# wildcards for how a user can pass a fmi[X]ValueReference -""" -Union of (wildcard for) all ways to describe and pass a fmi2ValueReference (e.g. String, Int64, Array, fmi2ValueReference, ...) -""" -fmi2ValueReferenceFormat = Union{Nothing, String, AbstractArray{String,1}, fmi2ValueReference, AbstractArray{fmi2ValueReference,1}, Int64, AbstractArray{Int64,1}, Symbol} -""" -Union of (wildcard for) all ways to describe and pass a fmi3ValueReference (e.g. String, Int64, Array, fmi3ValueReference, ...) -""" -fmi3ValueReferenceFormat = Union{Nothing, String, AbstractArray{String,1}, fmi3ValueReference, AbstractArray{fmi3ValueReference,1}, Int64, AbstractArray{Int64,1}} -export fmi2ValueReferenceFormat, fmi3ValueReferenceFormat +using RelocatableFolders -using EzXML -include("parse.jl") +import FMIBase: EzXML +include("convert.jl") +include("zip.jl") +include("binary.jl") +include("md_parse.jl") +include("info.jl") ### FMI2 ### -include("FMI2/prep.jl") -include("FMI2/convert.jl") include("FMI2/c.jl") include("FMI2/int.jl") +include("FMI2/prep.jl") include("FMI2/ext.jl") include("FMI2/md.jl") -include("FMI2/fmu_to_md.jl") - -# FMI2_c.jl -export fmi2CallbackLogger, fmi2CallbackAllocateMemory, fmi2CallbackFreeMemory, fmi2CallbackStepFinished -export fmi2ComponentState, fmi2ComponentStateModelSetableFMUstate, fmi2ComponentStateModelUnderEvaluation, fmi2ComponentStateModelInitialized # TODO might be imported from FMICOre -export fmi2Instantiate, fmi2FreeInstance!, fmi2GetTypesPlatform, fmi2GetVersion -export fmi2SetDebugLogging, fmi2SetupExperiment, fmi2EnterInitializationMode, fmi2ExitInitializationMode, fmi2Terminate, fmi2Reset -export fmi2GetReal!, fmi2SetReal, fmi2GetInteger!, fmi2SetInteger, fmi2GetBoolean!, fmi2SetBoolean, fmi2GetString!, fmi2SetString -export fmi2GetFMUstate!, fmi2SetFMUstate, fmi2FreeFMUstate!, fmi2SerializedFMUstateSize!, fmi2SerializeFMUstate!, fmi2DeSerializeFMUstate! -export fmi2GetDirectionalDerivative!, fmi2SetRealInputDerivatives, fmi2GetRealOutputDerivatives -export fmi2DoStep, fmi2CancelStep, fmi2GetStatus!, fmi2GetRealStatus!, fmi2GetIntegerStatus!, fmi2GetBooleanStatus!, fmi2GetStringStatus! -export fmi2SetTime, fmi2SetContinuousStates, fmi2EnterEventMode, fmi2NewDiscreteStates!, fmi2EnterContinuousTimeMode, fmi2CompletedIntegratorStep! -export fmi2SetDiscreteStates, fmi2GetDiscreteStates!, fmi2GetDiscreteStates -export fmi2GetDerivatives!, fmi2GetEventIndicators!, fmi2GetContinuousStates!, fmi2GetNominalsOfContinuousStates!, fmi2GetRealOutputDerivatives! - -# FMI2_convert.jl -export fmi2StringToValueReference, fmi2ValueReferenceToString, fmi2ModelVariablesForValueReference, fmi2DataTypeForValueReference -export fmi2GetSolutionState, fmi2GetSolutionTime, fmi2GetSolutionValue, fmi2GetSolutionDerivative - -# FMI2_int.jl -# almost everything exported in `FMI2_c.jl` -export fmi2GetReal, fmi2GetInteger, fmi2GetString, fmi2GetBoolean -export fmi2GetFMUstate, fmi2SerializedFMUstateSize, fmi2SerializeFMUstate, fmi2DeSerializeFMUstate, fmi2NewDiscreteStates -export fmi2GetDirectionalDerivative, fmi2GetDerivatives, fmi2GetEventIndicators, fmi2GetNominalsOfContinuousStates -export fmi2CompletedIntegratorStep - -# FMI2_ext.jl -export fmi2Unzip, fmi2Load, loadBinary, fmi2Reload, fmi2Unload, fmi2Instantiate! -export fmi2SampleJacobian! -export fmi2GetJacobian, fmi2GetJacobian!, fmi2GetFullJacobian, fmi2GetFullJacobian! -export fmi2Get, fmi2Get!, fmi2Set -export fmi2GetUnit, fmi2GetInitial, fmi2GetStartValue, fmi2SampleJacobian -export fmi2GetContinuousStates -export fmi2GetDeclaredType, fmi2GetSimpleTypeAttributeStruct - -# FMI2_md.jl -export fmi2LoadModelDescription -export fmi2GetDefaultStartTime, fmi2GetDefaultStopTime, fmi2GetDefaultTolerance, fmi2GetDefaultStepSize -export fmi2GetModelName, fmi2GetGUID, fmi2GetGenerationTool, fmi2GetGenerationDateAndTime, fmi2GetVariableNamingConvention, fmi2GetNumberOfEventIndicators, fmi2GetNumberOfStates, fmi2IsCoSimulation, fmi2IsModelExchange, fmi2GetNames, fmi2GetModelVariableIndices -export fmi2DependenciesSupported, fmi2DerivativeDependenciesSupported, fmi2GetModelIdentifier, fmi2CanGetSetState, fmi2CanSerializeFMUstate, fmi2ProvidesDirectionalDerivative -export fmi2GetInputValueReferencesAndNames, fmi2GetOutputValueReferencesAndNames, fmi2GetParameterValueReferencesAndNames, fmi2GetStateValueReferencesAndNames, fmi2GetDerivativeValueReferencesAndNames -export fmi2GetInputNames, fmi2GetOutputNames, fmi2GetParameterNames, fmi2GetStateNames, fmi2GetDerivativeNames -export fmi2GetValueReferencesAndNames, fmi2GetNamesAndDescriptions, fmi2GetNamesAndUnits, fmi2GetNamesAndInitials, fmi2GetInputNamesAndStarts, fmi2GetDerivateValueReferencesAndNames - -# FMI2_fmu_to_md.jl -# everything exported in `FMI2_md.jl` - -# FMI2_sens.jl -export eval! ### FMI3 ### - include("FMI3/c.jl") -include("FMI3/convert.jl") include("FMI3/int.jl") +include("FMI3/prep.jl") include("FMI3/ext.jl") include("FMI3/md.jl") -include("FMI3/fmu_to_md.jl") - -# FMI3_c.jl -export fmi3CallbackLogger, fmi3CallbackIntermediateUpdate, fmi3CallbackClockUpdate -export fmi3InstanceState -export fmi3InstantiateModelExchange, fmi3InstantiateCoSimulation, fmi3InstantiateScheduledExecution, fmi3FreeInstance! -export fmi3GetVersion, fmi3SetDebugLogging -export fmi3EnterInitializationMode, fmi3ExitInitializationMode, fmi3Terminate, fmi3Reset -export fmi3GetFloat32!, fmi3SetFloat32, fmi3GetFloat64!, fmi3SetFloat64 -export fmi3GetInt8!, fmi3SetInt8, fmi3GetUInt8!, fmi3SetUInt8, fmi3GetInt16!, fmi3SetInt16, fmi3GetUInt16!, fmi3SetUInt16, fmi3GetInt32!, fmi3SetInt32, fmi3GetUInt32!, fmi3SetUInt32, fmi3GetInt64!, fmi3SetInt64, fmi3GetUInt64!, fmi3SetUInt64 -export fmi3GetBoolean!, fmi3SetBoolean, fmi3GetString!, fmi3SetString, fmi3GetBinary!, fmi3SetBinary, fmi3GetClock!, fmi3SetClock -export fmi3GetFMUState!, fmi3SetFMUState, fmi3FreeFMUState!, fmi3SerializedFMUStateSize!, fmi3SerializeFMUState!, fmi3DeSerializeFMUState! -export fmi3SetIntervalDecimal, fmi3SetIntervalFraction, fmi3GetIntervalDecimal!, fmi3GetIntervalFraction!, fmi3GetShiftDecimal!, fmi3GetShiftFraction! -export fmi3ActivateModelPartition -export fmi3GetNumberOfVariableDependencies!, fmi3GetVariableDependencies! -export fmi3GetDirectionalDerivative!, fmi3GetAdjointDerivative!, fmi3GetOutputDerivatives -export fmi3EnterConfigurationMode, fmi3ExitConfigurationMode, fmi3GetNumberOfContinuousStates, fmi3GetNumberOfEventIndicators, fmi3GetContinuousStates!, fmi3GetNominalsOfContinuousStates! -export fmi3EvaluateDiscreteStates, fmi3UpdateDiscreteStates, fmi3EnterContinuousTimeMode, fmi3EnterStepMode -export fmi3SetTime, fmi3SetContinuousStates, fmi3GetContinuousStateDerivatives, fmi3GetEventIndicators!, fmi3CompletedIntegratorStep!, fmi3EnterEventMode, fmi3DoStep! - -# FMI3_convert.jl -export fmi3StringToValueReference, fmi3ValueReferenceToString, fmi3ModelVariablesForValueReference - -# FMI3_int.jl -export fmi3GetFloat32, fmi3GetFloat64 -export fmi3GetInt8, fmi3GetUInt8, fmi3GetInt16, fmi3GetUInt16, fmi3GetInt32, fmi3GetUInt32, fmi3GetInt64, fmi3GetUInt64 -export fmi3GetBoolean, fmi3GetString, fmi3GetBinary, fmi3GetClock -export fmi3GetFMUState, fmi3SerializedFMUStateSize, fmi3SerializeFMUState, fmi3DeSerializeFMUState -export fmi3GetDirectionalDerivative -export fmi3GetStartValue, fmi3SampleDirectionalDerivative, fmi3CompletedIntegratorStep - -# FMI3_ext.jl -export fmi3Unzip, fmi3Load, fmi3Reload, fmi3Unload, fmi3InstantiateModelExchange!, fmi3InstantiateCoSimulation!, fmi3InstantiateScheduledExecution! -export fmi3Get, fmi3Get!, fmi3Set -export fmi3SampleDirectionalDerivative! -export fmi3GetJacobian, fmi3GetJacobian!, fmi3GetFullJacobian, fmi3GetFullJacobian! - -# FMI3_md.jl -export fmi3LoadModelDescription -export fmi3GetModelName, fmi3GetInstantiationToken, fmi3GetGenerationTool, fmi3GetGenerationDateAndTime, fmi3GetVariableNamingConvention -export fmi3IsCoSimulation, fmi3IsModelExchange, fmi3IsScheduledExecution -export fmi3GetDefaultStartTime, fmi3GetDefaultStopTime, fmi3GetDefaultTolerance, fmi3GetDefaultStepSize -export fmi3GetModelIdentifier, fmi3CanGetSetState, fmi3CanSerializeFMUState, fmi3ProvidesDirectionalDerivatives, fmi3ProvidesAdjointDerivatives - -# FMI3_fmu_to_md.jl -# everything exported in `FMI2_md.jl` - -### - -""" -Union containing a [`FMU2`](@ref) or a [`FMU2Component`](@ref) -""" -fmi2Struct = Union{FMU2, FMU2Component} - -""" -Union containing a [`FMU3`](@ref) or a [`FMU3Instance`](@ref) -""" -fmi3Struct = Union{FMU3, FMU3Instance} -export fmi2Struct, fmi3Struct -""" -Union containing a [`FMU2`](@ref), a [`FMU2Component`](@ref) or a [`fmi2ModelDescription`](@ref) -""" -fmi2StructMD = Union{FMU2, FMU2Component, fmi2ModelDescription} +# extensions +using Requires +using PackageExtensionCompat +function __init__() + @require_extensions +end -""" -Union containing a [`FMU3`](@ref), a [`FMU3Instance`](@ref) or a [`fmi3ModelDescription`](@ref) -""" -fmi3StructMD = Union{FMU3, FMU3Instance , fmi3ModelDescription} -export fmi2StructMD, fmi3StructMD +# FMIZoo.jl +# nothing to declare end # module diff --git a/src/binary.jl b/src/binary.jl new file mode 100644 index 0000000..186e5d9 --- /dev/null +++ b/src/binary.jl @@ -0,0 +1,121 @@ +# +# Copyright (c) 2021 Tobias Thummerer, Lars Mikelsons, Josef Kircher +# Licensed under the MIT license. See LICENSE file in the project root for details. +# + +using FMIBase.EzXML + +function dlsym_opt(libHandle, symbol) + addr = dlsym(libHandle, symbol; throw_error=false) + if addr == nothing + logWarning(fmu, "This FMU does not support function '$symbol'.") + addr = Ptr{Cvoid}(C_NULL) + end + addr +end + +""" + reload(fmu::FMU2) + +Reloads the FMU-binary. This is useful, if the FMU does not support a clean reset implementation. + +# Arguments +- `fmu::FMU2`: Mutable struct representing a FMU and all it instantiated instances in the FMI 2.0.2 Standard. +""" +function reload(fmu::FMU) + dlclose(fmu.libHandle) + loadPointers(fmu) +end +export reload + +function loadFMU(pathToFMU::String; unpackPath=nothing, cleanup=true, type::Union{Symbol, Nothing}=nothing) + + unzippedAbsPath, zipAbsPath = unzip(pathToFMU; unpackPath=unpackPath, cleanup=cleanup) + + # read version tag + + doc = readxml(normpath(joinpath(unzippedAbsPath, "modelDescription.xml"))) + + root = doc.root + version = root["fmiVersion"] + + if version == "1.0" + @assert false "FMI version 1.0 deteted, this is (currently) not supported by FMI.jl." + elseif version == "2.0" + return createFMU2(unzippedAbsPath, zipAbsPath; type=type) + elseif version == "3.0" + return createFMU3(unzippedAbsPath, zipAbsPath; type=type) + else + @assert false, "Unknwon FMI version `$(version)`." + end +end +export loadFMU + +""" + unloadFMU(fmu::FMU2, cleanUp::Bool=true; secure_pointers::Bool=true) + +Unload a FMU. +Free the allocated memory, close the binaries and remove temporary zip and unziped FMU model description. + +# Arguments +- `fmu::FMU2`: Mutable struct representing a FMU and all it instantiated instances in the FMI 2.0.2 Standard. +- `cleanUp::Bool= true`: Defines if the file and directory should be deleted. + +# Keywords +- `secure_pointers=true` whether pointers to C-functions should be overwritten with dummies with Julia assertions, instead of pointing to dead memory (slower, but more user safe) +""" +function unloadFMU(fmu::FMU2, cleanUp::Bool=true; secure_pointers::Bool=true) + + while length(fmu.components) > 0 + c = fmu.components[end] + + # release allocated memory for snapshots (they might be used elsewhere too) + # if !isnothing(c.solution) + # for iter in c.solution.snapshots + # t, snapshot = iter + # cleanup!(c, snapshot) + # end + # end + + fmi2FreeInstance!(c) + end + + # the components are removed from the component list via call to fmi2FreeInstance! + @assert length(fmu.components) == 0 "fmi2Unload(...): Failure during deleting components, $(length(fmu.components)) remaining in stack." + + if secure_pointers + unloadPointers(fmu) + end + + dlclose(fmu.libHandle) + + if cleanUp + try + rm(fmu.path; recursive = true, force = true) + rm(fmu.zipPath; recursive = true, force = true) + catch e + @warn "Cannot delete unpacked data on disc. Maybe some files are opened in another application." + end + end +end +function unloadFMU(fmu::FMU3, cleanUp::Bool=true) + + while length(fmu.instances) > 0 + fmi3FreeInstance!(fmu.instances[end]) + end + + dlclose(fmu.libHandle) + + # the instances are removed from the instances list via call to fmi3FreeInstance! + @assert length(fmu.instances) == 0 "fmi3Unload(...): Failure during deleting instances, $(length(fmu.instances)) remaining in stack." + + if cleanUp + try + rm(fmu.path; recursive = true, force = true) + rm(fmu.zipPath; recursive = true, force = true) + catch e + @warn "Cannot delete unpacked data on disc. Maybe some files are opened in another application." + end + end +end +export unloadFMU \ No newline at end of file diff --git a/src/convert.jl b/src/convert.jl new file mode 100644 index 0000000..082ef4e --- /dev/null +++ b/src/convert.jl @@ -0,0 +1,175 @@ +# +# Copyright (c) 2021 Tobias Thummerer, Lars Mikelsons, Josef Kircher +# Licensed under the MIT license. See LICENSE file in the project root for details. +# + +""" + getState(solution::FMUSolution, i::fmi2ValueReferenceFormat; isIndex::Bool=false) + +Returns the solution state for a given value reference `i` (for `isIndex=false`) or the i-th state (for `isIndex=true`). +""" +function getState(solution::FMUSolution, vrs::fmi2ValueReferenceFormat; isIndex::Bool=false) + + indices = [] + + if isIndex + if length(vrs) == 1 + indices = [vrs] + else + indices = vrs + end + else + ignore_derivatives() do + vrs = prepareValueReference(solution.component.fmu, vrs) + + if !isnothing(solution.states) + for vr in vrs + found = false + for i in 1:length(solution.component.fmu.modelDescription.stateValueReferences) + if solution.component.fmu.modelDescription.stateValueReferences[i] == vr + push!(indices, i) + found = true + break + end + end + @assert found "Couldn't find the index for value reference `$(vr)`! This is probaly because this value reference does not belong to a system state." + end + end + + end # ignore_derivatives + end + + # found something + if length(indices) == length(vrs) + + if length(vrs) == 1 # single value + return collect(u[indices[1]] for u in solution.states.u) + + else # multi value + return collect(collect(u[indices[i]] for u in solution.states.u) for i in 1:length(indices)) + + end + end + + return nothing +end +export getState + +""" + getStateDerivative(solution::FMUSolution, i::fmi2ValueReferenceFormat; isIndex::Bool=false) + +Returns the solution state derivative for a given value reference `i` (for `isIndex=false`) or the i-th state (for `isIndex=true`). +""" +function getStateDerivative(solution::FMUSolution, vrs::fmi2ValueReferenceFormat; isIndex::Bool=false, order::Integer=1) + indices = [] + + if isIndex + if length(vrs) == 1 + indices = [vrs] + else + indices = vrs + end + else + ignore_derivatives() do + vrs = prepareValueReference(solution.component.fmu, vrs) + + if !isnothing(solution.states) + for vr in vrs + found = false + for i in 1:length(solution.component.fmu.modelDescription.stateValueReferences) + if solution.component.fmu.modelDescription.stateValueReferences[i] == vr + push!(indices, i) + found = true + break + end + end + @assert found "Couldn't find the index for value reference `$(vr)`! This is probaly because this value reference does not belong to a system state." + end + end + + end # ignore_derivatives + end + + # found something + if length(indices) == length(vrs) + + if length(vrs) == 1 # single value + return collect(solution.states(t, Val{order})[indices[1]] for t in solution.states.t) + + else # multi value + return collect(collect(solution.states(t, Val{order})[indices[i]] for t in solution.states.t) for i in 1:length(indices)) + end + end + + return nothing +end +export getStateDerivative + +""" + getValue(solution::FMU2Solution, i::fmi2ValueReferenceFormat; isIndex::Bool=false) + +Returns the values for a given value reference `i` (for `isIndex=false`) or the i-th value (for `isIndex=true`). +Recording of values must be enabled. +""" +function FMIBase.getValue(solution::FMUSolution, vrs::fmi2ValueReferenceFormat; isIndex::Bool=false) + + indices = [] + + if isIndex + if length(vrs) == 1 + indices = [vrs] + else + indices = vrs + end + else + ignore_derivatives() do + vrs = prepareValueReference(solution.component.fmu, vrs) + + if !isnothing(solution.values) + for vr in vrs + found = false + for i in 1:length(solution.valueReferences) + if solution.valueReferences[i] == vr + push!(indices, i) + found = true + break + end + end + @assert found "Couldn't find the index for value reference `$(vr)`! This is probaly because this value reference does not exist for this system." + end + end + + end # ignore_derivatives + end + + # found something + if length(indices) == length(vrs) + + if length(vrs) == 1 # single value + return collect(u[indices[1]] for u in solution.values.saveval) + + else # multi value + return collect(collect(u[indices[i]] for u in solution.values.saveval) for i in 1:length(indices)) + + end + end + + return nothing +end +export getValue + +""" + getTime(solution::FMU2Solution) + +Returns the points in time of the solution `solution`. +""" +function getTime(solution::FMUSolution) + if !isnothing(solution.states) + return solution.states.t + elseif !isnothing(solution.values) + return solution.values.t + else + return nothing + end +end +export getTime \ No newline at end of file diff --git a/src/info.jl b/src/info.jl new file mode 100644 index 0000000..aaf3ed7 --- /dev/null +++ b/src/info.jl @@ -0,0 +1,143 @@ +# +# Copyright (c) 2021 Tobias Thummerer, Lars Mikelsons, Josef Kircher +# Licensed under the MIT license. See LICENSE file in the project root for details. +# + +""" + info(fmu) + +Print information about the FMU. + +# Arguments +- `fmu::FMU`: The FMU you are interessted in. + +# Further reading +- FMISpec2.0.2 Link: [https://fmi-standard.org/](https://fmi-standard.org/) +""" +function info(fmu::FMU2) + println("#################### Begin information for FMU ####################") + + println("\tModel name:\t\t\t$(getModelName(fmu))") + println("\tFMI-Version:\t\t\t$(fmi2GetVersion(fmu))") + println("\tGUID:\t\t\t\t$(fmi2GetGUID(fmu))") + println("\tGeneration tool:\t\t$(getGenerationTool(fmu))") + println("\tGeneration time:\t\t$(generationDateAndTime(fmu))") + print("\tVar. naming conv.:\t\t") + if fmi2GetVariableNamingConvention(fmu) == fmi2VariableNamingConventionFlat + println("flat") + elseif fmi2GetVariableNamingConvention(fmu) == fmi2VariableNamingConventionStructured + println("structured") + else + println("[unknown]") + end + println("\tEvent indicators:\t\t$(fmi2GetNumberOfEventIndicators(fmu))") + + println("\tInputs:\t\t\t\t$(length(fmu.modelDescription.inputValueReferences))") + for vr in fmu.modelDescription.inputValueReferences + println("\t\t$(vr) $(fmi2ValueReferenceToString(fmu, vr))") + end + + println("\tOutputs:\t\t\t$(length(fmu.modelDescription.outputValueReferences))") + for vr in fmu.modelDescription.outputValueReferences + println("\t\t$(vr) $(fmi2ValueReferenceToString(fmu, vr))") + end + + println("\tStates:\t\t\t\t$(length(fmu.modelDescription.stateValueReferences))") + for vr in fmu.modelDescription.stateValueReferences + println("\t\t$(vr) $(fmi2ValueReferenceToString(fmu, vr))") + end + + println("\tSupports Co-Simulation:\t\t$(fmi2IsCoSimulation(fmu))") + if fmi2IsCoSimulation(fmu) + println("\t\tModel identifier:\t$(fmu.modelDescription.coSimulation.modelIdentifier)") + println("\t\tGet/Set State:\t\t$(fmu.modelDescription.coSimulation.canGetAndSetFMUstate)") + println("\t\tSerialize State:\t$(fmu.modelDescription.coSimulation.canSerializeFMUstate)") + println("\t\tDir. Derivatives:\t$(fmu.modelDescription.coSimulation.providesDirectionalDerivative)") + + println("\t\tVar. com. steps:\t$(fmu.modelDescription.coSimulation.canHandleVariableCommunicationStepSize)") + println("\t\tInput interpol.:\t$(fmu.modelDescription.coSimulation.canInterpolateInputs)") + println("\t\tMax order out. der.:\t$(fmu.modelDescription.coSimulation.maxOutputDerivativeOrder)") + end + + println("\tSupports Model-Exchange:\t$(fmi2IsModelExchange(fmu))") + if fmi2IsModelExchange(fmu) + println("\t\tModel identifier:\t$(fmu.modelDescription.modelExchange.modelIdentifier)") + println("\t\tGet/Set State:\t\t$(fmu.modelDescription.modelExchange.canGetAndSetFMUstate)") + println("\t\tSerialize State:\t$(fmu.modelDescription.modelExchange.canSerializeFMUstate)") + println("\t\tDir. Derivatives:\t$(fmu.modelDescription.modelExchange.providesDirectionalDerivative)") + end + + println("##################### End information for FMU #####################") +end +function info(fmu::FMU3) + println("#################### Begin information for FMU ####################") + + println("\tModel name:\t\t\t$(fmi3GetModelName(fmu))") + println("\tFMI-Version:\t\t\t$(fmi3GetVersion(fmu))") + println("\tInstantiation Token:\t\t\t\t$(fmi3GetInstantiationToken(fmu))") + println("\tGeneration tool:\t\t$(fmi3GetGenerationTool(fmu))") + println("\tGeneration time:\t\t$(fmi3GetGenerationDateAndTime(fmu))") + print("\tVar. naming conv.:\t\t") + if fmi3GetVariableNamingConvention(fmu) == fmi3VariableNamingConventionFlat + println("flat") + elseif fmi3GetVariableNamingConvention(fmu) == fmi3VariableNamingConventionStructured + println("structured") + else + println("[unknown]") + end + println("\tEvent indicators:\t\t$(fmi3GetNumberOfEventIndicators(fmu))") + + println("\tInputs:\t\t\t\t$(length(fmu.modelDescription.inputValueReferences))") + for vr in fmu.modelDescription.inputValueReferences + println("\t\t$(vr) $(fmi3ValueReferenceToString(fmu, vr))") + end + + println("\tOutputs:\t\t\t$(length(fmu.modelDescription.outputValueReferences))") + for vr in fmu.modelDescription.outputValueReferences + println("\t\t$(vr) $(fmi3ValueReferenceToString(fmu, vr))") + end + + println("\tStates:\t\t\t\t$(length(fmu.modelDescription.stateValueReferences))") + for vr in fmu.modelDescription.stateValueReferences + println("\t\t$(vr) $(fmi3ValueReferenceToString(fmu, vr))") + end + + println("\tSupports Co-Simulation:\t\t$(fmi3IsCoSimulation(fmu))") + if fmi3IsCoSimulation(fmu) + println("\t\tModel identifier:\t$(fmu.modelDescription.coSimulation.modelIdentifier)") + println("\t\tGet/Set State:\t\t$(fmu.modelDescription.coSimulation.canGetAndSetFMUstate)") + println("\t\tSerialize State:\t$(fmu.modelDescription.coSimulation.canSerializeFMUstate)") + println("\t\tDir. Derivatives:\t$(fmu.modelDescription.coSimulation.providesDirectionalDerivatives)") + println("\t\tAdj. Derivatives:\t$(fmu.modelDescription.coSimulation.providesAdjointDerivatives)") + println("\t\tEvent Mode:\t$(fmu.modelDescription.coSimulation.hasEventMode)") + + println("\t\tVar. com. steps:\t$(fmu.modelDescription.coSimulation.canHandleVariableCommunicationStepSize)") + println("\t\tInput interpol.:\t$(fmu.modelDescription.coSimulation.canInterpolateInputs)") + println("\t\tMax order out. der.:\t$(fmu.modelDescription.coSimulation.maxOutputDerivativeOrder)") + end + + println("\tSupports Model-Exchange:\t$(fmi3IsModelExchange(fmu))") + if fmi3IsModelExchange(fmu) + println("\t\tModel identifier:\t$(fmu.modelDescription.modelExchange.modelIdentifier)") + println("\t\tGet/Set State:\t\t$(fmu.modelDescription.modelExchange.canGetAndSetFMUstate)") + println("\t\tSerialize State:\t$(fmu.modelDescription.modelExchange.canSerializeFMUstate)") + println("\t\tDir. Derivatives:\t$(fmu.modelDescription.modelExchange.providesDirectionalDerivatives)") + println("\t\tAdj. Derivatives:\t$(fmu.modelDescription.modelExchange.providesAdjointDerivatives)") + end + + println("\tSupports Scheduled-Execution:\t$(fmi3IsScheduledExecution(fmu))") + if fmi3IsScheduledExecution(fmu) + println("\t\tModel identifier:\t$(fmu.modelDescription.scheduledExecution.modelIdentifier)") + println("\t\tGet/Set State:\t\t$(fmu.modelDescription.scheduledExecution.canGetAndSetFMUstate)") + println("\t\tSerialize State:\t$(fmu.modelDescription.scheduledExecution.canSerializeFMUstate)") + println("\t\tNeeds Execution Tool:\t$(fmu.modelDescription.scheduledExecution.needsExecutionTool)") + println("\t\tInstantiated Once Per Process:\t$(fmu.modelDescription.scheduledExecution.canBeInstantiatedOnlyOncePerProcess)") + println("\t\tPer Element Dependencies:\t$(fmu.modelDescription.scheduledExecution.providesPerElementDependencies)") + + println("\t\tDir. Derivatives:\t$(fmu.modelDescription.scheduledExecution.providesDirectionalDerivatives)") + println("\t\tAdj. Derivatives:\t$(fmu.modelDescription.scheduledExecution.providesAdjointDerivatives)") + end + + println("##################### End information for FMU #####################") +end +export info diff --git a/src/jacobian.jl b/src/jacobian.jl new file mode 100644 index 0000000..85324b2 --- /dev/null +++ b/src/jacobian.jl @@ -0,0 +1,332 @@ +# +# Copyright (c) 2021 Tobias Thummerer, Lars Mikelsons, Josef Kircher +# Licensed under the MIT license. See LICENSE file in the project root for details. +# + +""" + sampleJacobian(c::FMU2Component, + vUnknown_ref::Union{AbstractArray{fmi2ValueReference}, Symbol}, + vKnown_ref::AbstractArray{fmi2ValueReference}, + steps::Union{AbstractArray{fmi2Real}, Nothing} = nothing) + +This function samples the directional derivative by manipulating corresponding values (central differences). + +Computes the directional derivatives of an FMU. An FMU has different modes and in every Mode an FMU might be described by different equations and different unknowns. The precise definitions are given in the mathematical descriptions of Model Exchange (section 3.1) and Co-Simulation (section 4.1). In every Mode, the general form of the FMU equations are: +𝐯_unknown = 𝐡(𝐯_known, 𝐯_rest) + +- `v_unknown`: vector of unknown Real variables computed in the actual Mode: + - Initialization Mode: unkowns kisted under `` that have type Real. + - Continuous-Time Mode (ModelExchange): The continuous-time outputs and state derivatives. (= the variables listed under `` with type Real and variability = `continuous` and the variables listed as state derivatives under `)`. + - Event Mode (ModelExchange): The same variables as in the Continuous-Time Mode and additionally variables under `` with type Real and variability = `discrete`. + - Step Mode (CoSimulation): The variables listed under `` with type Real and variability = `continuous` or `discrete`. If `` is present, also the variables listed here as state derivatives. +- `v_known`: Real input variables of function h that changes its value in the actual Mode. +- `v_rest`:Set of input variables of function h that either changes its value in the actual Mode but are non-Real variables, or do not change their values in this Mode, but change their values in other Modes. + +Computes a linear combination of the partial derivatives of h with respect to the selected input variables 𝐯_known: + + Δv_unknown = (δh / δv_known) Δv_known + +# Arguments +- `c::FMU2Component`: Mutable struct represents an instantiated instance of an FMU in the FMI 2.0.2 Standard. +- `vUnknown_ref::AbstractArray{fmi2ValueReference}`: Argument `vUnknown_ref` contains values of type`fmi2ValueReference` which are identifiers of a variable value of the model. `vUnknown_ref` can be equated with `v_unknown`(variable described above). +- `vKnown_ref::AbstractArray{fmi2ValueReference}`: Argument `vKnown_ref` contains values of type `fmi2ValueReference` which are identifiers of a variable value of the model.`vKnown_ref` can be equated with `v_known`(variable described above). +- `steps::Union{AbstractArray{fmi2Real}, Nothing} = nothing)`: If sampling is used, sampling step size can be set (for each direction individually) using optional argument `steps`. + +# Returns +- `dvUnkonwn::Array{fmi2Real}`: Argument `vUnknown_ref` contains values of type`fmi2ValueReference` which are identifiers of a variable value of the model. `vUnknown_ref` can be equated with `v_unknown`(see function fmi2GetDirectionalDerivative!). + +# Source +- FMISpec2.0.2 Link: [https://fmi-standard.org/](https://fmi-standard.org/) +- FMISpec2.0.2: 2.2.7 Definition of Model Variables (ModelVariables) + +See also [`fmi2GetDirectionalDerivative!`](@ref). +""" +function sampleJacobian(c::FMU2Component, + vUnknown_ref::AbstractArray{fmi2ValueReference}, + vKnown_ref::AbstractArray{fmi2ValueReference}, + steps::Union{AbstractArray{fmi2Real}, Nothing} = nothing) + + mtx = zeros(fmi2Real, length(vUnknown_ref), length(vKnown_ref)) + + sampleJacobian!(mtx, vUnknown_ref, vKnown_ref, steps) + + return mtx +end + +""" + function sampleJacobian!(mtx::Matrix{<:Real}, + c::FMU2Component, + vUnknown_ref::Union{AbstractArray{fmi2ValueReference}, Symbol}, + vKnown_ref::Union{AbstractArray{fmi2ValueReference}, Symbol}, + steps::Union{AbstractArray{fmi2Real}, Nothing} = nothing) + +This function samples the directional derivative by manipulating corresponding values (central differences) and saves in-place. + + +Computes the directional derivatives of an FMU. An FMU has different Modes and in every Mode an FMU might be described by different equations and different unknowns. The precise definitions are given in the mathematical descriptions of Model Exchange (section 3.1) and Co-Simulation (section 4.1). In every Mode, the general form of the FMU equations are: +𝐯_unknown = 𝐡(𝐯_known, 𝐯_rest) + +- `v_unknown`: vector of unknown Real variables computed in the actual Mode: + - Initialization Mode: unkowns kisted under `` that have type Real. + - Continuous-Time Mode (ModelExchange): The continuous-time outputs and state derivatives. (= the variables listed under `` with type Real and variability = `continuous` and the variables listed as state derivatives under `)`. + - Event Mode (ModelExchange): The same variables as in the Continuous-Time Mode and additionally variables under `` with type Real and variability = `discrete`. + - Step Mode (CoSimulation): The variables listed under `` with type Real and variability = `continuous` or `discrete`. If `` is present, also the variables listed here as state derivatives. +- `v_known`: Real input variables of function h that changes its value in the actual Mode. +- `v_rest`:Set of input variables of function h that either changes its value in the actual Mode but are non-Real variables, or do not change their values in this Mode, but change their values in other Modes + +Computes a linear combination of the partial derivatives of h with respect to the selected input variables 𝐯_known: + + Δv_unknown = (δh / δv_known) Δv_known + +# Arguments +- `mtx::Matrix{<:Real}`:Output matrix to store the Jacobian. Its dimensions must be compatible with the number of unknown and known value references. +- `c::FMU2Component`: Mutable struct represents an instantiated instance of an FMU in the FMI 2.0.2 Standard. +- `vUnknown_ref::AbstractArray{fmi2ValueReference}`: Argument `vUnknown_ref` contains values of type`fmi2ValueReference` which are identifiers of a variable value of the model. `vUnknown_ref` can be equated with `v_unknown`(variable described above). +- `vKnown_ref::AbstractArray{fmi2ValueReference}`: Argument `vKnown_ref` contains values of type `fmi2ValueReference` which are identifiers of a variable value of the model.`vKnown_ref` can be equated with `v_known`(variable described above). +- `dvUnknown::AbstractArray{fmi2Real}`: Stores the directional derivative vector values. +- `steps::Union{AbstractArray{fmi2Real}, Nothing} = nothing)`: Step size to be used for numerical differentiation. If nothing, a default value will be chosen automatically. + +# Returns +- `nothing` + +# Source +- FMISpec2.0.2 Link: [https://fmi-standard.org/](https://fmi-standard.org/) +- FMISpec2.0.2: 2.2.7 Definition of Model Variables (ModelVariables) + +See also [`fmi2GetDirectionalDerivative!`](@ref). +""" +function sampleJacobian!(mtx::Matrix{<:Real}, + c::FMU2Component, + vUnknown_ref::AbstractArray{fmi2ValueReference}, + vKnown_ref::AbstractArray{fmi2ValueReference}, + steps::Union{AbstractArray{fmi2Real}, Nothing} = nothing) + + step = 0.0 + + negValues = zeros(length(vUnknown_ref)) + posValues = zeros(length(vUnknown_ref)) + + for i in 1:length(vKnown_ref) + vKnown = vKnown_ref[i] + origValue = fmi2GetReal(c, vKnown) + + if steps === nothing + # smaller than 1e-6 leads to issues + step = max(2.0 * eps(Float32(origValue)), 1e-6) + else + step = steps[i] + end + + fmi2SetReal(c, vKnown, origValue - step; track=false) + fmi2GetReal!(c, vUnknown_ref, negValues) + + fmi2SetReal(c, vKnown, origValue + step; track=false) + fmi2GetReal!(c, vUnknown_ref, posValues) + + fmi2SetReal(c, vKnown, origValue; track=false) + + if length(vUnknown_ref) == 1 + mtx[1,i] = (posValues-negValues) ./ (step * 2.0) + else + mtx[:,i] = (posValues-negValues) ./ (step * 2.0) + end + end + + nothing +end + +function sampleJacobian!(mtx::Matrix{<:Real}, + c::FMU2Component, + vUnknown_ref::Symbol, + vKnown_ref::AbstractArray{fmi2ValueReference}, + steps::Union{AbstractArray{fmi2Real}, Nothing} = nothing) + + @assert vUnknown_ref == :indicators "vUnknown_ref::Symbol must be `:indicators`!" + + step = 0.0 + + len_vUnknown_ref = c.fmu.modelDescription.numberOfEventIndicators + + negValues = zeros(len_vUnknown_ref) + posValues = zeros(len_vUnknown_ref) + + for i in 1:length(vKnown_ref) + vKnown = vKnown_ref[i] + origValue = fmi2GetReal(c, vKnown) + + if steps === nothing + step = max(2.0 * eps(Float32(origValue)), 1e-12) + else + step = steps[i] + end + + fmi2SetReal(c, vKnown, origValue - step; track=false) + fmi2GetEventIndicators!(c, negValues) + + fmi2SetReal(c, vKnown, origValue + step; track=false) + fmi2GetEventIndicators!(c, posValues) + + fmi2SetReal(c, vKnown, origValue; track=false) + + if len_vUnknown_ref == 1 + mtx[1,i] = (posValues-negValues) ./ (step * 2.0) + else + mtx[:,i] = (posValues-negValues) ./ (step * 2.0) + end + end + + nothing +end + +function sampleJacobian!(mtx::Matrix{<:Real}, + c::FMU2Component, + vUnknown_ref::AbstractArray{fmi2ValueReference}, + vKnown_ref::Symbol, + steps::Union{AbstractArray{fmi2Real}, Nothing} = nothing) + + @assert vKnown_ref == :time "vKnown_ref::Symbol must be `:time`!" + + step = 0.0 + + negValues = zeros(length(vUnknown_ref)) + posValues = zeros(length(vUnknown_ref)) + + for i in 1:length(vKnown_ref) + vKnown = vKnown_ref[i] + origValue = fmi2GetReal(c, vKnown) + + if steps === nothing + step = max(2.0 * eps(Float32(origValue)), 1e-12) + else + step = steps[i] + end + + fmi2SetReal(c, vKnown, origValue - step; track=false) + fmi2GetEventIndicators!(c, negValues) + + fmi2SetReal(c, vKnown, origValue + step; track=false) + fmi2GetEventIndicators!(c, posValues) + + fmi2SetReal(c, vKnown, origValue; track=false) + + if length(vUnknown_ref) == 1 + mtx[1,i] = (posValues-negValues) ./ (step * 2.0) + else + mtx[:,i] = (posValues-negValues) ./ (step * 2.0) + end + end + + nothing +end + +""" + getJacobian(comp::FMU2Component, + rdx::AbstractArray{fmi2ValueReference}, + rx::AbstractArray{fmi2ValueReference}; + steps::Union{AbstractArray{fmi2Real}, Nothing} = nothing) + +Builds the jacobian over the FMU `fmu` for FMU value references `rdx` and `rx`, so that the function returns the jacobian ∂rdx / ∂rx. + +If FMI built-in directional derivatives are supported, they are used. +As fallback, directional derivatives will be sampled with central differences. +For optimization, if the FMU's model description has the optional entry 'dependencies', only dependent variables are sampled/retrieved. This drastically boosts performance for systems with large variable count (like CFD). + +# Arguments +- `comp::FMU2Component`: Mutable struct represents an instantiated instance of an FMU in the FMI 2.0.2 Standard. +- `rdx::AbstractArray{fmi2ValueReference}`: Argument `vUnknown_ref` contains values of type`fmi2ValueReference` which are identifiers of a variable value of the model. +- `rx::AbstractArray{fmi2ValueReference}`: Argument `vKnown_ref` contains values of type `fmi2ValueReference` which are identifiers of a variable value of the model. + +# Keywords +- `steps::Union{AbstractArray{fmi2Real}, Nothing} = nothing)`: If sampling is used, sampling step size can be set (for each direction individually) using optional argument `steps`. + +# Returns +- `mat::Array{fmi2Real}`: Return `mat` contains the jacobian ∂rdx / ∂rx. + +# Source +- FMISpec2.0.2 Link: [https://fmi-standard.org/](https://fmi-standard.org/) +- FMISpec2.0.2: 2.2.7 Definition of Model Variables (ModelVariables) + +""" +function getJacobian(comp::FMU2Component, + rdx::AbstractArray{fmi2ValueReference}, + rx::AbstractArray{fmi2ValueReference}; + steps::Union{AbstractArray{fmi2Real}, Nothing} = nothing) + mat = zeros(fmi2Real, length(rdx), length(rx)) + fmi2GetJacobian!(mat, comp, rdx, rx; steps=steps) + return mat +end + +""" + getJacobian!(jac::AbstractMatrix{fmi2Real}, + comp::FMU2Component, + rdx::AbstractArray{fmi2ValueReference}, + rx::AbstractArray{fmi2ValueReference}; + steps::Union{AbstractArray{fmi2Real}, Nothing} = nothing) + +Fills the jacobian over the FMU `fmu` for FMU value references `rdx` and `rx`, so that the function stores the jacobian ∂rdx / ∂rx in an AbstractMatrix `jac`. + +If FMI built-in directional derivatives are supported, they are used. +As fallback, directional derivatives will be sampled with central differences. +For optimization, if the FMU's model description has the optional entry 'dependencies', only dependent variables are sampled/retrieved. This drastically boosts performance for systems with large variable count (like CFD). + +# Arguments +- `jac::AbstractMatrix{fmi2Real}`: A matrix that will hold the computed Jacobian matrix. +- `comp::FMU2Component`: Mutable struct represents an instantiated instance of an FMU in the FMI 2.0.2 Standard. +- `rdx::AbstractArray{fmi2ValueReference}`: Argument `vUnknown_ref` contains values of type`fmi2ValueReference` which are identifiers of a variable value of the model. +- `rx::AbstractArray{fmi2ValueReference}`: Argument `vKnown_ref` contains values of type `fmi2ValueReference` which are identifiers of a variable value of the model. + +# Keywords +- `steps::Union{AbstractArray{fmi2Real}, Nothing} = nothing)`: Step size to be used for numerical differentiation. If nothing, a default value will be chosen automatically. + +# Returns +- `nothing` + +# Source +- FMISpec2.0.2 Link: [https://fmi-standard.org/](https://fmi-standard.org/) +- FMISpec2.0.2: 2.2.7 Definition of Model Variables (ModelVariables) + +""" +function getJacobian!(jac::AbstractMatrix{fmi2Real}, + comp::FMU2Component, + rdx::AbstractArray{fmi2ValueReference}, + rx::AbstractArray{fmi2ValueReference}; + steps::Union{AbstractArray{fmi2Real}, Nothing} = nothing) + + @assert size(jac) == (length(rdx), length(rx)) ["fmi2GetJacobian!: Dimension missmatch between `jac` $(size(jac)), `rdx` $(length(rdx)) and `rx` $(length(rx))."] + + if length(rdx) == 0 || length(rx) == 0 + jac = zeros(length(rdx), length(rx)) + return nothing + end + + # ToDo: Pick entries based on dependency matrix! + #depMtx = fmi2GetDependencies(fmu) + rdx_inds = collect(comp.fmu.modelDescription.valueReferenceIndicies[vr] for vr in rdx) + rx_inds = collect(comp.fmu.modelDescription.valueReferenceIndicies[vr] for vr in rx) + + for i in 1:length(rx) + + sensitive_rdx_inds = 1:length(rdx) + sensitive_rdx = rdx + + # sensitive_rdx_inds = Int64[] + # sensitive_rdx = fmi2ValueReference[] + + # for j in 1:length(rdx) + # if depMtx[rdx_inds[j], rx_inds[i]] != fmi2DependencyIndependent + # push!(sensitive_rdx_inds, j) + # push!(sensitive_rdx, rdx[j]) + # end + # end + + if length(sensitive_rdx) > 0 + + fmi2GetDirectionalDerivative!(comp, sensitive_rdx, [rx[i]], view(jac, sensitive_rdx_inds, i)) + + # jac[sensitive_rdx_inds, i] = fmi2GetDirectionalDerivative(comp, sensitive_rdx, [rx[i]]) + + end + end + + return nothing +end \ No newline at end of file diff --git a/src/md_parse.jl b/src/md_parse.jl new file mode 100644 index 0000000..e641aa8 --- /dev/null +++ b/src/md_parse.jl @@ -0,0 +1,91 @@ +# +# Copyright (c) 2021 Tobias Thummerer, Lars Mikelsons, Josef Kircher +# Licensed under the MIT license. See LICENSE file in the project root for details. +# + +function parseNode(node, key, type::DataType=String; onfail=nothing) + if haskey(node, key) + return parseType(node[key], type; onfail=onfail) + else + return onfail + end +end + +function parseType(s::Union{String, SubString{String}}, type; onfail=nothing) + if onfail == nothing + return _parse(type, s) + else + try + return _parse(type, s) + catch + return onfail + end + end +end + +function _parse(type, s) + if s == "true" + return true + elseif s == "false" + return false + elseif type == String || type == Ptr{UInt8} # fmi3String + return s + else + return parse(type, s) + end +end + +parseNodeBoolean(node, key; onfail=nothing) = parseNode(node, key, Bool; onfail=onfail) + +# function parseNodeBoolean(node, key; onfail=nothing) +# if haskey(node, key) +# return parseBoolean(node[key]; onfail=onfail) +# else +# return onfail +# end +# end +# function parseBoolean(s::Union{String, SubString{String}}; onfail=nothing) +# if onfail == nothing +# return _parseBoolean(s) +# else +# try +# return _parseBoolean(s) +# catch +# return onfail +# end +# end +# end +# function _parseBoolean(s) +# if s == "1" +# return fmi2True # = fmi3True +# elseif s == "0" +# return fmi2False # = fmi3False +# else +# @assert false "parse(...) unknown boolean value '$s'." +# end +# end + +# [Todo] +function parseArrayValueReferences(md::fmi2ModelDescription, s::Union{String, SubString{String}}) + references = Array{fmi2ValueReference}(undef, 0) + substrings = split(s, " ") + + for string in substrings + push!(references, parse(fmi2ValueReferenceFormat, string)) + end + + return references +end +function parseArrayValueReferences(md::fmi3ModelDescription, s::Union{String, SubString{String}}) + references = Array{fmi3ValueReference}(undef, 0) + substrings = split(s, " ") + + for string in substrings + push!(references, parse(fmi3ValueReferenceFormat, string)) + end + + return references +end +function parseArrayValueReferences(md::fmiModelDescription, s::Nothing) + return nothing +end \ No newline at end of file diff --git a/src/parse.jl b/src/parse.jl deleted file mode 100644 index f1f66ba..0000000 --- a/src/parse.jl +++ /dev/null @@ -1,105 +0,0 @@ - -# -# Copyright (c) 2021 Tobias Thummerer, Lars Mikelsons, Josef Kircher -# Licensed under the MIT license. See LICENSE file in the project root for details. -# - -# Parses a Bool value represented by a string. -function parseBoolean(s::Union{String, SubString{String}}; onfail=nothing) - if s == "true" - return true - elseif s == "false" - return false - else - @assert onfail != nothing ["parseBoolean(...) unknown boolean value '$s'."] - return onfail - end -end - -# parses node (interpreted as boolean) -function parseNodeBoolean(node, key; onfail=nothing) - if haskey(node, key) - return parseBoolean(node[key]; onfail=onfail) - else - return onfail - end -end - -# Parses an Integer value represented by a string. -function parseType(s::Union{String, SubString{String}}, type; onfail=nothing) - if onfail == nothing - return parse(type, s) - else - try - return parse(type, s) - catch - return onfail - end - end -end - -function parseInteger(s::Union{String, SubString{String}}; kwargs...) - return parseType(s, Int; kwargs...) -end - -function parseUInt(s::Union{String, SubString{String}}; kwargs...) - return parseType(s, UInt; kwargs...) -end - -# parses node (interpreted as integer) -function parseNodeInteger(node, key; onfail=nothing) - if haskey(node, key) - return parseInteger(node[key]; onfail=onfail) - else - return onfail - end -end - -# parses node (interpreted as integer) -function parseNodeUInt(node, key; onfail=nothing) - if haskey(node, key) - return parseUInt(node[key]; onfail=onfail) - else - return onfail - end -end - -# Parses a real value represented by a string. -function parseReal(s::Union{String, SubString{String}}; onfail=nothing) - if onfail == nothing - return parse(fmi2Real, s) - else - try - return parse(fmi2Real, s) - catch - return onfail - end - end -end - -# parses node (interpreted as real) -function parseNodeReal(node, key; onfail=nothing) - if haskey(node, key) - return parseReal(node[key]; onfail=onfail) - else - return onfail - end -end - -# parses node (interpreted as string) -function parseNodeString(node, key; onfail=nothing) - if haskey(node, key) - return node[key] - else - return onfail - end -end - -# Parses a fmi2Boolean value represented by a string. -function parseFMI2Boolean(s::Union{String, SubString{String}}) - if parseBoolean(s) - return fmi2True - else - return fmi2False - end -end \ No newline at end of file diff --git a/src/zip.jl b/src/zip.jl new file mode 100644 index 0000000..0ac39ea --- /dev/null +++ b/src/zip.jl @@ -0,0 +1,92 @@ +# +# Copyright (c) 2021 Tobias Thummerer, Lars Mikelsons, Josef Kircher +# Licensed under the MIT license. See LICENSE file in the project root for details. +# + +using FMIBase.ZipFile +import Downloads + +""" + unzip(pathToFMU::String; unpackPath=nothing, cleanup=true) + +Create a copy of the .fmu file as a .zip folder and unzips it. +Returns the paths to the zipped and unzipped folders. + +# Arguments +- `pathToFMU::String`: The folder path to the .zip folder. + +# Keywords +- `unpackPath=nothing`: Via optional argument ```unpackPath```, a path to unpack the FMU can be specified (default: system temporary directory). +- `cleanup=true`: The cleanup option controls whether the temporary directory is automatically deleted when the process exits. + +# Returns +- `unzippedAbsPath::String`: Contains the Path to the uzipped Folder. +- `zipAbsPath::String`: Contains the Path to the zipped Folder. + +See also [`mktempdir`](https://docs.julialang.org/en/v1/base/file/#Base.Filesystem.mktempdir-Tuple{AbstractString}). +""" +function unzip(pathToFMU::String; unpackPath=nothing, cleanup=true) + + if startswith(pathToFMU, "http") + pathToFMU = Downloads.download(pathToFMU) + end + + fileNameExt = basename(pathToFMU) + (fileName, fileExt) = splitext(fileNameExt) + + if unpackPath == nothing + # cleanup = true leads to issues with automatic testing on linux server. + unpackPath = mktempdir(; prefix="fmijl_", cleanup=cleanup) + end + + zipPath = joinpath(unpackPath, fileName * ".zip") + unzippedPath = joinpath(unpackPath, fileName) + + # only copy ZIP if not already there + if !isfile(zipPath) + cp(pathToFMU, zipPath; force=true) + end + + @assert isfile(zipPath) ["unzip(...): ZIP-Archive couldn't be copied to `$zipPath`."] + + zipAbsPath = isabspath(zipPath) ? zipPath : joinpath(pwd(), zipPath) + unzippedAbsPath = isabspath(unzippedPath) ? unzippedPath : joinpath(pwd(), unzippedPath) + + @assert isfile(zipAbsPath) ["unzip(...): Can't deploy ZIP-Archive at `$(zipAbsPath)`."] + + numFiles = 0 + + # only unzip if not already done + if !isdir(unzippedAbsPath) + mkpath(unzippedAbsPath) + + zarchive = ZipFile.Reader(zipAbsPath) + for f in zarchive.files + fileAbsPath = normpath(joinpath(unzippedAbsPath, f.name)) + + if endswith(f.name,"/") || endswith(f.name,"\\") + mkpath(fileAbsPath) # mkdir(fileAbsPath) + + @assert isdir(fileAbsPath) ["unzip(...): Can't create directory `$(f.name)` at `$(fileAbsPath)`."] + else + # create directory if not forced by zip file folder + mkpath(dirname(fileAbsPath)) + + numBytes = write(fileAbsPath, read(f)) + + if numBytes == 0 + @debug "unzip(...): Written file `$(f.name)`, but file is empty." + end + + @assert isfile(fileAbsPath) ["unzip(...): Can't unzip file `$(f.name)` at `$(fileAbsPath)`."] + numFiles += 1 + end + end + close(zarchive) + end + + @assert isdir(unzippedAbsPath) ["unzip(...): ZIP-Archive couldn't be unzipped at `$(unzippedPath)`."] + @debug "funzip(...): Successfully unzipped $numFiles files at `$unzippedAbsPath`." + + (unzippedAbsPath, zipAbsPath) +end \ No newline at end of file diff --git a/test/FMI2/externalLogging.jl b/test/FMI2/externalLogging.jl index 1888d88..6d2e66a 100644 --- a/test/FMI2/externalLogging.jl +++ b/test/FMI2/externalLogging.jl @@ -5,7 +5,7 @@ import FMIImport: fmi2StatusError, fmi2StatusOK -myFMU = fmi2Load("SpringPendulum1D", ENV["EXPORTINGTOOL"], ENV["EXPORTINGVERSION"]) +myFMU = loadFMU("SpringPendulum1D", ENV["EXPORTINGTOOL"], ENV["EXPORTINGVERSION"]) myFMU.executionConfig.assertOnError = false ### CASE A: Print log ### @@ -22,7 +22,8 @@ open(joinpath(pwd(), "stdout"), "w") do out end end end -# ToDo: this test is wrong / not working (capture doesn't work for color output) + +# [ToDo]: the following test is wrong / not working (capture doesn't work for color output) #output = read(joinpath(pwd(), "stdout"), String) #@test output == "" #output = read(joinpath(pwd(), "stderr"), String) @@ -84,4 +85,4 @@ end # cleanup myFMU.executionConfig.assertOnError = true -fmi2Unload(myFMU) +unloadFMU(myFMU) diff --git a/test/FMI2/getter_setter.jl b/test/FMI2/getter_setter.jl index 355f964..2b9e3bc 100644 --- a/test/FMI2/getter_setter.jl +++ b/test/FMI2/getter_setter.jl @@ -7,8 +7,7 @@ # Prepare FMU # ############### -myFMU = fmi2Load("IO", ENV["EXPORTINGTOOL"], ENV["EXPORTINGVERSION"]) -myFMU.executionConfig.assertOnWarning = true +myFMU = loadFMU("IO", ENV["EXPORTINGTOOL"], ENV["EXPORTINGVERSION"]) comp = fmi2Instantiate!(myFMU; loggingOn=false) @test comp != 0 @@ -54,10 +53,10 @@ cacheString = "" @test fmi2SetString(comp, stringValueReferences[1], rndString) == 0 @test fmi2GetString(comp, stringValueReferences[1]) == rndString -fmi2Set(comp, +setValue(comp, [realValueReferences[1], integerValueReferences[1], booleanValueReferences[1], stringValueReferences[1]], [rndReal, rndInteger, rndBoolean, rndString]) -@test fmi2Get(comp, +@test getValue(comp, [realValueReferences[1], integerValueReferences[1], booleanValueReferences[1], stringValueReferences[1]]) == [rndReal, rndInteger, rndBoolean, rndString] @@ -124,4 +123,4 @@ dirs = fmi2GetRealOutputDerivatives(comp, ["y_real"], ones(fmi2Integer, 1)) # Clean up # ############ -fmi2Unload(myFMU) +unloadFMU(myFMU) diff --git a/test/FMI2/logging.jl b/test/FMI2/logging.jl index 1b81375..490c7b7 100644 --- a/test/FMI2/logging.jl +++ b/test/FMI2/logging.jl @@ -5,8 +5,9 @@ import FMIImport: fmi2StatusError, fmi2StatusOK -myFMU = fmi2Load("SpringPendulum1D", ENV["EXPORTINGTOOL"], ENV["EXPORTINGVERSION"]) +myFMU = loadFMU("SpringPendulum1D", ENV["EXPORTINGTOOL"], ENV["EXPORTINGVERSION"]) myFMU.executionConfig.assertOnError = false +myFMU.executionConfig.assertOnWarning = false ### CASE A: Print log ### comp = fmi2Instantiate!(myFMU; loggingOn=true) @@ -74,7 +75,8 @@ open(joinpath(pwd(), "stdout"), "w") do out end end end -# ToDo: this test is wrong / not working (capture doesn't work for color output) + +# [ToDo]: the following test is wrong / not working (capture doesn't work for color output) #output = read(joinpath(pwd(), "stdout"), String) #@test output == "" #output = read(joinpath(pwd(), "stderr"), String) @@ -82,4 +84,6 @@ end # cleanup myFMU.executionConfig.assertOnError = true -fmi2Unload(myFMU) +myFMU.executionConfig.assertOnWarning = true + +unloadFMU(myFMU) diff --git a/test/FMI2/model_description.jl b/test/FMI2/model_description.jl index 582e538..1fc80ca 100644 --- a/test/FMI2/model_description.jl +++ b/test/FMI2/model_description.jl @@ -5,79 +5,78 @@ using FMIImport.FMICore: fmi2VariableNamingConventionStructured, fmi2Unit -myFMU = fmi2Load("SpringFrictionPendulum1D", ENV["EXPORTINGTOOL"], ENV["EXPORTINGVERSION"]) -myFMU.executionConfig.assertOnWarning = true +myFMU = loadFMU("SpringFrictionPendulum1D", ENV["EXPORTINGTOOL"], ENV["EXPORTINGVERSION"]) @test fmi2GetVersion(myFMU) == "2.0" @test fmi2GetTypesPlatform(myFMU) == "default" -@test fmi2GetModelName(myFMU) == "SpringFrictionPendulum1D" -@test fmi2GetVariableNamingConvention(myFMU) == fmi2VariableNamingConventionStructured -@test fmi2IsCoSimulation(myFMU) == true -@test fmi2IsModelExchange(myFMU) == true - -@test fmi2GetGUID(myFMU) == "{2e178ad3-5e9b-48ec-a7b2-baa5669efc0c}" -@test fmi2GetGenerationTool(myFMU) == "Dymola Version 2022x (64-bit), 2021-10-08" -@test fmi2GetGenerationDateAndTime(myFMU) == "2022-05-19T06:54:12Z" -@test fmi2GetNumberOfEventIndicators(myFMU) == 24 -@test fmi2CanGetSetState(myFMU) == true -@test fmi2CanSerializeFMUstate(myFMU) == true -@test fmi2ProvidesDirectionalDerivative(myFMU) == true -@test fmi2DependenciesSupported(myFMU.modelDescription) == true -@test fmi2DerivativeDependenciesSupported(myFMU.modelDescription) == true - -@test fmi2GetDefaultStartTime(myFMU.modelDescription) ≈ 0.0 -@test fmi2GetDefaultStopTime(myFMU.modelDescription) ≈ 1.0 -@test fmi2GetDefaultTolerance(myFMU.modelDescription) ≈ 1e-4 -@test fmi2GetDefaultStepSize(myFMU.modelDescription) === nothing +@test getModelName(myFMU) == "SpringFrictionPendulum1D" +@test getVariableNamingConvention(myFMU) == fmi2VariableNamingConventionStructured +@test isCoSimulation(myFMU) == true +@test isModelExchange(myFMU) == true + +@test getGUID(myFMU) == "{2e178ad3-5e9b-48ec-a7b2-baa5669efc0c}" +@test getGenerationTool(myFMU) == "Dymola Version 2022x (64-bit), 2021-10-08" +@test getGenerationDateAndTime(myFMU) == "2022-05-19T06:54:12Z" +@test getNumberOfEventIndicators(myFMU) == 24 +@test canGetSetFMUState(myFMU) == true +@test canSerializeFMUState(myFMU) == true +@test providesDirectionalDerivatives(myFMU) == true +# @test dependenciesSupported(myFMU.modelDescription) == true +# @test derivativeDependenciesSupported(myFMU.modelDescription) == true + +@test getDefaultStartTime(myFMU.modelDescription) ≈ 0.0 +@test getDefaultStopTime(myFMU.modelDescription) ≈ 1.0 +@test getDefaultTolerance(myFMU.modelDescription) ≈ 1e-4 +@test getDefaultStepSize(myFMU.modelDescription) === nothing # comfort getters (dictionaries) -@test length(fmi2GetValueReferencesAndNames(myFMU.modelDescription)) == 42 -@test length(fmi2GetValueReferencesAndNames(myFMU)) == 42 +@test length(getValueReferencesAndNames(myFMU.modelDescription)) == 42 +@test length(getValueReferencesAndNames(myFMU)) == 42 -@test length(fmi2GetInputNames(myFMU.modelDescription)) == 0 -@test length(fmi2GetInputNames(myFMU)) == 0 +@test length(getInputNames(myFMU.modelDescription)) == 0 +@test length(getInputNames(myFMU)) == 0 -@test length(fmi2GetOutputNames(myFMU.modelDescription)) == 0 -@test length(fmi2GetOutputNames(myFMU)) == 0 +@test length(getOutputNames(myFMU.modelDescription)) == 0 +@test length(getOutputNames(myFMU)) == 0 -@test length(fmi2GetParameterNames(myFMU.modelDescription)) == 12 -@test fmi2GetParameterNames(myFMU) == ["fricScale", "s0", "v0", "fixed.s0", "spring.c", "spring.s_rel0", "mass.smax", "mass.smin", "mass.v_small", "mass.L", "mass.m", "mass.fexp"] +@test length(getParameterNames(myFMU.modelDescription)) == 12 +@test getParameterNames(myFMU) == ["fricScale", "s0", "v0", "fixed.s0", "spring.c", "spring.s_rel0", "mass.smax", "mass.smin", "mass.v_small", "mass.L", "mass.m", "mass.fexp"] -@test length(fmi2GetStateNames(myFMU.modelDescription)) == 2 -@test length(fmi2GetStateNames(myFMU)) == 2 -@test fmi2GetStateNames(myFMU; mode=:first) == ["mass.s", "mass.v"] -@test fmi2GetStateNames(myFMU; mode=:flat) == ["mass.s", "mass.v", "mass.v_relfric"] -@test fmi2GetStateNames(myFMU; mode=:group) == [["mass.s"], ["mass.v", "mass.v_relfric"]] +@test length(getStateNames(myFMU.modelDescription)) == 2 +@test length(getStateNames(myFMU)) == 2 +@test getStateNames(myFMU; mode=:first) == ["mass.s", "mass.v"] +@test getStateNames(myFMU; mode=:flat) == ["mass.s", "mass.v", "mass.v_relfric"] +@test getStateNames(myFMU; mode=:group) == [["mass.s"], ["mass.v", "mass.v_relfric"]] -@test length(fmi2GetDerivativeNames(myFMU.modelDescription)) == 2 -@test length(fmi2GetDerivativeNames(myFMU)) == 2 -@test fmi2GetDerivativeNames(myFMU; mode=:first) == ["der(mass.s)", "mass.a_relfric"] -@test fmi2GetDerivativeNames(myFMU; mode=:flat) == ["der(mass.s)", "mass.a_relfric", "mass.a", "der(mass.v)"] -@test fmi2GetDerivativeNames(myFMU; mode=:group) == [["der(mass.s)"], ["mass.a_relfric", "mass.a", "der(mass.v)"]] +@test length(getDerivativeNames(myFMU.modelDescription)) == 2 +@test length(getDerivativeNames(myFMU)) == 2 +@test getDerivativeNames(myFMU; mode=:first) == ["der(mass.s)", "mass.a_relfric"] +@test getDerivativeNames(myFMU; mode=:flat) == ["der(mass.s)", "mass.a_relfric", "mass.a", "der(mass.v)"] +@test getDerivativeNames(myFMU; mode=:group) == [["der(mass.s)"], ["mass.a_relfric", "mass.a", "der(mass.v)"]] -@test length(fmi2GetNamesAndDescriptions(myFMU.modelDescription)) == 50 -@test length(fmi2GetNamesAndDescriptions(myFMU)) == 50 +@test length(getNamesAndDescriptions(myFMU.modelDescription)) == 50 +@test length(getNamesAndDescriptions(myFMU)) == 50 -@test length(fmi2GetNamesAndUnits(myFMU.modelDescription)) == 50 -dict = fmi2GetNamesAndUnits(myFMU) +@test length(getNamesAndUnits(myFMU.modelDescription)) == 50 +dict = getNamesAndUnits(myFMU) @test length(dict) == 50 @test dict["der(mass.s)"] == "m/s" @test dict["mass.F_prop"] == "N.s/m" @test dict["mass.fexp"] == "s/m" @test dict["der(mass.v)"] == "m/s2" -@test length(fmi2GetNamesAndInitials(myFMU.modelDescription)) == 50 -dict = fmi2GetNamesAndInitials(myFMU) +@test length(getNamesAndInitials(myFMU.modelDescription)) == 50 +dict = getNamesAndInitials(myFMU) @test length(dict) == 50 @test dict["mass.startForward"] == 0 @test dict["mass.startBackward"] == 0 @test dict["mass.locked"] == 1 # ToDo: Improve test, use another FMU -@test length(fmi2GetInputNamesAndStarts(myFMU.modelDescription)) == 0 -dict = fmi2GetInputNamesAndStarts(myFMU) +@test length(getInputNamesAndStarts(myFMU.modelDescription)) == 0 +dict = getInputNamesAndStarts(myFMU) @test length(dict) == 0 @test length(myFMU.modelDescription.unitDefinitions) == 10 @@ -89,35 +88,25 @@ stype_attr = myFMU.modelDescription.typeDefinitions[1].Real @test stype_attr != nothing @test stype_attr.quantity == "Acceleration" @test stype_attr.unit == "m/s2" -stype_unit = FMIImport.fmi2GetUnit(myFMU.modelDescription, myFMU.modelDescription.typeDefinitions[1]); +stype_unit = getUnit(myFMU.modelDescription, myFMU.modelDescription.typeDefinitions[1]); @test stype_unit isa fmi2Unit @test stype_unit.name == "m/s2" @test stype_unit.baseUnit.m == 1 @test stype_unit.baseUnit.s == -2 for sv in myFMU.modelDescription.modelVariables - declared_type = FMIImport.fmi2GetDeclaredType(myFMU.modelDescription, sv) + declared_type = getDeclaredType(myFMU.modelDescription, sv) if !isnothing(declared_type) @test isdefined(sv.attribute, :declaredType) @test sv.attribute.declaredType == declared_type.name - # ToDo: I don't understand the following test, please add comments - # test, if fallback setting of attributes has worked: - # declared_type_attr_struct = FMIImport.fmi2GetSimpleTypeAttributeStruct( declared_type ) - # for attr_name in fieldnames(typeof(declared_type_attr_struct)) - # attr_val = getfield( declared_type_attr_struct, attr_name ) - # if !isnothing(attr_val) - # @test !isnothing(getfield(sv.attribute, attr_name)) - # end - # end - # is the correct `fmi2Unit` found? if !isnothing(sv.attribute.unit) - sv_unit = FMIImport.fmi2GetUnit(myFMU.modelDescription, sv) + sv_unit = getUnit(myFMU.modelDescription, sv) @test sv_unit isa fmi2Unit @test sv_unit.name == sv.attribute.unit end end end -fmi2Unload(myFMU) \ No newline at end of file +unloadFMU(myFMU) \ No newline at end of file diff --git a/test/FMI2/state.jl b/test/FMI2/state.jl index d6563ac..bf84b92 100644 --- a/test/FMI2/state.jl +++ b/test/FMI2/state.jl @@ -9,8 +9,7 @@ using FMIImport.FMICore: fmi2FMUstate -myFMU = fmi2Load("SpringPendulum1D", ENV["EXPORTINGTOOL"], ENV["EXPORTINGVERSION"]) -myFMU.executionConfig.assertOnWarning = true +myFMU = loadFMU("SpringPendulum1D", ENV["EXPORTINGTOOL"], ENV["EXPORTINGVERSION"]) comp = fmi2Instantiate!(myFMU; loggingOn=true) @test comp != 0 @@ -24,7 +23,7 @@ comp = fmi2Instantiate!(myFMU; loggingOn=true) # Testing state functions # ########################### -if fmi2CanGetSetState(myFMU) && fmi2CanSerializeFMUstate(myFMU) +if canGetSetFMUState(myFMU) && canSerializeFMUState(myFMU) @test fmi2GetReal(comp, "mass.s") == 0.5 FMUstate = fmi2GetFMUstate(comp) @test typeof(FMUstate) == fmi2FMUstate @@ -44,8 +43,8 @@ if fmi2CanGetSetState(myFMU) && fmi2CanSerializeFMUstate(myFMU) @test fmi2GetReal(comp, "mass.s") == 0.5 fmi2SetFMUstate(comp, FMUstate) @test fmi2GetReal(comp, "mass.s") == 10.0 - fmi2FreeFMUstate!(comp, FMUstate) - fmi2FreeFMUstate!(comp, FMUstate2) + fmi2FreeFMUstate(comp, FMUstate) + fmi2FreeFMUstate(comp, FMUstate2) else @info "The FMU provided from the tool `$(ENV["EXPORTINGTOOL"])` does not support state get, set, serialization and deserialization. Skipping related tests." end @@ -55,4 +54,4 @@ end ############ @test fmi2Terminate(comp) == 0 -fmi2Unload(myFMU) +unloadFMU(myFMU) diff --git a/test/FMI3/dir_ders.jl b/test/FMI3/dir_ders.jl deleted file mode 100644 index 1c4f3cb..0000000 --- a/test/FMI3/dir_ders.jl +++ /dev/null @@ -1,49 +0,0 @@ -# -# Copyright (c) 2021 Tobias Thummerer, Lars Mikelsons, Josef Kircher -# Licensed under the MIT license. See LICENSE file in the project root for details. -# - -myFMU = fmi3Load("BouncingBall", "ModelicaReferenceFMUs", "0.0.20") - -inst = fmi3InstantiateModelExchange!(myFMU; loggingOn=true) -@test inst != 0 - -@test fmi3EnterInitializationMode(inst) == 0 -@test fmi3ExitInitializationMode(inst) == 0 - -numStates = length(myFMU.modelDescription.stateValueReferences) -targetValues = [[0.0, 0.0], [1.0, 0.0]] -dir_ders_buffer = zeros(fmi3Float64, numStates) -sample_ders_buffer = zeros(fmi3Float64, numStates, 1) -for i in 1:numStates - - if fmi3ProvidesDirectionalDerivatives(myFMU) - # multi derivatives calls - sample_ders = fmi3SampleDirectionalDerivative(inst, myFMU.modelDescription.derivativeValueReferences, [myFMU.modelDescription.stateValueReferences[i]]) - fmi3SampleDirectionalDerivative!(inst, myFMU.modelDescription.derivativeValueReferences, [myFMU.modelDescription.stateValueReferences[i]], sample_ders_buffer) - - @test sum(abs.(sample_ders[:,1] - targetValues[i])) < 1e-3 - @test sum(abs.(sample_ders_buffer[:,1] - targetValues[i])) < 1e-3 - - dir_ders = fmi3GetDirectionalDerivative(inst, myFMU.modelDescription.derivativeValueReferences, [myFMU.modelDescription.stateValueReferences[i]]) - fmi3GetDirectionalDerivative!(inst, myFMU.modelDescription.derivativeValueReferences, [myFMU.modelDescription.stateValueReferences[i]], dir_ders_buffer) - - @test sum(abs.(dir_ders - targetValues[i])) < 1e-3 - @test sum(abs.(dir_ders_buffer - targetValues[i])) < 1e-3 - - # single derivative call - dir_der = fmi3GetDirectionalDerivative(inst, myFMU.modelDescription.derivativeValueReferences[1], myFMU.modelDescription.stateValueReferences[1]) - @test dir_der == targetValues[1][1] - else - @warn "Skipping directional derivative testing, this FMU from $(ENV["EXPORTINGTOOL"]) doesn't support directional derivatives." - end -end - -# Bug in the FMU -jac = fmi3GetJacobian(inst, myFMU.modelDescription.derivativeValueReferences, myFMU.modelDescription.stateValueReferences) -@test jac ≈ hcat(targetValues...) - -jac = fmi3SampleDirectionalDerivative(inst, myFMU.modelDescription.derivativeValueReferences, myFMU.modelDescription.stateValueReferences) -@test jac ≈ hcat(targetValues...) - -fmi3Unload(myFMU) diff --git a/test/FMI3/getter_setter.jl b/test/FMI3/getter_setter.jl index 79b4bb3..28a2fd6 100644 --- a/test/FMI3/getter_setter.jl +++ b/test/FMI3/getter_setter.jl @@ -7,7 +7,7 @@ # Prepare FMU # ############### -myFMU = fmi3Load("Feedthrough", "ModelicaReferenceFMUs", "0.0.20") +myFMU = loadFMU("Feedthrough", "ModelicaReferenceFMUs", "0.0.20", "3.0") inst = fmi3InstantiateCoSimulation!(myFMU; loggingOn=false) @test inst != 0 @@ -98,10 +98,10 @@ binary = fmi3GetBinary(inst, binaryValueReferences[1]) @test unsafe_string(binary) == rndString # TODO test after latest PR -fmi3Set(inst, +setValue(inst, [float64ValueReferences[1], int32ValueReferences[1], booleanValueReferences[1], stringValueReferences[1], binaryValueReferences[1]], [rndReal, Int32(rndInteger), rndBoolean, rndString, rndString]) -# @test fmi3Get(inst, +# @test getValue(inst, # [float64ValueReferences[1], integerValueReferences[1], booleanValueReferences[1], stringValueReferences[1], binaryValueReferences[1]]) == # [rndReal, Int32(rndInteger), rndBoolean, rndString, unsafe_string(rndString)] @@ -255,4 +255,4 @@ fmi3GetFloat64!(inst, float64ValueReferences, cacheFloat64) # Clean up # ############ -fmi3Unload(myFMU) +unloadFMU(myFMU) diff --git a/test/FMI3/logging.jl b/test/FMI3/logging.jl index 4b3213a..28806af 100644 --- a/test/FMI3/logging.jl +++ b/test/FMI3/logging.jl @@ -3,9 +3,7 @@ # Licensed under the MIT license. See LICENSE file in the project root for details. # -import FMIImport: fmi3StatusError - -myFMU = fmi3Load("BouncingBall", "ModelicaReferenceFMUs", "0.0.20") +myFMU = loadFMU("BouncingBall", "ModelicaReferenceFMUs", "0.0.20", "3.0") myFMU.executionConfig.assertOnError = false ### CASE A: Print log ### @@ -75,4 +73,4 @@ end # cleanup myFMU.executionConfig.assertOnError = true -fmi3Unload(myFMU) +unloadFMU(myFMU) diff --git a/test/FMI3/model_description.jl b/test/FMI3/model_description.jl index a23aa61..e94b3a7 100644 --- a/test/FMI3/model_description.jl +++ b/test/FMI3/model_description.jl @@ -5,27 +5,29 @@ import FMIImport.FMICore: fmi3VariableNamingConventionFlat -myFMU = fmi3Load("BouncingBall", "ModelicaReferenceFMUs", "0.0.20") +myFMU = loadFMU("BouncingBall", "ModelicaReferenceFMUs", "0.0.20", "3.0") @test fmi3GetVersion(myFMU) == "3.0" -@test fmi3GetModelName(myFMU) == "BouncingBall" -@test fmi3GetVariableNamingConvention(myFMU) == fmi3VariableNamingConventionFlat -@test fmi3IsCoSimulation(myFMU) == true -@test fmi3IsModelExchange(myFMU) == true -# TODO scheduledExecution -@test fmi3GetInstantiationToken(myFMU) == "{1AE5E10D-9521-4DE3-80B9-D0EAAA7D5AF1}" # TODO update -@test fmi3GetGenerationTool(myFMU) == "Reference FMUs (v0.0.20)" -@test fmi3GetGenerationDateAndTime(myFMU) == "[Unknown generation date and time]" -@test fmi3GetNumberOfEventIndicators(myFMU) == 1 -@test fmi3CanGetSetState(myFMU) == true -@test fmi3CanSerializeFMUState(myFMU) == true -@test fmi3ProvidesDirectionalDerivatives(myFMU) == false -@test fmi3ProvidesAdjointDerivatives(myFMU) == false - -@test fmi3GetDefaultStartTime(myFMU.modelDescription) ≈ 0.0 -@test fmi3GetDefaultStopTime(myFMU.modelDescription) ≈ 3.0 -#@test fmi3GetDefaultTolerance(myFMU.modelDescription) ≈ 1e-4 -@test fmi3GetDefaultStepSize(myFMU.modelDescription) === 0.01 - -fmi3Unload(myFMU) +@test getModelName(myFMU) == "BouncingBall" +@test getVariableNamingConvention(myFMU) == fmi3VariableNamingConventionFlat +@test isCoSimulation(myFMU) +@test isModelExchange(myFMU) + +# [TODO] scheduledExecution + +@test getInstantiationToken(myFMU) == "{1AE5E10D-9521-4DE3-80B9-D0EAAA7D5AF1}" # [TODO] update +@test getGenerationTool(myFMU) == "Reference FMUs (v0.0.20)" +@test getGenerationDateAndTime(myFMU) == "[Unknown generation date and time]" +@test getNumberOfEventIndicators(myFMU) == 1 +@test canGetSetFMUState(myFMU) +@test canSerializeFMUState(myFMU) +@test !providesDirectionalDerivatives(myFMU) +@test !providesAdjointDerivatives(myFMU) + +@test getDefaultStartTime(myFMU.modelDescription) ≈ 0.0 +@test getDefaultStopTime(myFMU.modelDescription) ≈ 3.0 +@test getDefaultTolerance(myFMU.modelDescription) === nothing +@test getDefaultStepSize(myFMU.modelDescription) === 0.01 + +unloadFMU(myFMU) diff --git a/test/FMI3/state.jl b/test/FMI3/state.jl index 95b47be..7ad774c 100644 --- a/test/FMI3/state.jl +++ b/test/FMI3/state.jl @@ -9,7 +9,7 @@ import FMIImport.FMICore: fmi3FMUState -myFMU = fmi3Load("BouncingBall", "ModelicaReferenceFMUs", "0.0.20") +myFMU = loadFMU("BouncingBall", "ModelicaReferenceFMUs", "0.0.20", "3.0") inst = fmi3InstantiateCoSimulation!(myFMU; loggingOn=true) @test inst != 0 @@ -20,7 +20,7 @@ inst = fmi3InstantiateCoSimulation!(myFMU; loggingOn=true) # Testing state functions # ########################### -if fmi3CanGetSetState(myFMU) && fmi3CanSerializeFMUState(myFMU) +if canGetSetFMUState(myFMU) && canSerializeFMUState(myFMU) @test fmi3GetFloat64(inst, "h") == 1.0 FMUState = fmi3GetFMUState(inst) @test typeof(FMUState) == fmi3FMUState @@ -40,8 +40,8 @@ if fmi3CanGetSetState(myFMU) && fmi3CanSerializeFMUState(myFMU) @test fmi3GetFloat64(inst, "h") == 1.0 fmi3SetFMUState(inst, FMUState) @test fmi3GetFloat64(inst, "h") == 10.0 - fmi3FreeFMUState!(inst, FMUState) - fmi3FreeFMUState!(inst, FMUState2) + fmi3FreeFMUState(inst, FMUState) + fmi3FreeFMUState(inst, FMUState2) else @info "The FMU provided from the tool `$(ENV["EXPORTINGTOOL"])` does not support state get, set, serialization and deserialization. Skipping related tests." end @@ -51,4 +51,4 @@ end ############ @test fmi3Terminate(inst) == 0 -fmi3Unload(myFMU) +unloadFMU(myFMU) diff --git a/test/FMI2/eval.jl b/test/eval.jl similarity index 78% rename from test/FMI2/eval.jl rename to test/eval.jl index 3a9628d..f8d1081 100644 --- a/test/FMI2/eval.jl +++ b/test/eval.jl @@ -1,15 +1,15 @@ -using PkgEval -using FMIImport - -config = Configuration(; julia="1.8"); - -package = Package(; name="FMIImport"); - -@info "PkgEval" -result = evaluate([config], [package]) - -@info "Result" -println(result) - -@info "Log" +using PkgEval +using FMIImport + +config = Configuration(; julia="1.10"); + +package = Package(; name="FMIImport"); + +@info "PkgEval" +result = evaluate([config], [package]) + +@info "Result" +println(result) + +@info "Log" println(result.log) \ No newline at end of file diff --git a/test/runtests.jl b/test/runtests.jl index 4ac2bff..ec848aa 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -12,8 +12,6 @@ using FMIImport.FMICore: fmi2Integer, fmi2Boolean, fmi2Real, fmi2String using FMIImport.FMICore: fmi3Float32, fmi3Float64, fmi3Int8, fmi3UInt8, fmi3Int16, fmi3UInt16, fmi3Int32, fmi3UInt32, fmi3Int64, fmi3UInt64 using FMIImport.FMICore: fmi3Boolean, fmi3String, fmi3Binary -import FMIImport.FMICore: FMU2_EXECUTION_CONFIGURATIONS, FMU3_EXECUTION_CONFIGURATIONS - exportingToolsWindows = [("Dymola", "2022x")] exportingToolsLinux = [("Dymola", "2022x")] @@ -22,7 +20,7 @@ function runtestsFMI2(exportingTool) ENV["EXPORTINGVERSION"] = exportingTool[2] # enable assertions for warnings/errors for all default execution configurations - for exec in FMU2_EXECUTION_CONFIGURATIONS + for exec in FMU_EXECUTION_CONFIGURATIONS exec.assertOnError = true exec.assertOnWarning = true end @@ -55,7 +53,7 @@ function runtestsFMI3(exportingTool) ENV["EXPORTINGVERSION"] = exportingTool[2] # enable assertions for warnings/errors for all default execution configurations - for exec in FMU3_EXECUTION_CONFIGURATIONS + for exec in FMU_EXECUTION_CONFIGURATIONS exec.assertOnError = true exec.assertOnWarning = true end @@ -68,10 +66,6 @@ function runtestsFMI3(exportingTool) @testset "State Manipulation" begin include("FMI3/state.jl") end - @testset "Directional derivatives" begin - @warn "Skipping FMI3 directional derivative testing..." - #include("FMI3/dir_ders.jl") - end end @testset "Model Description Parsing" begin