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/.github/workflows/Formatter.yml b/.github/workflows/Formatter.yml new file mode 100644 index 0000000..0155d9b --- /dev/null +++ b/.github/workflows/Formatter.yml @@ -0,0 +1,14 @@ +name: Format suggestions +on: + pull_request: + # this argument is not required if you don't use the `suggestion-label` input + types: [ opened, reopened, synchronize, labeled, unlabeled ] +jobs: + code-style: + runs-on: ubuntu-latest + steps: + - uses: julia-actions/julia-format@v3 + with: + version: '1' # Set `version` to '1.0.54' if you need to use JuliaFormatter.jl v1.0.54 (default: '1') + suggestion-label: 'format-suggest' # leave this unset or empty to show suggestions for all PRs + diff --git a/.github/workflows/callbackFunctions.yml b/.github/workflows/callbackFunctions.yml index 0ba6379..fe4ca1d 100644 --- a/.github/workflows/callbackFunctions.yml +++ b/.github/workflows/callbackFunctions.yml @@ -1,6 +1,7 @@ name: CallbackFunctions on: + workflow_dispatch: pull_request: push: branches: @@ -15,6 +16,10 @@ jobs: fail-fast: true matrix: os: [ubuntu-latest, windows-latest, macos-latest] + arch: [''] + include: + - os: windows-latest + arch: -A Win32 env: BUILD_TYPE: RelWithDebInfo CMAKE_SOURCE_DIR: ${{github.workspace}}/src/FMI2/callbackFunctions @@ -22,10 +27,10 @@ jobs: steps: - name: Check out repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Configure CMake - run: cmake -S ${{env.CMAKE_SOURCE_DIR}}/callbackFunctions -B ${{env.CMAKE_SOURCE_DIR}}/callbackFunctions/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + run: cmake -S ${{env.CMAKE_SOURCE_DIR}}/callbackFunctions -B ${{env.CMAKE_SOURCE_DIR}}/callbackFunctions/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} ${{ matrix.arch }} - name: Build C++ Sources run: cmake --build ${{env.CMAKE_SOURCE_DIR}}/callbackFunctions/build --config ${{env.BUILD_TYPE}} --target install @@ -39,22 +44,29 @@ jobs: run: ${{env.CMAKE_SOURCE_DIR}}/callbackFunctions/build/test - name: Archive production artifacts (win) - if: success() && matrix.os == 'windows-latest' - uses: actions/upload-artifact@v3 + if: success() && matrix.os == 'windows-latest' && matrix.arch == '' + uses: actions/upload-artifact@v4 with: name: callbackFunctions-win-binaries path: src/FMI2/callbackFunctions/binaries/win64/ + - name: Archive production artifacts (win32) + if: success() && matrix.os == 'windows-latest' && matrix.arch != '' + uses: actions/upload-artifact@v4 + with: + name: callbackFunctions-win32-binaries + path: src/FMI2/callbackFunctions/binaries/win32/ + - name: Archive production artifacts (linux) if: success() && matrix.os == 'ubuntu-latest' - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: callbackFunctions-linux-binaries path: src/FMI2/callbackFunctions/binaries/linux64/ - name: Archive production artifacts (darwin) if: success() && matrix.os == 'macos-latest' - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: callbackFunctions-mac-binaries path: src/FMI2/callbackFunctions/binaries/darwin64/ @@ -64,29 +76,37 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out repository - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Download windows binaries - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: callbackFunctions-win-binaries path: src/FMI2/callbackFunctions/binaries/win64/ + - name: Download windows x86 binaries + uses: actions/download-artifact@v4 + with: + name: callbackFunctions-win32-binaries + path: src/FMI2/callbackFunctions/binaries/win32/ + - name: Download linux binaries - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: callbackFunctions-linux-binaries path: src/FMI2/callbackFunctions/binaries/linux64/ - name: Download darwin binaries - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: callbackFunctions-mac-binaries path: src/FMI2/callbackFunctions/binaries/darwin64/ - name: "Auto commit" - if: ${{ github.event_name == 'push' }} - uses: stefanzweifel/git-auto-commit-action@v4 + if: ${{ github.event_name == 'push' || github.event_name == 'workflow_dispatch'}} + uses: stefanzweifel/git-auto-commit-action@v5 with: commit_message: Update callbackFunctions push_options: '--force-with-lease' + + \ No newline at end of file diff --git a/Project.toml b/Project.toml index 843624d..6bb08fb 100644 --- a/Project.toml +++ b/Project.toml @@ -1,21 +1,23 @@ name = "FMIImport" uuid = "9fcbc62e-52a0-44e9-a616-1359a0008194" authors = ["TT ", "LM ", "JK "] -version = "0.16.2" +version = "1.0.4" [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" RelocatableFolders = "05181044-ff0b-4ac5-8273-598c1e38db00" -ZipFile = "a5390f91-8eb1-5f08-bee0-b1d1ffed6cea" + +[weakdeps] +FMIZoo = "724179cf-c260-40a9-bd27-cccc6fe2f195" + +[extensions] +FMIZooExt = ["FMIZoo"] [compat] Downloads = "1" -EzXML = "1.1.0" -FMICore = "0.19.0" +FMIBase = "1.0.0" Libdl = "1" RelocatableFolders = "1" -ZipFile = "0.10.0" julia = "1.6" diff --git a/README.md b/README.md index 85e45ff..edfb51b 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ [![Run PkgEval](https://github.com/ThummeTo/FMIImport.jl/actions/workflows/Eval.yml/badge.svg)](https://github.com/ThummeTo/FMIImport.jl/actions/workflows/Eval.yml) [![Coverage](https://codecov.io/gh/ThummeTo/FMIImport.jl/branch/main/graph/badge.svg)](https://codecov.io/gh/ThummeTo/FMIImport.jl) [![ColPrac: Contributor's Guide on Collaborative Practices for Community Packages](https://img.shields.io/badge/ColPrac-Contributor's%20Guide-blueviolet)](https://github.com/SciML/ColPrac) +[![SciML Code Style](https://img.shields.io/static/v1?label=code%20style&message=SciML&color=9558b2&labelColor=389826)](https://github.com/SciML/SciMLStyle) ## How can I use FMIImport.jl? [*FMIImport.jl*](https://github.com/ThummeTo/FMIImport.jl) is part of [*FMI.jl*](https://github.com/ThummeTo/FMI.jl). However, if you only need the import functionality without anything around and want to keep the dependencies as small as possible, [*FMIImport.jl*](https://github.com/ThummeTo/FMIImport.jl) might be the right way to go. You can install it via: @@ -35,10 +36,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..9f381ce --- /dev/null +++ b/ext/FMIZooExt.jl @@ -0,0 +1,21 @@ +# +# 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 86485a7..e0e1904 100644 --- a/src/FMI2/c.jl +++ b/src/FMI2/c.jl @@ -7,32 +7,20 @@ # - 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! - -using FMICore: invalidate!, check_invalidate! +# 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 Function that is called in the FMU, usually if an fmi2XXX function, does not behave as desired. If “logger” is called with “status = fmi2OK”, then the message is a pure information message. “instanceName” is the instance name of the model that calls this function. “category” is the category of the message. The meaning of “category” is defined by the modeling environment that generated the FMU. Depending on this modeling environment, none, some or all allowed values of “category” for this FMU are defined in the modelDescription.xml file via element “”, see section 2.2.4. Only messages are provided by function logger that have a category according to a call to fmi2SetDebugLogging (see below). Argument “message” is provided in the same way and with the same format control as in function “printf” from the C standard library. [Typically, this function prints the message and stores it optionally in a log file.] """ -function fmi2CallbackLogger(_componentEnvironment::Ptr{FMU2ComponentEnvironment}, - _instanceName::Ptr{Cchar}, - _status::Cuint, - _category::Ptr{Cchar}, - _message::Ptr{Cchar}) +function fmi2CallbackLogger( + _componentEnvironment::Ptr{FMU2ComponentEnvironment}, + _instanceName::Ptr{Cchar}, + _status::Cuint, + _category::Ptr{Cchar}, + _message::Ptr{Cchar}, +) message = unsafe_string(_message) category = unsafe_string(_category) @@ -46,21 +34,14 @@ function fmi2CallbackLogger(_componentEnvironment::Ptr{FMU2ComponentEnvironment} (status == fmi2StatusPending && componentEnvironment.logStatusPending) @warn "[$status][$category][$instanceName]: $message" elseif (status == fmi2StatusDiscard && componentEnvironment.logStatusDiscard) || - (status == fmi2StatusError && componentEnvironment.logStatusError) || - (status == fmi2StatusFatal && componentEnvironment.logStatusFatal) + (status == fmi2StatusError && componentEnvironment.logStatusError) || + (status == fmi2StatusFatal && componentEnvironment.logStatusFatal) @error "[$status][$category][$instanceName]: $message" end 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 @@ -69,7 +50,7 @@ Function that is called in the FMU if memory needs to be allocated. If attribute function fmi2CallbackAllocateMemory(nobj::Csize_t, size::Csize_t) ptr = Libc.calloc(nobj, size) @debug "cbAllocateMemory($(nobj), $(size)): Allocated $(nobj) x $(size) bytes at $(ptr)." - ptr + ptr end """ @@ -79,7 +60,7 @@ Function that must be called in the FMU if memory is freed that has been allocat """ function fmi2CallbackFreeMemory(obj::Ptr{Cvoid}) @debug "cbFreeMemory($(obj)): Freeing object at $(obj)." - Libc.free(obj) + Libc.free(obj) nothing end @@ -95,54 +76,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) - - global lk_fmi2FreeInstance - - compAddr = c.compAddr - - @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 - - fmi2FreeInstance!(c.fmu.cFreeInstance, compAddr) - - nothing -end - +import FMIBase.FMICore: fmi2GetTypesPlatform """ fmi2GetTypesPlatform(fmu::FMU2) @@ -160,37 +94,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) @@ -208,33 +120,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) @@ -251,14 +143,15 @@ function checkStatus(c::FMU2Component, status::fmi2Status) end end +import FMIBase.FMICore: fmi2SetDebugLogging """ - fmi2SetDebugLogging(c::FMU2Component, logginOn::fmi2Boolean, nCategories::Unsigned, categories::Ptr{Nothing}) + fmi2SetDebugLogging(c::FMU2Component, loggingOn::fmi2Boolean, nCategories::Unsigned, categories::Ptr{Nothing}) Control the use of the logging callback function, version independent. # Arguments - `c::FMU2Component`: Argument `c` is a Mutable struct represents an instantiated instance of an FMU in the FMI 2.0.2 Standard. -- `logginOn::fmi2Boolean`: If `loggingOn = fmi2True`, debug logging is enabled for the log categories specified in categories, otherwise it is disabled. Type `fmi2Boolean` is defined as an alias Type for the C-Type Boolean and is to be used with `fmi2True` and `fmi2False`. +- `loggingOn::fmi2Boolean`: If `loggingOn = fmi2True`, debug logging is enabled for the log categories specified in categories, otherwise it is disabled. Type `fmi2Boolean` is defined as an alias Type for the C-Type Boolean and is to be used with `fmi2True` and `fmi2False`. - `nCategories::Unsigned`: Argument `nCategories` defines the length of the argument `categories`. - `categories::Ptr{Nothing}`: @@ -278,13 +171,25 @@ 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}) - - status = fmi2SetDebugLogging(c.fmu.cSetDebugLogging, c.compAddr, logginOn, nCategories, categories) +function FMICore.fmi2SetDebugLogging( + c::FMU2Component, + loggingOn::fmi2Boolean, + nCategories::Unsigned, + categories::Ptr{Nothing}, +) + + status = fmi2SetDebugLogging( + c.fmu.cSetDebugLogging, + c.addr, + loggingOn, + 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) @@ -318,12 +223,14 @@ More detailed: See also [`fmi2SetupExperiment`](@ref). """ -function fmi2SetupExperiment(c::FMU2Component, +function FMICore.fmi2SetupExperiment( + c::FMU2Component, toleranceDefined::fmi2Boolean, tolerance::fmi2Real, startTime::fmi2Real, stopTimeDefined::fmi2Boolean, - stopTime::fmi2Real) + stopTime::fmi2Real, +) if c.state != fmi2ComponentStateInstantiated @warn "fmi2SetupExperiment(...): Needs to be called in state `fmi2ComponentStateInstantiated`." @@ -338,8 +245,15 @@ function fmi2SetupExperiment(c::FMU2Component, end end - status = fmi2SetupExperiment(c.fmu.cSetupExperiment, - c.compAddr, toleranceDefined, tolerance, startTime, stopTimeDefined, stopTime) + status = fmi2SetupExperiment( + c.fmu.cSetupExperiment, + c.addr, + toleranceDefined, + tolerance, + startTime, + stopTimeDefined, + stopTime, + ) checkStatus(c, status) # remain in status on success, nothing to do here @@ -347,6 +261,7 @@ function fmi2SetupExperiment(c::FMU2Component, return status end +import FMIBase.FMICore: fmi2EnterInitializationMode """ fmi2EnterInitializationMode(c::FMU2Component) @@ -373,12 +288,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 @@ -386,6 +301,7 @@ function fmi2EnterInitializationMode(c::FMU2Component) return status end +import FMIBase.FMICore: fmi2ExitInitializationMode """ fmi2ExitInitializationMode(c::FMU2Component) @@ -410,13 +326,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 @@ -424,6 +340,7 @@ function fmi2ExitInitializationMode(c::FMU2Component) return status end +import FMIBase.FMICore: fmi2Terminate """ fmi2Terminate(c::FMU2Component; soft::Bool=false) @@ -452,8 +369,9 @@ 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) - if c.state != fmi2ComponentStateContinuousTimeMode && c.state != fmi2ComponentStateEventMode +function FMICore.fmi2Terminate(c::FMU2Component; soft::Bool = false) + if c.state != fmi2ComponentStateContinuousTimeMode && + c.state != fmi2ComponentStateEventMode if soft return fmi2StatusOK else @@ -461,7 +379,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 @@ -469,6 +387,7 @@ function fmi2Terminate(c::FMU2Component; soft::Bool=false) return status end +import FMIBase.FMICore: fmi2Reset """ fmi2Reset(c::FMU2Component; soft::Bool=false) @@ -497,7 +416,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 @@ -510,18 +429,27 @@ 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)) - - if compAddr == Ptr{Cvoid}(C_NULL) + 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 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 @@ -530,6 +458,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}) @@ -558,15 +487,19 @@ 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) + status = fmi2GetReal!(c.fmu.cGetReal, 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}) @@ -593,26 +526,27 @@ More detailed: See also [`fmi2GetReal`](@ref). """ -function fmi2SetReal(c::FMU2Component, - vr::AbstractArray{fmi2ValueReference}, - nvr::Csize_t, +function FMICore.fmi2SetReal( + c::FMU2Component, + vr::AbstractArray{fmi2ValueReference}, + nvr::Csize_t, value::AbstractArray{fmi2Real}; - track::Bool=true) + track::Bool = true, +) - status = fmi2SetReal(c.fmu.cSetReal, - c.compAddr, vr, nvr, value) + status = fmi2SetReal(c.fmu.cSetReal, c.addr, vr, nvr, value) checkStatus(c, status) - if track && status == fmi2StatusOK - check_invalidate!(vr, c.∂ẋ_∂x) + 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_∂x) check_invalidate!(vr, c.∂y_∂u) check_invalidate!(vr, c.∂y_∂p) - check_invalidate!(vr, c.∂e_∂x) + check_invalidate!(vr, c.∂e_∂x) check_invalidate!(vr, c.∂e_∂u) check_invalidate!(vr, c.∂e_∂p) @@ -625,6 +559,7 @@ function fmi2SetReal(c::FMU2Component, return status end +import FMIBase.FMICore: fmi2GetInteger! """ fmi2GetInteger!(c::FMU2Component, vr::AbstractArray{fmi2ValueReference}, nvr::Csize_t, value::AbstractArray{fmi2Integer}) @@ -657,15 +592,19 @@ 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) + status = fmi2GetInteger!(c.fmu.cGetInteger, 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}) @@ -695,15 +634,19 @@ 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) + status = fmi2SetInteger(c.fmu.cSetInteger, 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}) @@ -735,15 +678,19 @@ 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) + status = fmi2GetBoolean!(c.fmu.cGetBoolean, 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}) @@ -771,15 +718,19 @@ 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) + status = fmi2SetBoolean(c.fmu.cSetBoolean, 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}}}) @@ -811,15 +762,19 @@ 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) + status = fmi2GetString!(c.fmu.cGetString, 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}}}) @@ -849,15 +804,19 @@ 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) + status = fmi2SetString(c.fmu.cSetString, c.addr, vr, nvr, value) checkStatus(c, status) return status end - +import FMIBase.FMICore: fmi2GetFMUstate! """ fmi2GetFMUstate!(c::FMU2Component, FMUstate::Ref{fmi2FMUstate}) @@ -884,14 +843,14 @@ 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) + status = fmi2GetFMUstate!(c.fmu.cGetFMUstate, c.addr, FMUstate) checkStatus(c, status) return status end +import FMIBase.FMICore: fmi2SetFMUstate """ fmi2SetFMUstate(c::FMU2Component, FMUstate::fmi2FMUstate) @@ -923,16 +882,16 @@ 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) + status = fmi2SetFMUstate(c.fmu.cSetFMUstate, 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. @@ -957,14 +916,14 @@ 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}) @@ -991,14 +950,19 @@ 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) + status = + fmi2SerializedFMUstateSize!(c.fmu.cSerializedFMUstateSize, 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) @@ -1028,15 +992,25 @@ More detailed: See also [`fmi2SerializeFMUstate`](@ref). """ -function fmi2SerializeFMUstate!(c::FMU2Component, FMUstate::fmi2FMUstate, serialzedState::AbstractArray{fmi2Byte}, size::Csize_t) - - status = fmi2SerializeFMUstate!(c.fmu.cSerializeFMUstate, - c.compAddr, FMUstate, serialzedState, size) +function FMICore.fmi2SerializeFMUstate!( + c::FMU2Component, + FMUstate::fmi2FMUstate, + serialzedState::AbstractArray{fmi2Byte}, + size::Csize_t, +) + + status = fmi2SerializeFMUstate!( + c.fmu.cSerializeFMUstate, + 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}) @@ -1068,15 +1042,25 @@ More detailed: See also [`fmi2DeSerializeFMUstate!`](@ref). """ -function fmi2DeSerializeFMUstate!(c::FMU2Component, serializedState::AbstractArray{fmi2Byte}, size::Csize_t, FMUstate::Ref{fmi2FMUstate}) - - status = fmi2DeSerializeFMUstate!(c.fmu.cDeSerializeFMUstate, - c.compAddr, serializedState, size, FMUstate) +function FMICore.fmi2DeSerializeFMUstate!( + c::FMU2Component, + serializedState::AbstractArray{fmi2Byte}, + size::Csize_t, + FMUstate::Ref{fmi2FMUstate}, +) + + status = fmi2DeSerializeFMUstate!( + c.fmu.cDeSerializeFMUstate, + c.addr, + serializedState, + size, + FMUstate, + ) checkStatus(c, status) return status end - +import FMIBase.FMICore: fmi2GetDirectionalDerivative! """ fmi2GetDirectionalDerivative!(c::FMU2Component, vUnknown_ref::AbstractArray{fmi2ValueReference}, @@ -1129,22 +1113,66 @@ More detailed: - FMISpec2.0.2[p.25]: 2.1.9 Getting Partial Derivatives See also [`fmi2GetDirectionalDerivative!`](@ref). """ -function 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!"] - - status = fmi2GetDirectionalDerivative!(c.fmu.cGetDirectionalDerivative, - c.compAddr, vUnknown_ref, nUnknown, vKnown_ref, nKnown, dvKnown, dvUnknown) +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 providesDirectionalDerivatives(c.fmu) [ + "fmi2GetDirectionalDerivative!(...): This FMU does not support build-in directional derivatives!", + ] + + status = fmi2GetDirectionalDerivative!( + c.fmu.cGetDirectionalDerivative, + c.addr, + vUnknown_ref, + nUnknown, + vKnown_ref, + nKnown, + dvKnown, + dvUnknown, + ) checkStatus(c, status) return status end +function FMICore.fmi2GetDirectionalDerivative!( + c::FMU2Component, + vUnknown_ref::AbstractArray{fmi2ValueReference}, + nUnknown::Csize_t, + vKnown_ref::AbstractArray{fmi2ValueReference}, + nKnown::Csize_t, + dvKnown::AbstractArray{fmi2Real}, + dvUnknown::AbstractArray{<:Real}, +) + + logWarning( + c.fmu, + "fmi2GetDirectionalDerivative! is called on `dvUnknown::AbstractArray{<:Real}`, this is slow.\nConsider using `Float64` instead.", + 1, + ) + + _dvUnknown = zeros(fmi2Real, length(dvUnknown)) + status = fmi2GetDirectionalDerivative!( + c::FMU2Component, + vUnknown_ref, + nUnknown, + vKnown_ref, + nKnown, + dvKnown, + _dvUnknown, + ) + dvUnknown[:] = _dvUnknown + return status +end # Functions specificly for isCoSimulation + +import FMIBase.FMICore: fmi2SetRealInputDerivatives """ fmi2SetRealInputDerivatives(c::FMU2Component, vr::AbstractArray{fmi2ValueReference}, @@ -1179,15 +1207,27 @@ More detailed: See also [`fmi2SetRealInputDerivatives`](@ref). """ -function 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) +function FMICore.fmi2SetRealInputDerivatives( + c::FMU2Component, + vr::Array{fmi2ValueReference}, + nvr::Csize_t, + order::Array{fmi2Integer}, + value::Array{fmi2Real}, +) + + status = fmi2SetRealInputDerivatives( + c.fmu.cSetRealInputDerivatives, + c.addr, + vr, + nvr, + order, + value, + ) checkStatus(c, status) return status end - +import FMIBase.FMICore: fmi2GetRealOutputDerivatives! """ fmi2GetRealOutputDerivatives!(c::FMU2Component, vr::AbstractArray{fmi2ValueReference}, @@ -1219,15 +1259,27 @@ 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}) - - status = fmi2GetRealOutputDerivatives!(c.fmu.cGetRealOutputDerivatives, - c.compAddr, vr, nvr, order, value) +function FMICore.fmi2GetRealOutputDerivatives!( + c::FMU2Component, + vr::Array{fmi2ValueReference}, + nvr::Csize_t, + order::Array{fmi2Integer}, + value::Array{fmi2Real}, +) + + status = fmi2GetRealOutputDerivatives!( + c.fmu.cGetRealOutputDerivatives, + c.addr, + vr, + nvr, + order, + value, + ) checkStatus(c, status) return status end - +import FMIBase.FMICore: fmi2DoStep """ fmi2DoStep(c::FMU2Component, currentCommunicationPoint::fmi2Real, @@ -1259,15 +1311,28 @@ 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) - @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) +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.addr, + currentCommunicationPoint, + communicationStepSize, + noSetFMUStatePriorToCurrentPoint, + ) checkStatus(c, status) return status end +import FMIBase.FMICore: fmi2CancelStep """ fmi2CancelStep(c::FMU2Component) @@ -1293,13 +1358,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, @@ -1333,7 +1399,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 @@ -1344,13 +1410,13 @@ function fmi2GetStatus!(c::FMU2Component, s::fmi2StatusKind, value) status = fmi2Error if rtype == fmi2Boolean - status = fmi2GetStatus!(c.fmu.cGetRealStatus, - c.compAddr, s, Ref(value)) + status = fmi2GetStatus!(c.fmu.cGetRealStatus, c.addr, s, Ref(value)) checkStatus(c, status) end return status end +import FMIBase.FMICore: fmi2GetRealStatus! """ fmi2GetRealStatus!(c::FMU2Component, s::fmi2StatusKind, @@ -1384,14 +1450,14 @@ 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)) + status = fmi2GetRealStatus!(c.fmu.cGetRealStatus, c.addr, s, Ref(value)) checkStatus(c, status) return status end +import FMIBase.FMICore: fmi2GetIntegerStatus! """ fmi2GetIntegerStatus!(c::FMU2Component, s::fmi2StatusKind, @@ -1425,14 +1491,18 @@ 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)) + status = fmi2GetIntegerStatus!(c.fmu.cGetIntegerStatus, c.addr, s, Ref(value)) checkStatus(c, status) return status end +import FMIBase.FMICore: fmi2GetBooleanStatus! """ fmi2GetBooleanStatus!(c::FMU2Component, s::fmi2StatusKind, @@ -1466,14 +1536,18 @@ 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)) + status = fmi2GetBooleanStatus!(c.fmu.cGetBooleanStatus, c.addr, s, Ref(value)) checkStatus(c, status) return status end +import FMIBase.FMICore: fmi2GetStringStatus! """ fmi2GetStringStatus!(c::FMU2Component, s::fmi2StatusKind, @@ -1507,15 +1581,20 @@ 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)) + status = fmi2GetStringStatus!(c.fmu.cGetStringStatus, c.addr, s, Ref(value)) checkStatus(c, status) return status end # Model Exchange specific Functions + +import FMIBase.FMICore: fmi2SetTime """ fmi2SetTime(c::FMU2Component, time::fmi2Real; @@ -1552,7 +1631,14 @@ 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) @@ -1570,12 +1656,12 @@ function fmi2SetTime(c::FMU2Component, time::fmi2Real; soft::Bool=false, track:: end if !force - if c.t == time + if c.t == time return fmi2StatusOK end end - status = fmi2SetTime(c.fmu.cSetTime, c.compAddr, time) + status = fmi2SetTime(c.fmu.cSetTime, c.addr, time) checkStatus(c, status) if track @@ -1591,6 +1677,7 @@ function fmi2SetTime(c::FMU2Component, time::fmi2Real; soft::Bool=false, track:: return status end +import FMIBase.FMICore: fmi2SetContinuousStates """ fmi2SetContinuousStates(c::FMU2Component, x::AbstractArray{fmi2Real}, @@ -1621,24 +1708,26 @@ More detailed: See also [`fmi2SetContinuousStates`](@ref). """ -function fmi2SetContinuousStates(c::FMU2Component, +function FMICore.fmi2SetContinuousStates( + c::FMU2Component, x::AbstractArray{fmi2Real}, nx::Csize_t; - track::Bool=true, - force::Bool=c.force) + track::Bool = true, + force::Bool = c.force, +) if !force - if c.x == x - return fmi2StatusOK + if c.x == x + return fmi2StatusOK 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 if status == fmi2StatusOK - isnothing(c.x) ? (c.x = copy(x);) : copyto!(c.x, x) + isnothing(c.x) ? (c.x = copy(x)) : copyto!(c.x, x) invalidate!(c.∂ẋ_∂x) invalidate!(c.∂y_∂x) @@ -1649,6 +1738,7 @@ function fmi2SetContinuousStates(c::FMU2Component, return status end +import FMIBase.FMICore: fmi2EnterEventMode """ fmi2EnterEventMode(c::FMU2Component; soft::Bool=false) @@ -1677,7 +1767,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 @@ -1687,8 +1777,7 @@ function fmi2EnterEventMode(c::FMU2Component; soft::Bool=false) end end - status = fmi2EnterEventMode(c.fmu.cEnterEventMode, - c.compAddr) + status = fmi2EnterEventMode(c.fmu.cEnterEventMode, c.addr) checkStatus(c, status) if status == fmi2StatusOK c.state = fmi2ComponentStateEventMode @@ -1730,14 +1819,17 @@ 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)) ) + status = fmi2NewDiscreteStates!( + c.fmu.cNewDiscreteStates, + c.addr, + Ptr{fmi2EventInfo}(pointer_from_objref(eventInfo)), + ) if eventInfo.nextEventTimeDefined == fmi2True eventInfo.nextEventTime -= c.t_offset @@ -1748,6 +1840,7 @@ function fmi2NewDiscreteStates!(c::FMU2Component, eventInfo::fmi2EventInfo) return status end +import FMIBase.FMICore: fmi2EnterContinuousTimeMode """ fmi2EnterContinuousTimeMode(c::FMU2Component; soft::Bool=false) @@ -1779,7 +1872,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 @@ -1789,8 +1882,7 @@ function fmi2EnterContinuousTimeMode(c::FMU2Component; soft::Bool=false) end end - status = fmi2EnterContinuousTimeMode(c.fmu.cEnterContinuousTimeMode, - c.compAddr) + status = fmi2EnterContinuousTimeMode(c.fmu.cEnterContinuousTimeMode, c.addr) checkStatus(c, status) if status == fmi2StatusOK c.state = fmi2ComponentStateContinuousTimeMode @@ -1798,11 +1890,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. @@ -1827,17 +1920,25 @@ More detailed: - FMISpec2.0.2[p.83]: 3.2.2 Evaluation of Model Equations See also [`fmi2CompletedIntegratorStep!`](@ref). """ -function fmi2CompletedIntegratorStep!(c::FMU2Component, - noSetFMUStatePriorToCurrentPoint::fmi2Boolean, - enterEventMode::Ptr{fmi2Boolean}, - terminateSimulation::Ptr{fmi2Boolean}) - - status = fmi2CompletedIntegratorStep!(c.fmu.cCompletedIntegratorStep, - c.compAddr, noSetFMUStatePriorToCurrentPoint, enterEventMode, terminateSimulation) +function FMICore.fmi2CompletedIntegratorStep!( + c::FMU2Component, + noSetFMUStatePriorToCurrentPoint::fmi2Boolean, + enterEventMode::Ptr{fmi2Boolean}, + terminateSimulation::Ptr{fmi2Boolean}, +) + + status = fmi2CompletedIntegratorStep!( + c.fmu.cCompletedIntegratorStep, + c.addr, + noSetFMUStatePriorToCurrentPoint, + enterEventMode, + terminateSimulation, + ) checkStatus(c, status) return status end +import FMIBase.FMICore: fmi2GetDerivatives! """ fmi2GetDerivatives!(c::FMU2Component, derivatives::AbstractArray{fmi2Real}, @@ -1868,17 +1969,19 @@ More detailed: See also [`fmi2GetDerivatives!`](@ref). """ -function fmi2GetDerivatives!(c::FMU2Component, - derivatives::AbstractArray{fmi2Real}, - nx::Csize_t) +function FMICore.fmi2GetDerivatives!( + c::FMU2Component, + derivatives::AbstractArray{fmi2Real}, + nx::Csize_t, +) - status = fmi2GetDerivatives!(c.fmu.cGetDerivatives, - c.compAddr, derivatives, nx) + status = fmi2GetDerivatives!(c.fmu.cGetDerivatives, c.addr, derivatives, nx) checkStatus(c, status) - + return status end +import FMIBase.FMICore: fmi2GetEventIndicators! """ fmi2GetEventIndicators!(c::FMU2Component, eventIndicators::AbstractArray{fmi2Real}, ni::Csize_t) @@ -1905,14 +2008,18 @@ 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) + status = fmi2GetEventIndicators!(c.fmu.cGetEventIndicators, c.addr, eventIndicators, ni) checkStatus(c, status) return status end +import FMIBase.FMICore: fmi2GetContinuousStates! """ fmi2GetContinuousStates!(c::FMU2Component, x::AbstractArray{fmi2Real}, @@ -1941,16 +2048,18 @@ More detailed: - FMISpec2.0.2[p.83]: 3.2.2 Evaluation of Model Equations See also [`fmi2GetEventIndicators!`](@ref). """ -function fmi2GetContinuousStates!(c::FMU2Component, - x::AbstractArray{fmi2Real}, - nx::Csize_t) +function FMICore.fmi2GetContinuousStates!( + c::FMU2Component, + x::AbstractArray{fmi2Real}, + nx::Csize_t, +) - status = fmi2GetContinuousStates!(c.fmu.cGetContinuousStates, - c.compAddr, x, nx) + status = fmi2GetContinuousStates!(c.fmu.cGetContinuousStates, c.addr, x, nx) checkStatus(c, status) return status end +import FMIBase.FMICore: fmi2GetNominalsOfContinuousStates! """ fmi2GetNominalsOfContinuousStates!(c::FMU2Component, x_nominal::AbstractArray{fmi2Real}, nx::Csize_t) @@ -1977,10 +2086,18 @@ 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) - - status = fmi2GetNominalsOfContinuousStates!(c.fmu.cGetNominalsOfContinuousStates, - c.compAddr, x_nominal, nx) +function FMICore.fmi2GetNominalsOfContinuousStates!( + c::FMU2Component, + x_nominal::AbstractArray{fmi2Real}, + nx::Csize_t, +) + + status = fmi2GetNominalsOfContinuousStates!( + c.fmu.cGetNominalsOfContinuousStates, + c.addr, + x_nominal, + nx, + ) checkStatus(c, status) return status end diff --git a/src/FMI2/callbackFunctions/binaries/darwin64/libcallbackFunctions.dylib b/src/FMI2/callbackFunctions/binaries/darwin64/libcallbackFunctions.dylib index b5e5d52..c6830ec 100644 Binary files a/src/FMI2/callbackFunctions/binaries/darwin64/libcallbackFunctions.dylib and b/src/FMI2/callbackFunctions/binaries/darwin64/libcallbackFunctions.dylib differ diff --git a/src/FMI2/callbackFunctions/binaries/linux64/libcallbackFunctions.so b/src/FMI2/callbackFunctions/binaries/linux64/libcallbackFunctions.so index f3d0a81..535d1ad 100644 Binary files a/src/FMI2/callbackFunctions/binaries/linux64/libcallbackFunctions.so and b/src/FMI2/callbackFunctions/binaries/linux64/libcallbackFunctions.so differ diff --git a/src/FMI2/callbackFunctions/binaries/win32/callbackFunctions.dll b/src/FMI2/callbackFunctions/binaries/win32/callbackFunctions.dll new file mode 100644 index 0000000..23dd589 Binary files /dev/null and b/src/FMI2/callbackFunctions/binaries/win32/callbackFunctions.dll differ diff --git a/src/FMI2/callbackFunctions/binaries/win32/callbackFunctions.lib b/src/FMI2/callbackFunctions/binaries/win32/callbackFunctions.lib new file mode 100644 index 0000000..b2b8d46 Binary files /dev/null and b/src/FMI2/callbackFunctions/binaries/win32/callbackFunctions.lib differ diff --git a/src/FMI2/callbackFunctions/binaries/win64/callbackFunctions.dll b/src/FMI2/callbackFunctions/binaries/win64/callbackFunctions.dll index c2b2b67..c22efef 100644 Binary files a/src/FMI2/callbackFunctions/binaries/win64/callbackFunctions.dll and b/src/FMI2/callbackFunctions/binaries/win64/callbackFunctions.dll differ diff --git a/src/FMI2/callbackFunctions/binaries/win64/callbackFunctions.lib b/src/FMI2/callbackFunctions/binaries/win64/callbackFunctions.lib index 4a90862..247e243 100644 Binary files a/src/FMI2/callbackFunctions/binaries/win64/callbackFunctions.lib and b/src/FMI2/callbackFunctions/binaries/win64/callbackFunctions.lib differ diff --git a/src/FMI2/convert.jl b/src/FMI2/convert.jl deleted file mode 100644 index 3e7688c..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) - 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(ForwardDiff.derivative(t -> solution.states(t)[indices[1]], myt) for myt in solution.states.t) - - else # multi value - return collect(collect(ForwardDiff.derivative(t -> solution.states(t)[indices[i]], myt) for myt 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 f0116db..3b91095 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,13 @@ 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 +43,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 +71,17 @@ 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 +89,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,19 +100,22 @@ 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 - directories = [joinpath("binaries", "win64"), joinpath("binaries","x86_64-windows")] + directories = + [joinpath("binaries", "win64"), joinpath("binaries", "x86_64-windows")] else - directories = [joinpath("binaries", "win32"), joinpath("binaries","i686-windows")] + directories = + [joinpath("binaries", "win32"), joinpath("binaries", "i686-windows")] end osStr = "Windows" fmuExt = "dll" elseif Sys.islinux() if juliaArch == 64 - directories = [joinpath("binaries", "linux64"), joinpath("binaries", "x86_64-linux")] + directories = + [joinpath("binaries", "linux64"), joinpath("binaries", "x86_64-linux")] else directories = [] end @@ -224,17 +123,18 @@ function fmi2Load(pathToFMU::String; unpackPath::Union{String, Nothing}=nothing, fmuExt = "so" elseif Sys.isapple() if juliaArch == 64 - directories = [joinpath("binaries", "darwin64"), joinpath("binaries", "x86_64-darwin")] + directories = + [joinpath("binaries", "darwin64"), joinpath("binaries", "x86_64-darwin")] else directories = [] end 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 +142,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)) @@ -271,90 +171,147 @@ function loadBinary(fmu::FMU2) cd(lastDirectory) # retrieve functions - fmu.cInstantiate = dlsym(fmu.libHandle, :fmi2Instantiate) - fmu.cGetTypesPlatform = dlsym(fmu.libHandle, :fmi2GetTypesPlatform) - fmu.cGetVersion = dlsym(fmu.libHandle, :fmi2GetVersion) - fmu.cFreeInstance = dlsym(fmu.libHandle, :fmi2FreeInstance) - fmu.cSetDebugLogging = dlsym(fmu.libHandle, :fmi2SetDebugLogging) - fmu.cSetupExperiment = dlsym(fmu.libHandle, :fmi2SetupExperiment) - fmu.cEnterInitializationMode = dlsym(fmu.libHandle, :fmi2EnterInitializationMode) - fmu.cExitInitializationMode = dlsym(fmu.libHandle, :fmi2ExitInitializationMode) - fmu.cTerminate = dlsym(fmu.libHandle, :fmi2Terminate) - fmu.cReset = dlsym(fmu.libHandle, :fmi2Reset) - fmu.cGetReal = dlsym(fmu.libHandle, :fmi2GetReal) - fmu.cSetReal = dlsym(fmu.libHandle, :fmi2SetReal) - fmu.cGetInteger = dlsym(fmu.libHandle, :fmi2GetInteger) - fmu.cSetInteger = dlsym(fmu.libHandle, :fmi2SetInteger) - fmu.cGetBoolean = dlsym(fmu.libHandle, :fmi2GetBoolean) - fmu.cSetBoolean = dlsym(fmu.libHandle, :fmi2SetBoolean) - fmu.cGetString = dlsym_opt(fmu.libHandle, :fmi2GetString) - fmu.cSetString = dlsym_opt(fmu.libHandle, :fmi2SetString) - - if fmi2CanGetSetState(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) - 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) - fmu.cGetDirectionalDerivative = dlsym_opt(fmu.libHandle, :fmi2GetDirectionalDerivative) + fmu.cInstantiate = dlsym(fmu.libHandle, :fmi2Instantiate) + fmu.cGetTypesPlatform = dlsym(fmu.libHandle, :fmi2GetTypesPlatform) + fmu.cGetVersion = dlsym(fmu.libHandle, :fmi2GetVersion) + fmu.cFreeInstance = dlsym(fmu.libHandle, :fmi2FreeInstance) + fmu.cSetDebugLogging = dlsym(fmu.libHandle, :fmi2SetDebugLogging) + fmu.cSetupExperiment = dlsym(fmu.libHandle, :fmi2SetupExperiment) + fmu.cEnterInitializationMode = dlsym(fmu.libHandle, :fmi2EnterInitializationMode) + fmu.cExitInitializationMode = dlsym(fmu.libHandle, :fmi2ExitInitializationMode) + fmu.cTerminate = dlsym(fmu.libHandle, :fmi2Terminate) + fmu.cReset = dlsym(fmu.libHandle, :fmi2Reset) + fmu.cGetReal = dlsym(fmu.libHandle, :fmi2GetReal) + fmu.cSetReal = dlsym(fmu.libHandle, :fmi2SetReal) + fmu.cGetInteger = dlsym(fmu.libHandle, :fmi2GetInteger) + fmu.cSetInteger = dlsym(fmu.libHandle, :fmi2SetInteger) + fmu.cGetBoolean = dlsym(fmu.libHandle, :fmi2GetBoolean) + fmu.cSetBoolean = dlsym(fmu.libHandle, :fmi2SetBoolean) + fmu.cGetString = dlsym_opt(fmu, fmu.libHandle, :fmi2GetString) + fmu.cSetString = dlsym_opt(fmu, fmu.libHandle, :fmi2SetString) + + if canGetSetFMUState(fmu.modelDescription) + fmu.cGetFMUstate = dlsym_opt(fmu, fmu.libHandle, :fmi2GetFMUstate) + fmu.cSetFMUstate = dlsym_opt(fmu, fmu.libHandle, :fmi2SetFMUstate) + fmu.cFreeFMUstate = dlsym_opt(fmu, fmu.libHandle, :fmi2FreeFMUstate) + end + + if canSerializeFMUState(fmu.modelDescription) + fmu.cSerializedFMUstateSize = + dlsym_opt(fmu, fmu.libHandle, :fmi2SerializedFMUstateSize) + fmu.cSerializeFMUstate = dlsym_opt(fmu, fmu.libHandle, :fmi2SerializeFMUstate) + fmu.cDeSerializeFMUstate = dlsym_opt(fmu, fmu.libHandle, :fmi2DeSerializeFMUstate) + end + + if providesDirectionalDerivatives(fmu.modelDescription) + fmu.cGetDirectionalDerivative = + dlsym_opt(fmu, fmu.libHandle, :fmi2GetDirectionalDerivative) end # CS specific function calls - if fmi2IsCoSimulation(fmu.modelDescription) - fmu.cSetRealInputDerivatives = dlsym(fmu.libHandle, :fmi2SetRealInputDerivatives) - fmu.cGetRealOutputDerivatives = dlsym(fmu.libHandle, :fmi2GetRealOutputDerivatives) - fmu.cDoStep = dlsym(fmu.libHandle, :fmi2DoStep) - fmu.cCancelStep = dlsym(fmu.libHandle, :fmi2CancelStep) - fmu.cGetStatus = dlsym(fmu.libHandle, :fmi2GetStatus) - fmu.cGetRealStatus = dlsym(fmu.libHandle, :fmi2GetRealStatus) - fmu.cGetIntegerStatus = dlsym(fmu.libHandle, :fmi2GetIntegerStatus) - fmu.cGetBooleanStatus = dlsym(fmu.libHandle, :fmi2GetBooleanStatus) - fmu.cGetStringStatus = dlsym(fmu.libHandle, :fmi2GetStringStatus) + if isCoSimulation(fmu.modelDescription) + fmu.cSetRealInputDerivatives = dlsym(fmu.libHandle, :fmi2SetRealInputDerivatives) + fmu.cGetRealOutputDerivatives = dlsym(fmu.libHandle, :fmi2GetRealOutputDerivatives) + fmu.cDoStep = dlsym(fmu.libHandle, :fmi2DoStep) + fmu.cCancelStep = dlsym(fmu.libHandle, :fmi2CancelStep) + fmu.cGetStatus = dlsym(fmu.libHandle, :fmi2GetStatus) + fmu.cGetRealStatus = dlsym(fmu.libHandle, :fmi2GetRealStatus) + fmu.cGetIntegerStatus = dlsym(fmu.libHandle, :fmi2GetIntegerStatus) + fmu.cGetBooleanStatus = dlsym(fmu.libHandle, :fmi2GetBooleanStatus) + fmu.cGetStringStatus = dlsym(fmu.libHandle, :fmi2GetStringStatus) end # ME specific function calls - if fmi2IsModelExchange(fmu.modelDescription) - fmu.cEnterContinuousTimeMode = dlsym(fmu.libHandle, :fmi2EnterContinuousTimeMode) - fmu.cGetContinuousStates = dlsym(fmu.libHandle, :fmi2GetContinuousStates) - fmu.cGetDerivatives = dlsym(fmu.libHandle, :fmi2GetDerivatives) - fmu.cSetTime = dlsym(fmu.libHandle, :fmi2SetTime) - fmu.cSetContinuousStates = dlsym(fmu.libHandle, :fmi2SetContinuousStates) - fmu.cCompletedIntegratorStep = dlsym(fmu.libHandle, :fmi2CompletedIntegratorStep) - fmu.cEnterEventMode = dlsym(fmu.libHandle, :fmi2EnterEventMode) - fmu.cNewDiscreteStates = dlsym(fmu.libHandle, :fmi2NewDiscreteStates) - fmu.cGetEventIndicators = dlsym(fmu.libHandle, :fmi2GetEventIndicators) - fmu.cGetNominalsOfContinuousStates= dlsym(fmu.libHandle, :fmi2GetNominalsOfContinuousStates) + if isModelExchange(fmu.modelDescription) + fmu.cEnterContinuousTimeMode = dlsym(fmu.libHandle, :fmi2EnterContinuousTimeMode) + fmu.cGetContinuousStates = dlsym(fmu.libHandle, :fmi2GetContinuousStates) + fmu.cGetDerivatives = dlsym(fmu.libHandle, :fmi2GetDerivatives) + fmu.cSetTime = dlsym(fmu.libHandle, :fmi2SetTime) + fmu.cSetContinuousStates = dlsym(fmu.libHandle, :fmi2SetContinuousStates) + fmu.cCompletedIntegratorStep = dlsym(fmu.libHandle, :fmi2CompletedIntegratorStep) + fmu.cEnterEventMode = dlsym(fmu.libHandle, :fmi2EnterEventMode) + fmu.cNewDiscreteStates = dlsym(fmu.libHandle, :fmi2NewDiscreteStates) + fmu.cGetEventIndicators = dlsym(fmu.libHandle, :fmi2GetEventIndicators) + fmu.cGetNominalsOfContinuousStates = + dlsym(fmu.libHandle, :fmi2GetNominalsOfContinuousStates) 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)) - fmu.cGetTypesPlatform = @cfunction(FMICore.unload_fmi2GetTypesPlatform, fmi2String, ()) - fmu.cGetVersion = @cfunction(FMICore.unload_fmi2GetVersion, fmi2String, ()) - fmu.cFreeInstance = @cfunction(FMICore.unload_fmi2FreeInstance, Cvoid, (fmi2Component,)) - fmu.cSetDebugLogging = @cfunction(FMICore.unload_fmi2SetDebugLogging, fmi2Status, (fmi2Component, fmi2Boolean, Csize_t, Ptr{fmi2String})) - fmu.cSetupExperiment = @cfunction(FMICore.unload_fmi2SetupExperiment, fmi2Status, (fmi2Component, fmi2Boolean, fmi2Real, fmi2Real, fmi2Boolean, fmi2Real)) - fmu.cEnterInitializationMode = @cfunction(FMICore.unload_fmi2EnterInitializationMode, fmi2Status, (fmi2Component,)) - fmu.cExitInitializationMode = @cfunction(FMICore.unload_fmi2ExitInitializationMode, fmi2Status, (fmi2Component,)) - fmu.cTerminate = @cfunction(FMICore.unload_fmi2Terminate, fmi2Status, (fmi2Component,)) - fmu.cReset = @cfunction(FMICore.unload_fmi2Reset, fmi2Status, (fmi2Component,)) - fmu.cGetReal = @cfunction(FMICore.unload_fmi2GetReal, fmi2Status, (fmi2Component, Ptr{fmi2ValueReference}, Csize_t, Ptr{fmi2Real})) - fmu.cSetReal = @cfunction(FMICore.unload_fmi2SetReal, fmi2Status, (fmi2Component, Ptr{fmi2ValueReference}, Csize_t, Ptr{fmi2Real})) - fmu.cGetInteger = @cfunction(FMICore.unload_fmi2GetInteger, fmi2Status, (fmi2Component, Ptr{fmi2ValueReference}, Csize_t, Ptr{fmi2Integer})) - fmu.cSetInteger = @cfunction(FMICore.unload_fmi2SetInteger, fmi2Status, (fmi2Component, Ptr{fmi2ValueReference}, Csize_t, Ptr{fmi2Integer})) - fmu.cGetBoolean = @cfunction(FMICore.unload_fmi2GetBoolean, fmi2Status, (fmi2Component, Ptr{fmi2ValueReference}, Csize_t, Ptr{fmi2Boolean})) - fmu.cSetBoolean = @cfunction(FMICore.unload_fmi2SetBoolean, fmi2Status, (fmi2Component, Ptr{fmi2ValueReference}, Csize_t, Ptr{fmi2Boolean})) - fmu.cGetString = @cfunction(FMICore.unload_fmi2GetString, fmi2Status, (fmi2Component, Ptr{fmi2ValueReference}, Csize_t, Ptr{fmi2String})) - fmu.cSetString = @cfunction(FMICore.unload_fmi2SetString, fmi2Status, (fmi2Component, Ptr{fmi2ValueReference}, Csize_t, Ptr{fmi2String})) + fmu.cInstantiate = @cfunction( + FMICore.unload_fmi2Instantiate, + fmi2Component, + ( + fmi2String, + fmi2Type, + fmi2String, + fmi2String, + Ptr{fmi2CallbackFunctions}, + fmi2Boolean, + fmi2Boolean, + ) + ) + fmu.cGetTypesPlatform = @cfunction(FMICore.unload_fmi2GetTypesPlatform, fmi2String, ()) + fmu.cGetVersion = @cfunction(FMICore.unload_fmi2GetVersion, fmi2String, ()) + fmu.cFreeInstance = @cfunction(FMICore.unload_fmi2FreeInstance, Cvoid, (fmi2Component,)) + fmu.cSetDebugLogging = @cfunction( + FMICore.unload_fmi2SetDebugLogging, + fmi2Status, + (fmi2Component, fmi2Boolean, Csize_t, Ptr{fmi2String}) + ) + fmu.cSetupExperiment = @cfunction( + FMICore.unload_fmi2SetupExperiment, + fmi2Status, + (fmi2Component, fmi2Boolean, fmi2Real, fmi2Real, fmi2Boolean, fmi2Real) + ) + fmu.cEnterInitializationMode = + @cfunction(FMICore.unload_fmi2EnterInitializationMode, fmi2Status, (fmi2Component,)) + fmu.cExitInitializationMode = + @cfunction(FMICore.unload_fmi2ExitInitializationMode, fmi2Status, (fmi2Component,)) + fmu.cTerminate = @cfunction(FMICore.unload_fmi2Terminate, fmi2Status, (fmi2Component,)) + fmu.cReset = @cfunction(FMICore.unload_fmi2Reset, fmi2Status, (fmi2Component,)) + fmu.cGetReal = @cfunction( + FMICore.unload_fmi2GetReal, + fmi2Status, + (fmi2Component, Ptr{fmi2ValueReference}, Csize_t, Ptr{fmi2Real}) + ) + fmu.cSetReal = @cfunction( + FMICore.unload_fmi2SetReal, + fmi2Status, + (fmi2Component, Ptr{fmi2ValueReference}, Csize_t, Ptr{fmi2Real}) + ) + fmu.cGetInteger = @cfunction( + FMICore.unload_fmi2GetInteger, + fmi2Status, + (fmi2Component, Ptr{fmi2ValueReference}, Csize_t, Ptr{fmi2Integer}) + ) + fmu.cSetInteger = @cfunction( + FMICore.unload_fmi2SetInteger, + fmi2Status, + (fmi2Component, Ptr{fmi2ValueReference}, Csize_t, Ptr{fmi2Integer}) + ) + fmu.cGetBoolean = @cfunction( + FMICore.unload_fmi2GetBoolean, + fmi2Status, + (fmi2Component, Ptr{fmi2ValueReference}, Csize_t, Ptr{fmi2Boolean}) + ) + fmu.cSetBoolean = @cfunction( + FMICore.unload_fmi2SetBoolean, + fmi2Status, + (fmi2Component, Ptr{fmi2ValueReference}, Csize_t, Ptr{fmi2Boolean}) + ) + fmu.cGetString = @cfunction( + FMICore.unload_fmi2GetString, + fmi2Status, + (fmi2Component, Ptr{fmi2ValueReference}, Csize_t, Ptr{fmi2String}) + ) + fmu.cSetString = @cfunction( + FMICore.unload_fmi2SetString, + fmi2Status, + (fmi2Component, Ptr{fmi2ValueReference}, Csize_t, Ptr{fmi2String}) + ) # ToDo: Implement for pecial functions! # if fmi2CanGetSetState(fmu.modelDescription) @@ -387,1201 +344,50 @@ function unloadBinary(fmu::FMU2) # end # ME specific function calls - if fmi2IsModelExchange(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)) - fmu.cSetTime = @cfunction(FMICore.unload_fmi2SetTime, fmi2Status, (fmi2Component, fmi2Real)) - fmu.cSetContinuousStates = @cfunction(FMICore.unload_fmi2SetContinuousStates, fmi2Status, (fmi2Component, Ptr{fmi2Real}, Csize_t)) - fmu.cCompletedIntegratorStep = @cfunction(FMICore.unload_fmi2CompletedIntegratorStep, fmi2Status, (fmi2Component, fmi2Boolean, Ptr{fmi2Boolean}, Ptr{fmi2Boolean})) - fmu.cEnterEventMode = @cfunction(FMICore.unload_fmi2EnterEventMode, fmi2Status, (fmi2Component,)) - fmu.cNewDiscreteStates = @cfunction(FMICore.unload_fmi2NewDiscreteStates, fmi2Status, (fmi2Component, Ptr{fmi2EventInfo})) - 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) - -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 - fmi2FreeInstance!(fmu.components[end]) - 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 doesn't match number of `srcArray` 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 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 + 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) + ) + fmu.cSetTime = + @cfunction(FMICore.unload_fmi2SetTime, fmi2Status, (fmi2Component, fmi2Real)) + fmu.cSetContinuousStates = @cfunction( + FMICore.unload_fmi2SetContinuousStates, + fmi2Status, + (fmi2Component, Ptr{fmi2Real}, Csize_t) + ) + fmu.cCompletedIntegratorStep = @cfunction( + FMICore.unload_fmi2CompletedIntegratorStep, + fmi2Status, + (fmi2Component, fmi2Boolean, Ptr{fmi2Boolean}, Ptr{fmi2Boolean}) + ) + fmu.cEnterEventMode = + @cfunction(FMICore.unload_fmi2EnterEventMode, fmi2Status, (fmi2Component,)) + fmu.cNewDiscreteStates = @cfunction( + FMICore.unload_fmi2NewDiscreteStates, + fmi2Status, + (fmi2Component, Ptr{fmi2EventInfo}) + ) + 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 - 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 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 3d753f4..e77b1e2 100644 --- a/src/FMI2/int.jl +++ b/src/FMI2/int.jl @@ -3,11 +3,262 @@ # 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) @@ -74,13 +325,15 @@ More detailed: See also [`fmi2SetupExperiment`](@ref). """ -function fmi2SetupExperiment(c::FMU2Component, - startTime::Union{Real, Nothing} = nothing, - stopTime::Union{Real, Nothing} = nothing; - tolerance::Union{Real, Nothing} = nothing) +function fmi2SetupExperiment( + c::FMU2Component, + startTime::Union{Real,Nothing} = nothing, + stopTime::Union{Real,Nothing} = nothing; + 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 +341,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 @@ -108,7 +361,14 @@ function fmi2SetupExperiment(c::FMU2Component, stopTime = 0.0 # dummy value, will be ignored end - fmi2SetupExperiment(c, fmi2Boolean(toleranceDefined), fmi2Real(tolerance), fmi2Real(startTime), fmi2Boolean(stopTimeDefined), fmi2Real(stopTime)) + fmi2SetupExperiment( + c, + fmi2Boolean(toleranceDefined), + fmi2Real(tolerance), + fmi2Real(startTime), + fmi2Boolean(stopTimeDefined), + fmi2Real(stopTime), + ) end """ @@ -148,6 +408,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}) @@ -181,7 +443,11 @@ More detailed: - FMISpec2.0.2[p.18]: 2.1.3 Status Returned by Functions See also [`fmi2GetReal!`](@ref). """ -function fmi2GetReal!(c::FMU2Component, vr::fmi2ValueReferenceFormat, values::AbstractArray{fmi2Real}) +function fmi2GetReal!( + c::FMU2Component, + vr::fmi2ValueReferenceFormat, + values::AbstractArray{fmi2Real}, +) vr = prepareValueReference(c, vr) # values = prepareValue(values) @@ -227,12 +493,18 @@ More detailed: - FMISpec2.0.2[p.18]: 2.1.3 Status Returned by Functions See also [`fmi2SetReal`](@ref). """ -function fmi2SetReal(c::FMU2Component, vr::fmi2ValueReferenceFormat, values::AbstractVector{fmi2Real}; kwargs...) +function fmi2SetReal( + c::FMU2Component, + vr::fmi2ValueReferenceFormat, + values::AbstractVector{fmi2Real}; + kwargs..., +) @assert length(vr) == length(values) "fmi2SetReal(...): `vr` ($(length(vr))) and `values` ($(length(values))) need to be the same length." nvr = Csize_t(length(vr)) fmi2SetReal(c, prepareValueReference(c, vr), nvr, prepareValue(values); kwargs...) end -fmi2SetReal(c::FMU2Component, vr::fmi2ValueReferenceFormat, values::Real; kwargs...) = fmi2SetReal(c, prepareValueReference(c, vr), prepareValue(values); kwargs...) +fmi2SetReal(c::FMU2Component, vr::fmi2ValueReferenceFormat, values::Real; kwargs...) = + fmi2SetReal(c, prepareValueReference(c, vr), prepareValue(values); kwargs...) """ fmi2GetInteger(c::FMU2Component, vr::fmi2ValueReferenceFormat) @@ -272,6 +544,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}) @@ -309,7 +583,11 @@ More detailed: See also [`fmi2GetInteger!`](@ref). """ -function fmi2GetInteger!(c::FMU2Component, vr::fmi2ValueReferenceFormat, values::AbstractArray{fmi2Integer}) +function fmi2GetInteger!( + c::FMU2Component, + vr::fmi2ValueReferenceFormat, + values::AbstractArray{fmi2Integer}, +) vr = prepareValueReference(c, vr) # values = prepareValue(values) @@ -348,7 +626,11 @@ More detailed: `fmi2ValueReferenceFormat = Union{Nothing, String, Array{String,1 See also [`fmi2SetInteger`](@ref). """ -function fmi2SetInteger(c::FMU2Component, vr::fmi2ValueReferenceFormat, values::Union{AbstractArray{<:Integer}, <:Integer}) +function fmi2SetInteger( + c::FMU2Component, + vr::fmi2ValueReferenceFormat, + values::Union{AbstractArray{<:Integer},<:Integer}, +) vr = prepareValueReference(c, vr) values = prepareValue(values) @@ -390,6 +672,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}) @@ -420,7 +704,11 @@ More detailed: `fmi2ValueReferenceFormat = Union{Nothing, String, Array{String,1 - FMISpec2.0.2[p.18]: 2.1.3 Status Returned by Functions See also [`fmi2GetBoolean!`](@ref). """ -function fmi2GetBoolean!(c::FMU2Component, vr::fmi2ValueReferenceFormat, values::AbstractArray{fmi2Boolean}) +function fmi2GetBoolean!( + c::FMU2Component, + vr::fmi2ValueReferenceFormat, + values::AbstractArray{fmi2Boolean}, +) vr = prepareValueReference(c, vr) # values = prepareValue(values) @@ -463,7 +751,11 @@ More detailed: - FMISpec2.0.2[p.18]: 2.1.3 Status Returned by Functions See also [`fmi2GetBoolean!`](@ref). """ -function fmi2SetBoolean(c::FMU2Component, vr::fmi2ValueReferenceFormat, values::Union{AbstractArray{Bool}, Bool}) +function fmi2SetBoolean( + c::FMU2Component, + vr::fmi2ValueReferenceFormat, + values::Union{AbstractArray{Bool},Bool}, +) vr = prepareValueReference(c, vr) values = prepareValue(values) @@ -509,6 +801,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}) @@ -537,7 +831,11 @@ More detailed: `fmi2ValueReferenceFormat = Union{Nothing, String, Array{String,1 - FMISpec2.0.2[p.18]: 2.1.3 Status Returned by Functions See also [`fmi2GetString!`](@ref). """ -function fmi2GetString!(c::FMU2Component, vr::fmi2ValueReferenceFormat, values::AbstractArray{fmi2String}) +function fmi2GetString!( + c::FMU2Component, + vr::fmi2ValueReferenceFormat, + values::AbstractArray{fmi2String}, +) vr = prepareValueReference(c, vr) @assert length(vr) == length(values) "fmi2GetString!(...): `vr` and `values` need to be the same length." @@ -586,7 +884,11 @@ More detailed: - FMISpec2.0.2[p.108]: 4.2.4 State Machine of Calling Sequence from Master to Slave See also [`fmi2SetString`](@ref). """ -function fmi2SetString(c::FMU2Component, vr::fmi2ValueReferenceFormat, values::Union{AbstractArray{String}, String}) +function fmi2SetString( + c::FMU2Component, + vr::fmi2ValueReferenceFormat, + values::Union{AbstractArray{String},String}, +) vr = prepareValueReference(c, vr) values = prepareValue(values) @@ -622,6 +924,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 +947,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 +980,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 +1013,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 +1046,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, @@ -779,26 +1089,36 @@ Computes a linear combination of the partial derivatives of h with respect to th - FMISpec2.0.2[p.25]: 2.1.9 Getting Partial Derivatives See also [`fmi2GetDirectionalDerivative!`](@ref). """ -function fmi2GetDirectionalDerivative(c::FMU2Component, - vUnknown_ref::AbstractArray{fmi2ValueReference}, - vKnown_ref::AbstractArray{fmi2ValueReference}, - dvKnown::Union{AbstractArray{fmi2Real}, Nothing} = nothing) +function fmi2GetDirectionalDerivative( + c::FMU2Component, + vUnknown_ref::AbstractArray{fmi2ValueReference}, + vKnown_ref::AbstractArray{fmi2ValueReference}, + dvKnown::AbstractArray{fmi2Real}, +) - nUnknown = Csize_t(length(vUnknown_ref)) + 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`. @@ -843,71 +1163,30 @@ More detailed: - FMISpec2.0.2[p.25]: 2.1.9 Getting Partial Derivatives 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) +function fmi2GetDirectionalDerivative!( + c::FMU2Component, + vUnknown_ref::AbstractArray{fmi2ValueReference}, + vKnown_ref::AbstractArray{fmi2ValueReference}, + 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) + 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, @@ -941,7 +1220,12 @@ More detailed: See also [`fmi2SetRealInputDerivatives`](@ref). """ -function fmi2SetRealInputDerivatives(c::FMU2Component, vr::fmi2ValueReferenceFormat, order::AbstractArray{fmi2Integer}, values::AbstractArray{fmi2Real}) +function fmi2SetRealInputDerivatives( + c::FMU2Component, + vr::fmi2ValueReferenceFormat, + order::AbstractArray{fmi2Integer}, + values::AbstractArray{fmi2Real}, +) @assert c.type == fmi2TypeCoSimulation "`fmi2SetRealInputDerivatives` only available for CS-FMUs." @@ -972,7 +1256,11 @@ Sets the n-th time derivative of real input variables. - FMISpec2.0.2[p.104]: 4.2.1 Transfer of Input / Output Values and Parameters """ -function fmi2GetRealOutputDerivatives(c::FMU2Component, vr::fmi2ValueReferenceFormat, order::AbstractArray{fmi2Integer}) +function fmi2GetRealOutputDerivatives( + c::FMU2Component, + vr::fmi2ValueReferenceFormat, + order::AbstractArray{fmi2Integer}, +) @assert c.type == fmi2TypeCoSimulation "`fmi2GetRealOutputDerivatives` only available for CS-FMUs." @@ -988,6 +1276,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, @@ -1023,15 +1313,20 @@ More detailed: - FMISpec2.0.2[p.104]: 4.2.2 Computation See also [`fmi2DoStep`](@ref). """ -function fmi2DoStep(c::FMU2Component, communicationStepSize::Union{Real, Nothing} = nothing; currentCommunicationPoint::Union{Real, Nothing} = nothing, noSetFMUStatePriorToCurrentPoint::Bool = true) +function fmi2DoStep( + c::FMU2Component, + communicationStepSize::Union{Real,Nothing} = nothing; + currentCommunicationPoint::Union{Real,Nothing} = nothing, + noSetFMUStatePriorToCurrentPoint::Bool = true, +) @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 @@ -1045,7 +1340,12 @@ function fmi2DoStep(c::FMU2Component, communicationStepSize::Union{Real, Nothing end c.t = currentCommunicationPoint - status = fmi2DoStep(c, fmi2Real(currentCommunicationPoint), fmi2Real(communicationStepSize), fmi2Boolean(noSetFMUStatePriorToCurrentPoint)) + status = fmi2DoStep( + c, + fmi2Real(currentCommunicationPoint), + fmi2Real(communicationStepSize), + fmi2Boolean(noSetFMUStatePriorToCurrentPoint), + ) c.t += communicationStepSize return status @@ -1123,7 +1423,8 @@ function fmi2SetContinuousStates(c::FMU2Component, x::AbstractArray{fmi2Real}; k end return status end -fmi2SetContinuousStates(c::FMU2Component, x::AbstractArray{Float32}; kwargs...) = fmi2SetContinuousStates(c, Array{fmi2Real}(x); kwargs...) +fmi2SetContinuousStates(c::FMU2Component, x::AbstractArray{Float32}; kwargs...) = + fmi2SetContinuousStates(c, Array{fmi2Real}(x); kwargs...) """ fmi2NewDiscreteStates(c::FMU2Component) @@ -1155,6 +1456,7 @@ function fmi2NewDiscreteStates(c::FMU2Component) fmi2NewDiscreteStates!(c, eventInfo) eventInfo end +export fmi2NewDiscreteStates """ fmiCompletedIntegratorStep(c::FMU2Component, noSetFMUStatePriorToCurrentPoint::fmi2Boolean) @@ -1183,15 +1485,20 @@ More detailed: - FMISpec2.0.2[p.83]: 3.2.2 Evaluation of Model Equations See also [`fmi2CompletedIntegratorStep`](@ref). """ -function fmi2CompletedIntegratorStep(c::FMU2Component, - noSetFMUStatePriorToCurrentPoint::fmi2Boolean) - status = fmi2CompletedIntegratorStep!(c, - noSetFMUStatePriorToCurrentPoint, - c._ptr_enterEventMode, - c._ptr_terminateSimulation) - - return (status, c._enterEventMode, c._terminateSimulation) +function fmi2CompletedIntegratorStep( + c::FMU2Component, + noSetFMUStatePriorToCurrentPoint::fmi2Boolean, +) + status = fmi2CompletedIntegratorStep!( + c, + noSetFMUStatePriorToCurrentPoint, + c._ptr_enterEventMode, + c._ptr_terminateSimulation, + ) + + return (status, c.enterEventMode, c.terminateSimulation) end +export fmi2CompletedIntegratorStep """ fmi2GetDerivatives(c::FMU2Component) @@ -1217,6 +1524,7 @@ function fmi2GetDerivatives(c::FMU2Component) fmi2GetDerivatives!(c, derivatives) return derivatives end +export fmi2GetDerivatives """ fmi2GetDerivatives!(c::FMU2Component, derivatives::AbstractArray{fmi2Real}) @@ -1273,6 +1581,7 @@ function fmi2GetEventIndicators(c::FMU2Component) fmi2GetEventIndicators!(c, eventIndicators, ni) return eventIndicators end +export fmi2GetEventIndicators """ fmi2GetEventIndicators!(c::FMU2Component, eventIndicators::AbstractArray{fmi2Real}) @@ -1315,6 +1624,7 @@ function fmi2GetContinuousStates(c::FMU2Component) fmi2GetContinuousStates!(c, x, nx) x end +export fmi2GetContinuousStates """ fmi2GetNominalsOfContinuousStates(c::FMU2Component) @@ -1338,6 +1648,7 @@ function fmi2GetNominalsOfContinuousStates(c::FMU2Component) fmi2GetNominalsOfContinuousStates!(c, x, nx) x end +export fmi2GetNominalsOfContinuousStates """ ToDo @@ -1358,3 +1669,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..5eb44bd 100644 --- a/src/FMI2/md.jl +++ b/src/FMI2/md.jl @@ -9,12 +9,25 @@ # - [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,14 +44,11 @@ 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() - md.stringValueReferences = Dict{String, fmi2ValueReference}() + md.stringValueReferences = Dict{String,fmi2ValueReference}() md.outputValueReferences = Array{fmi2ValueReference}(undef, 0) md.inputValueReferences = Array{fmi2ValueReference}(undef, 0) md.stateValueReferences = Array{fmi2ValueReference}(undef, 0) @@ -54,13 +64,29 @@ 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") - @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.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 = parseNode(root, "numberOfEventIndicators", Int; onfail = 0) + md.description = + parseNode(root, "description", String; onfail = "[Unknown Description]") # defaults md.modelExchange = nothing @@ -69,49 +95,62 @@ function fmi2LoadModelDescription(pathToModelDescription::String) # additionals md.valueReferences = [] - md.valueReferenceIndicies = Dict{UInt, UInt}() + md.valueReferenceIndicies = Dict{UInt,UInt}() for node in eachelement(root) 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,15 +158,15 @@ 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 # creating an index for value references (fast look-up for dependencies) - for i in 1:length(md.valueReferences) + for i = 1:length(md.valueReferences) md.valueReferenceIndicies[md.valueReferences[i]] = i end @@ -140,112 +179,81 @@ 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 + typenode = nothing if typename == "Real" - if ext + 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 + 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 elseif typename == "Boolean" - if ext + 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 elseif typename == "Integer" - - if ext + + 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 + + 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 +268,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 +298,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 +308,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,25 +325,26 @@ 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) + scalarVariables[index] = + fmi2ScalarVariable(name, valueReference, causality, variability, initial) if !(valueReference in md.valueReferences) 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) @@ -347,10 +356,10 @@ function parseModelVariables(nodes::EzXML.Node, md::fmi2ModelDescription) elseif isa(typenode, fmi2EnumerationAttributesExt) scalarVariables[index].Enumeration = typenode end - + # generic attributes if !isnothing(typenode) - typenode.declaredType = parseNodeString(node.firstelement, "declaredType") + typenode.declaredType = parseNode(node.firstelement, "declaredType", String) end md.stringValueReferences[name] = valueReference @@ -362,9 +371,9 @@ 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}() + simpleTypes = Array{fmi2SimpleType,1}() for node in eachelement(nodes) @@ -375,11 +384,11 @@ 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 +396,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 +415,9 @@ 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 +435,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 +466,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 +485,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,37 +549,41 @@ 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 - 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 - end - if haskey(typenode, "quantity") && (type.datatype == fmi2Real || type.datatype == fmi2Integer || type.datatype == fmi2Enum) + if haskey(typenode, "min") && ( + type.datatype == fmi2Real || + type.datatype == fmi2Integer || + type.datatype == fmi2Enum + ) + type.min = parseNode(typenode, "min", type.datatype) + end + if haskey(typenode, "max") && ( + type.datatype == fmi2Real || + type.datatype == fmi2Integer || + type.datatype == fmi2Enum + ) + 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"] end if haskey(typenode, "unit") && type.datatype == fmi2Real @@ -578,16 +593,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,56 +614,56 @@ 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) unit = fmi2Unit(node["name"]) - for subNode = eachelement(node) + for subNode in 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 +676,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 +689,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 +714,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 +732,18 @@ 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 22cdeaf..3a4f4e7 100644 --- a/src/FMI2/prep.jl +++ b/src/FMI2/prep.jl @@ -3,80 +3,73 @@ # 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) - + causality, variability, initial = getAttributes(mv) - return variability != fmi2VariabilityConstant && initial ∈ (fmi2InitialApprox, fmi2InitialExact) + return variability != fmi2VariabilityConstant && + initial ∈ (fmi2InitialApprox, fmi2InitialExact) end import FMIImport: fmi2CausalityInput, fmi2CausalityParameter, fmi2VariabilityTunable function setInInitialization(mv::fmi2ScalarVariable) causality, variability, initial = getAttributes(mv) - return causality == fmi2CausalityInput || (causality != fmi2CausalityParameter && variability == fmi2VariabilityTunable) || (variability != fmi2VariabilityConstant && initial == fmi2InitialExact) + 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, - 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) +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, + x0::Union{AbstractArray{<:Real},Nothing} = nothing, + inputs::Union{Dict{<:Any,<:Any},Nothing} = nothing, + cleanup::Bool = false, + 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 @@ -86,33 +79,48 @@ function prepareSolveFMU(fmu::FMU2, # soft reset (if necessary) if reset - retcode = fmi2Reset(c; soft=true) + retcode = fmi2Reset(c; soft = true) @assert retcode == fmi2StatusOK "fmi2Simulate(...): Reset failed with return code $(retcode)." - end + end # setup experiment (hard) if setup - retcode = fmi2SetupExperiment(c, t_start, t_stop; tolerance=tolerance) + retcode = fmi2SetupExperiment(c, t_start, t_stop; tolerance = tolerance) @assert retcode == fmi2StatusOK "fmi2Simulate(...): Setting up experiment failed with return code $(retcode)." end # 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,22 +132,40 @@ 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 + c.x = copy(x0) end # exit setup (hard) @@ -149,248 +175,76 @@ 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 if instantiate || reset # we have a fresh instance @debug "[NEW INST]" - handleEvents(c) + 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!" - - #@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 + if isnothing(c) + return end ignore_derivatives() do - if terminate === nothing + if terminate === nothing terminate = fmu.executionConfig.terminate end - if freeInstance === nothing + if freeInstance === nothing freeInstance = fmu.executionConfig.freeInstance end # soft terminate (if necessary) if terminate - retcode = fmi2Terminate(c; soft=true) + retcode = fmi2Terminate(c; soft = true) @assert retcode == fmi2StatusOK "fmi2Simulate(...): Termination failed with return code $(retcode)." end # freeInstance (hard) if freeInstance - fmi2FreeInstance!(c; popComponent=popComponent) + fmi2FreeInstance!(c; popComponent = popComponent) # , doccall=freeInstance c = 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..ce98a80 100644 --- a/src/FMI3/c.jl +++ b/src/FMI3/c.jl @@ -10,21 +10,75 @@ # 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 """ @@ -48,24 +102,39 @@ Function that is called in the FMU, usually if an fmi3XXX function, does not beh - FMISpec3.0 Link: [https://fmi-standard.org/](https://fmi-standard.org/) - FMISpec3.0: 2.3.1. Super State: FMU State Setable """ -function fmi3CallbackLogger(_instanceEnvironment::Ptr{FMU3InstanceEnvironment}, - _status::Cuint, +function fmi3CallbackLogger( + _instanceEnvironment::Ptr{FMU3InstanceEnvironment}, + status::fmi3Status, _category::Ptr{Cchar}, - _message::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 @@ -106,15 +175,18 @@ If the ModelDescription has the "providesIntermediateUpdate" flag, the Intermedi - FMISpec3.0 Link: [https://fmi-standard.org/](https://fmi-standard.org/) - FMISpec3.0: 4.2.2. State: Intermediate Update Mode """ -function fmi3CallbackIntermediateUpdate(instanceEnvironment::Ptr{Cvoid}, +function fmi3CallbackIntermediateUpdate( + instanceEnvironment::Ptr{Cvoid}, intermediateUpdateTime::fmi3Float64, intermediateVariableSetRequested::fmi3Boolean, intermediateVariableGetAllowed::fmi3Boolean, intermediateStepFinished::fmi3Boolean, canReturnEarly::fmi3Boolean, earlyReturnRequested::Ptr{fmi3Boolean}, - earlyReturnTime::Ptr{fmi3Float64}) - @debug "To be implemented!" + earlyReturnTime::Ptr{fmi3Float64}, +) + + @debug "fmi3CallbackIntermediateUpdate be implemented!" # [ToDo] end """ @@ -138,41 +210,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 """ @@ -216,12 +255,12 @@ end # helper function checkStatus(c::FMU3Instance, status::fmi3Status) @assert (status != fmi3StatusWarning) || !c.fmu.executionConfig.assertOnWarning "Assert on `fmi3StatusWarning`. See stack for errors." - + if status == fmi3StatusError c.state = fmi3InstanceStateError @assert !c.fmu.executionConfig.assertOnError "Assert on `fmi3StatusError`. See stack for errors." - - elseif status == fmi3StatusFatal + + elseif status == fmi3StatusFatal c.state = fmi3InstanceStateFatal @assert false "Assert on `fmi3StatusFatal`. See stack for errors." end @@ -255,8 +294,19 @@ More detailed: - FMISpec3.0: 2.3.1. Super State: FMU State Setable 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) +function fmi3SetDebugLogging( + c::FMU3Instance, + logginOn::fmi3Boolean, + nCategories::UInt, + categories::Ptr{Nothing}, +) + status = fmi3SetDebugLogging( + c.fmu.cSetDebugLogging, + c.addr, + logginOn, + nCategories, + categories, + ) checkStatus(c, status) return status end @@ -299,16 +349,27 @@ More detailed: - FMISpec3.0: 2.3.2. State: Instantiated See also [`fmi3EnterInitializationMode`](@ref). """ -function fmi3EnterInitializationMode(c::FMU3Instance, toleranceDefined::fmi3Boolean, +function fmi3EnterInitializationMode( + c::FMU3Instance, + toleranceDefined::fmi3Boolean, tolerance::fmi3Float64, startTime::fmi3Float64, stopTimeDefined::fmi3Boolean, - stopTime::fmi3Float64) + stopTime::fmi3Float64, +) 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 @@ -346,18 +407,19 @@ function fmi3ExitInitializationMode(c::FMU3Instance) if c.state != fmi3InstanceStateInitializationMode @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 + if c.type == fmi3TypeCoSimulation && + !c.fmu.modelDescription.coSimulation.hasEventMode c.state = fmi3InstanceStateStepMode elseif c.type == fmi3TypeScheduledExecution c.state = fmi3InstanceStateClockActivationMode else c.state = fmi3InstanceStateEventMode end - end + end return status end @@ -390,21 +452,24 @@ More detailed: - FMISpec3.0: 2.3.4. Super State: Initialized See also [`fmi3Terminate`](@ref). """ -function fmi3Terminate(c::FMU3Instance; soft::Bool=false) - if c.state != fmi3InstanceStateContinuousTimeMode && c.state != fmi3InstanceStateEventMode && c.state != fmi3InstanceStateClockActivationMode && c.state != fmi3InstanceStateStepMode - if soft +function fmi3Terminate(c::FMU3Instance; soft::Bool = false) + if c.state != fmi3InstanceStateContinuousTimeMode && + c.state != fmi3InstanceStateEventMode && + c.state != fmi3InstanceStateClockActivationMode && + c.state != fmi3InstanceStateStepMode + if soft return fmi3StatusOK else @warn "fmi3Terminate(_): Needs to be called in state `fmi3InstanceStateContinuousTimeMode`, `fmi3InstanceStateEventMode`, `fmi3InstanceStateClockActivationMode` or `fmi3InstanceStateStepMode`." end end - - status = fmi3Terminate(c.fmu.cTerminate, c.compAddr) + + status = fmi3Terminate(c.fmu.cTerminate, c.addr) checkStatus(c, status) - if status == fmi3StatusOK + if status == fmi3StatusOK c.state = fmi3InstanceStateTerminated - end - + end + return status end @@ -438,36 +503,36 @@ See also [`fmi3Reset`](@ref). """ function fmi3Reset(c::FMU3Instance; soft::Bool = false) if c.state != fmi3InstanceStateTerminated && c.state != fmi3InstanceStateError - if soft + if soft return fmi3StatusOK else @warn "fmi3Reset(_): Needs to be called in state `fmi3InstanceStateTerminated` or `fmi3InstanceStateError`." end 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 - end + end return status end end @@ -502,9 +567,14 @@ More detailed: - FMISpec3.0: 2.2.6.2. Getting and Setting Variable Values 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) +function fmi3GetFloat32!( + c::FMU3Instance, + vr::AbstractArray{fmi3ValueReference}, + nvr::Csize_t, + value::AbstractArray{fmi3Float32}, + nvalue::Csize_t, +) + status = fmi3GetFloat32!(c.fmu.cGetFloat32, c.addr, vr, nvr, value, nvalue) checkStatus(c, status) return status end @@ -539,9 +609,14 @@ More detailed: - FMISpec3.0: 2.2.6.2. Getting and Setting Variable Values 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) +function fmi3SetFloat32( + c::FMU3Instance, + vr::AbstractArray{fmi3ValueReference}, + nvr::Csize_t, + value::AbstractArray{fmi3Float32}, + nvalue::Csize_t, +) + status = fmi3SetFloat32(c.fmu.cSetFloat32, c.addr, vr, nvr, value, nvalue) checkStatus(c, status) return status end @@ -575,9 +650,14 @@ More detailed: - FMISpec3.0: 2.2.6.2. Getting and Setting Variable Values 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) +function fmi3GetFloat64!( + c::FMU3Instance, + vr::AbstractArray{fmi3ValueReference}, + nvr::Csize_t, + value::AbstractArray{fmi3Float64}, + nvalue::Csize_t, +) + status = fmi3GetFloat64!(c.fmu.cGetFloat64, c.addr, vr, nvr, value, nvalue) checkStatus(c, status) return status end @@ -612,10 +692,36 @@ 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) - status = fmi3SetFloat64(c.fmu.cSetFloat64, - c.compAddr, vr, nvr, value, nvalue) - checkStatus(c, status) +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.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 @@ -649,9 +755,14 @@ More detailed: - FMISpec3.0: 2.2.6.2. Getting and Setting Variable Values 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) +function fmi3GetInt8!( + c::FMU3Instance, + vr::AbstractArray{fmi3ValueReference}, + nvr::Csize_t, + value::AbstractArray{fmi3Int8}, + nvalue::Csize_t, +) + status = fmi3GetInt8!(c.fmu.cGetInt8, c.addr, vr, nvr, value, nvalue) checkStatus(c, status) return status end @@ -684,9 +795,14 @@ More detailed: - FMISpec3.0: 2.2.4 Status Returned by Functions - FMISpec3.0: 2.2.6.2. Getting and Setting Variable Values """ -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) +function fmi3SetInt8( + c::FMU3Instance, + vr::AbstractArray{fmi3ValueReference}, + nvr::Csize_t, + value::AbstractArray{fmi3Int8}, + nvalue::Csize_t, +) + status = fmi3SetInt8(c.fmu.cSetInt8, c.addr, vr, nvr, value, nvalue) checkStatus(c, status) return status end @@ -721,9 +837,14 @@ More detailed: - FMISpec3.0: 2.2.6.2. Getting and Setting Variable Values 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) +function fmi3GetUInt8!( + c::FMU3Instance, + vr::AbstractArray{fmi3ValueReference}, + nvr::Csize_t, + value::AbstractArray{fmi3UInt8}, + nvalue::Csize_t, +) + status = fmi3GetUInt8!(c.fmu.cGetUInt8, c.addr, vr, nvr, value, nvalue) checkStatus(c, status) return status end @@ -757,9 +878,14 @@ More detailed: - FMISpec3.0: 2.2.6.2. Getting and Setting Variable Values 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) +function fmi3SetUInt8( + c::FMU3Instance, + vr::AbstractArray{fmi3ValueReference}, + nvr::Csize_t, + value::AbstractArray{fmi3UInt8}, + nvalue::Csize_t, +) + status = fmi3SetUInt8(c.fmu.cSetUInt8, c.addr, vr, nvr, value, nvalue) checkStatus(c, status) return status end @@ -794,11 +920,16 @@ More detailed: - FMISpec3.0: 2.2.6.2. Getting and Setting Variable Values 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) +function fmi3GetInt16!( + c::FMU3Instance, + vr::AbstractArray{fmi3ValueReference}, + nvr::Csize_t, + value::AbstractArray{fmi3Int16}, + nvalue::Csize_t, +) + status = fmi3GetInt16!(c.fmu.cGetInt16, c.addr, vr, nvr, value, nvalue) checkStatus(c, status) - return status + return status end """ @@ -830,9 +961,14 @@ More detailed: - FMISpec3.0: 2.2.6.2. Getting and Setting Variable Values 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) +function fmi3SetInt16( + c::FMU3Instance, + vr::AbstractArray{fmi3ValueReference}, + nvr::Csize_t, + value::AbstractArray{fmi3Int16}, + nvalue::Csize_t, +) + status = fmi3SetInt16(c.fmu.cSetInt16, c.addr, vr, nvr, value, nvalue) checkStatus(c, status) return status end @@ -867,9 +1003,14 @@ More detailed: - FMISpec3.0: 2.2.6.2. Getting and Setting Variable Values 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) +function fmi3GetUInt16!( + c::FMU3Instance, + vr::AbstractArray{fmi3ValueReference}, + nvr::Csize_t, + value::AbstractArray{fmi3UInt16}, + nvalue::Csize_t, +) + status = fmi3GetUInt16!(c.fmu.cGetUInt16, c.addr, vr, nvr, value, nvalue) checkStatus(c, status) return status end @@ -902,9 +1043,14 @@ More detailed: - FMISpec3.0: 2.2.4 Status Returned by Functions - FMISpec3.0: 2.2.6.2. Getting and Setting Variable Values """ -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) +function fmi3SetUInt16( + c::FMU3Instance, + vr::AbstractArray{fmi3ValueReference}, + nvr::Csize_t, + value::AbstractArray{fmi3UInt16}, + nvalue::Csize_t, +) + status = fmi3SetUInt16(c.fmu.cSetUInt16, c.addr, vr, nvr, value, nvalue) checkStatus(c, status) return status end @@ -938,9 +1084,14 @@ More detailed: - FMISpec3.0: 2.2.6.2. Getting and Setting Variable Values 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) +function fmi3GetInt32!( + c::FMU3Instance, + vr::AbstractArray{fmi3ValueReference}, + nvr::Csize_t, + value::AbstractArray{fmi3Int32}, + nvalue::Csize_t, +) + status = fmi3GetInt32!(c.fmu.cGetInt32, c.addr, vr, nvr, value, nvalue) checkStatus(c, status) return status end @@ -974,9 +1125,14 @@ More detailed: - FMISpec3.0: 2.2.6.2. Getting and Setting Variable Values 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) +function fmi3SetInt32( + c::FMU3Instance, + vr::AbstractArray{fmi3ValueReference}, + nvr::Csize_t, + value::AbstractArray{fmi3Int32}, + nvalue::Csize_t, +) + status = fmi3SetInt32(c.fmu.cSetInt32, c.addr, vr, nvr, value, nvalue) checkStatus(c, status) return status end @@ -1011,9 +1167,14 @@ More detailed: - FMISpec3.0: 2.2.6.2. Getting and Setting Variable Values 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) +function fmi3GetUInt32!( + c::FMU3Instance, + vr::AbstractArray{fmi3ValueReference}, + nvr::Csize_t, + value::AbstractArray{fmi3UInt32}, + nvalue::Csize_t, +) + status = fmi3GetUInt32!(c.fmu.cGetUInt32, c.addr, vr, nvr, value, nvalue) checkStatus(c, status) return status end @@ -1047,9 +1208,14 @@ More detailed: - FMISpec3.0: 2.2.6.2. Getting and Setting Variable Values 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) +function fmi3SetUInt32( + c::FMU3Instance, + vr::AbstractArray{fmi3ValueReference}, + nvr::Csize_t, + value::AbstractArray{fmi3UInt32}, + nvalue::Csize_t, +) + status = fmi3SetUInt32(c.fmu.cSetUInt32, c.addr, vr, nvr, value, nvalue) checkStatus(c, status) return status end @@ -1084,9 +1250,14 @@ More detailed: - FMISpec3.0: 2.2.6.2. Getting and Setting Variable Values 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) +function fmi3GetInt64!( + c::FMU3Instance, + vr::AbstractArray{fmi3ValueReference}, + nvr::Csize_t, + value::AbstractArray{fmi3Int64}, + nvalue::Csize_t, +) + status = fmi3GetInt64!(c.fmu.cGetInt64, c.addr, vr, nvr, value, nvalue) checkStatus(c, status) return status end @@ -1120,9 +1291,14 @@ More detailed: - FMISpec3.0: 2.2.6.2. Getting and Setting Variable Values 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) +function fmi3SetInt64( + c::FMU3Instance, + vr::AbstractArray{fmi3ValueReference}, + nvr::Csize_t, + value::AbstractArray{fmi3Int64}, + nvalue::Csize_t, +) + status = fmi3SetInt64(c.fmu.cSetInt64, c.addr, vr, nvr, value, nvalue) checkStatus(c, status) return status end @@ -1157,9 +1333,14 @@ More detailed: - FMISpec3.0: 2.2.6.2. Getting and Setting Variable Values 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) +function fmi3GetUInt64!( + c::FMU3Instance, + vr::AbstractArray{fmi3ValueReference}, + nvr::Csize_t, + value::AbstractArray{fmi3UInt64}, + nvalue::Csize_t, +) + status = fmi3GetUInt64!(c.fmu.cGetUInt64, c.addr, vr, nvr, value, nvalue) checkStatus(c, status) return status end @@ -1193,9 +1374,14 @@ More detailed: - FMISpec3.0: 2.2.6.2. Getting and Setting Variable Values 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) +function fmi3SetUInt64( + c::FMU3Instance, + vr::AbstractArray{fmi3ValueReference}, + nvr::Csize_t, + value::AbstractArray{fmi3UInt64}, + nvalue::Csize_t, +) + status = fmi3SetUInt64(c.fmu.cSetUInt64, c.addr, vr, nvr, value, nvalue) checkStatus(c, status) return status end @@ -1229,9 +1415,14 @@ More detailed: - FMISpec3.0: 2.2.6.2. Getting and Setting Variable Values 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) +function fmi3GetBoolean!( + c::FMU3Instance, + vr::AbstractArray{fmi3ValueReference}, + nvr::Csize_t, + value::AbstractArray{fmi3Boolean}, + nvalue::Csize_t, +) + status = fmi3GetBoolean!(c.fmu.cGetBoolean, c.addr, vr, nvr, value, nvalue) checkStatus(c, status) return status end @@ -1265,9 +1456,14 @@ More detailed: - FMISpec3.0: 2.2.6.2. Getting and Setting Variable Values 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) +function fmi3SetBoolean( + c::FMU3Instance, + vr::AbstractArray{fmi3ValueReference}, + nvr::Csize_t, + value::AbstractArray{fmi3Boolean}, + nvalue::Csize_t, +) + status = fmi3SetBoolean(c.fmu.cSetBoolean, c.addr, vr, nvr, value, nvalue) checkStatus(c, status) return status end @@ -1302,9 +1498,14 @@ More detailed: - FMISpec3.0: 2.2.6.2. Getting and Setting Variable Values 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) +function fmi3GetString!( + c::FMU3Instance, + vr::AbstractArray{fmi3ValueReference}, + nvr::Csize_t, + value::Vector{Ptr{Cchar}}, + nvalue::Csize_t, +) + status = fmi3GetString!(c.fmu.cGetString, c.addr, vr, nvr, value, nvalue) checkStatus(c, status) return status end @@ -1337,10 +1538,15 @@ More detailed: - FMISpec3.0: 2.2.4 Status Returned by Functions - FMISpec3.0: 2.2.6.2. Getting and Setting Variable Values 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) +""" +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.addr, vr, nvr, value, nvalue) checkStatus(c, status) return status end @@ -1375,9 +1581,15 @@ More detailed: - FMISpec3.0: 2.2.6.2. Getting and Setting Variable Values 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) +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.addr, vr, nvr, valueSizes, value, nvalue) checkStatus(c, status) return status end @@ -1412,9 +1624,15 @@ More detailed: - FMISpec3.0: 2.2.6.2. Getting and Setting Variable Values 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) +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.addr, vr, nvr, valueSizes, value, nvalue) checkStatus(c, status) return status end @@ -1449,9 +1667,13 @@ More detailed: - FMISpec3.0: 2.2.6.2. Getting and Setting Variable Values 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) +function fmi3GetClock!( + c::FMU3Instance, + vr::AbstractArray{fmi3ValueReference}, + nvr::Csize_t, + value::AbstractArray{fmi3Clock}, +) + status = fmi3GetClock!(c.fmu.cGetClock, c.addr, vr, nvr, value) checkStatus(c, status) return status end @@ -1485,9 +1707,13 @@ More detailed: - FMISpec3.0: 2.2.6.2. Getting and Setting Variable Values 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) +function fmi3SetClock( + c::FMU3Instance, + vr::AbstractArray{fmi3ValueReference}, + nvr::Csize_t, + value::AbstractArray{fmi3Clock}, +) + status = fmi3SetClock(c.fmu.cSetClock, c.addr, vr, nvr, value) checkStatus(c, status) return status end @@ -1519,8 +1745,7 @@ More detailed: See also [`fmi3GetFMUState!`](@ref). """ function fmi3GetFMUState!(c::FMU3Instance, FMUstate::Ref{fmi3FMUState}) - status = fmi3GetFMUState!(c.fmu.cGetFMUState, - c.compAddr, FMUstate) + status = fmi3GetFMUState!(c.fmu.cGetFMUState, c.addr, FMUstate) checkStatus(c, status) return status end @@ -1552,15 +1777,14 @@ More detailed: See also [`fmi3SetFMUState`](@ref). """ function fmi3SetFMUState(c::FMU3Instance, FMUstate::fmi3FMUState) - status = fmi3SetFMUState(c.fmu.cSetFMUState, - c.compAddr, FMUstate) + status = fmi3SetFMUState(c.fmu.cSetFMUState, 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 +1807,8 @@ 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 @@ -1617,9 +1840,13 @@ More detailed: - FMISpec3.0: 2.2.6.4. Getting and Setting the Complete FMU State See also [`fmi3SerializedFMUStateSize!`](@ref). """ -function fmi3SerializedFMUStateSize!(c::FMU3Instance, FMUstate::fmi3FMUState, size::Ref{Csize_t}) - status = fmi3SerializedFMUStateSize!(c.fmu.cSerializedFMUStateSize, - c.compAddr, FMUstate, size) +function fmi3SerializedFMUStateSize!( + c::FMU3Instance, + FMUstate::fmi3FMUState, + size::Ref{Csize_t}, +) + status = + fmi3SerializedFMUStateSize!(c.fmu.cSerializedFMUStateSize, c.addr, FMUstate, size) checkStatus(c, status) return status end @@ -1652,11 +1879,21 @@ More detailed: - FMISpec3.0: 2.2.6.4. Getting and Setting the Complete FMU State 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) +function fmi3SerializeFMUState!( + c::FMU3Instance, + FMUstate::fmi3FMUState, + serialzedState::AbstractArray{fmi3Byte}, + size::Csize_t, +) + status = fmi3SerializeFMUState!( + c.fmu.cSerializeFMUState, + c.addr, + FMUstate, + serialzedState, + size, + ) checkStatus(c, status) - return status + return status end """ @@ -1687,9 +1924,19 @@ More detailed: - FMISpec3.0: 2.2.6.4. Getting and Setting the Complete FMU State 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) +function fmi3DeSerializeFMUState!( + c::FMU3Instance, + serialzedState::AbstractArray{fmi3Byte}, + size::Csize_t, + FMUstate::Ref{fmi3FMUState}, +) + status = fmi3DeSerializeFMUState!( + c.fmu.cDeSerializeFMUState, + c.addr, + serialzedState, + size, + FMUstate, + ) checkStatus(c, status) return status end @@ -1722,9 +1969,13 @@ More detailed: - FMISpec3.0: 2.2.9. Clocks 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) +function fmi3SetIntervalDecimal( + c::FMU3Instance, + vr::AbstractArray{fmi3ValueReference}, + nvr::Csize_t, + intervals::AbstractArray{fmi3Float64}, +) + status = fmi3SetIntervalDecimal(c.fmu.cSetIntervalDecimal, c.addr, vr, nvr, intervals) checkStatus(c, status) return status end @@ -1758,9 +2009,21 @@ More detailed: - FMISpec3.0: 2.2.9. Clocks 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) +function fmi3SetIntervalFraction( + c::FMU3Instance, + vr::AbstractArray{fmi3ValueReference}, + nvr::Csize_t, + intervalCounters::AbstractArray{fmi3UInt64}, + resolutions::AbstractArray{fmi3UInt64}, +) + status = fmi3SetIntervalFraction( + c.fmu.cSetIntervalFraction, + c.addr, + vr, + nvr, + intervalCounters, + resolutions, + ) checkStatus(c, status) return status end @@ -1801,9 +2064,21 @@ More detailed: - FMISpec3.0: 2.2.9. Clocks 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) +function fmi3GetIntervalDecimal!( + c::FMU3Instance, + vr::AbstractArray{fmi3ValueReference}, + nvr::Csize_t, + intervals::AbstractArray{fmi3Float64}, + qualifiers::fmi3IntervalQualifier, +) + status = fmi3GetIntervalDecimal!( + c.fmu.cGetIntervalDecimal, + c.addr, + vr, + nvr, + intervals, + qualifiers, + ) checkStatus(c, status) return status end @@ -1845,9 +2120,23 @@ More detailed: - FMISpec3.0: 2.2.9. Clocks 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) +function fmi3GetIntervalFraction!( + c::FMU3Instance, + vr::AbstractArray{fmi3ValueReference}, + nvr::Csize_t, + intervalCounters::AbstractArray{fmi3UInt64}, + resolutions::AbstractArray{fmi3UInt64}, + qualifiers::fmi3IntervalQualifier, +) + status = fmi3GetIntervalFraction!( + c.fmu.cGetIntervalFraction, + c.addr, + vr, + nvr, + intervalCounters, + resolutions, + qualifiers, + ) checkStatus(c, status) return status end @@ -1881,9 +2170,13 @@ More detailed: - FMISpec3.0: 2.2.9. Clocks 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) +function fmi3GetShiftDecimal!( + c::FMU3Instance, + vr::AbstractArray{fmi3ValueReference}, + nvr::Csize_t, + shifts::AbstractArray{fmi3Float64}, +) + status = fmi3GetShiftDecimal!(c.fmu.cGetShiftDecimal, c.addr, vr, nvr, shifts) checkStatus(c, status) return status end @@ -1918,9 +2211,21 @@ More detailed: - FMISpec3.0: 2.2.9. Clocks 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) +function fmi3GetShiftFraction!( + c::FMU3Instance, + vr::AbstractArray{fmi3ValueReference}, + nvr::Csize_t, + shiftCounters::AbstractArray{fmi3UInt64}, + resolutions::AbstractArray{fmi3UInt64}, +) + status = fmi3GetShiftFraction!( + c.fmu.cGetShiftFraction, + c.addr, + vr, + nvr, + shiftCounters, + resolutions, + ) checkStatus(c, status) return status end @@ -1955,9 +2260,17 @@ More detailed: - FMISpec3.0: 5.2.2. State: Clock Activation Mode See also [`fmi3ActivateModelPartition`](@ref). """ -function fmi3ActivateModelPartition(c::FMU3Instance, vr::fmi3ValueReference, activationTime::AbstractArray{fmi3Float64}) - status = fmi3ActivateModelPartition(c.fmu.cActivateModelPartition, - c.compAddr, vr, activationTime) +function fmi3ActivateModelPartition( + c::FMU3Instance, + vr::fmi3ValueReference, + activationTime::AbstractArray{fmi3Float64}, +) + status = fmi3ActivateModelPartition( + c.fmu.cActivateModelPartition, + c.addr, + vr, + activationTime, + ) checkStatus(c, status) return status end @@ -1991,9 +2304,17 @@ More detailed: - FMISpec3.0: 2.2.10. Dependencies of Variables See also [`fmi3GetNumberOfVariableDependencies!`](@ref). """ -function fmi3GetNumberOfVariableDependencies!(c::FMU3Instance, vr::fmi3ValueReference, nvr::Ref{Csize_t}) - status = fmi3GetNumberOfVariableDependencies!(c.fmu.cGetNumberOfVariableDependencies, - c.compAddr, vr, nvr) +function fmi3GetNumberOfVariableDependencies!( + c::FMU3Instance, + vr::fmi3ValueReference, + nvr::Ref{Csize_t}, +) + status = fmi3GetNumberOfVariableDependencies!( + c.fmu.cGetNumberOfVariableDependencies, + c.addr, + vr, + nvr, + ) checkStatus(c, status) return status end @@ -2035,10 +2356,25 @@ More detailed: - FMISpec3.0: 2.2.10. Dependencies of Variables 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) +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.addr, + vr, + elementIndiceOfDependents, + independents, + elementIndiceOfInpendents, + dependencyKind, + ndependencies, + ) checkStatus(c, status) return status end @@ -2100,22 +2436,37 @@ More detailed: - FMISpec3.0: 2.2.11. Getting Partial Derivatives See also [`fmi3GetDirectionalDerivative`](@ref). """ -function fmi3GetDirectionalDerivative!(c::FMU3Instance, - unknowns::AbstractArray{fmi3ValueReference}, - nUnknowns::Csize_t, - knowns::AbstractArray{fmi3ValueReference}, - nKnowns::Csize_t, - seed::AbstractArray{fmi3Float64}, - nSeed::Csize_t, - sensitivity::AbstractArray{fmi3Float64}, - nSensitivity::Csize_t) - @assert fmi3ProvidesDirectionalDerivatives(c.fmu) ["fmi3GetDirectionalDerivative!(...): This FMU does not support build-in directional derivatives!"] +function fmi3GetDirectionalDerivative!( + c::FMU3Instance, + unknowns::AbstractArray{fmi3ValueReference}, + nUnknowns::Csize_t, + knowns::AbstractArray{fmi3ValueReference}, + nKnowns::Csize_t, + seed::AbstractArray{fmi3Float64}, + nSeed::Csize_t, + sensitivity::AbstractArray{fmi3Float64}, + nSensitivity::Csize_t, +) + + @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) + status = fmi3GetDirectionalDerivative!( + c.fmu.cGetDirectionalDerivative, + c.addr, + unknowns, + nUnknowns, + knowns, + nKnowns, + seed, + nSeed, + sensitivity, + nSensitivity, + ) checkStatus(c, status) return status - + end # TODO not tested @@ -2175,22 +2526,36 @@ More detailed: - FMISpec3.0: 2.2.11. Getting Partial Derivatives See also [`fmi3GetAdjointDerivative!`](@ref). """ -function fmi3GetAdjointDerivative!(c::FMU3Instance, - unknowns::AbstractArray{fmi3ValueReference}, - nUnknowns::Csize_t, - knowns::AbstractArray{fmi3ValueReference}, - nKnowns::Csize_t, - seed::AbstractArray{fmi3Float64}, - nSeed::Csize_t, - sensitivity::AbstractArray{fmi3Float64}, - nSensitivity::Csize_t) - @assert fmi3ProvidesAdjointDerivatives(c.fmu) ["fmi3GetAdjointDerivative!(...): This FMU does not support build-in adjoint derivatives!"] +function fmi3GetAdjointDerivative!( + c::FMU3Instance, + unknowns::AbstractArray{fmi3ValueReference}, + nUnknowns::Csize_t, + knowns::AbstractArray{fmi3ValueReference}, + nKnowns::Csize_t, + seed::AbstractArray{fmi3Float64}, + nSeed::Csize_t, + sensitivity::AbstractArray{fmi3Float64}, + nSensitivity::Csize_t, +) + @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) + status = fmi3GetAdjointDerivative!( + c.fmu.cGetAdjointDerivative, + c.addr, + unknowns, + nUnknowns, + knowns, + nKnowns, + seed, + nSeed, + sensitivity, + nSensitivity, + ) checkStatus(c, status) return status - + end """ @@ -2224,9 +2589,23 @@ More detailed: 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) +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.addr, + vr, + nValueReferences, + order, + values, + nValues, + ) checkStatus(c, status) return status end @@ -2260,20 +2639,21 @@ More detailed: See also [`fmi3EnterConfigurationMode`](@ref). """ -function fmi3EnterConfigurationMode(c::FMU3Instance; soft::Bool=false) - if c.state != fmi3InstanceStateInstantiated && (c.state != fmi3InstanceStateStepMode && fmi3IsCoSimulation(c.fmu)) && (c.state != fmi3InstanceEventMode && fmi3IsModelExchange(c.fmu)) - if soft +function fmi3EnterConfigurationMode(c::FMU3Instance; soft::Bool = false) + if c.state != fmi3InstanceStateInstantiated && + (c.state != fmi3InstanceStateStepMode && fmi3IsCoSimulation(c.fmu)) && + (c.state != fmi3InstanceEventMode && fmi3IsModelExchange(c.fmu)) + if soft return fmi3StatusOK else @warn "fmi3EnterConfigurationMode(...): Called at the wrong time." end end - status = fmi3EnterConfigurationMode(c.fmu.cEnterConfigurationMode, - c.compAddr) + status = fmi3EnterConfigurationMode(c.fmu.cEnterConfigurationMode, c.addr) checkStatus(c, status) if status == fmi3StatusOK - if c.state == fmi3InstanceStateInstantiate + if c.state == fmi3InstanceStateInstantiate c.state = fmi3InstanceStateConfigurationMode else c.state = fmi3InstanceStateReconfigurationMode @@ -2312,20 +2692,20 @@ More detailed: See also [`fmi3ExitConfigurationMode`](@ref). """ function fmi3ExitConfigurationMode(c::FMU3Instance; soft::Bool = false) - if c.state != fmi3InstanceStateConfigurationMode && c.state != fmi3InstanceStateReconfigurationMode - if soft + if c.state != fmi3InstanceStateConfigurationMode && + c.state != fmi3InstanceStateReconfigurationMode + if soft return fmi3StatusOK else @warn "fmi3ExitConfigurationMode(...): Called at the wrong time." end end - status = fmi3ExitConfigurationMode(c.fmu.cExitConfigurationMode, - c.compAddr) + status = fmi3ExitConfigurationMode(c.fmu.cExitConfigurationMode, c.addr) checkStatus(c, status) if status == fmi3StatusOK if c.state == fmi3InstanceStateConfigurationMode - c.state = fmi3InstanceStateInstantiate + c.state = fmi3InstanceStateInstantiate elseif fmi3IsCoSimulation(c.fmu) c.state = fmi3InstanceStateStepMode elseif fmi3IsModelExchange(c.fmu) @@ -2367,8 +2747,11 @@ More detailed: See also [`fmi3GetNumberOfContinuousStates!`](@ref). """ function fmi3GetNumberOfContinuousStates!(c::FMU3Instance, nContinuousStates::Ref{Csize_t}) - status = fmi3GetNumberOfContinuousStates!(c.fmu.cGetNumberOfContinuousStates, - c.compAddr, nContinuousStates) + status = fmi3GetNumberOfContinuousStates!( + c.fmu.cGetNumberOfContinuousStates, + c.addr, + nContinuousStates, + ) checkStatus(c, status) return status end @@ -2403,8 +2786,11 @@ More detailed: See also [`fmi3GetNumberOfEventIndicators!`](@ref). """ function fmi3GetNumberOfEventIndicators!(c::FMU3Instance, nEventIndicators::Ref{Csize_t}) - status = fmi3GetNumberOfEventIndicators!(c.fmu.cGetNumberOfEventIndicators, - c.compAddr, nEventIndicators) + status = fmi3GetNumberOfEventIndicators!( + c.fmu.cGetNumberOfEventIndicators, + c.addr, + nEventIndicators, + ) checkStatus(c, status) return status end @@ -2439,9 +2825,17 @@ More detailed: See also [`fmi3GetContinuousStates!`](@ref). """ -function fmi3GetContinuousStates!(c::FMU3Instance, nominals::AbstractArray{fmi3Float64}, nContinuousStates::Csize_t) - status = fmi3GetContinuousStates!(c.fmu.cGetContinuousStates, - c.compAddr, nominals, nContinuousStates) +function fmi3GetContinuousStates!( + c::FMU3Instance, + nominals::AbstractArray{fmi3Float64}, + nContinuousStates::Csize_t, +) + status = fmi3GetContinuousStates!( + c.fmu.cGetContinuousStates, + c.addr, + nominals, + nContinuousStates, + ) checkStatus(c, status) return status end @@ -2477,9 +2871,17 @@ More detailed: 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) +function fmi3GetNominalsOfContinuousStates!( + c::FMU3Instance, + x_nominal::AbstractArray{fmi3Float64}, + nx::Csize_t, +) + status = fmi3GetNominalsOfContinuousStates!( + c.fmu.cGetNominalsOfContinuousStates, + c.addr, + x_nominal, + nx, + ) checkStatus(c, status) return status end @@ -2513,8 +2915,7 @@ More detailed: See also [`fmi3EvaluateDiscreteStates`](@ref). """ function fmi3EvaluateDiscreteStates(c::FMU3Instance) - status = fmi3EvaluateDiscreteStates(c.fmu.cEvaluateDiscreteStates, - c.compAddr) + status = fmi3EvaluateDiscreteStates(c.fmu.cEvaluateDiscreteStates, c.addr) checkStatus(c, status) return status end @@ -2526,7 +2927,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}`: @@ -2552,11 +2952,25 @@ More detailed: - FMISpec3.0: 2.3.5. State: Event Mode """ -function fmi3UpdateDiscreteStates(c::FMU3Instance, discreteStatesNeedUpdate::Ref{fmi3Boolean}, terminateSimulation::Ref{fmi3Boolean}, - 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) +function fmi3UpdateDiscreteStates( + c::FMU3Instance, + discreteStatesNeedUpdate::Ref{fmi3Boolean}, + terminateSimulation::Ref{fmi3Boolean}, + nominalsOfContinuousStatesChanged::Ref{fmi3Boolean}, + valuesOfContinuousStatesChanged::Ref{fmi3Boolean}, + nextEventTimeDefined::Ref{fmi3Boolean}, + nextEventTime::Ref{fmi3Float64}, +) + status = fmi3UpdateDiscreteStates( + c.fmu.cUpdateDiscreteStates, + c.addr, + discreteStatesNeedUpdate, + terminateSimulation, + nominalsOfContinuousStatesChanged, + valuesOfContinuousStatesChanged, + nextEventTimeDefined, + nextEventTime, + ) checkStatus(c, status) return status end @@ -2591,17 +3005,16 @@ More detailed: See also [`fmi3EnterContinuousTimeMode`](@ref). """ -function fmi3EnterContinuousTimeMode(c::FMU3Instance; soft::Bool=false) +function fmi3EnterContinuousTimeMode(c::FMU3Instance; soft::Bool = false) if c.state != fmi3InstanceStateEventMode - if soft + if soft return fmi3StatusOK else @warn "fmi3EnterContinuousTimeMode(...): Needs to be called in state `fmi3InstanceStateEventMode`." end end - status = fmi3EnterContinuousTimeMode(c.fmu.cEnterContinuousTimeMode, - c.compAddr) + status = fmi3EnterContinuousTimeMode(c.fmu.cEnterContinuousTimeMode, c.addr) checkStatus(c, status) if status == fmi3StatusOK c.state = fmi3InstanceStateContinuousTimeMode @@ -2640,21 +3053,20 @@ See also [`fmi3EnterStepMode`](@ref). """ function fmi3EnterStepMode(c::FMU3Instance; soft::Bool = false) if c.state != fmi3InstanceStateEventMode - if soft + if soft return fmi3StatusOK else @warn "fmi3EnterStepMode(...): Needs to be called in state `fmi3InstanceStateEventMode`." end end - status = fmi3EnterStepMode(c.fmu.cEnterStepMode, - c.compAddr) + status = fmi3EnterStepMode(c.fmu.cEnterStepMode, c.addr) checkStatus(c, status) if status == fmi3StatusOK c.state = fmi3InstanceStateStepMode end return status - + end """ @@ -2684,14 +3096,21 @@ More detailed: See also [`fmi3SetTime`](@ref). """ -function fmi3SetTime(c::FMU3Instance, time::fmi3Float64) - - status = fmi3SetTime(c.fmu.cSetTime, - c.compAddr, time + c.t_offset) +function fmi3SetTime(c::FMU3Instance, time::fmi3Float64; track::Bool = true) + + status = fmi3SetTime(c.fmu.cSetTime, 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 @@ -2727,11 +3146,12 @@ More detailed: See also [`fmi3SetContinuousStates`](@ref). """ -function fmi3SetContinuousStates(c::FMU3Instance, - x::AbstractArray{fmi3Float64}, - nx::Csize_t) - status = fmi3SetContinuousStates(c.fmu.cSetContinuousStates, - c.compAddr, x, nx) +function fmi3SetContinuousStates( + c::FMU3Instance, + x::AbstractArray{fmi3Float64}, + nx::Csize_t, +) + status = fmi3SetContinuousStates(c.fmu.cSetContinuousStates, c.addr, x, nx) checkStatus(c, status) return status end @@ -2766,11 +3186,17 @@ More detailed: See also [`fmi3GetContinuousStateDerivatives!`](@ref). """ -function fmi3GetContinuousStateDerivatives!(c::FMU3Instance, - derivatives::AbstractArray{fmi3Float64}, - nx::Csize_t) - status = fmi3GetContinuousStateDerivatives!(c.fmu.cGetContinuousStateDerivatives, - c.compAddr, derivatives, nx) +function fmi3GetContinuousStateDerivatives!( + c::FMU3Instance, + derivatives::AbstractArray{fmi3Float64}, + nx::Csize_t, +) + status = fmi3GetContinuousStateDerivatives!( + c.fmu.cGetContinuousStateDerivatives, + c.addr, + derivatives, + nx, + ) checkStatus(c, status) return status end @@ -2803,9 +3229,12 @@ More detailed: See also [`fmi3GetEventIndicators!`](@ref). """ -function fmi3GetEventIndicators!(c::FMU3Instance, eventIndicators::AbstractArray{fmi3Float64}, ni::Csize_t) - status = fmi3GetEventIndicators!(c.fmu.cGetEventIndicators, - c.compAddr, eventIndicators, ni) +function fmi3GetEventIndicators!( + c::FMU3Instance, + eventIndicators::AbstractArray{fmi3Float64}, + ni::Csize_t, +) + status = fmi3GetEventIndicators!(c.fmu.cGetEventIndicators, c.addr, eventIndicators, ni) checkStatus(c, status) return status end @@ -2843,12 +3272,19 @@ More detailed: - FMISpec3.0: 3.2.1. State: Continuous-Time Mode See also [`fmi3CompletedIntegratorStep!`](@ref). """ -function fmi3CompletedIntegratorStep!(c::FMU3Instance, - noSetFMUStatePriorToCurrentPoint::fmi3Boolean, - enterEventMode::Ref{fmi3Boolean}, - terminateSimulation::Ref{fmi3Boolean}) - status = fmi3CompletedIntegratorStep!(c.fmu.cCompletedIntegratorStep, - c.compAddr, noSetFMUStatePriorToCurrentPoint, enterEventMode, terminateSimulation) +function fmi3CompletedIntegratorStep!( + c::FMU3Instance, + noSetFMUStatePriorToCurrentPoint::fmi3Boolean, + enterEventMode::Ref{fmi3Boolean}, + terminateSimulation::Ref{fmi3Boolean}, +) + status = fmi3CompletedIntegratorStep!( + c.fmu.cCompletedIntegratorStep, + c.addr, + noSetFMUStatePriorToCurrentPoint, + enterEventMode, + terminateSimulation, + ) checkStatus(c, status) return status end @@ -2889,23 +3325,39 @@ More detailed: See also [`fmi3EnterEventMode`](@ref). """ -function fmi3EnterEventMode(c::FMU3Instance, stepEvent::fmi3Boolean, stateEvent::fmi3Boolean, rootsFound::AbstractArray{fmi3Int32}, nEventIndicators::Csize_t, timeEvent::fmi3Boolean; soft::Bool=false) - if c.state != fmi3InstanceStateContinuousTimeMode && c.state != fmi3InstanceStateStepMode - if soft +function fmi3EnterEventMode( + c::FMU3Instance, + stepEvent::fmi3Boolean, + stateEvent::fmi3Boolean, + rootsFound::AbstractArray{fmi3Int32}, + nEventIndicators::Csize_t, + timeEvent::fmi3Boolean; + soft::Bool = false, +) + if c.state != fmi3InstanceStateContinuousTimeMode && + c.state != fmi3InstanceStateStepMode + if soft return fmi3StatusOK else @warn "fmi3EnterEventMode(...): Called at the wrong time." end end - status = fmi3EnterEventMode(c.fmu.cEnterEventMode, - c.compAddr, stepEvent, stateEvent, rootsFound, nEventIndicators, timeEvent) + status = fmi3EnterEventMode( + c.fmu.cEnterEventMode, + c.addr, + stepEvent, + stateEvent, + rootsFound, + nEventIndicators, + timeEvent, + ) checkStatus(c, status) if status == fmi3StatusOK c.state = fmi3InstanceStateEventMode end return status - + end """ @@ -2943,12 +3395,31 @@ More detailed: See also [`fmi3DoStep!`](@ref). """ -function fmi3DoStep!(c::FMU3Instance, currentCommunicationPoint::fmi3Float64, communicationStepSize::fmi3Float64, noSetFMUStatePriorToCurrentPoint::fmi3Boolean, - eventEncountered::Ref{fmi3Boolean}, terminateSimulation::Ref{fmi3Boolean}, earlyReturn::Ref{fmi3Boolean}, lastSuccessfulTime::Ref{fmi3Float64}) - @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) +function fmi3DoStep!( + c::FMU3Instance, + currentCommunicationPoint::fmi3Float64, + communicationStepSize::fmi3Float64, + noSetFMUStatePriorToCurrentPoint::fmi3Boolean, + eventEncountered::Ref{fmi3Boolean}, + terminateSimulation::Ref{fmi3Boolean}, + earlyReturn::Ref{fmi3Boolean}, + lastSuccessfulTime::Ref{fmi3Float64}, +) + @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.addr, + currentCommunicationPoint, + communicationStepSize, + noSetFMUStatePriorToCurrentPoint, + eventEncountered, + terminateSimulation, + earlyReturn, + lastSuccessfulTime, + ) checkStatus(c, status) return status -end \ No newline at end of file +end 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..371313d 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,44 @@ 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,37 +80,41 @@ 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 - directories = [joinpath("binaries", "win64"), joinpath("binaries","x86_64-windows")] - else - directories = [joinpath("binaries", "win32"), joinpath("binaries","i686-windows")] + directories = + [joinpath("binaries", "win64"), joinpath("binaries", "x86_64-windows")] + else + directories = + [joinpath("binaries", "win32"), joinpath("binaries", "i686-windows")] end osStr = "Windows" fmuExt = "dll" elseif Sys.islinux() if juliaArch == 64 - directories = [joinpath("binaries", "linux64"), joinpath("binaries", "x86_64-linux")] - else + directories = + [joinpath("binaries", "linux64"), joinpath("binaries", "x86_64-linux")] + else directories = [] end osStr = "Linux" fmuExt = "so" elseif Sys.isapple() if juliaArch == 64 - directories = [joinpath("binaries", "darwin64"), joinpath("binaries", "x86_64-darwin")] - else + directories = + [joinpath("binaries", "darwin64"), joinpath("binaries", "x86_64-darwin")] + else directories = [] end 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 +122,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) - - # dependency matrix - # fmu.dependencies + loadPointers(fmu) - 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)) @@ -240,1281 +156,112 @@ function loadBinary(fmu::FMU3) cd(lastDirectory) # retrieve functions - fmu.cInstantiateModelExchange = dlsym(fmu.libHandle, :fmi3InstantiateModelExchange) - fmu.cInstantiateCoSimulation = dlsym(fmu.libHandle, :fmi3InstantiateCoSimulation) - fmu.cInstantiateScheduledExecution = dlsym(fmu.libHandle, :fmi3InstantiateScheduledExecution) - fmu.cGetVersion = dlsym(fmu.libHandle, :fmi3GetVersion) - fmu.cFreeInstance = dlsym(fmu.libHandle, :fmi3FreeInstance) - fmu.cSetDebugLogging = dlsym(fmu.libHandle, :fmi3SetDebugLogging) - fmu.cEnterConfigurationMode = dlsym(fmu.libHandle, :fmi3EnterConfigurationMode) - fmu.cExitConfigurationMode = dlsym(fmu.libHandle, :fmi3ExitConfigurationMode) - fmu.cEnterInitializationMode = dlsym(fmu.libHandle, :fmi3EnterInitializationMode) - fmu.cExitInitializationMode = dlsym(fmu.libHandle, :fmi3ExitInitializationMode) - fmu.cTerminate = dlsym(fmu.libHandle, :fmi3Terminate) - fmu.cReset = dlsym(fmu.libHandle, :fmi3Reset) - fmu.cEvaluateDiscreteStates = dlsym(fmu.libHandle, :fmi3EvaluateDiscreteStates) - fmu.cGetNumberOfVariableDependencies = dlsym(fmu.libHandle, :fmi3GetNumberOfVariableDependencies) - fmu.cGetVariableDependencies = dlsym(fmu.libHandle, :fmi3GetVariableDependencies) - - fmu.cGetFloat32 = dlsym(fmu.libHandle, :fmi3GetFloat32) - fmu.cSetFloat32 = dlsym(fmu.libHandle, :fmi3SetFloat32) - fmu.cGetFloat64 = dlsym(fmu.libHandle, :fmi3GetFloat64) - fmu.cSetFloat64 = dlsym(fmu.libHandle, :fmi3SetFloat64) - fmu.cGetInt8 = dlsym(fmu.libHandle, :fmi3GetInt8) - fmu.cSetInt8 = dlsym(fmu.libHandle, :fmi3SetInt8) - fmu.cGetUInt8 = dlsym(fmu.libHandle, :fmi3GetUInt8) - fmu.cSetUInt8 = dlsym(fmu.libHandle, :fmi3SetUInt8) - fmu.cGetInt16 = dlsym(fmu.libHandle, :fmi3GetInt16) - fmu.cSetInt16 = dlsym(fmu.libHandle, :fmi3SetInt16) - fmu.cGetUInt16 = dlsym(fmu.libHandle, :fmi3GetUInt16) - fmu.cSetUInt16 = dlsym(fmu.libHandle, :fmi3SetUInt16) - fmu.cGetInt32 = dlsym(fmu.libHandle, :fmi3GetInt32) - fmu.cSetInt32 = dlsym(fmu.libHandle, :fmi3SetInt32) - fmu.cGetUInt32 = dlsym(fmu.libHandle, :fmi3GetUInt32) - fmu.cSetUInt32 = dlsym(fmu.libHandle, :fmi3SetUInt32) - fmu.cGetInt64 = dlsym(fmu.libHandle, :fmi3GetInt64) - fmu.cSetInt64 = dlsym(fmu.libHandle, :fmi3SetInt64) - fmu.cGetUInt64 = dlsym(fmu.libHandle, :fmi3GetUInt64) - fmu.cSetUInt64 = dlsym(fmu.libHandle, :fmi3SetUInt64) - fmu.cGetBoolean = dlsym(fmu.libHandle, :fmi3GetBoolean) - fmu.cSetBoolean = dlsym(fmu.libHandle, :fmi3SetBoolean) - - fmu.cGetString = dlsym_opt(fmu.libHandle, :fmi3GetString) - fmu.cSetString = dlsym_opt(fmu.libHandle, :fmi3SetString) - fmu.cGetBinary = dlsym_opt(fmu.libHandle, :fmi3GetBinary) - fmu.cSetBinary = dlsym_opt(fmu.libHandle, :fmi3SetBinary) - - if fmi3CanGetSetState(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) - 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) - fmu.cGetDirectionalDerivative = dlsym_opt(fmu.libHandle, :fmi3GetDirectionalDerivative) - end - - if fmi3ProvidesAdjointDerivatives(fmu) - fmu.cGetAdjointDerivative = dlsym_opt(fmu.libHandle, :fmi3GetAdjointDerivative) + fmu.cInstantiateModelExchange = dlsym(fmu.libHandle, :fmi3InstantiateModelExchange) + fmu.cInstantiateCoSimulation = dlsym(fmu.libHandle, :fmi3InstantiateCoSimulation) + fmu.cInstantiateScheduledExecution = + dlsym(fmu.libHandle, :fmi3InstantiateScheduledExecution) + fmu.cGetVersion = dlsym(fmu.libHandle, :fmi3GetVersion) + fmu.cFreeInstance = dlsym(fmu.libHandle, :fmi3FreeInstance) + fmu.cSetDebugLogging = dlsym(fmu.libHandle, :fmi3SetDebugLogging) + fmu.cEnterConfigurationMode = dlsym(fmu.libHandle, :fmi3EnterConfigurationMode) + fmu.cExitConfigurationMode = dlsym(fmu.libHandle, :fmi3ExitConfigurationMode) + fmu.cEnterInitializationMode = dlsym(fmu.libHandle, :fmi3EnterInitializationMode) + fmu.cExitInitializationMode = dlsym(fmu.libHandle, :fmi3ExitInitializationMode) + fmu.cTerminate = dlsym(fmu.libHandle, :fmi3Terminate) + fmu.cReset = dlsym(fmu.libHandle, :fmi3Reset) + fmu.cEvaluateDiscreteStates = dlsym(fmu.libHandle, :fmi3EvaluateDiscreteStates) + fmu.cGetNumberOfVariableDependencies = + dlsym(fmu.libHandle, :fmi3GetNumberOfVariableDependencies) + fmu.cGetVariableDependencies = dlsym(fmu.libHandle, :fmi3GetVariableDependencies) + + fmu.cGetFloat32 = dlsym(fmu.libHandle, :fmi3GetFloat32) + fmu.cSetFloat32 = dlsym(fmu.libHandle, :fmi3SetFloat32) + fmu.cGetFloat64 = dlsym(fmu.libHandle, :fmi3GetFloat64) + fmu.cSetFloat64 = dlsym(fmu.libHandle, :fmi3SetFloat64) + fmu.cGetInt8 = dlsym(fmu.libHandle, :fmi3GetInt8) + fmu.cSetInt8 = dlsym(fmu.libHandle, :fmi3SetInt8) + fmu.cGetUInt8 = dlsym(fmu.libHandle, :fmi3GetUInt8) + fmu.cSetUInt8 = dlsym(fmu.libHandle, :fmi3SetUInt8) + fmu.cGetInt16 = dlsym(fmu.libHandle, :fmi3GetInt16) + fmu.cSetInt16 = dlsym(fmu.libHandle, :fmi3SetInt16) + fmu.cGetUInt16 = dlsym(fmu.libHandle, :fmi3GetUInt16) + fmu.cSetUInt16 = dlsym(fmu.libHandle, :fmi3SetUInt16) + fmu.cGetInt32 = dlsym(fmu.libHandle, :fmi3GetInt32) + fmu.cSetInt32 = dlsym(fmu.libHandle, :fmi3SetInt32) + fmu.cGetUInt32 = dlsym(fmu.libHandle, :fmi3GetUInt32) + fmu.cSetUInt32 = dlsym(fmu.libHandle, :fmi3SetUInt32) + fmu.cGetInt64 = dlsym(fmu.libHandle, :fmi3GetInt64) + fmu.cSetInt64 = dlsym(fmu.libHandle, :fmi3SetInt64) + fmu.cGetUInt64 = dlsym(fmu.libHandle, :fmi3GetUInt64) + fmu.cSetUInt64 = dlsym(fmu.libHandle, :fmi3SetUInt64) + fmu.cGetBoolean = dlsym(fmu.libHandle, :fmi3GetBoolean) + fmu.cSetBoolean = dlsym(fmu.libHandle, :fmi3SetBoolean) + + fmu.cGetString = dlsym_opt(fmu, fmu.libHandle, :fmi3GetString) + fmu.cSetString = dlsym_opt(fmu, fmu.libHandle, :fmi3SetString) + fmu.cGetBinary = dlsym_opt(fmu, fmu.libHandle, :fmi3GetBinary) + fmu.cSetBinary = dlsym_opt(fmu, fmu.libHandle, :fmi3SetBinary) + + if canGetSetFMUState(fmu) + fmu.cGetFMUState = dlsym_opt(fmu, fmu.libHandle, :fmi3GetFMUState) + fmu.cSetFMUState = dlsym_opt(fmu, fmu.libHandle, :fmi3SetFMUState) + fmu.cFreeFMUState = dlsym_opt(fmu, fmu.libHandle, :fmi3FreeFMUState) + end + + if canSerializeFMUState(fmu) + fmu.cSerializedFMUStateSize = + dlsym_opt(fmu, fmu.libHandle, :fmi3SerializedFMUStateSize) + fmu.cSerializeFMUState = dlsym_opt(fmu, fmu.libHandle, :fmi3SerializeFMUState) + fmu.cDeSerializeFMUState = dlsym_opt(fmu, fmu.libHandle, :fmi3DeserializeFMUState) + end + + if providesDirectionalDerivatives(fmu) + fmu.cGetDirectionalDerivative = + dlsym_opt(fmu, fmu.libHandle, :fmi3GetDirectionalDerivative) + end + + if providesAdjointDerivatives(fmu) + fmu.cGetAdjointDerivative = dlsym_opt(fmu, fmu.libHandle, :fmi3GetAdjointDerivative) end # CS specific function calls - if fmi3IsCoSimulation(fmu) - fmu.cGetOutputDerivatives = dlsym(fmu.libHandle, :fmi3GetOutputDerivatives) - fmu.cEnterStepMode = dlsym(fmu.libHandle, :fmi3EnterStepMode) - fmu.cDoStep = dlsym(fmu.libHandle, :fmi3DoStep) + 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) - fmu.cGetNumberOfContinuousStates = dlsym(fmu.libHandle, :fmi3GetNumberOfContinuousStates) - fmu.cGetNumberOfEventIndicators = dlsym(fmu.libHandle, :fmi3GetNumberOfEventIndicators) - fmu.cGetContinuousStates = dlsym(fmu.libHandle, :fmi3GetContinuousStates) - fmu.cGetNominalsOfContinuousStates = dlsym(fmu.libHandle, :fmi3GetNominalsOfContinuousStates) - fmu.cEnterContinuousTimeMode = dlsym(fmu.libHandle, :fmi3EnterContinuousTimeMode) - fmu.cSetTime = dlsym(fmu.libHandle, :fmi3SetTime) - fmu.cSetContinuousStates = dlsym(fmu.libHandle, :fmi3SetContinuousStates) - fmu.cGetContinuousStateDerivatives = dlsym(fmu.libHandle, :fmi3GetContinuousStateDerivatives) - fmu.cGetEventIndicators = dlsym(fmu.libHandle, :fmi3GetEventIndicators) - fmu.cCompletedIntegratorStep = dlsym(fmu.libHandle, :fmi3CompletedIntegratorStep) - fmu.cEnterEventMode = dlsym(fmu.libHandle, :fmi3EnterEventMode) - fmu.cUpdateDiscreteStates = dlsym(fmu.libHandle, :fmi3UpdateDiscreteStates) - end - - if fmi3IsScheduledExecution(fmu) - fmu.cSetIntervalDecimal = dlsym(fmu.libHandle, :fmi3SetIntervalDecimal) - fmu.cSetIntervalFraction = dlsym(fmu.libHandle, :fmi3SetIntervalFraction) - fmu.cGetIntervalDecimal = dlsym(fmu.libHandle, :fmi3GetIntervalDecimal) - fmu.cGetIntervalFraction = dlsym(fmu.libHandle, :fmi3GetIntervalFraction) - fmu.cGetShiftDecimal = dlsym(fmu.libHandle, :fmi3GetShiftDecimal) - fmu.cGetShiftFraction = dlsym(fmu.libHandle, :fmi3GetShiftFraction) - fmu.cActivateModelPartition = dlsym(fmu.libHandle, :fmi3ActivateModelPartition) - 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]) ) + if isModelExchange(fmu) + fmu.cGetNumberOfContinuousStates = + dlsym(fmu.libHandle, :fmi3GetNumberOfContinuousStates) + fmu.cGetNumberOfEventIndicators = + dlsym(fmu.libHandle, :fmi3GetNumberOfEventIndicators) + fmu.cGetContinuousStates = dlsym(fmu.libHandle, :fmi3GetContinuousStates) + fmu.cGetNominalsOfContinuousStates = + dlsym(fmu.libHandle, :fmi3GetNominalsOfContinuousStates) + fmu.cEnterContinuousTimeMode = dlsym(fmu.libHandle, :fmi3EnterContinuousTimeMode) + fmu.cSetTime = dlsym(fmu.libHandle, :fmi3SetTime) + fmu.cSetContinuousStates = dlsym(fmu.libHandle, :fmi3SetContinuousStates) + fmu.cGetContinuousStateDerivatives = + dlsym(fmu.libHandle, :fmi3GetContinuousStateDerivatives) + fmu.cGetEventIndicators = dlsym(fmu.libHandle, :fmi3GetEventIndicators) + fmu.cCompletedIntegratorStep = dlsym(fmu.libHandle, :fmi3CompletedIntegratorStep) + fmu.cEnterEventMode = dlsym(fmu.libHandle, :fmi3EnterEventMode) + fmu.cUpdateDiscreteStates = dlsym(fmu.libHandle, :fmi3UpdateDiscreteStates) + end + + if isScheduledExecution(fmu) + fmu.cSetIntervalDecimal = dlsym(fmu.libHandle, :fmi3SetIntervalDecimal) + fmu.cSetIntervalFraction = dlsym(fmu.libHandle, :fmi3SetIntervalFraction) + fmu.cGetIntervalDecimal = dlsym(fmu.libHandle, :fmi3GetIntervalDecimal) + fmu.cGetIntervalFraction = dlsym(fmu.libHandle, :fmi3GetIntervalFraction) + fmu.cGetShiftDecimal = dlsym(fmu.libHandle, :fmi3GetShiftDecimal) + fmu.cGetShiftFraction = dlsym(fmu.libHandle, :fmi3GetShiftFraction) + fmu.cActivateModelPartition = dlsym(fmu.libHandle, :fmi3ActivateModelPartition) 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 +function unloadPointers(fmu::FMU3) + # [TODO]: Write a macro for that. 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 -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 a441620..1ffa4e3 100644 --- a/src/FMI3/int.jl +++ b/src/FMI3/int.jl @@ -1,13 +1,433 @@ # -# 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 """ @@ -70,13 +490,18 @@ More detailed: See also [`fmi3EnterInitializationMode`](@ref). """ -function fmi3EnterInitializationMode(c::FMU3Instance, startTime::Union{Real, Nothing} = nothing, stopTime::Union{Real, Nothing} = nothing; tolerance::Union{Real, Nothing} = nothing) +function fmi3EnterInitializationMode( + c::FMU3Instance, + startTime::Union{Real,Nothing} = nothing, + stopTime::Union{Real,Nothing} = nothing; + tolerance::Union{Real,Nothing} = nothing, +) if c.state != fmi3InstanceStateInstantiated @warn "fmi3EnterInitializationMode(...): Needs to be called in state `fmi3IntanceStateInstantiated`." end if startTime === nothing - startTime = fmi3GetDefaultStartTime(c.fmu.modelDescription) + startTime = getDefaultStartTime(c.fmu.modelDescription) if startTime === nothing startTime = 0.0 end @@ -93,7 +518,15 @@ 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 +569,8 @@ function fmi3GetFloat32(c::FMU3Instance, vr::fmi3ValueReferenceFormat) return values end end +# [NOTE] needs to be exported, because FMICore only exports `fmi3GetFloat32!` +export fmi3GetFloat32 """ @@ -167,7 +602,11 @@ More detailed: See also [`fmi3GetFloat32!`](@ref). """ -function fmi3GetFloat32!(c::FMU3Instance, vr::fmi3ValueReferenceFormat, values::AbstractArray{fmi3Float32}) +function fmi3GetFloat32!( + c::FMU3Instance, + vr::fmi3ValueReferenceFormat, + values::AbstractArray{fmi3Float32}, +) vr = prepareValueReference(c, vr) @assert length(vr) == length(values) "fmi3GetFloat32!(...): `vr` and `values` need to be the same length." @@ -208,7 +647,11 @@ More detailed: See also [`fmi3SetFloat32`](@ref). """ -function fmi3SetFloat32(c::FMU3Instance, vr::fmi3ValueReferenceFormat, values::Union{AbstractArray{fmi3Float32}, fmi3Float32}) +function fmi3SetFloat32( + c::FMU3Instance, + vr::fmi3ValueReferenceFormat, + values::Union{AbstractArray{fmi3Float32},fmi3Float32}, +) vr = prepareValueReference(c, vr) values = prepareValue(values) @@ -253,6 +696,8 @@ function fmi3GetFloat64(c::FMU3Instance, vr::fmi3ValueReferenceFormat) return values end end +# [NOTE] needs to be exported, because FMICore only exports `fmi3GetFloat64!` +export fmi3GetFloat64 """ @@ -284,7 +729,11 @@ More detailed: See also [`fmi3GetFloat64!`](@ref). """ -function fmi3GetFloat64!(c::FMU3Instance, vr::fmi3ValueReferenceFormat, values::AbstractArray{fmi3Float64}) +function fmi3GetFloat64!( + c::FMU3Instance, + vr::fmi3ValueReferenceFormat, + values::AbstractArray{fmi3Float64}, +) vr = prepareValueReference(c, vr) @@ -326,14 +775,19 @@ 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 +825,8 @@ function fmi3GetInt8(c::FMU3Instance, vr::fmi3ValueReferenceFormat) return values end end +# [NOTE] needs to be exported, because FMICore only exports `fmi3GetInt8!` +export fmi3GetInt8 """ @@ -402,7 +858,11 @@ More detailed: See also [`fmi3GetInt8!`](@ref). """ -function fmi3GetInt8!(c::FMU3Instance, vr::fmi3ValueReferenceFormat, values::AbstractArray{fmi3Int8}) +function fmi3GetInt8!( + c::FMU3Instance, + vr::fmi3ValueReferenceFormat, + values::AbstractArray{fmi3Int8}, +) vr = prepareValueReference(c, vr) @assert length(vr) == length(values) "fmi3GetInt8!(...): `vr` and `values` need to be the same length." @@ -443,7 +903,11 @@ More detailed: See also [`fmi3SetInt8`](@ref). """ -function fmi3SetInt8(c::FMU3Instance, vr::fmi3ValueReferenceFormat, values::Union{AbstractArray{fmi3Int8}, fmi3Int8}) +function fmi3SetInt8( + c::FMU3Instance, + vr::fmi3ValueReferenceFormat, + values::Union{AbstractArray{fmi3Int8},fmi3Int8}, +) vr = prepareValueReference(c, vr) values = prepareValue(values) @@ -488,6 +952,8 @@ function fmi3GetUInt8(c::FMU3Instance, vr::fmi3ValueReferenceFormat) return values end end +# [NOTE] needs to be exported, because FMICore only exports `fmi3GetUInt8!` +export fmi3GetUInt8 """ @@ -519,7 +985,11 @@ More detailed: See also [`fmi3GetUInt8!`](@ref). """ -function fmi3GetUInt8!(c::FMU3Instance, vr::fmi3ValueReferenceFormat, values::AbstractArray{fmi3UInt8}) +function fmi3GetUInt8!( + c::FMU3Instance, + vr::fmi3ValueReferenceFormat, + values::AbstractArray{fmi3UInt8}, +) vr = prepareValueReference(c, vr) @assert length(vr) == length(values) "fmi3GetUInt8!(...): `vr` and `values` need to be the same length." @@ -560,7 +1030,11 @@ More detailed: See also [`fmi3SetUInt8`](@ref). """ -function fmi3SetUInt8(c::FMU3Instance, vr::fmi3ValueReferenceFormat, values::Union{AbstractArray{fmi3UInt8}, fmi3UInt8}) +function fmi3SetUInt8( + c::FMU3Instance, + vr::fmi3ValueReferenceFormat, + values::Union{AbstractArray{fmi3UInt8},fmi3UInt8}, +) vr = prepareValueReference(c, vr) values = prepareValue(values) @@ -605,6 +1079,8 @@ function fmi3GetInt16(c::FMU3Instance, vr::fmi3ValueReferenceFormat) return values end end +# [NOTE] needs to be exported, because FMICore only exports `fmi3GetInt16!` +export fmi3GetInt16 """ @@ -636,7 +1112,11 @@ More detailed: See also [`fmi3GetInt16!`](@ref). """ -function fmi3GetInt16!(c::FMU3Instance, vr::fmi3ValueReferenceFormat, values::AbstractArray{fmi3Int16}) +function fmi3GetInt16!( + c::FMU3Instance, + vr::fmi3ValueReferenceFormat, + values::AbstractArray{fmi3Int16}, +) vr = prepareValueReference(c, vr) @assert length(vr) == length(values) "fmi3GetInt16!(...): `vr` and `values` need to be the same length." @@ -677,7 +1157,11 @@ More detailed: See also [`fmi3SetInt16`](@ref). """ -function fmi3SetInt16(c::FMU3Instance, vr::fmi3ValueReferenceFormat, values::Union{AbstractArray{fmi3Int16}, fmi3Int16}) +function fmi3SetInt16( + c::FMU3Instance, + vr::fmi3ValueReferenceFormat, + values::Union{AbstractArray{fmi3Int16},fmi3Int16}, +) vr = prepareValueReference(c, vr) values = prepareValue(values) @@ -722,6 +1206,8 @@ function fmi3GetUInt16(c::FMU3Instance, vr::fmi3ValueReferenceFormat) return values end end +# [NOTE] needs to be exported, because FMICore only exports `fmi3GetUInt16!` +export fmi3GetUInt16 """ @@ -753,7 +1239,11 @@ More detailed: See also [`fmi3GetUInt16!`](@ref). """ -function fmi3GetUInt16!(c::FMU3Instance, vr::fmi3ValueReferenceFormat, values::AbstractArray{fmi3UInt16}) +function fmi3GetUInt16!( + c::FMU3Instance, + vr::fmi3ValueReferenceFormat, + values::AbstractArray{fmi3UInt16}, +) vr = prepareValueReference(c, vr) @assert length(vr) == length(values) "fmi3GetUInt16!(...): `vr` and `values` need to be the same length." @@ -794,7 +1284,11 @@ More detailed: See also [`fmi3SetUInt16`](@ref). """ -function fmi3SetUInt16(c::FMU3Instance, vr::fmi3ValueReferenceFormat, values::Union{AbstractArray{fmi3UInt16}, fmi3UInt16}) +function fmi3SetUInt16( + c::FMU3Instance, + vr::fmi3ValueReferenceFormat, + values::Union{AbstractArray{fmi3UInt16},fmi3UInt16}, +) vr = prepareValueReference(c, vr) values = prepareValue(values) @@ -839,6 +1333,8 @@ function fmi3GetInt32(c::FMU3Instance, vr::fmi3ValueReferenceFormat) return values end end +# [NOTE] needs to be exported, because FMICore only exports `fmi3GetInt32!` +export fmi3GetInt32 """ @@ -870,7 +1366,11 @@ More detailed: See also [`fmi3GetInt32!`](@ref). """ -function fmi3GetInt32!(c::FMU3Instance, vr::fmi3ValueReferenceFormat, values::AbstractArray{fmi3Int32}) +function fmi3GetInt32!( + c::FMU3Instance, + vr::fmi3ValueReferenceFormat, + values::AbstractArray{fmi3Int32}, +) vr = prepareValueReference(c, vr) @assert length(vr) == length(values) "fmi3GetInt32!(...): `vr` and `values` need to be the same length." @@ -911,7 +1411,11 @@ More detailed: See also [`fmi3SetInt32`](@ref). """ -function fmi3SetInt32(c::FMU3Instance, vr::fmi3ValueReferenceFormat, values::Union{AbstractArray{fmi3Int32}, fmi3Int32}) +function fmi3SetInt32( + c::FMU3Instance, + vr::fmi3ValueReferenceFormat, + values::Union{AbstractArray{fmi3Int32},fmi3Int32}, +) vr = prepareValueReference(c, vr) values = prepareValue(values) @@ -956,6 +1460,8 @@ function fmi3GetUInt32(c::FMU3Instance, vr::fmi3ValueReferenceFormat) return values end end +# [NOTE] needs to be exported, because FMICore only exports `fmi3GetUInt32!` +export fmi3GetUInt32 """ @@ -987,7 +1493,11 @@ More detailed: See also [`fmi3GetUInt32!`](@ref). """ -function fmi3GetUInt32!(c::FMU3Instance, vr::fmi3ValueReferenceFormat, values::AbstractArray{fmi3UInt32}) +function fmi3GetUInt32!( + c::FMU3Instance, + vr::fmi3ValueReferenceFormat, + values::AbstractArray{fmi3UInt32}, +) vr = prepareValueReference(c, vr) @assert length(vr) == length(values) "fmi3GetUInt32!(...): `vr` and `values` need to be the same length." @@ -1028,7 +1538,11 @@ More detailed: See also [`fmi3SetUInt32`](@ref). """ -function fmi3SetUInt32(c::FMU3Instance, vr::fmi3ValueReferenceFormat, values::Union{AbstractArray{fmi3UInt32}, fmi3UInt32}) +function fmi3SetUInt32( + c::FMU3Instance, + vr::fmi3ValueReferenceFormat, + values::Union{AbstractArray{fmi3UInt32},fmi3UInt32}, +) vr = prepareValueReference(c, vr) values = prepareValue(values) @@ -1073,6 +1587,9 @@ function fmi3GetInt64(c::FMU3Instance, vr::fmi3ValueReferenceFormat) return values end end +# [NOTE] needs to be exported, because FMICore only exports `fmi3GetInt64!` +export fmi3GetInt64 + """ @@ -1104,7 +1621,11 @@ More detailed: See also [`fmi3GetInt64!`](@ref). """ -function fmi3GetInt64!(c::FMU3Instance, vr::fmi3ValueReferenceFormat, values::AbstractArray{fmi3Int64}) +function fmi3GetInt64!( + c::FMU3Instance, + vr::fmi3ValueReferenceFormat, + values::AbstractArray{fmi3Int64}, +) vr = prepareValueReference(c, vr) @assert length(vr) == length(values) "fmi3GetInt64!(...): `vr` and `values` need to be the same length." @@ -1145,7 +1666,11 @@ More detailed: See also [`fmi3SetInt64`](@ref). """ -function fmi3SetInt64(c::FMU3Instance, vr::fmi3ValueReferenceFormat, values::Union{AbstractArray{fmi3Int64}, fmi3Int64}) +function fmi3SetInt64( + c::FMU3Instance, + vr::fmi3ValueReferenceFormat, + values::Union{AbstractArray{fmi3Int64},fmi3Int64}, +) vr = prepareValueReference(c, vr) values = prepareValue(values) @@ -1190,6 +1715,8 @@ function fmi3GetUInt64(c::FMU3Instance, vr::fmi3ValueReferenceFormat) return values end end +# [NOTE] needs to be exported, because FMICore only exports `fmi3GetUInt64!` +export fmi3GetUInt64 """ @@ -1221,7 +1748,11 @@ More detailed: See also [`fmi3GetUInt64!`](@ref). """ -function fmi3GetUInt64!(c::FMU3Instance, vr::fmi3ValueReferenceFormat, values::AbstractArray{fmi3UInt64}) +function fmi3GetUInt64!( + c::FMU3Instance, + vr::fmi3ValueReferenceFormat, + values::AbstractArray{fmi3UInt64}, +) vr = prepareValueReference(c, vr) @assert length(vr) == length(values) "fmi3GetUInt64!(...): `vr` and `values` need to be the same length." @@ -1262,7 +1793,11 @@ More detailed: See also [`fmi3SetUInt64`](@ref). """ -function fmi3SetUInt64(c::FMU3Instance, vr::fmi3ValueReferenceFormat, values::Union{AbstractArray{fmi3UInt64}, fmi3UInt64}) +function fmi3SetUInt64( + c::FMU3Instance, + vr::fmi3ValueReferenceFormat, + values::Union{AbstractArray{fmi3UInt64},fmi3UInt64}, +) vr = prepareValueReference(c, vr) values = prepareValue(values) @@ -1307,6 +1842,8 @@ function fmi3GetBoolean(c::FMU3Instance, vr::fmi3ValueReferenceFormat) return values end end +# [NOTE] needs to be exported, because FMICore only exports `fmi3GetBoolean!` +export fmi3GetBoolean """ @@ -1338,7 +1875,11 @@ More detailed: See also [`fmi3GetBoolean!`](@ref). """ -function fmi3GetBoolean!(c::FMU3Instance, vr::fmi3ValueReferenceFormat, values::AbstractArray{fmi3Boolean}) +function fmi3GetBoolean!( + c::FMU3Instance, + vr::fmi3ValueReferenceFormat, + values::AbstractArray{fmi3Boolean}, +) vr = prepareValueReference(c, vr) @assert length(vr) == length(values) "fmi3GetBoolean!(...): `vr` and `values` need to be the same length." @@ -1380,7 +1921,11 @@ More detailed: See also [`fmi3SetBoolean`](@ref). """ -function fmi3SetBoolean(c::FMU3Instance, vr::fmi3ValueReferenceFormat, values::Union{AbstractArray{Bool}, Bool}) +function fmi3SetBoolean( + c::FMU3Instance, + vr::fmi3ValueReferenceFormat, + values::Union{AbstractArray{Bool},Bool}, +) vr = prepareValueReference(c, vr) values = prepareValue(values) @@ -1427,6 +1972,8 @@ function fmi3GetString(c::FMU3Instance, vr::fmi3ValueReferenceFormat) return values end end +# [NOTE] needs to be exported, because FMICore only exports `fmi3GetString!` +export fmi3GetString """ @@ -1458,7 +2005,11 @@ More detailed: See also [`fmi3GetString!`](@ref). """ -function fmi3GetString!(c::FMU3Instance, vr::fmi3ValueReferenceFormat, values::AbstractArray{fmi3String}) +function fmi3GetString!( + c::FMU3Instance, + vr::fmi3ValueReferenceFormat, + values::AbstractArray{fmi3String}, +) vr = prepareValueReference(c, vr) @assert length(vr) == length(values) "fmi3GetString!(...): `vr` and `values` need to be the same length." @@ -1502,7 +2053,11 @@ More detailed: See also [`fmi3SetString`](@ref). """ -function fmi3SetString(c::FMU3Instance, vr::fmi3ValueReferenceFormat, values::Union{AbstractArray{String}, String}) +function fmi3SetString( + c::FMU3Instance, + vr::fmi3ValueReferenceFormat, + values::Union{AbstractArray{String},String}, +) vr = prepareValueReference(c, vr) values = prepareValue(values) @@ -1548,6 +2103,8 @@ function fmi3GetBinary(c::FMU3Instance, vr::fmi3ValueReferenceFormat) return values end end +# [NOTE] needs to be exported, because FMICore only exports `fmi3GetBinary!` +export fmi3GetBinary """ @@ -1579,7 +2136,11 @@ More detailed: See also [`fmi3GetBinary!`](@ref). """ -function fmi3GetBinary!(c::FMU3Instance, vr::fmi3ValueReferenceFormat, values::AbstractArray{fmi3Binary}) +function fmi3GetBinary!( + c::FMU3Instance, + vr::fmi3ValueReferenceFormat, + values::AbstractArray{fmi3Binary}, +) vr = prepareValueReference(c, vr) @assert length(vr) == length(values) "fmi3GetBinary!(...): `vr` and `values` need to be the same length." @@ -1621,7 +2182,12 @@ More detailed: See also [`fmi3SetBinary`](@ref). """ -function fmi3SetBinary(c::FMU3Instance, vr::fmi3ValueReferenceFormat, valueSizes::Union{AbstractArray{Csize_t}, Csize_t}, values::Union{AbstractArray{fmi3Binary}, fmi3Binary}) +function fmi3SetBinary( + c::FMU3Instance, + vr::fmi3ValueReferenceFormat, + valueSizes::Union{AbstractArray{Csize_t},Csize_t}, + values::Union{AbstractArray{fmi3Binary},fmi3Binary}, +) vr = prepareValueReference(c, vr) values = prepareValue(values) @@ -1666,6 +2232,8 @@ function fmi3GetClock(c::FMU3Instance, vr::fmi3ValueReferenceFormat) return values end end +# [NOTE] needs to be exported, because FMICore only exports `fmi3GetClock!` +export fmi3GetClock """ @@ -1697,7 +2265,11 @@ More detailed: See also [`fmi3GetClock!`](@ref). """ -function fmi3GetClock!(c::FMU3Instance, vr::fmi3ValueReferenceFormat, values::AbstractArray{fmi3Clock}) +function fmi3GetClock!( + c::FMU3Instance, + vr::fmi3ValueReferenceFormat, + values::AbstractArray{fmi3Clock}, +) vr = prepareValueReference(c, vr) @assert length(vr) == length(values) "fmi3GetClock!(...): `vr` and `values` need to be the same length." @@ -1738,7 +2310,11 @@ More detailed: See also [`fmi3SetClock`](@ref). """ -function fmi3SetClock(c::FMU3Instance, vr::fmi3ValueReferenceFormat, values::Union{AbstractArray{fmi3Clock}, fmi3Clock}) +function fmi3SetClock( + c::FMU3Instance, + vr::fmi3ValueReferenceFormat, + values::Union{AbstractArray{fmi3Clock},fmi3Clock}, +) vr = prepareValueReference(c, vr) values = prepareValue(values) @@ -1773,6 +2349,8 @@ function fmi3GetFMUState(c::FMU3Instance) state = stateRef[] state end +# [NOTE] needs to be exported, because FMICore only exports `fmi3GetFMUState!` +export fmi3GetFMUState """ @@ -1792,9 +2370,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 @@ -1824,6 +2402,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 """ @@ -1852,6 +2432,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 """ @@ -1880,9 +2462,11 @@ function fmi3DeSerializeFMUState(c::FMU3Instance, serializedState::AbstractArray status = fmi3DeSerializeFMUState!(c, serializedState, Csize_t(size), stateRef) @assert status == Int(fmi3StatusOK) ["Failed with status `$status`."] - + state = stateRef[] end +# [NOTE] needs to be exported, because FMICore only exports `fmi3DeSerializeFMUState!` +export fmi3DeSerializeFMUState """ @@ -1924,20 +2508,29 @@ Computes a linear combination of the partial derivatives of h with respect to th See also [`fmi3GetDirectionalDerivative`](@ref). """ -function fmi3GetDirectionalDerivative(c::FMU3Instance, - unknowns::AbstractArray{fmi3ValueReference}, - knowns::AbstractArray{fmi3ValueReference}, - seed::AbstractArray{fmi3Float64} = Array{fmi3Float64}([])) - +function fmi3GetDirectionalDerivative( + c::FMU3Instance, + unknowns::AbstractArray{fmi3ValueReference}, + knowns::AbstractArray{fmi3ValueReference}, + 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 """ @@ -1988,75 +2581,35 @@ More detailed: See also [`fmi3GetDirectionalDerivative!`](@ref). """ -function fmi3GetDirectionalDerivative!(c::FMU3Instance, - unknowns::AbstractArray{fmi3ValueReference}, - knowns::AbstractArray{fmi3ValueReference}, - sensitivity::AbstractArray, - seed::Union{AbstractArray{fmi3Float64}, Nothing} = nothing) +function fmi3GetDirectionalDerivative!( + c::FMU3Instance, + unknowns::AbstractArray{fmi3ValueReference}, + knowns::AbstractArray{fmi3ValueReference}, + 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)) - status = fmi3GetDirectionalDerivative!(c, unknowns, nUnknowns, knowns, nKnowns, seed, nSeed, sensitivity, nSensitivity) + status = fmi3GetDirectionalDerivative!( + c, + unknowns, + nUnknowns, + knowns, + nKnowns, + seed, + nSeed, + sensitivity, + nSensitivity, + ) 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, @@ -2096,19 +2649,29 @@ Computes a linear combination of the partial derivatives of h with respect to th See also [`fmi3GetAdjointDerivative`](@ref). """ -function fmi3GetAdjointDerivative(c::FMU3Instance, - unknowns::AbstractArray{fmi3ValueReference}, - knowns::AbstractArray{fmi3ValueReference}, - seed::AbstractArray{fmi3Float64} = Array{fmi3Float64}([])) - nUnknown = Csize_t(length(unknowns)) +function fmi3GetAdjointDerivative( + c::FMU3Instance, + unknowns::AbstractArray{fmi3ValueReference}, + knowns::AbstractArray{fmi3ValueReference}, + 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 """ @@ -2159,73 +2722,33 @@ More detailed: See also [`fmi3GetAdjointDerivative!`](@ref). """ -function fmi3GetAdjointDerivative!(c::FMU3Instance, - unknowns::AbstractArray{fmi3ValueReference}, - knowns::AbstractArray{fmi3ValueReference}, - sensitivity::AbstractArray, - seed::Union{AbstractArray{fmi3Float64}, Nothing} = nothing) +function fmi3GetAdjointDerivative!( + c::FMU3Instance, + unknowns::AbstractArray{fmi3ValueReference}, + knowns::AbstractArray{fmi3ValueReference}, + sensitivity::AbstractArray, + 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 - -""" + status = fmi3GetAdjointDerivative!( + c, + unknowns, + nUnknowns, + knowns, + nKnowns, + seed, + nSeed, + sensitivity, + nSensitivity, + ) - 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 """ @@ -2249,19 +2772,25 @@ Retrieves the n-th derivative of output values. See also [`fmi3GetOutputDerivatives`](@ref). """ -function fmi3GetOutputDerivatives(c::FMU3Instance, vr::fmi3ValueReferenceFormat, order::AbstractArray{Integer}) +function fmi3GetOutputDerivatives( + c::FMU3Instance, + vr::fmi3ValueReferenceFormat, + order::AbstractArray{Integer}, +) vr = prepareValueReference(c, vr) order = prepareValue(order) nvr = Csize_t(length(vr)) values = zeros(fmi3Float64, nvr) fmi3GetOutputDerivatives!(c, vr, nvr, order, values, nvr) - + if length(values) == 1 return values[1] else return values end end +# [NOTE] needs to be exported, because FMICore only exports `fmi3GetOutputDerivatives!` +export fmi3GetOutputDerivatives """ @@ -2287,10 +2816,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 """ @@ -2316,10 +2847,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 """ @@ -2343,16 +2876,21 @@ This information can only be retrieved if the 'providesPerElementDependencies' t See also [`fmi3GetNumberOfVariableDependencies`](@ref). """ -function fmi3GetNumberOfVariableDependencies(c::FMU3Instance, vr::Union{fmi3ValueReference, String}) +function fmi3GetNumberOfVariableDependencies( + c::FMU3Instance, + vr::Union{fmi3ValueReference,String}, +) if typeof(vr) == String vr = fmi3String2ValueReference(c.fmu.modelDescription, vr) 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 """ @@ -2382,7 +2920,7 @@ The actual dependencies (of type dependenciesKind) can be retrieved by calling t See also [`fmi3GetVariableDependencies!`](@ref). """ -function fmi3GetVariableDependencies(c::FMU3Instance, vr::Union{fmi3ValueReference, String}) +function fmi3GetVariableDependencies(c::FMU3Instance, vr::Union{fmi3ValueReference,String}) if typeof(vr) == String vr = fmi3String2ValueReference(c.fmu.modelDescription, vr) end @@ -2392,10 +2930,23 @@ function fmi3GetVariableDependencies(c::FMU3Instance, vr::Union{fmi3ValueReferen elementIndiceOfIndependents = Array{Csize_t}(undef, nDependencies) dependencyKinds = Array{fmi3DependencyKind}(undef, nDependencies) - fmi3GetVariableDependencies!(c, vr, elementIndiceOfDependents, independents, elementIndiceOfIndependents, dependencyKinds, nDependencies) + fmi3GetVariableDependencies!( + c, + vr, + elementIndiceOfDependents, + independents, + elementIndiceOfIndependents, + dependencyKinds, + nDependencies, + ) - return elementIndiceOfDependents, independents, elementIndiceOfIndependents, dependencyKinds + return elementIndiceOfDependents, + independents, + elementIndiceOfIndependents, + dependencyKinds end +# [NOTE] needs to be exported, because FMICore only exports `fmi3GetVariableDependencies!` +export fmi3GetVariableDependencies """ @@ -2420,8 +2971,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 """ @@ -2446,8 +2999,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 """ @@ -2508,12 +3063,15 @@ More detailed: See also [`fmi3SetContinuousStates`](@ref). """ -function fmi3SetContinuousStates(c::FMU3Instance, x::Union{AbstractArray{Float32}, AbstractArray{Float64}}) +function fmi3SetContinuousStates( + c::FMU3Instance, + x::Union{AbstractArray{Float32},AbstractArray{Float64}}, +) nx = Csize_t(length(x)) status = fmi3SetContinuousStates(c, Array{fmi3Float64}(x), nx) if status == fmi3StatusOK c.x = x - end + end return status end @@ -2537,12 +3095,14 @@ vector. See also [`fmi3GetContinuousStateDerivatives`](@ref). """ -function fmi3GetContinuousStateDerivatives(c::FMU3Instance) +function fmi3GetContinuousStateDerivatives(c::FMU3Instance) nx = Csize_t(c.fmu.modelDescription.numberOfContinuousStates) derivatives = zeros(fmi3Float64, nx) fmi3GetContinuousStateDerivatives!(c, derivatives) return derivatives end +# [NOTE] needs to be exported, because FMICore only exports `fmi3GetContinuousStateDerivatives!` +export fmi3GetContinuousStateDerivatives """ @@ -2571,8 +3131,12 @@ More detailed: See also [`fmi3GetContinuousStateDerivatives!`](@ref). """ -function fmi3GetContinuousStateDerivatives!(c::FMU3Instance, derivatives::AbstractArray{fmi3Float64}) - status = fmi3GetContinuousStateDerivatives!(c, derivatives, Csize_t(length(derivatives))) +function fmi3GetContinuousStateDerivatives!( + c::FMU3Instance, + derivatives::AbstractArray{fmi3Float64}, +) + status = + fmi3GetContinuousStateDerivatives!(c, derivatives, Csize_t(length(derivatives))) if status == fmi3StatusOK c.ẋ = derivatives end @@ -2583,11 +3147,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` @@ -2603,6 +3167,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 @@ -2620,12 +3185,49 @@ function fmi3UpdateDiscreteStates(c::FMU3Instance) discreteStatesNeedUpdate = refdS[] terminateSimulation = reftS[] - nominalsOfContinuousStatesChanged =refnOCS[] + nominalsOfContinuousStatesChanged = refnOCS[] valuesOfContinuousStatesChanged = refvOCS[] nextEventTimeDefined = refnETD[] nextEventTime = refnET[] - discreteStatesNeedUpdate, terminateSimulation, nominalsOfContinuousStatesChanged, valuesOfContinuousStatesChanged, nextEventTimeDefined, nextEventTime + 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 """ @@ -2653,6 +3255,8 @@ function fmi3GetEventIndicators(c::FMU3Instance) fmi3GetEventIndicators!(c, eventIndicators, ni) return eventIndicators end +# [NOTE] needs to be exported, because FMICore only exports `fmi3GetEventIndicators!` +export fmi3GetEventIndicators """ @@ -2683,20 +3287,19 @@ More detailed: 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) +function fmi3CompletedIntegratorStep( + c::FMU3Instance, + noSetFMUStatePriorToCurrentPoint::fmi3Boolean, +) + + status = fmi3CompletedIntegratorStep!( + c, + noSetFMUStatePriorToCurrentPoint, + c._ptr_enterEventMode, + c._ptr_terminateSimulation, + ) + + return (status, c.enterEventMode, c.terminateSimulation) end """ @@ -2731,8 +3334,22 @@ More detailed: See also [`fmi3EnterEventMode`](@ref). """ -function fmi3EnterEventMode(c::FMU3Instance, stepEvent::Bool, stateEvent::Bool, rootsFound::AbstractArray{fmi3Int32}, nEventIndicators::Csize_t, timeEvent::Bool) - fmi3EnterEventMode(c, fmi3Boolean(stepEvent), fmi3Boolean(stateEvent), rootsFound, nEventIndicators, fmi3Boolean(timeEvent)) +function fmi3EnterEventMode( + c::FMU3Instance, + stepEvent::Bool, + stateEvent::Bool, + rootsFound::AbstractArray{fmi3Int32}, + nEventIndicators::Csize_t, + timeEvent::Bool, +) + fmi3EnterEventMode( + c, + fmi3Boolean(stepEvent), + fmi3Boolean(stateEvent), + rootsFound, + nEventIndicators, + fmi3Boolean(timeEvent), + ) end """ @@ -2769,14 +3386,16 @@ More detailed: 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 +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, +) if currentCommunicationPoint === nothing currentCommunicationPoint = c.t @@ -2795,7 +3414,16 @@ function fmi3DoStep!(c::FMU3Instance, currentCommunicationPoint::Union{Real, Not reflastSuccessfulTime = Ref(lastSuccessfulTime) c.t = currentCommunicationPoint - status = fmi3DoStep!(c, fmi3Float64(currentCommunicationPoint), fmi3Float64(communicationStepSize), fmi3Boolean(noSetFMUStatePriorToCurrentPoint), refeventEncountered, refterminateSimulation, refearlyReturn, reflastSuccessfulTime) + status = fmi3DoStep!( + c, + fmi3Float64(currentCommunicationPoint), + fmi3Float64(communicationStepSize), + fmi3Boolean(noSetFMUStatePriorToCurrentPoint), + refeventEncountered, + refterminateSimulation, + refearlyReturn, + reflastSuccessfulTime, + ) c.t += communicationStepSize eventEncountered = refeventEncountered[] diff --git a/src/FMI3/md.jl b/src/FMI3/md.jl index d7bee73..60ac466 100644 --- a/src/FMI3/md.jl +++ b/src/FMI3/md.jl @@ -9,24 +9,41 @@ # - [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 """ function fmi3LoadModelDescription(pathToModellDescription::String) md = fmi3ModelDescription() - md.stringValueReferences = Dict{String, fmi3ValueReference}() + md.stringValueReferences = Dict{String,fmi3ValueReference}() md.outputValueReferences = Array{fmi3ValueReference}(undef, 0) md.inputValueReferences = Array{fmi3ValueReference}(undef, 0) md.stateValueReferences = Array{fmi3ValueReference}(undef, 0) @@ -49,12 +66,28 @@ 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") - @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.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 = + parseNode(root, "description", String; onfail = "[Unknown Description]") # defaults md.modelExchange = nothing @@ -64,75 +97,102 @@ function fmi3LoadModelDescription(pathToModellDescription::String) # additionals md.valueReferences = [] - md.valueReferenceIndicies = Dict{UInt, UInt}() + md.valueReferenceIndicies = Dict{UInt,UInt}() for node in eachelement(root) 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.modelIdentifier = 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.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.modelIdentifier = node["modelIdentifier"] + 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.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) - - elseif node.name == "TypeDefinitions" - #ToDo: md.enumerations = createEnum(node) - @warn "ToDo: FMU has TypeDefinitions, but this is not implemented yet." + md.scheduledExecution = fmi3ModelDescriptionScheduledExecution() + md.scheduledExecution.modelIdentifier = node["modelIdentifier"] + 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", "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 # creating an index for value references (fast look-up for dependencies) - for i in 1:length(md.valueReferences) + for i = 1:length(md.valueReferences) md.valueReferenceIndicies[md.valueReferences[i]] = i - end + end # 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 +206,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,8 +215,8 @@ 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 typename = node.name @@ -191,11 +251,11 @@ function parseModelVariables(nodes::EzXML.Node, md::fmi3ModelDescription) modelVariables[index] = fmi3VariableClock(name, valueReference) elseif typename == "Enumeration" modelVariables[index] = fmi3VariableEnumeration(name, valueReference) - else + else @warn "Unknown data type `$(typename)`." # ToDo: how to handle unknown types end - + # modelVariables[index] = fmi3Variable(name, valueReference) if !(valueReference in md.valueReferences) @@ -207,246 +267,176 @@ 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 + modelVariables[index].canHandleMultipleSetPerTimeInstant = + parseNode(node, "canHandleMultipleSetPerTimeInstant", Bool) + modelVariables[index].annotations = parseNode(node, "annotations", String) + modelVariables[index].clocks = + parseArrayValueReferences(md, parseNode(node, "clocks", String)) - if haskey(node, "canHandleMultipleSetPerTimeInstant") - modelVariables[index].canHandleMultipleSetPerTimeInstant = fmi3parseBoolean(node["canHandleMultipleSetPerTimeInstant"]) + if typename ∉ ("Clock", "String") + modelVariables[index].intermediateUpdate = + parseNode(node, "intermediateUpdate", Bool) + else + @warn "Unsupported typename `$(typename)` for modelVariable attribute `intermediateUpdate`." end - if haskey(node, "annotations") - modelVariables[index].annotations = node["annotations"] + if typename ∉ ("Clock", "String") + modelVariables[index].previous = parseNode(node, "previous", Bool) + else + @warn "Unsupported typename `$(typename)` for modelVariable attribute `previous`." end - if haskey(node, "clocks") - modelVariables[index].clocks = fmi3parseArrayValueReferences(node["clocks"]) + 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, "intermediateUpdate") && typename != "Clock" && typename != "String" - modelVariables[index].intermediateUpdate = fmi3parseBoolean(node["intermediateUpdate"]) - end - - if haskey(node, "previous") && typename != "Clock" && typename != "String" - modelVariables[index].previous = fmi3parseBoolean(node["previous"]) + 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, "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"] - end - - if haskey(node, "unit") && (typename == "Float32" || typename == "Float64") - modelVariables[index].unit = node["unit"] - end - - if haskey(node, "displayUnit") && (typename == "Float32" || typename == "Float64") - modelVariables[index].displayUnit = node["displayUnit"] - end - - if haskey(node, "declaredType") && typename != "String" - modelVariables[index].declaredType = node["declaredType"] + if typename == "Float64" || typename == "Float32" + modelVariables[index].unit = parseNode(node, "unit", String) + modelVariables[index].displayUnit = parseNode(node, "displayUnit", String) 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 != "String" + modelVariables[index].declaredType = parseNode(node, "declaredType", String) + else + @warn "Unsupported typename `$(typename)` for modelVariable attribute `declaredType`." 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].min = + parseNode(node, "min", stringToDataType(md, typename)) + else + @warn "Unsupported typename `$(typename)` for modelVariable attribute `min`." end - if haskey(node, "nominal") && (typename == "Float32" || typename == "Float64") - modelVariables[index].nominal = parse(Real, node["nominal"]) + 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, "unbounded") && (typename == "Float32" || typename == "Float64") - modelVariables[index].unbounded = fmi3parseBoolean(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, "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" - for i in 1:length(md.enumerations) + if typename == "Enum" + for i = 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"]) - end - - if haskey(node, "reinit") && (typename == "Float32" || typename == "Float64") - modelVariables[index].reinit = parseFMI3Boolean(node["reinit"]) + if typename == "Float64" || typename == "Float32" + modelVariables[index].derivative = + parseNode(node, "derivative", fmi3ValueReference) + modelVariables[index].reinit = parseNode(node, "reinit", Bool) 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 - + md.stringValueReferences[name] = valueReference 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 - - if stateVR ∉ md.stateValueReferences - push!(md.stateValueReferences, stateVR) + # find states and derivatives + derValueRef = parseNode(node, "valueReference", fmi3ValueReference) + derVar = modelVariablesForValueReference(md, derValueRef)[1] + stateValueRef = + modelVariablesForValueReference(md, derVar.derivative)[1].valueReference + + 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" + elseif node.name == "Output" # find outputs - outVR = fmi3ValueReference(fmi3parseInteger(node["valueReference"])) - + outVR = parseNode(node, "valueReference", fmi3ValueReference) + if outVR ∉ md.outputValueReferences push!(md.outputValueReferences, outVR) end @@ -455,14 +445,14 @@ function parseModelStructure(nodes::EzXML.Node, md::fmi3ModelDescription) else @warn "Unknown entry in `ModelStructure` named `$(node.name)`." end - else + else @warn "Invalid entry for node `$(node.name)` in `ModelStructure`, missing entry `valueReference`." end 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"] @@ -472,34 +462,35 @@ function parseDependencies(node::EzXML.Node) varDep.dependencies = collect(parse(UInt, e) for e in dependenciesSplit) end end - end + end if haskey(node, "dependenciesKind") dependenciesKind = node["dependenciesKind"] 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 if varDep.dependencies !== nothing && varDep.dependenciesKind !== nothing if length(varDep.dependencies) != length(varDep.dependenciesKind) - @warn "Length of field dependencies ($(length(varDep.dependencies))) doesn't match length of dependenciesKind ($(length(varDep.dependenciesKind)))." + @warn "Length of field dependencies ($(length(varDep.dependencies))) doesn't match length of dependenciesKind ($(length(varDep.dependenciesKind)))." end end return varDep -end +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] @@ -514,7 +505,7 @@ function parseContinuousStateDerivative(nodes::EzXML.Node, md::fmi3ModelDescript end push!(md.modelStructure.derivatives, varDep) - else + else @warn "Invalid entry for node `Unknown` in `ModelStructure`, missing entry `index`." end elseif node.name == "EventIndicator" @@ -522,279 +513,6 @@ function parseContinuousStateDerivative(nodes::EzXML.Node, md::fmi3ModelDescript # TODO parse valueReferences to another array else @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 diff --git a/src/FMI3/prep.jl b/src/FMI3/prep.jl new file mode 100644 index 0000000..6301957 --- /dev/null +++ b/src/FMI3/prep.jl @@ -0,0 +1,285 @@ +# +# 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 = 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 diff --git a/src/FMIImport.jl b/src/FMIImport.jl index ec1018c..5e21755 100644 --- a/src/FMIImport.jl +++ b/src/FMIImport.jl @@ -5,168 +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 - -# 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 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 FMIBase.Requires +using FMIBase.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..76f53b4 --- /dev/null +++ b/src/binary.jl @@ -0,0 +1,153 @@ +# +# 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 + +""" + dlsym_opt(fmu, libHandle, symbol) + +The same as `dlsym(libHandle, symbol)`, but returns - `Ptr{Cvoid}(C_NULL)` if the symbol `symbol` is not available. + +# Arguments +- `fmu`: The FMU to log a info message if not available. +- `libHandle`: The library handle, see `dlsym`. +- `symbol`: The library symbol, see `dlsym`. + +# Returns +Library symbol if available, else `Ptr{Cvoid}(C_NULL)`. +""" +function dlsym_opt(fmu, libHandle, symbol) + addr = dlsym(libHandle, symbol; throw_error = false) + if isnothing(addr) + logInfo(fmu, "This FMU does not support the optional function '$symbol'.") + addr = Ptr{Cvoid}(C_NULL) + end + return 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 + +""" + loadFMU(pathToFMU; unpackPath, cleanup, type) + +Loads an FMU, independent of the used FMI-version (the version is checked during unpacking the archive). + +# Arguments +- `path::String` the path pointing on the FMU file. + +# Keywords +- `unpackPath::Union{String, Nothing}=nothing` the optional unpack path, if `nothing` a temporary directory depending on the OS is picked. +- `cleanup::Bool=true` a boolean indicating whether the temporary directory should be cleaned automatically. +- `type::Union{Symbol, Nothing}=nothing` the type of FMU (:CS, :ME, :SE), if multiple types are available. If `nothing` one of the available types is chosen automatically with the priority CS > ME > SE. +""" +function loadFMU( + pathToFMU::String; + unpackPath::Union{String,Nothing} = nothing, + cleanup::Bool = 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 diff --git a/src/convert.jl b/src/convert.jl new file mode 100644 index 0000000..c8a9c23 --- /dev/null +++ b/src/convert.jl @@ -0,0 +1,207 @@ +# +# 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 = + 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 = 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 = + 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 = 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 = 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 = 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 diff --git a/src/info.jl b/src/info.jl new file mode 100644 index 0000000..b472e2c --- /dev/null +++ b/src/info.jl @@ -0,0 +1,223 @@ +# +# Copyright (c) 2021 Tobias Thummerer, Lars Mikelsons, Josef Kircher +# Licensed under the MIT license. See LICENSE file in the project root for details. +# + +# Prints value references, but shortens if the number exceeds `max`. +function printValueReferences(fmu, vrs; max = 10) + len = length(vrs) + if len <= max + for vr in vrs + println("\t\t$(vr) $(valueReferenceToString(fmu, vr))") + end + else + half = floor(Integer, max) - 1 + for vr in vrs[1:half] + println("\t\t$(vr) $(valueReferenceToString(fmu, vr))") + end + println(".") + println(".") + println(".") + for vr in vrs[len-half:end] + println("\t\t$(vr) $(valueReferenceToString(fmu, vr))") + end + end + nothing +end + +""" + 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$(getGUID(fmu))") + println("\tGeneration tool:\t\t$(getGenerationTool(fmu))") + println("\tGeneration time:\t\t$(getGenerationDateAndTime(fmu))") + print("\tVar. naming conv.:\t\t") + if getVariableNamingConvention(fmu) == fmi2VariableNamingConventionFlat + println("flat") + elseif getVariableNamingConvention(fmu) == fmi2VariableNamingConventionStructured + println("structured") + else + println("[unknown]") + end + println("\tEvent indicators:\t\t$(getNumberOfEventIndicators(fmu))") + + println("\tInputs:\t\t\t\t$(length(fmu.modelDescription.inputValueReferences))") + printValueReferences(fmu, fmu.modelDescription.inputValueReferences) + + println("\tOutputs:\t\t\t$(length(fmu.modelDescription.outputValueReferences))") + printValueReferences(fmu, fmu.modelDescription.outputValueReferences) + + println("\tStates:\t\t\t\t$(length(fmu.modelDescription.stateValueReferences))") + printValueReferences(fmu, fmu.modelDescription.stateValueReferences) + + println("\tParameters:\t\t\t\t$(length(fmu.modelDescription.parameterValueReferences))") + printValueReferences(fmu, fmu.modelDescription.parameterValueReferences) + + println("\tSupports Co-Simulation:\t\t$(isCoSimulation(fmu))") + if isCoSimulation(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$(isModelExchange(fmu))") + if isModelExchange(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$(getModelName(fmu))") + println("\tFMI-Version:\t\t\t$(fmi3GetVersion(fmu))") + println("\tInstantiation Token:\t\t\t\t$(getInstantiationToken(fmu))") + println("\tGeneration tool:\t\t$(getGenerationTool(fmu))") + println("\tGeneration time:\t\t$(getGenerationDateAndTime(fmu))") + print("\tVar. naming conv.:\t\t") + if getVariableNamingConvention(fmu) == fmi3VariableNamingConventionFlat + println("flat") + elseif getVariableNamingConvention(fmu) == fmi3VariableNamingConventionStructured + println("structured") + else + println("[unknown]") + end + println("\tEvent indicators:\t\t$(getNumberOfEventIndicators(fmu))") + + println("\tInputs:\t\t\t\t$(length(fmu.modelDescription.inputValueReferences))") + printValueReferences(fmu, fmu.modelDescription.inputValueReferences) + + println("\tOutputs:\t\t\t$(length(fmu.modelDescription.outputValueReferences))") + printValueReferences(fmu, fmu.modelDescription.outputValueReferences) + + println("\tStates:\t\t\t\t$(length(fmu.modelDescription.stateValueReferences))") + printValueReferences(fmu, fmu.modelDescription.stateValueReferences) + + println("\tParameters:\t\t\t\t$(length(fmu.modelDescription.parameterValueReferences))") + printValueReferences(fmu, fmu.modelDescription.parameterValueReferences) + + println("\tSupports Co-Simulation:\t\t$(isCoSimulation(fmu))") + if isCoSimulation(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$(isModelExchange(fmu))") + if isModelExchange(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$(isScheduledExecution(fmu))") + if isScheduledExecution(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..a2b0a44 --- /dev/null +++ b/src/jacobian.jl @@ -0,0 +1,351 @@ +# +# 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 = 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 = 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 = 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 = 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 diff --git a/src/md_parse.jl b/src/md_parse.jl new file mode 100644 index 0000000..f674449 --- /dev/null +++ b/src/md_parse.jl @@ -0,0 +1,97 @@ +# +# 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 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..ef4b474 --- /dev/null +++ b/src/zip.jl @@ -0,0 +1,98 @@ +# +# 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 diff --git a/test/FMI2/externalLogging.jl b/test/FMI2/externalLogging.jl index 1888d88..05a36e2 100644 --- a/test/FMI2/externalLogging.jl +++ b/test/FMI2/externalLogging.jl @@ -5,11 +5,11 @@ 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 ### -comp = fmi2Instantiate!(myFMU; loggingOn=true, externalCallbacks=true) +comp = fmi2Instantiate!(myFMU; loggingOn = true, externalCallbacks = true) @test comp != 0 @info "The following warning is forced and not an issue:" @@ -20,9 +20,10 @@ open(joinpath(pwd(), "stdout"), "w") do out @test fmi2SetupExperiment(comp) == fmi2StatusOK end end - 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) @@ -30,7 +31,12 @@ end ### CASE B: Print log, but catch infos ### -comp = fmi2Instantiate!(myFMU; loggingOn=true, logStatusError=false, externalCallbacks=true) +comp = fmi2Instantiate!( + myFMU; + loggingOn = true, + logStatusError = false, + externalCallbacks = true, +) @test comp != 0 # # deactivate errors to capture them @@ -45,7 +51,7 @@ open(joinpath(pwd(), "stdout"), "w") do out @test fmi2ExitInitializationMode(comp) == fmi2StatusError end end - end + end end # ToDo: this test is wrong / not working (capture doesn't work for color output) @@ -63,7 +69,7 @@ end ### CASE C: Disable Log ### -comp = fmi2Instantiate!(myFMU; loggingOn=false, externalCallbacks=true) +comp = fmi2Instantiate!(myFMU; loggingOn = false, externalCallbacks = true) @test comp != 0 @info "The following warning is forced and not an issue:" @@ -74,7 +80,7 @@ open(joinpath(pwd(), "stdout"), "w") do out @test fmi2SetupExperiment(comp) == fmi2StatusOK end end - end + end end # ToDo: this test is wrong / not working (capture doesn't work for color output) #output = read(joinpath(pwd(), "stdout"), String) @@ -84,4 +90,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..c923386 100644 --- a/test/FMI2/getter_setter.jl +++ b/test/FMI2/getter_setter.jl @@ -7,10 +7,9 @@ # 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) +comp = fmi2Instantiate!(myFMU; loggingOn = false) @test comp != 0 @test fmi2SetupExperiment(comp, 0.0) == 0 @@ -54,12 +53,25 @@ cacheString = "" @test fmi2SetString(comp, stringValueReferences[1], rndString) == 0 @test fmi2GetString(comp, stringValueReferences[1]) == rndString -fmi2Set(comp, - [realValueReferences[1], integerValueReferences[1], booleanValueReferences[1], stringValueReferences[1]], - [rndReal, rndInteger, rndBoolean, rndString]) -@test fmi2Get(comp, - [realValueReferences[1], integerValueReferences[1], booleanValueReferences[1], stringValueReferences[1]]) == - [rndReal, rndInteger, rndBoolean, rndString] +setValue( + comp, + [ + realValueReferences[1], + integerValueReferences[1], + booleanValueReferences[1], + stringValueReferences[1], + ], + [rndReal, rndInteger, rndBoolean, rndString], +) +@test getValue( + comp, + [ + realValueReferences[1], + integerValueReferences[1], + booleanValueReferences[1], + stringValueReferences[1], + ], +) == [rndReal, rndInteger, rndBoolean, rndString] ################## # Testing Arrays # @@ -72,7 +84,7 @@ tmp = Random.randstring(8) rndString = [tmp, tmp] cacheReal = [0.0, 0.0] -cacheInteger = [fmi2Integer(0), fmi2Integer(0)] +cacheInteger = [fmi2Integer(0), fmi2Integer(0)] cacheBoolean = [fmi2Boolean(false), fmi2Boolean(false)] cacheString = [pointer(""), pointer("")] @@ -124,4 +136,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..8cb931b 100644 --- a/test/FMI2/logging.jl +++ b/test/FMI2/logging.jl @@ -5,11 +5,12 @@ 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) +comp = fmi2Instantiate!(myFMU; loggingOn = true) @test comp != 0 @info "The following warning is forced and not an issue:" @@ -20,7 +21,7 @@ open(joinpath(pwd(), "stdout"), "w") do out @test fmi2SetupExperiment(comp) == fmi2StatusOK end end - end + end end # ToDo: this test is wrong / not working (capture doesn't work for color output) #output = read(joinpath(pwd(), "stdout"), String) @@ -30,7 +31,7 @@ end ### CASE B: Print log, but catch infos ### -comp = fmi2Instantiate!(myFMU; loggingOn=true, logStatusError=false) +comp = fmi2Instantiate!(myFMU; loggingOn = true, logStatusError = false) @test comp != 0 # # deactivate errors to capture them @@ -45,7 +46,7 @@ open(joinpath(pwd(), "stdout"), "w") do out @test fmi2ExitInitializationMode(comp) == fmi2StatusError end end - end + end end if VERSION >= v"1.7.0" @@ -56,12 +57,15 @@ if VERSION >= v"1.7.0" output = read(joinpath(pwd(), "stderr"), String) println(output) - @test startswith(output, "┌ Warning: fmi2ExitInitializationMode(...): Needs to be called in state `fmi2ComponentStateInitializationMode`.\n") -end + @test startswith( + output, + "┌ Warning: fmi2ExitInitializationMode(...): Needs to be called in state `fmi2ComponentStateInitializationMode`.\n", + ) +end ### CASE C: Disable Log ### -comp = fmi2Instantiate!(myFMU; loggingOn=false) +comp = fmi2Instantiate!(myFMU; loggingOn = false) @test comp != 0 @info "The following warning is forced and not an issue:" @@ -72,9 +76,10 @@ open(joinpath(pwd(), "stdout"), "w") do out @test fmi2SetupExperiment(comp) == fmi2StatusOK end end - 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 +87,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..eb99c61 100644 --- a/test/FMI2/model_description.jl +++ b/test/FMI2/model_description.jl @@ -5,82 +5,98 @@ 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 -# comfort getters (dictionaries) - -@test length(fmi2GetValueReferencesAndNames(myFMU.modelDescription)) == 42 -@test length(fmi2GetValueReferencesAndNames(myFMU)) == 42 - -@test length(fmi2GetInputNames(myFMU.modelDescription)) == 0 -@test length(fmi2GetInputNames(myFMU)) == 0 - -@test length(fmi2GetOutputNames(myFMU.modelDescription)) == 0 -@test length(fmi2GetOutputNames(myFMU)) == 0 +@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 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 getDefaultStartTime(myFMU.modelDescription) ≈ 0.0 +@test getDefaultStopTime(myFMU.modelDescription) ≈ 1.0 +@test getDefaultTolerance(myFMU.modelDescription) ≈ 1e-4 +@test getDefaultStepSize(myFMU.modelDescription) === nothing -@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"]] +info(myFMU) # check if there is an error thrown -@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(fmi2GetNamesAndDescriptions(myFMU.modelDescription)) == 50 -@test length(fmi2GetNamesAndDescriptions(myFMU)) == 50 +# comfort getters (dictionaries) -@test length(fmi2GetNamesAndUnits(myFMU.modelDescription)) == 50 -dict = fmi2GetNamesAndUnits(myFMU) +@test length(getValueReferencesAndNames(myFMU.modelDescription)) == 42 +@test length(getValueReferencesAndNames(myFMU)) == 42 + +@test length(getInputNames(myFMU.modelDescription)) == 0 +@test length(getInputNames(myFMU)) == 0 + +@test length(getOutputNames(myFMU.modelDescription)) == 0 +@test length(getOutputNames(myFMU)) == 0 + +@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(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(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(getNamesAndDescriptions(myFMU.modelDescription)) == 50 +@test length(getNamesAndDescriptions(myFMU)) == 50 + +@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 +@test length(myFMU.modelDescription.unitDefinitions) == 10 @test length(myFMU.modelDescription.typeDefinitions) == 9 @test myFMU.modelDescription.unitDefinitions[5].name == "W" @test myFMU.modelDescription.unitDefinitions[6].baseUnit.kg == 1 @@ -89,35 +105,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 end -fmi2Unload(myFMU) \ No newline at end of file +unloadFMU(myFMU) diff --git a/test/FMI2/state.jl b/test/FMI2/state.jl index d6563ac..5fcd64c 100644 --- a/test/FMI2/state.jl +++ b/test/FMI2/state.jl @@ -9,10 +9,9 @@ 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) +comp = fmi2Instantiate!(myFMU; loggingOn = true) @test comp != 0 @test fmi2EnterInitializationMode(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..662866c 100644 --- a/test/FMI3/getter_setter.jl +++ b/test/FMI3/getter_setter.jl @@ -7,8 +7,8 @@ # Prepare FMU # ############### -myFMU = fmi3Load("Feedthrough", "ModelicaReferenceFMUs", "0.0.20") -inst = fmi3InstantiateCoSimulation!(myFMU; loggingOn=false) +myFMU = loadFMU("Feedthrough", "ModelicaReferenceFMUs", "0.0.20", "3.0") +inst = fmi3InstantiateCoSimulation!(myFMU; loggingOn = false) @test inst != 0 @test fmi3EnterInitializationMode(inst) == 0 @@ -93,15 +93,28 @@ cacheString = "" @test fmi3SetString(inst, stringValueReferences[1], rndString) == 0 @test fmi3GetString(inst, stringValueReferences[1]) == rndString -@test fmi3SetBinary(inst, binaryValueReferences[1], Csize_t(length(rndString)), pointer(rndString)) == 0 +@test fmi3SetBinary( + inst, + binaryValueReferences[1], + Csize_t(length(rndString)), + pointer(rndString), +) == 0 binary = fmi3GetBinary(inst, binaryValueReferences[1]) @test unsafe_string(binary) == rndString # TODO test after latest PR -fmi3Set(inst, - [float64ValueReferences[1], int32ValueReferences[1], booleanValueReferences[1], stringValueReferences[1], binaryValueReferences[1]], - [rndReal, Int32(rndInteger), rndBoolean, rndString, rndString]) -# @test fmi3Get(inst, +setValue( + inst, + [ + float64ValueReferences[1], + int32ValueReferences[1], + booleanValueReferences[1], + stringValueReferences[1], + binaryValueReferences[1], + ], + [rndReal, Int32(rndInteger), rndBoolean, rndString, rndString], +) +# @test getValue(inst, # [float64ValueReferences[1], integerValueReferences[1], booleanValueReferences[1], stringValueReferences[1], binaryValueReferences[1]]) == # [rndReal, Int32(rndInteger), rndBoolean, rndString, unsafe_string(rndString)] @@ -117,7 +130,7 @@ rndString = [tmp, tmp] cacheFloat32 = [Float32(0.0), Float32(0.0)] cacheFloat64 = [0.0, 0.0] -cacheInteger = [fmi3Int32(0), fmi3Int32(0)] +cacheInteger = [fmi3Int32(0), fmi3Int32(0)] cacheBoolean = [fmi3Boolean(false), fmi3Boolean(false)] cacheString = [pointer(""), pointer("")] @@ -255,4 +268,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..af1f93b 100644 --- a/test/FMI3/logging.jl +++ b/test/FMI3/logging.jl @@ -3,13 +3,11 @@ # 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 ### -inst = fmi3InstantiateCoSimulation!(myFMU; loggingOn=true) +inst = fmi3InstantiateCoSimulation!(myFMU; loggingOn = true) @test inst != 0 @info "The following warning is forced and not an issue:" @@ -20,7 +18,7 @@ open(joinpath(pwd(), "stdout"), "w") do out @test fmi3ExitInitializationMode(inst) == fmi3StatusError end end - end + end end # ToDo: this test is wrong / not working (capture doesn't work for color output) #output = read(joinpath(pwd(), "stdout"), String) @@ -30,7 +28,7 @@ end ### CASE B: Print log, but catch infos ### -inst = fmi3InstantiateCoSimulation!(myFMU; loggingOn=true, logStatusError=false) +inst = fmi3InstantiateCoSimulation!(myFMU; loggingOn = true, logStatusError = false) @test inst != 0 @info "The following warning is forced and not an issue:" @@ -41,7 +39,7 @@ open(joinpath(pwd(), "stdout"), "w") do out @test fmi3ExitInitializationMode(inst) == fmi3StatusError end end - end + end end output = read(joinpath(pwd(), "stdout"), String) @@ -49,12 +47,15 @@ output = read(joinpath(pwd(), "stdout"), String) if VERSION >= v"1.7.0" output = read(joinpath(pwd(), "stderr"), String) - @test startswith(output, "┌ Warning: fmi3ExitInitializationMode(...): Needs to be called in state `fmi3InstanceStateInitializationMode`.\n") -end + @test startswith( + output, + "┌ Warning: fmi3ExitInitializationMode(...): Needs to be called in state `fmi3InstanceStateInitializationMode`.\n", + ) +end ### CASE C: Disable Log ### -inst = fmi3InstantiateCoSimulation!(myFMU; loggingOn=false) +inst = fmi3InstantiateCoSimulation!(myFMU; loggingOn = false) @test inst != 0 @info "The following warning is forced and not an issue:" @@ -65,7 +66,7 @@ open(joinpath(pwd(), "stdout"), "w") do out @test fmi3ExitInitializationMode(inst) == fmi3StatusError end end - end + end end # ToDo: this test is wrong / not working (capture doesn't work for color output) #output = read(joinpath(pwd(), "stdout"), String) @@ -75,4 +76,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..cc37ecf 100644 --- a/test/FMI3/model_description.jl +++ b/test/FMI3/model_description.jl @@ -5,27 +5,31 @@ 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 + +info(myFMU) # check if there is an error thrown + +unloadFMU(myFMU) diff --git a/test/FMI3/state.jl b/test/FMI3/state.jl index 95b47be..7576e6a 100644 --- a/test/FMI3/state.jl +++ b/test/FMI3/state.jl @@ -9,8 +9,8 @@ import FMIImport.FMICore: fmi3FMUState -myFMU = fmi3Load("BouncingBall", "ModelicaReferenceFMUs", "0.0.20") -inst = fmi3InstantiateCoSimulation!(myFMU; loggingOn=true) +myFMU = loadFMU("BouncingBall", "ModelicaReferenceFMUs", "0.0.20", "3.0") +inst = fmi3InstantiateCoSimulation!(myFMU; loggingOn = true) @test inst != 0 @test fmi3EnterInitializationMode(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 54% rename from test/FMI2/eval.jl rename to test/eval.jl index 3a9628d..59c0248 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" -println(result.log) \ No newline at end of file +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) diff --git a/test/runtests.jl b/test/runtests.jl index 4ac2bff..2151fa6 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -9,11 +9,19 @@ import Random using FMIZoo using FMIImport.FMICore: fmi2Integer, fmi2Boolean, fmi2Real, fmi2String -using FMIImport.FMICore: fmi3Float32, fmi3Float64, fmi3Int8, fmi3UInt8, fmi3Int16, fmi3UInt16, fmi3Int32, fmi3UInt32, fmi3Int64, fmi3UInt64 +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 +30,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 +63,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 +76,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