diff --git a/README.md b/README.md index 90f99ffd..a6254820 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ | Package evaluation| [![Run PkgEval](https://github.com/ThummeTo/FMI.jl/actions/workflows/Eval.yml/badge.svg)](https://github.com/ThummeTo/FMI.jl/actions/workflows/Eval.yml) | | Code coverage | [![Coverage](https://codecov.io/gh/ThummeTo/FMI.jl/branch/main/graph/badge.svg)](https://codecov.io/gh/ThummeTo/FMI.jl) | | Collaboration | [![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) | +| Formatting | [![SciML Code Style](https://img.shields.io/static/v1?label=code%20style&message=SciML&color=9558b2&labelColor=389826)](https://github.com/SciML/SciMLStyle) | ## Breaking Changes in FMI.jl (starting from v0.14.0 until release of v1.0.0) If you want to migrate your project from [*FMI.jl*](https://github.com/ThummeTo/FMI.jl) < v1.0.0 to >= v1.0.0, you will face some breaking changes - but they are worth it as you will see! We decided to do multiple smaller breaking changes starting with v0.14.0, instead of one big one. Some of them are already implemented (checked), some are still on the todo (unchecked) but will be implemented before releasing v1.0.0. diff --git a/cross_checks/cross_check.jl b/cross_checks/cross_check.jl index 67e77126..3408ee24 100644 --- a/cross_checks/cross_check.jl +++ b/cross_checks/cross_check.jl @@ -22,7 +22,7 @@ include("cross_check_lib.jl") # Main Array that holds all information about the excecuted cross checks and results crossChecks = [] -getInputValues = function(t, u) +getInputValues = function (t, u) return nothing end @@ -31,108 +31,158 @@ function parse_commandline() @add_arg_table s begin "--os" - help = "The operating system for which the cross checks should be excecuted" - arg_type = String - default = "windows-latest" + help = "The operating system for which the cross checks should be excecuted" + arg_type = String + default = "windows-latest" "--ccrepo" - help = "The Url to the git repository that contains the cross checks." - arg_type = String - default = "https://github.com/modelica/fmi-cross-check" + help = "The Url to the git repository that contains the cross checks." + arg_type = String + default = "https://github.com/modelica/fmi-cross-check" "--ccbranch" - help = "The name of the branch in which the results will be pushed" - arg_type = String - default = "master" + help = "The name of the branch in which the results will be pushed" + arg_type = String + default = "master" "--tempdir" - help = "temporary directive that is used for cross checks and results" - arg_type = String + help = "temporary directive that is used for cross checks and results" + arg_type = String "--fmiversion" - help = "FMI version that should be used for the cross checks" - arg_type = String - default = "2.0" + help = "FMI version that should be used for the cross checks" + arg_type = String + default = "2.0" "--includefatals" - help = "Include FMUs that have caused the cross check runner to fail and exit" - action = :store_true + help = "Include FMUs that have caused the cross check runner to fail and exit" + action = :store_true "--skipnotcompliant" - help = "Reject officially not compliant FMUs and don't excecute them" - action = :store_true + help = "Reject officially not compliant FMUs and don't excecute them" + action = :store_true "--commitrejected" - help = "Also commit the result file for FMUs that hasn't been excecuted (e.g. officially not compliant FMUs if they are not skipped)" - action = :store_true + help = "Also commit the result file for FMUs that hasn't been excecuted (e.g. officially not compliant FMUs if they are not skipped)" + action = :store_true "--commitfailed" - help = "Also commit the result file for failed FMUs" - action = :store_true + help = "Also commit the result file for failed FMUs" + action = :store_true end println("Arguments used for cross check:") - for (arg,val) in parse_args(s) + for (arg, val) in parse_args(s) println("\t$(arg):\t\t\t$(val)") end return parse_args(s) end -function runCrossCheckFmu(checkPath::String, resultPath::String, check::FmuCrossCheck, skipnotcompliant::Bool, commitrejected::Bool, commitfailed::Bool)::FmuCrossCheck +function runCrossCheckFmu( + checkPath::String, + resultPath::String, + check::FmuCrossCheck, + skipnotcompliant::Bool, + commitrejected::Bool, + commitfailed::Bool, +)::FmuCrossCheck pathToFMU = joinpath(checkPath, "$(check.fmuCheck).fmu") fmuToCheck = nothing - try + try if !(check.notCompliant && skipnotcompliant) fmuToCheck = loadFMU(pathToFMU) info(fmuToCheck) hasInputValues = false # Read Options - fmuOptions = CSV.File(joinpath(checkPath, "$(check.fmuCheck)_ref.opt"), header=false) |> Dict + fmuOptions = + CSV.File( + joinpath(checkPath, "$(check.fmuCheck)_ref.opt"), + header = false, + ) |> Dict tStart = fmuOptions["StartTime"] tStop = fmuOptions["StopTime"] relTol = fmuOptions["RelTol"] # Read Ref values - fmuRecordValueNames = map((x) -> replace(strip(x),"\"" => "" ), (readdlm(joinpath(checkPath, "$(check.fmuCheck)_ref.csv"), ',', String)[1, 2:end])) - fmuRefValues = CSV.File(joinpath(checkPath, "$(check.fmuCheck)_ref.csv")) |> Tables.rowtable |> Tables.columntable + fmuRecordValueNames = map( + (x) -> replace(strip(x), "\"" => ""), + (readdlm(joinpath(checkPath, "$(check.fmuCheck)_ref.csv"), ',', String)[ + 1, + 2:end, + ]), + ) + fmuRefValues = + CSV.File(joinpath(checkPath, "$(check.fmuCheck)_ref.csv")) |> + Tables.rowtable |> + Tables.columntable if isfile(joinpath(checkPath, "$(check.fmuCheck)_in.csv")) - inputValues = CSV.File(joinpath(checkPath, "$(check.fmuCheck)_in.csv")) |> Tables.rowtable + inputValues = + CSV.File(joinpath(checkPath, "$(check.fmuCheck)_in.csv")) |> + Tables.rowtable hasInputValues = true - getInputValues = function(t, u) + getInputValues = function (t, u) for (valIndex, val) in enumerate(inputValues) if val.time >= t - u[:] = inputValues[valIndex][2:end] + u[:] = inputValues[valIndex][2:end] # a = collect(inputValues[valIndex])[2:end] # return a - break; + break end end end end - + if hasInputValues if check.type == CS - simData = simulateCS(fmuToCheck, (tStart, tStop); tolerance=relTol, saveat=fmuRefValues[1], inputFunction=getInputValues, inputValueReferences=:inputs, recordValues=fmuRecordValueNames) + simData = simulateCS( + fmuToCheck, + (tStart, tStop); + tolerance = relTol, + saveat = fmuRefValues[1], + inputFunction = getInputValues, + inputValueReferences = :inputs, + recordValues = fmuRecordValueNames, + ) elseif check.type == ME - simData = simulateME(fmuToCheck, (tStart, tStop); reltol=relTol, saveat=fmuRefValues[1], inputFunction=getInputValues, inputValueReferences=:inputs, recordValues=fmuRecordValueNames) + simData = simulateME( + fmuToCheck, + (tStart, tStop); + reltol = relTol, + saveat = fmuRefValues[1], + inputFunction = getInputValues, + inputValueReferences = :inputs, + recordValues = fmuRecordValueNames, + ) else @error "Unkown FMU Type. Only 'cs' and 'me' are valid types" end else if check.type == CS - simData = simulateCS(fmuToCheck, (tStart, tStop); tolerance=relTol, saveat=fmuRefValues[1], recordValues=fmuRecordValueNames) + simData = simulateCS( + fmuToCheck, + (tStart, tStop); + tolerance = relTol, + saveat = fmuRefValues[1], + recordValues = fmuRecordValueNames, + ) elseif check.type == ME - simData = simulateME(fmuToCheck, (tStart, tStop); reltol=relTol, saveat=fmuRefValues[1], recordValues=fmuRecordValueNames) + simData = simulateME( + fmuToCheck, + (tStart, tStop); + reltol = relTol, + saveat = fmuRefValues[1], + recordValues = fmuRecordValueNames, + ) else @error "Unkown FMU Type. Only 'cs' and 'me' are valid types" end end - + check.result = calucateNRMSE(fmuRecordValueNames, simData, fmuRefValues) check.skipped = false - + if (check.result < NRMSE_THRESHHOLD) check.success = true mkpath(resultPath) cd(resultPath) - rm("failed", force=true) - rm("rejected", force=true) - rm("README.md", force=true) + rm("failed", force = true) + rm("rejected", force = true) + rm("README.md", force = true) touch("passed") touch("README.md") file = open("README.md", "w") @@ -143,9 +193,9 @@ function runCrossCheckFmu(checkPath::String, resultPath::String, check::FmuCross if commitfailed mkpath(resultPath) cd(resultPath) - rm("passed", force=true) - rm("rejected", force=true) - rm("README.md", force=true) + rm("passed", force = true) + rm("rejected", force = true) + rm("README.md", force = true) touch("failed") end end @@ -154,9 +204,9 @@ function runCrossCheckFmu(checkPath::String, resultPath::String, check::FmuCross if commitrejected mkpath(resultPath) cd(resultPath) - rm("failed", force=true) - rm("passed", force=true) - rm("README.md", force=true) + rm("failed", force = true) + rm("passed", force = true) + rm("README.md", force = true) touch("rejected") end end @@ -165,15 +215,15 @@ function runCrossCheckFmu(checkPath::String, resultPath::String, check::FmuCross @warn e check.result = nothing check.skipped = false - io = IOBuffer(); + io = IOBuffer() showerror(io, e) check.error = String(take!(io)) check.success = false mkpath(resultPath) cd(resultPath) - rm("rejected", force=true) - rm("passed", force=true) - rm("README.md", force=true) + rm("rejected", force = true) + rm("passed", force = true) + rm("README.md", force = true) if commitfailed touch("failed") end @@ -184,7 +234,7 @@ function runCrossCheckFmu(checkPath::String, resultPath::String, check::FmuCross end end return check - + end function main() @@ -215,27 +265,37 @@ function main() # set up the github access for the fmi-cross-checks repo and checkout the respective branch github_token = get(ENV, "GITHUB_TOKEN", "") - tmp_dir = mktempdir(; cleanup=true) + tmp_dir = mktempdir(; cleanup = true) pkey_filename = create_ssh_private_key(tmp_dir, github_token, os) if os == "win64" pkey_filename = replace(pkey_filename, "\\" => "/") end - + cross_check_repo_name = get(ENV, "CROSS_CHECK_REPO_NAME", "") cross_check_repo_user = get(ENV, "CROSS_CHECK_REPO_USER", "") if github_token != "" && cross_check_repo_name != "" && cross_check_repo_user != "" withenv( - "GIT_SSH_COMMAND" => isnothing(github_token) ? "ssh" : "ssh -i $pkey_filename -o StrictHostKeyChecking=no" + "GIT_SSH_COMMAND" => + isnothing(github_token) ? "ssh" : + "ssh -i $pkey_filename -o StrictHostKeyChecking=no", ) do run( - Cmd(`$(git()) remote set-url origin git@github.com:$cross_check_repo_user/$cross_check_repo_name`, dir=fmiCrossCheckRepoPath) + Cmd( + `$(git()) remote set-url origin git@github.com:$cross_check_repo_user/$cross_check_repo_name`, + dir = fmiCrossCheckRepoPath, + ), ) end try - run(Cmd(`$(git()) checkout $(crossCheckBranch)`, dir=fmiCrossCheckRepoPath)) + run(Cmd(`$(git()) checkout $(crossCheckBranch)`, dir = fmiCrossCheckRepoPath)) catch - run(Cmd(`$(git()) checkout -b $(crossCheckBranch)`, dir=fmiCrossCheckRepoPath)) + run( + Cmd( + `$(git()) checkout -b $(crossCheckBranch)`, + dir = fmiCrossCheckRepoPath, + ), + ) end end @@ -244,31 +304,64 @@ function main() if !includeFatals crossChecks = filter(c -> (!(c.system in EXCLUDED_SYSTEMS)), crossChecks) end - + for (index, check) in enumerate(crossChecks) - checkPath = joinpath(fmiCrossCheckRepoPath, "fmus", check.fmiVersion, check.type, check.os, check.system, check.systemVersion, check.fmuCheck) - resultPath = joinpath(fmiCrossCheckRepoPath, "results", check.fmiVersion, check.type, check.os, TOOL_ID, TOOL_VERSION, check.system, check.systemVersion, check.fmuCheck) + checkPath = joinpath( + fmiCrossCheckRepoPath, + "fmus", + check.fmiVersion, + check.type, + check.os, + check.system, + check.systemVersion, + check.fmuCheck, + ) + resultPath = joinpath( + fmiCrossCheckRepoPath, + "results", + check.fmiVersion, + check.type, + check.os, + TOOL_ID, + TOOL_VERSION, + check.system, + check.systemVersion, + check.fmuCheck, + ) cd(checkPath) println("Checking $check for $checkPath and expecting $resultPath") - check = runCrossCheckFmu(checkPath, resultPath, check, skipnotcompliant, commitrejected, commitfailed) + check = runCrossCheckFmu( + checkPath, + resultPath, + check, + skipnotcompliant, + commitrejected, + commitfailed, + ) crossChecks[index] = check end println("#################### End FMI Cross checks Run ####################") - + # Write Summary of Cross Check run println("#################### Start FMI Cross check Summary ####################") println("\tTotal Cross checks:\t\t\t$(count(c -> (true), crossChecks))") println("\tSuccessfull Cross checks:\t\t$(count(c -> (c.success), crossChecks))") - println("\tFailed Cross checks:\t\t\t$(count(c -> (!c.success && c.error === nothing && !c.skipped), crossChecks))") - println("\tCross checks with errors:\t\t$(count(c -> (c.error !== nothing), crossChecks))") + println( + "\tFailed Cross checks:\t\t\t$(count(c -> (!c.success && c.error === nothing && !c.skipped), crossChecks))", + ) + println( + "\tCross checks with errors:\t\t$(count(c -> (c.error !== nothing), crossChecks))", + ) println("\tSkipped Cross checks:\t\t\t$(count(c -> (c.skipped), crossChecks))") println("\tList of successfull Cross checks") for (index, success) in enumerate(filter(c -> (c.success), crossChecks)) println("\u001B[32m\t\t$(index):\t$(success)\u001B[0m") end println("\tList of failed Cross checks") - for (index, success) in enumerate(filter(c -> (!c.success && c.error === nothing && !c.skipped), crossChecks)) + for (index, success) in enumerate( + filter(c -> (!c.success && c.error === nothing && !c.skipped), crossChecks), + ) println("\u001B[33m\t\t$(index):\t$(success)\u001B[0m") end println("\tList of Cross checks with errors") @@ -276,21 +369,33 @@ function main() println("\u001B[31m\t\t$(index):\t$(error)\u001B[0m") end println("#################### End FMI Cross check Summary ####################") - + if github_token != "" && cross_check_repo_name != "" && cross_check_repo_user != "" - run(Cmd(`$(git()) add -A`, dir=fmiCrossCheckRepoPath)) - run(Cmd(`$(git()) commit -a --allow-empty -m "Run FMI cross checks for FMI.JL"`, dir=fmiCrossCheckRepoPath)) - + run(Cmd(`$(git()) add -A`, dir = fmiCrossCheckRepoPath)) + run( + Cmd( + `$(git()) commit -a --allow-empty -m "Run FMI cross checks for FMI.JL"`, + dir = fmiCrossCheckRepoPath, + ), + ) + withenv( - "GIT_SSH_COMMAND" => isnothing(github_token) ? "ssh" : "ssh -i $pkey_filename -o StrictHostKeyChecking=no" + "GIT_SSH_COMMAND" => + isnothing(github_token) ? "ssh" : + "ssh -i $pkey_filename -o StrictHostKeyChecking=no", ) do try - run(Cmd(`$(git()) push`, dir=fmiCrossCheckRepoPath)) + run(Cmd(`$(git()) push`, dir = fmiCrossCheckRepoPath)) catch - run(Cmd(`$(git()) push --set-upstream origin $(crossCheckBranch)`, dir=fmiCrossCheckRepoPath)) + run( + Cmd( + `$(git()) push --set-upstream origin $(crossCheckBranch)`, + dir = fmiCrossCheckRepoPath, + ), + ) end end - rm(tmp_dir; force=true, recursive=true) + rm(tmp_dir; force = true, recursive = true) end end diff --git a/cross_checks/cross_check_lib.jl b/cross_checks/cross_check_lib.jl index 7f17376f..78cfb852 100644 --- a/cross_checks/cross_check_lib.jl +++ b/cross_checks/cross_check_lib.jl @@ -13,11 +13,14 @@ Hint: This will not check the cross check repository for integrety # Returns - `repoPath::String`: The path where the repository can be found locally (including the repository name) """ -function getFmuCrossCheckRepo(crossCheckRepo::String, unpackPath::Union{String, Nothing} = nothing)::String +function getFmuCrossCheckRepo( + crossCheckRepo::String, + unpackPath::Union{String,Nothing} = nothing, +)::String @info "Create temporary working directory" if unpackPath === nothing # cleanup=true leads to issues with automatic testing on linux server. - unpackPath = mktempdir(; prefix="fmicrosschecks_", cleanup=false) + unpackPath = mktempdir(; prefix = "fmicrosschecks_", cleanup = false) @info "temporary working directory created at $(unpackPath)" end @@ -25,7 +28,7 @@ function getFmuCrossCheckRepo(crossCheckRepo::String, unpackPath::Union{String, fmiCrossCheckRepoPath = joinpath(unpackPath, FMI_CROSS_CHECK_REPO_NAME) if !isdir(fmiCrossCheckRepoPath) println("Checking out cross-checks from $(crossCheckRepo)...") - run(Cmd(`$(git()) clone $(crossCheckRepo)`, dir=unpackPath)) + run(Cmd(`$(git()) clone $(crossCheckRepo)`, dir = unpackPath)) else println("Using existing cross-checks at $(fmiCrossCheckRepoPath)") end @@ -39,7 +42,11 @@ Returns a array of all available FMI Cross Checks - `fmiVersion::String`: FMI Version used for running the FMUs. Note: Currently only 2.0 officially supported - `os::String`: The operating system that is used for running the FMUs """ -function getFmusToTest(repoPath::String, fmiVersion::String, os::String)::Vector{FmuCrossCheck} +function getFmusToTest( + repoPath::String, + fmiVersion::String, + os::String, +)::Vector{FmuCrossCheck} results = [] fmiTypes = [ME, CS] for type in fmiTypes @@ -65,7 +72,22 @@ function getFmusToTest(repoPath::String, fmiVersion::String, os::String)::Vector @info "$checkPath is not compliant with latest rules" notCompliant = true end - push!(results, FmuCrossCheck(fmiVersion, type, os, system, version, check, notCompliant, missing, missing, missing, missing)) + push!( + results, + FmuCrossCheck( + fmiVersion, + type, + os, + system, + version, + check, + notCompliant, + missing, + missing, + missing, + missing, + ), + ) end end end @@ -83,7 +105,11 @@ It is normalized to the difference between the smallest and largest values of th # Returns - `nrmse::Float64`: the mean of the nrmse of all recorded variables """ -function calucateNRMSE(recordedVariables::Vector{String}, simData::FMUSolution, referenceData)::Float64 +function calucateNRMSE( + recordedVariables::Vector{String}, + simData::FMUSolution, + referenceData, +)::Float64 squaredErrorSums = zeros(length(recordedVariables)) valueCount = zeros(length(recordedVariables)) minimalValues = [] @@ -96,16 +122,26 @@ function calucateNRMSE(recordedVariables::Vector{String}, simData::FMUSolution, if (length(minimalValues) < nameIndex + 1) push!(minimalValues, referenceData[nameIndex+1][valIndex]) else - minimalValues[nameIndex] = min(minimalValues[nameIndex], referenceData[nameIndex+1][valIndex]) + minimalValues[nameIndex] = min( + minimalValues[nameIndex], + referenceData[nameIndex+1][valIndex], + ) end if (length(maximalValues) < nameIndex + 1) push!(maximalValues, referenceData[nameIndex+1][valIndex]) else - maximalValues[nameIndex] = max(maximalValues[nameIndex], referenceData[nameIndex+1][valIndex]) + maximalValues[nameIndex] = max( + maximalValues[nameIndex], + referenceData[nameIndex+1][valIndex], + ) end - squaredErrorSums[nameIndex] += ((simData.values.saveval[simIndex][nameIndex] - referenceData[nameIndex+1][valIndex]))^2 + squaredErrorSums[nameIndex] += + (( + simData.values.saveval[simIndex][nameIndex] - + referenceData[nameIndex+1][valIndex] + ))^2 end - break; + break end end end @@ -115,13 +151,18 @@ function calucateNRMSE(recordedVariables::Vector{String}, simData::FMUSolution, if (valueRange == 0) valueRange = 1 end - value = (sqrt(squaredErrorSums[recordValue]/valueCount[recordValue])/(valueRange)) + value = + (sqrt(squaredErrorSums[recordValue] / valueCount[recordValue]) / (valueRange)) push!(errors, value) end return mean(errors) end -function create_ssh_private_key(dir::AbstractString, ssh_pkey::AbstractString, os::AbstractString)::String +function create_ssh_private_key( + dir::AbstractString, + ssh_pkey::AbstractString, + os::AbstractString, +)::String is_linux = occursin("linux", os) if is_linux run(`chmod 700 $dir`) @@ -158,10 +199,8 @@ function decode_ssh_private_key(content::AbstractString)::String return content end - @info( - "This doesn't look like a raw SSH private key. - I will assume that it is a Base64-encoded SSH private key." - ) + @info("This doesn't look like a raw SSH private key. + I will assume that it is a Base64-encoded SSH private key.") decoded_content = String(Base64.base64decode(content)) return decoded_content -end \ No newline at end of file +end diff --git a/docs/make.jl b/docs/make.jl index f00ca742..a578342d 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -3,72 +3,77 @@ # Licensed under the MIT license. See LICENSE file in the project root for details. # -import Pkg; Pkg.develop(path=joinpath(@__DIR__,"../../FMI.jl")) +import Pkg; +Pkg.develop(path = joinpath(@__DIR__, "../../FMI.jl")); using Documenter, Plots, JLD2, DataFrames, CSV, MAT, FMI, FMIImport, FMICore using Documenter: GitHubActions -makedocs(sitename="FMI.jl", - format = Documenter.HTML( - collapselevel = 1, - sidebar_sitename = false, - edit_link = nothing, - size_threshold = 512000, - size_threshold_ignore = ["deprecated.md"] - ), - modules = [FMI, FMIImport, FMICore], - checkdocs=:exports, - linkcheck=true, - linkcheck_ignore=["https://thummeto.github.io/FMI.jl/dev/examples/inputs/", "https://github.com/ThummeTo/FMICore.jl/blob/main/src/FMI2_c.jl#L718"], - pages= Any[ - "Introduction" => "index.md" - "Features" => "features.md" - "FAQ" => "faq.md" - "Examples" => [ - "Overview" => "examples/overview.md" - "Simulate" => "examples/simulate.md" - "Parameterize" => "examples/parameterize.md" - "Multiple instances" => "examples/multiple_instances.md" - "Modelica conference 2021" => "examples/modelica_conference_2021.md" - "Manipulation" => "examples/manipulation.md" - "Multithreading" => "examples/multithreading.md" - "Multiprocessing" => "examples/multiprocessing.md" - ] - "User Level API - FMI.jl" => "library.md" - "Developer Level API" => Any[ - "fmi version independent content" => Any[ - "fmi_lowlevel_library_types.md", - "fmi_lowlevel_library_constants.md", - "fmi_lowlevel_library_functions.md" - ], - "FMI2 specific content" => Any[ - "fmi2_lowlevel_library_types.md", - "fmi2_lowlevel_library_constants.md", - "FMI2 Functions in FMI Import/Core .jl" => Any[ - "fmi2_lowlevel_modeldescription_functions.md", - "fmi2_lowlevel_library_functions.md", - "fmi2_lowlevel_ME_functions.md", - "fmi2_lowlevel_CS_functions.md", - ] +makedocs( + sitename = "FMI.jl", + format = Documenter.HTML( + collapselevel = 1, + sidebar_sitename = false, + edit_link = nothing, + size_threshold = 512000, + size_threshold_ignore = ["deprecated.md"], + ), + modules = [FMI, FMIImport, FMICore], + checkdocs = :exports, + linkcheck = true, + linkcheck_ignore = [ + "https://thummeto.github.io/FMI.jl/dev/examples/inputs/", + "https://github.com/ThummeTo/FMICore.jl/blob/main/src/FMI2_c.jl#L718", + ], + pages = Any[ + "Introduction" => "index.md" + "Features" => "features.md" + "FAQ" => "faq.md" + "Examples" => [ + "Overview" => "examples/overview.md" + "Simulate" => "examples/simulate.md" + "Parameterize" => "examples/parameterize.md" + "Multiple instances" => "examples/multiple_instances.md" + "Modelica conference 2021" => "examples/modelica_conference_2021.md" + "Manipulation" => "examples/manipulation.md" + "Multithreading" => "examples/multithreading.md" + "Multiprocessing" => "examples/multiprocessing.md" + ] + "User Level API - FMI.jl" => "library.md" + "Developer Level API" => Any[ + "fmi version independent content"=>Any[ + "fmi_lowlevel_library_types.md", + "fmi_lowlevel_library_constants.md", + "fmi_lowlevel_library_functions.md", + ], + "FMI2 specific content"=>Any[ + "fmi2_lowlevel_library_types.md", + "fmi2_lowlevel_library_constants.md", + "FMI2 Functions in FMI Import/Core .jl"=>Any[ + "fmi2_lowlevel_modeldescription_functions.md", + "fmi2_lowlevel_library_functions.md", + "fmi2_lowlevel_ME_functions.md", + "fmi2_lowlevel_CS_functions.md", ], - "FMI3 specific content" => Any[ - "fmi3_lowlevel_library_types.md", - "fmi3_lowlevel_library_constants.md", - "FMI3 Functions in FMI Import/Core .jl" => Any[ - "fmi3_lowlevel_modeldescription_functions.md", - "fmi3_lowlevel_library_functions.md", - "fmi3_lowlevel_ME_functions.md", - "fmi3_lowlevel_CS_functions.md", - "fmi3_lowlevel_SE_functions.md", - ] + ], + "FMI3 specific content"=>Any[ + "fmi3_lowlevel_library_types.md", + "fmi3_lowlevel_library_constants.md", + "FMI3 Functions in FMI Import/Core .jl"=>Any[ + "fmi3_lowlevel_modeldescription_functions.md", + "fmi3_lowlevel_library_functions.md", + "fmi3_lowlevel_ME_functions.md", + "fmi3_lowlevel_CS_functions.md", + "fmi3_lowlevel_SE_functions.md", ], - ] - "API Index" => "index_library.md" - "FMI Tool Information" => "fmi-tool-info.md" - "Related Publication" => "related.md" - "Contents" => "contents.md" - hide("Deprecated" => "deprecated.md") + ], ] - ) + "API Index" => "index_library.md" + "FMI Tool Information" => "fmi-tool-info.md" + "Related Publication" => "related.md" + "Contents" => "contents.md" + hide("Deprecated" => "deprecated.md") + ], +) function deployConfig() github_repository = get(ENV, "GITHUB_REPOSITORY", "") @@ -80,4 +85,8 @@ function deployConfig() return GitHubActions(github_repository, github_event_name, github_ref) end -deploydocs(repo = "github.com/ThummeTo/FMI.jl.git", devbranch = "main", deploy_config = deployConfig()) +deploydocs( + repo = "github.com/ThummeTo/FMI.jl.git", + devbranch = "main", + deploy_config = deployConfig(), +) diff --git a/examples/pluto-src/FMUExplorer/FMUExplorer.jl b/examples/pluto-src/FMUExplorer/FMUExplorer.jl index 70f214e3..d327fb05 100644 --- a/examples/pluto-src/FMUExplorer/FMUExplorer.jl +++ b/examples/pluto-src/FMUExplorer/FMUExplorer.jl @@ -1,5 +1,5 @@ ### A Pluto.jl notebook ### -# v0.19.43 +# v0.19.46 using Markdown using InteractiveUtils @@ -7,7 +7,14 @@ using InteractiveUtils # This Pluto notebook uses @bind for interactivity. When running this notebook outside of Pluto, the following 'mock version' of @bind gives bound variables a default value (instead of an error). macro bind(def, element) quote - local iv = try Base.loaded_modules[Base.PkgId(Base.UUID("6e696c72-6542-2067-7265-42206c756150"), "AbstractPlutoDingetjes")].Bonds.initial_value catch; b -> missing; end + local iv = try + Base.loaded_modules[Base.PkgId( + Base.UUID("6e696c72-6542-2067-7265-42206c756150"), + "AbstractPlutoDingetjes", + )].Bonds.initial_value + catch + b -> missing + end local el = $(esc(element)) global $(esc(def)) = Core.applicable(Base.get, el) ? Base.get(el) : iv(el) el @@ -48,18 +55,18 @@ FMUExplorer is a *Pluto.jl* notebook that allows - as the name suggests - to exp import PlotlyJS # ╔═╡ 57d2a3e8-835d-40bb-8e47-47ea5c0eedf0 -begin - plotlyjs() - - function lastn(str, n::Integer) - if length(str) > n-3 - return "..." * last(str, n-3) - else - return str - end - end - - md""" Source Code. """ +begin + plotlyjs() + + function lastn(str, n::Integer) + if length(str) > n - 3 + return "..." * last(str, n - 3) + else + return str + end + end + + md""" Source Code. """ end # ╔═╡ 92514cc6-a468-4d87-9104-d4459025de02 @@ -77,101 +84,104 @@ FMU source: $(@bind mode Select([:zoo => "FMIZoo.jl", :local => "Local file", :u # ╔═╡ 9aa11335-eb16-4310-9fc0-0672c64f3be1 begin - if mode == :zoo - tools_path = joinpath((splitpath(FMIZoo.get_model_filename("BouncingBall1D", "Dymola", "2022x"))[1:end-4])...) - tools = readdir(tools_path) - md""" - Exporting-Tool: $(@bind zoo_pick_tool Select(tools)) - """ - else - nothing - end + if mode == :zoo + tools_path = joinpath( + (splitpath(FMIZoo.get_model_filename("BouncingBall1D", "Dymola", "2022x"))[1:end-4])..., + ) + tools = readdir(tools_path) + md""" + Exporting-Tool: $(@bind zoo_pick_tool Select(tools)) + """ + else + nothing + end end # ╔═╡ d4769552-4083-4a3e-a477-168ac288b742 begin - if mode == :zoo - versions_path = joinpath(tools_path, zoo_pick_tool) - versions = readdir(versions_path) - md""" - Tool Version: $(@bind zoo_pick_version Select(versions)) - """ - else - nothing - end + if mode == :zoo + versions_path = joinpath(tools_path, zoo_pick_tool) + versions = readdir(versions_path) + md""" + Tool Version: $(@bind zoo_pick_version Select(versions)) + """ + else + nothing + end end # ╔═╡ 876653d2-6a68-4c5a-a3c6-a3ca23629989 begin - if mode == :zoo - fmivers_path = joinpath(versions_path, zoo_pick_version) - fmivers = readdir(fmivers_path) - filter!(e->e == "2.0", fmivers) # ToDo! - md""" - FMI version: $(@bind zoo_pick_fmiver Select(fmivers)) - """ - else - nothing - end + if mode == :zoo + fmivers_path = joinpath(versions_path, zoo_pick_version) + fmivers = readdir(fmivers_path) + filter!(e -> e == "2.0", fmivers) # ToDo! + md""" + FMI version: $(@bind zoo_pick_fmiver Select(fmivers)) + """ + else + nothing + end end # ╔═╡ 445f18d6-66db-4cf2-9205-f98fc3dd8d83 begin - if mode == :zoo - fmus_path = joinpath(fmivers_path, zoo_pick_fmiver) - fmus = readdir(fmus_path) - filter!(e->endswith(e, ".fmu"), fmus) - md""" - FMU: $(@bind zoo_pick_fmu Select(fmus)) - """ - else - nothing - end + if mode == :zoo + fmus_path = joinpath(fmivers_path, zoo_pick_fmiver) + fmus = readdir(fmus_path) + filter!(e -> endswith(e, ".fmu"), fmus) + md""" + FMU: $(@bind zoo_pick_fmu Select(fmus)) + """ + else + nothing + end end # ╔═╡ cd8ddd20-2ce8-4b5e-a431-47078416bbc9 begin - if mode == :local - md""" - Local file path: $(@bind fmu_path confirm(TextField(), label="Load")) - """ - elseif mode == :url - md""" - Online URL: $(@bind fmu_path confirm(TextField(), label="Load")) - """ - elseif mode == :zoo - fmu_path = joinpath(fmus_path, zoo_pick_fmu) - nothing - else - md""" - Fatal error. Unknown mode `$(mode)`. - """ - end + if mode == :local + md""" + Local file path: $(@bind fmu_path confirm(TextField(), label="Load")) + """ + elseif mode == :url + md""" + Online URL: $(@bind fmu_path confirm(TextField(), label="Load")) + """ + elseif mode == :zoo + fmu_path = joinpath(fmus_path, zoo_pick_fmu) + nothing + else + md""" + Fatal error. Unknown mode `$(mode)`. + """ + end end # ╔═╡ 6d0232ef-b181-42d4-9160-b2f0ffcee84b -if (mode ∈ (:local, :zoo) && isfile(fmu_path)) || (mode == :url && startswith(fmu_path, "http")) - if endswith(fmu_path, ".fmu") - fmu = fmiLoad(fmu_path) - fmu.executionConfig.loggingOn = true - fmu.executionConfig.externalCallbacks = true - descr = fmu.modelDescription - md""" - ✔️ Sucessfully loaded FMU *$(fmu.modelName)*. - """ - else - fmu = nothing - descr = nothing - md"""❌ *$(fmu_path)* does not end with `.fmu`""" - end +if (mode ∈ (:local, :zoo) && isfile(fmu_path)) || + (mode == :url && startswith(fmu_path, "http")) + if endswith(fmu_path, ".fmu") + fmu = fmiLoad(fmu_path) + fmu.executionConfig.loggingOn = true + fmu.executionConfig.externalCallbacks = true + descr = fmu.modelDescription + md""" + ✔️ Sucessfully loaded FMU *$(fmu.modelName)*. + """ + else + fmu = nothing + descr = nothing + md"""❌ *$(fmu_path)* does not end with `.fmu`""" + end else - fmu = nothing - descr = nothing - if mode == :url - md"""❌ *$(fmu_path)* is not a valid URL!""" - else - md"""❌ *$(fmu_path)* is not a valid file path!""" - end + fmu = nothing + descr = nothing + if mode == :url + md"""❌ *$(fmu_path)* is not a valid URL!""" + else + md"""❌ *$(fmu_path)* is not a valid file path!""" + end end # ╔═╡ 98663a7e-69a0-4938-9ac2-b7d8f248592d @@ -182,22 +192,22 @@ md""" # ╔═╡ dfaee7a3-03af-45b2-a2e4-84c103b0001a if !isnothing(fmu) - md""" - | | | - | -------- | ------- | - | File path | $(lastn(fmu_path, 48)) | - | File size | $(round(filesize(fmu_path) / 2^20; digits=1))MB | - | Model name | $(lastn(fmu.modelName, 48)) | - | Number of variables | $(length(descr.valueReferences)) | - | Number of continuous states / derivatives | $(length(descr.stateValueReferences)) | - | Number of inputs | $(length(descr.inputValueReferences)) | - | Number of outputs | $(length(descr.outputValueReferences)) | - | Number of parameters | $(length(descr.parameterValueReferences)) | - """ + md""" + | | | + | -------- | ------- | + | File path | $(lastn(fmu_path, 48)) | + | File size | $(round(filesize(fmu_path) / 2^20; digits=1))MB | + | Model name | $(lastn(fmu.modelName, 48)) | + | Number of variables | $(length(descr.valueReferences)) | + | Number of continuous states / derivatives | $(length(descr.stateValueReferences)) | + | Number of inputs | $(length(descr.inputValueReferences)) | + | Number of outputs | $(length(descr.outputValueReferences)) | + | Number of parameters | $(length(descr.parameterValueReferences)) | + """ else - md""" - ⚠️ No FMU loaded. - """ + md""" + ⚠️ No FMU loaded. + """ end # ╔═╡ a41ce5ae-4e0c-40de-b63f-e469c982367b @@ -207,17 +217,17 @@ md""" # ╔═╡ 1ab6b2c8-d58e-48c5-95cb-cc35936413a9 if !isnothing(fmu) - md""" - | | | - | -------- | ------- | - | ME supported | $(!isnothing(descr.modelExchange) ? "✔️" : "❌") | - | Model identifier | $(!isnothing(descr.modelExchange) ? lastn(descr.modelExchange.modelIdentifier, 48) : "n.a.") | - | Directional derivatives | $(!isnothing(descr.modelExchange) && descr.modelExchange.providesDirectionalDerivative == FMI.fmi2True ? "✔️" : "❌") | - """ + md""" + | | | + | -------- | ------- | + | ME supported | $(!isnothing(descr.modelExchange) ? "✔️" : "❌") | + | Model identifier | $(!isnothing(descr.modelExchange) ? lastn(descr.modelExchange.modelIdentifier, 48) : "n.a.") | + | Directional derivatives | $(!isnothing(descr.modelExchange) && descr.modelExchange.providesDirectionalDerivative == FMI.fmi2True ? "✔️" : "❌") | + """ else - md""" - ⚠️ No FMU loaded. - """ + md""" + ⚠️ No FMU loaded. + """ end # ╔═╡ 6472cac2-cd31-4272-95cd-8e8e7245058d @@ -227,76 +237,76 @@ md""" # ╔═╡ 0cd23f48-18c7-4012-ae77-4a9f77934b60 if !isnothing(fmu) - md""" - | | | - | -------- | ------- | - | CS supported | $(!isnothing(descr.coSimulation) ? "✔️" : "❌") | - | Model identifier | $(!isnothing(descr.coSimulation) ? lastn(descr.coSimulation.modelIdentifier, 48) : "n.a.") | - | Directional derivatives | $(!isnothing(descr.coSimulation) && descr.coSimulation.providesDirectionalDerivative == FMI.fmi2True ? "✔️" : "❌") | - """ + md""" + | | | + | -------- | ------- | + | CS supported | $(!isnothing(descr.coSimulation) ? "✔️" : "❌") | + | Model identifier | $(!isnothing(descr.coSimulation) ? lastn(descr.coSimulation.modelIdentifier, 48) : "n.a.") | + | Directional derivatives | $(!isnothing(descr.coSimulation) && descr.coSimulation.providesDirectionalDerivative == FMI.fmi2True ? "✔️" : "❌") | + """ else - md""" - ⚠️ No FMU loaded. - """ + md""" + ⚠️ No FMU loaded. + """ end # ╔═╡ 1eb6c4fc-be1b-491b-95e0-bc6ca060216c begin - state_vrs = fmu.modelDescription.stateValueReferences - ders_vrs = fmu.modelDescription.derivativeValueReferences - input_vrs = fmu.modelDescription.inputValueReferences - output_vrs = fmu.modelDescription.outputValueReferences - param_vrs = fmu.modelDescription.parameterValueReferences - - internal_vrs = copy(fmu.modelDescription.valueReferences) - filter!(e->e∉state_vrs, internal_vrs) - filter!(e->e∉ders_vrs, internal_vrs) - filter!(e->e∉input_vrs, internal_vrs) - filter!(e->e∉output_vrs, internal_vrs) - filter!(e->e∉param_vrs, internal_vrs) - - modes = Vector{Pair{Symbol, String}}() - if !isnothing(descr.modelExchange) - push!(modes, :ME => "Model Exchange (ME)") - end - if !isnothing(descr.coSimulation) - push!(modes, :CS => "Co-Simulation (CS)") - end - if hasproperty(descr, :scheduledExecution) && !isnothing(descr.scheduledExecution) - push!(modes, :SE => "Scheduled Execution (SE)") - end - - def_start = FMI.fmi2GetDefaultStartTime(descr) - if isnothing(def_start) - def_start = 0.0 - end - - def_stop = FMI.fmi2GetDefaultStopTime(descr) - if isnothing(def_stop) - def_stop = 0.0 - end - - md""" - # Simulation - ## General Setup - Select simulation mode: $(@bind sim_mode Select(modes)) - - Simulate from $(@bind t_start_str TextField(default=string(def_start)))s to $(@bind t_stop_str TextField(default=string(def_stop)))s. - """ + state_vrs = fmu.modelDescription.stateValueReferences + ders_vrs = fmu.modelDescription.derivativeValueReferences + input_vrs = fmu.modelDescription.inputValueReferences + output_vrs = fmu.modelDescription.outputValueReferences + param_vrs = fmu.modelDescription.parameterValueReferences + + internal_vrs = copy(fmu.modelDescription.valueReferences) + filter!(e -> e ∉ state_vrs, internal_vrs) + filter!(e -> e ∉ ders_vrs, internal_vrs) + filter!(e -> e ∉ input_vrs, internal_vrs) + filter!(e -> e ∉ output_vrs, internal_vrs) + filter!(e -> e ∉ param_vrs, internal_vrs) + + modes = Vector{Pair{Symbol,String}}() + if !isnothing(descr.modelExchange) + push!(modes, :ME => "Model Exchange (ME)") + end + if !isnothing(descr.coSimulation) + push!(modes, :CS => "Co-Simulation (CS)") + end + if hasproperty(descr, :scheduledExecution) && !isnothing(descr.scheduledExecution) + push!(modes, :SE => "Scheduled Execution (SE)") + end + + def_start = FMI.fmi2GetDefaultStartTime(descr) + if isnothing(def_start) + def_start = 0.0 + end + + def_stop = FMI.fmi2GetDefaultStopTime(descr) + if isnothing(def_stop) + def_stop = 0.0 + end + + md""" + # Simulation + ## General Setup + Select simulation mode: $(@bind sim_mode Select(modes)) + + Simulate from $(@bind t_start_str TextField(default=string(def_start)))s to $(@bind t_stop_str TextField(default=string(def_stop)))s. + """ end # ╔═╡ 831e1919-9ac8-4934-80ce-03a691ea1a41 -if sim_mode == :CS - solver_sym = :none - md""" - ℹ️ For CS, the ODE solver is part of the compiled FMU. Choose ME or SE if you want to pick solvers. - """ +if sim_mode == :CS + solver_sym = :none + md""" + ℹ️ For CS, the ODE solver is part of the compiled FMU. Choose ME or SE if you want to pick solvers. + """ else - md""" - Solver: $(@bind solver_sym Select([:Tsit5 => "Tsit5"])) - ℹ️ Additional solvers will be deployed soon. - """ # :auto => "auto (DifferentialEquations.jl heurisitc)", - #:Rosenbrock23 => "Rosenbrock23" + md""" + Solver: $(@bind solver_sym Select([:Tsit5 => "Tsit5"])) + ℹ️ Additional solvers will be deployed soon. + """ # :auto => "auto (DifferentialEquations.jl heurisitc)", + #:Rosenbrock23 => "Rosenbrock23" end # ╔═╡ 43021052-25b4-43a9-a9e6-47f3c56d1341 @@ -307,34 +317,39 @@ Select parameters to change parameterization (select multiple by holding *Ctrl*) # ╔═╡ df122e2a-f4af-4ac7-b332-4c07e7aa4b0e if length(param_vrs) > 0 - @bind changeParameters MultiSelect(collect(param_vrs[i] => FMI.fmi2ValueReferenceToString(fmu, param_vrs[i])[1] for i in 1:length(param_vrs))) + @bind changeParameters MultiSelect( + collect( + param_vrs[i] => FMI.fmi2ValueReferenceToString(fmu, param_vrs[i])[1] for + i = 1:length(param_vrs) + ), + ) else - changeParameters = [] - md""" - ℹ️ The FMU doesn't contain parameters. - """ + changeParameters = [] + md""" + ℹ️ The FMU doesn't contain parameters. + """ end # ╔═╡ dbaf5ff3-9ee2-4355-aad2-e48c25eee514 -begin - function parameter_input(ps::Vector) - - return PlutoUI.combine() do Child - - inputs = [ - md""" $(FMI.fmi2ValueReferenceToString(fmu, vr)[1]): $( - Child(string(vr), TextField(default=string(FMI.fmi2GetStartValue(fmu, vr)))) - ) default: $(FMI.fmi2GetStartValue(fmu, vr))""" - - for vr in ps - ] - - md""" - $(inputs) - """ - end - end - nothing +begin + function parameter_input(ps::Vector) + + return PlutoUI.combine() do Child + + inputs = [ + md""" $(FMI.fmi2ValueReferenceToString(fmu, vr)[1]): $( + Child(string(vr), TextField(default=string(FMI.fmi2GetStartValue(fmu, vr)))) + ) default: $(FMI.fmi2GetStartValue(fmu, vr))""" + + for vr in ps + ] + + md""" + $(inputs) + """ + end + end + nothing end # ╔═╡ 942e6e27-04a1-4dce-9364-ccafd105facb @@ -344,11 +359,11 @@ Change parameter values: # ╔═╡ 095b6d5a-58af-4e3a-8ce0-b08a25629b38 if length(changeParameters) == 0 - md""" - ℹ️ No parameters selected above. - """ + md""" + ℹ️ No parameters selected above. + """ else - @bind params parameter_input(changeParameters) + @bind params parameter_input(changeParameters) end # ╔═╡ 43cab175-38df-42dd-b012-e28deec442b0 @@ -357,26 +372,26 @@ Changed parameters: """ # ╔═╡ 21178414-11ae-479b-9aae-b495f3c9c76e -begin - if length(changeParameters) == 0 - parameters = nothing - md""" - ℹ️ No parameters selected above. - """ - else - # parse named tuple to Dict - parameters = Dict{FMI.fmi2ValueReference, Any}() - for i in 1:length(params) - k = parse(FMI.fmi2ValueReference, string(keys(params)[i])) - v = values(params)[i] - typ = FMI.fmi2DataTypeForValueReference(descr, k) - if typ != FMI.fmi2String - v = parse(typ, v) - end - parameters[k] = v - end - parameters - end +begin + if length(changeParameters) == 0 + parameters = nothing + md""" + ℹ️ No parameters selected above. + """ + else + # parse named tuple to Dict + parameters = Dict{FMI.fmi2ValueReference,Any}() + for i = 1:length(params) + k = parse(FMI.fmi2ValueReference, string(keys(params)[i])) + v = values(params)[i] + typ = FMI.fmi2DataTypeForValueReference(descr, k) + if typ != FMI.fmi2String + v = parse(typ, v) + end + parameters[k] = v + end + parameters + end end # ╔═╡ 82c6de3e-66f2-4965-8bad-2faae667fb7e @@ -402,76 +417,94 @@ $(@bind recordInternals MultiSelect(collect(internal_vrs[i] => FMI.fmi2ValueRefe """ # ╔═╡ d28612f7-1405-4f6b-87db-56e3ebb852ff -begin - fmu_path; - - solver_sym; - sim_mode; - t_start_str; - t_stop_str; - - recordStates; - recordDerivatives; - recordInputs; - recordOutputs; - recordParameters; - recordInternals; - - parameters; - - md""" - ## Run Simulation - Select to start simulation: $(@bind simulation_trigger CheckBox(default=false)) - """ +begin + fmu_path + + solver_sym + sim_mode + t_start_str + t_stop_str + + recordStates + recordDerivatives + recordInputs + recordOutputs + recordParameters + recordInternals + + parameters + + md""" + ## Run Simulation + Select to start simulation: $(@bind simulation_trigger CheckBox(default=false)) + """ end # ╔═╡ 05d51ac1-4804-4641-b8b1-f4f4e10dcae6 -begin - t_start = parse(Float64, t_start_str) - t_stop = parse(Float64, t_stop_str) - - recordValues = Vector{FMI.fmi2ValueReference}([recordStates..., recordDerivatives..., recordInputs..., recordOutputs..., recordParameters..., recordInternals...]) - - if sim_mode == :ME || sim_mode == :SE - if solver_sym == :auto - solver = nothing - elseif solver_sym == :Tsit5 - solver = Tsit5() - elseif solver_sym == :Rosenbrock23 - solver = Rosenbrock23(autodiff=false) - else - md""" - ⛔ Unknwon solver `$(solver_sym)`. - """ - return nothing - end - end - - if simulation_trigger - try - global solution - if sim_mode == :ME - solution = fmiSimulateME(fmu, (t_start, t_stop); recordValues=recordValues, parameters=parameters, solver=solver) - nothing - elseif sim_mode == :CS - solution = fmiSimulateCS(fmu, (t_start, t_stop); recordValues=recordValues, parameters=parameters) - nothing - elseif sim_mode == :SE - solution = nothing - md"""🚧 SE not supported for now.""" - else - solution = nothing - md"""⛔ Unknown simulation mode.""" - end - catch e - global solution - solution = nothing - md"""⛔ Simulation failed. Reasons might be $br - the FMU couldn't be initialized because of insufficient default values $br - ... $br The error message is: $br $(e)""" - end - else - solution = nothing - md"""ℹ️ Start simulation to show results.""" - end +begin + t_start = parse(Float64, t_start_str) + t_stop = parse(Float64, t_stop_str) + + recordValues = Vector{FMI.fmi2ValueReference}([ + recordStates..., + recordDerivatives..., + recordInputs..., + recordOutputs..., + recordParameters..., + recordInternals..., + ]) + + if sim_mode == :ME || sim_mode == :SE + if solver_sym == :auto + solver = nothing + elseif solver_sym == :Tsit5 + solver = Tsit5() + elseif solver_sym == :Rosenbrock23 + solver = Rosenbrock23(autodiff = false) + else + md""" + ⛔ Unknwon solver `$(solver_sym)`. + """ + return nothing + end + end + + if simulation_trigger + try + global solution + if sim_mode == :ME + solution = fmiSimulateME( + fmu, + (t_start, t_stop); + recordValues = recordValues, + parameters = parameters, + solver = solver, + ) + nothing + elseif sim_mode == :CS + solution = fmiSimulateCS( + fmu, + (t_start, t_stop); + recordValues = recordValues, + parameters = parameters, + ) + nothing + elseif sim_mode == :SE + solution = nothing + md"""🚧 SE not supported for now.""" + else + solution = nothing + md"""⛔ Unknown simulation mode.""" + end + catch e + global solution + solution = nothing + md"""⛔ Simulation failed. Reasons might be $br - the FMU couldn't be initialized because of insufficient default values $br - ... $br The error message is: $br $(e)""" + end + else + solution = nothing + md"""ℹ️ Start simulation to show results.""" + end end # ╔═╡ 5058777f-beb6-43f4-b3d2-1127abbe1ac9 @@ -487,76 +520,102 @@ x-axis: $(@bind xaxis Select([0 => "time", collect(recordValues[i] => FMI.fmi2Va # ╔═╡ a8b53f45-5ff7-41dd-89d9-040dcf0e9898 begin - function get_xaxis(xaxis, recordValues) - xaxis_vals = nothing - for i in 1:length(recordValues) - rv = recordValues[i] - if xaxis == rv - xaxis_vals = collect(vals[i] for vals in solution.values.saveval) - break; - end - end - return xaxis_vals - end - - if isnothing(solution) - md"""ℹ️ Please run simulation in order to plot.""" - elseif isnothing(solution.values) || length(solution.values.saveval) == 0 || length(solution.values.saveval[1]) == 0 - md"""ℹ️ Simulation successfull, but no values recorded. Please select at least one value to be recorded in order to plot some results.""" - else - if xaxis == 0 # time - xaxis_vals = solution.values.t - nothing - else - xaxis_vals = get_xaxis(xaxis, recordValues) - md""" - ℹ️ Plotting of events is disabled if x-axis is not `time`. - """ - end - end + function get_xaxis(xaxis, recordValues) + xaxis_vals = nothing + for i = 1:length(recordValues) + rv = recordValues[i] + if xaxis == rv + xaxis_vals = collect(vals[i] for vals in solution.values.saveval) + break + end + end + return xaxis_vals + end + + if isnothing(solution) + md"""ℹ️ Please run simulation in order to plot.""" + elseif isnothing(solution.values) || + length(solution.values.saveval) == 0 || + length(solution.values.saveval[1]) == 0 + md"""ℹ️ Simulation successfull, but no values recorded. Please select at least one value to be recorded in order to plot some results.""" + else + if xaxis == 0 # time + xaxis_vals = solution.values.t + nothing + else + xaxis_vals = get_xaxis(xaxis, recordValues) + md""" + ℹ️ Plotting of events is disabled if x-axis is not `time`. + """ + end + end end # ╔═╡ 28026b82-83ba-42cc-89f9-a521daef717a -begin - function plotResults() - fig = Plots.plot() - _min = Inf - _max = -Inf - for i in 1:length(recordValues) - rv = recordValues[i] - if xaxis != rv - vals = collect(vals[i] for vals in solution.values.saveval) - _min = min(_min, vals...) - _max = max(_max, vals...) - Plots.plot!(fig, xaxis_vals, vals; label=FMI.fmi2ValueReferenceToString(fmu, rv)[1] * " [" * string(rv) * "]") - end - end - - if xaxis == 0 - firstTime = true - firstState = true - for i in 1:length(solution.events) - event = solution.events[i] - if event.indicator == 0 - plot!(fig, [event.t, event.t], [_min, _max]; label=(firstTime ? "Time-Event" : :none), style=:dash, color=:red) - firstTime = false - else - plot!(fig, [event.t, event.t], [_min, _max]; label=(firstState ? "State-Event" : :none), style=:dash, color=:blue) - firstState = false - end - end - end - - fig - end - - if isnothing(solution) - md"""ℹ️ Please run simulation in order to plot.""" - elseif isnothing(solution.values) || length(solution.values.saveval) == 0 || length(solution.values.saveval[1]) == 0 - md"""ℹ️ Simulation successfull, but no values recorded. Please select at least one value to be recorded in order to plot some results.""" - else - plotResults() - end +begin + function plotResults() + fig = Plots.plot() + _min = Inf + _max = -Inf + for i = 1:length(recordValues) + rv = recordValues[i] + if xaxis != rv + vals = collect(vals[i] for vals in solution.values.saveval) + _min = min(_min, vals...) + _max = max(_max, vals...) + Plots.plot!( + fig, + xaxis_vals, + vals; + label = FMI.fmi2ValueReferenceToString(fmu, rv)[1] * + " [" * + string(rv) * + "]", + ) + end + end + + if xaxis == 0 + firstTime = true + firstState = true + for i = 1:length(solution.events) + event = solution.events[i] + if event.indicator == 0 + plot!( + fig, + [event.t, event.t], + [_min, _max]; + label = (firstTime ? "Time-Event" : :none), + style = :dash, + color = :red, + ) + firstTime = false + else + plot!( + fig, + [event.t, event.t], + [_min, _max]; + label = (firstState ? "State-Event" : :none), + style = :dash, + color = :blue, + ) + firstState = false + end + end + end + + fig + end + + if isnothing(solution) + md"""ℹ️ Please run simulation in order to plot.""" + elseif isnothing(solution.values) || + length(solution.values.saveval) == 0 || + length(solution.values.saveval[1]) == 0 + md"""ℹ️ Simulation successfull, but no values recorded. Please select at least one value to be recorded in order to plot some results.""" + else + plotResults() + end end # ╔═╡ c7cceb40-ff26-4d3d-912a-199c6ddd49d2 @@ -566,13 +625,13 @@ md""" # ╔═╡ ca0ba941-8eb1-4641-ae4b-0925df73dedd if isnothing(solution) || isnothing(solution.states) - if sim_mode == :ME - md"""ℹ️ Please run simulation in order to display statistics.""" - else - md"""ℹ️ Please switch simulation mode to ME to show solver statistics.""" - end + if sim_mode == :ME + md"""ℹ️ Please run simulation in order to display statistics.""" + else + md"""ℹ️ Please switch simulation mode to ME to show solver statistics.""" + end else - solution.states.destats + solution.states.destats end # ╔═╡ 00000000-0000-0000-0000-000000000001 diff --git a/src/FMI2/sim.jl b/src/FMI2/sim.jl deleted file mode 100644 index 5416a11a..00000000 --- a/src/FMI2/sim.jl +++ /dev/null @@ -1,859 +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. -# - -using DifferentialEquations, DifferentialEquations.DiffEqCallbacks -import DifferentialEquations.SciMLBase: RightRootFind, ReturnCode - -using FMIImport: fmi2SetupExperiment, fmi2EnterInitializationMode, fmi2ExitInitializationMode, fmi2NewDiscreteStates, fmi2GetContinuousStates, fmi2GetNominalsOfContinuousStates, fmi2SetContinuousStates, fmi2GetDerivatives! -using FMIImport.FMICore: fmi2StatusOK, fmi2TypeCoSimulation, fmi2TypeModelExchange -using FMIImport.FMICore: fmi2ComponentState, fmi2ComponentStateInstantiated, fmi2ComponentStateInitializationMode, fmi2ComponentStateEventMode, fmi2ComponentStateContinuousTimeMode, fmi2ComponentStateTerminated, fmi2ComponentStateError, fmi2ComponentStateFatal -using FMIImport: FMU2Solution, FMU2Event - -import FMIImport: prepareSolveFMU, finishSolveFMU, handleEvents - -using FMIImport.FMICore.ChainRulesCore -using FMIImport.FMICore: FMU2InputFunction - -import LinearAlgebra: eigvals - -import ProgressMeter -import ThreadPools - -using FMIImport.FMICore: EMPTY_fmi2Real, EMPTY_fmi2ValueReference, ERR_MSG_CONT_TIME_MODE - -############ Model-Exchange ############ - -# Read next time event from fmu and provide it to the integrator -function time_choice(c::FMU2Component, integrator, tStart, tStop) - - #@info "TC" - - c.solution.evals_timechoice += 1 - - if c.eventInfo.nextEventTimeDefined == fmi2True - - if c.eventInfo.nextEventTime >= tStart && c.eventInfo.nextEventTime <= tStop - return c.eventInfo.nextEventTime - else - # the time event is outside the simulation range! - @debug "Next time event @$(c.eventInfo.nextEventTime)s is outside simulation time range ($(tStart), $(tStop)), skipping." - return nothing - end - else - return nothing - end - -end - -# Returns the event indicators for an FMU. -function condition(c::FMU2Component, out, x, t, integrator, inputFunction) - - @assert c.state == fmi2ComponentStateContinuousTimeMode "condition(...):\n" * ERR_MSG_CONT_TIME_MODE - - indicators!(c, out, x, t, inputFunction) - - return nothing -end - -# Handles the upcoming events. -# Sets a new state for the solver from the FMU (if needed). -function affectFMU!(c::FMU2Component, integrator, idx, inputFunction, solution::FMU2Solution) - - @assert c.state == fmi2ComponentStateContinuousTimeMode "affectFMU!(...):\n" * ERR_MSG_CONT_TIME_MODE - - c.solution.evals_affect += 1 - - # there are fx-evaluations before the event is handled, reset the FMU state to the current integrator step - fx_set(c, integrator.u, integrator.t, inputFunction; force=true) - - fmi2EnterEventMode(c) - - # Event found - handle it - handleEvents(c) - - left_x = nothing - right_x = nothing - - if c.eventInfo.valuesOfContinuousStatesChanged == fmi2True - left_x = integrator.u - right_x = fmi2GetContinuousStates(c) - @debug "affectFMU!(...): Handled event at t=$(integrator.t), new state is $(right_x)" - integrator.u = right_x - - u_modified!(integrator, true) - else - u_modified!(integrator, false) - @debug "affectFMU!(...): Handled event at t=$(integrator.t), no new state." - end - - if c.eventInfo.nominalsOfContinuousStatesChanged == fmi2True - x_nom = fmi2GetNominalsOfContinuousStates(c) - end - - ignore_derivatives() do - if idx != -1 # -1 no event, 0, time event, >=1 state event with indicator - e = FMU2Event(integrator.t, UInt64(idx), left_x, right_x) - push!(solution.events, e) - end - end - - #fmi2EnterContinuousTimeMode(c) -end - -# This callback is called every time the integrator finishes an (accepted) integration step. -function stepCompleted(c::FMU2Component, x, t, integrator, inputFunction, progressMeter, tStart, tStop, solution::FMU2Solution) - - @assert c.state == fmi2ComponentStateContinuousTimeMode "stepCompleted(...):\n" * ERR_MSG_CONT_TIME_MODE - - c.solution.evals_stepcompleted += 1 - - if progressMeter !== nothing - stat = 1000.0*(t-tStart)/(tStop-tStart) - if !isnan(stat) - stat = floor(Integer, stat) - ProgressMeter.update!(progressMeter, stat) - end - end - - (status, enterEventMode, terminateSimulation) = fmi2CompletedIntegratorStep(c, fmi2True) - - if terminateSimulation == fmi2True - @error "stepCompleted(...): FMU requested termination!" - end - - if enterEventMode == fmi2True - affectFMU!(c, integrator, -1, inputFunction, solution) - else - if !isnothing(inputFunction) - u = eval!(inputFunction, c, x, t) - u_refs = inputFunction.vrs - fmi2SetReal(c, u_refs, u) - end - end -end - -# save FMU values -function saveValues(c::FMU2Component, recordValues, x, t, integrator, inputFunction) - - @assert c.state == fmi2ComponentStateContinuousTimeMode "saveValues(...):\n" * ERR_MSG_CONT_TIME_MODE - - c.solution.evals_savevalues += 1 - - fx_set(c, x, t, inputFunction) - - # ToDo: Replace by inplace statement! - return (fmiGet(c, recordValues)...,) -end - -function saveEventIndicators(c::FMU2Component, recordEventIndicators, x, t, integrator, inputFunction) - - @assert c.state == fmi2ComponentStateContinuousTimeMode "saveEventIndicators(...):\n" * ERR_MSG_CONT_TIME_MODE - - c.solution.evals_saveeventindicators += 1 - - out = zeros(fmi2Real, c.fmu.modelDescription.numberOfEventIndicators) - indicators!(c, out, x, t, inputFunction) - - # ToDo: Replace by inplace statement! - return (out[recordEventIndicators]...,) -end - -function saveEigenvalues(c::FMU2Component, x, t, integrator, inputFunction) - - @assert c.state == fmi2ComponentStateContinuousTimeMode "saveEigenvalues(...):\n" * ERR_MSG_CONT_TIME_MODE - - c.solution.evals_saveeigenvalues += 1 - - fx_set(c, x, t, inputFunction) - - # ToDo: Replace this by an directional derivative call! - A = ReverseDiff.jacobian(_x -> FMI.fx(c, _x, [], t), x) - eigs = eigvals(A) - - vals = [] - for e in eigs - push!(vals, real(e)) - push!(vals, imag(e)) - end - - # ToDo: Replace by inplace statement! - return (vals...,) -end - -function fx(c::FMU2Component, - dx::AbstractArray{<:Real}, - x::AbstractArray{<:Real}, - p::Tuple, - t::Real, - inputFunction::Union{Nothing, FMU2InputFunction}) - - c.solution.evals_fx_inplace += 1 - - u = EMPTY_fmi2Real - u_refs = EMPTY_fmi2ValueReference - if !isnothing(inputFunction) - u = eval!(inputFunction, c, x, t) - u_refs = inputFunction.vrs - end - - # for zero state FMUs, don't request a `dx` - if c.fmu.isZeroState - c(;x=x, u=u, u_refs=u_refs, t=t) - else - c(;dx=dx, x=x, u=u, u_refs=u_refs, t=t) - end - - return nothing -end - -function fx(c::FMU2Component, - x::AbstractArray{<:Real}, - p::Tuple, - t::Real, - inputFunction::Union{Nothing, FMU2InputFunction}) - - c.solution.evals_fx_outofplace += 1 - - dx = zeros(fmi2Real, length(x)) - - fx(c, dx, x, p, t) - c.solution.evals_fx_inplace -= 1 # correct statistics, because fx-call above -> this was in fact an out-of-place evaluation - - return dx -end - -function fx_set(c::FMU2Component, - x::AbstractArray{<:Real}, - t::Real, - inputFunction::Union{Nothing, FMU2InputFunction}; force::Bool=false) - - u = EMPTY_fmi2Real - u_refs = EMPTY_fmi2ValueReference - if !isnothing(inputFunction) - u = eval!(inputFunction, c, x, t) - u_refs = inputFunction.vrs - end - - oldForce = c.force - c.force = force - c(;x=x, u=u, u_refs=u_refs, t=t) - c.force = oldForce - - return nothing -end - -function indicators!(c::FMU2Component, - ec, - x::AbstractArray{<:Real}, - t::Real, - inputFunction::Union{Nothing, FMU2InputFunction}) - - c.solution.evals_condition += 1 - - u = EMPTY_fmi2Real - u_refs = EMPTY_fmi2ValueReference - if !isnothing(inputFunction) - u = eval!(inputFunction, c, x, t) - u_refs = inputFunction.vrs - end - - c(;x=x, u=u, u_refs=u_refs, t=t, ec=ec) - - return nothing -end - -""" - fmi2SimulateME(c::FMU2Component, tspan::Union{Tuple{Float64, Float64}, Nothing}=nothing; kwargs...) - -Wrapper for `fmi2SimulateME(fmu::FMU2, c::Union{FMU2Component, Nothing}, tspan::Union{Tuple{Float64, Float64}, Nothing}=nothing; kwargs...)` without a provided FMU2. -(FMU2 `fmu` is taken from `c`) -""" -function fmi2SimulateME(c::FMU2Component, tspan::Union{Tuple{Float64, Float64}, Nothing}=nothing; kwargs...) - fmi2SimulateME(c.fmu, c, tspan; kwargs...) -end - -# sets up the ODEProblem for simulating a ME-FMU -function setupODEProblem(c::FMU2Component, x0::AbstractArray{fmi2Real}, tspan::Union{Tuple{Float64, Float64}, Nothing}=nothing; p=(), customFx=nothing, inputFunction::Union{FMU2InputFunction, Nothing}=nothing) - - if customFx === nothing - customFx = (dx, x, p, t) -> fx(c, dx, x, p, t, inputFunction) - end - - ff = ODEFunction{true}(customFx, - tgrad=nothing) - c.problem = ODEProblem{true}(ff, x0, tspan, p) - - return c.problem -end - -""" - fmi2SimulateME(fmu::FMU2, - c::Union{FMU2Component, Nothing}=nothing, - tspan::Union{Tuple{Float64, Float64}, Nothing}=nothing; - [tolerance::Union{Real, Nothing} = nothing, - dt::Union{Real, Nothing} = nothing, - solver = nothing, - customFx = nothing, - recordValues::fmi2ValueReferenceFormat = nothing, - recordEventIndicators::Union{AbstractArray{<:Integer, 1}, UnitRange{<:Integer}, Nothing} = nothing, - recordEigenvalues::Bool=false, - saveat = nothing, - x0::Union{AbstractArray{<:Real}, Nothing} = nothing, - setup::Union{Bool, Nothing} = nothing, - reset::Union{Bool, Nothing} = nothing, - instantiate::Union{Bool, Nothing} = nothing, - freeInstance::Union{Bool, Nothing} = nothing, - terminate::Union{Bool, Nothing} = nothing, - inputValueReferences::fmi2ValueReferenceFormat = nothing, - inputFunction = nothing, - parameters::Union{Dict{<:Any, <:Any}, Nothing} = nothing, - dtmax::Union{Real, Nothing} = nothing, - callbacksBefore = [], - callbacksAfter = [], - showProgress::Bool = true, - kwargs...]) - -Simulate ME-FMU for the given simulation time interval. - -State- and Time-Events are handled correctly. - -# Arguments -- `fmu::FMU2`: Mutable struct representing a FMU and all it instantiated instances. -- `c::Union{FMU2Component, Nothing}=nothing`: Mutable struct representing an instantiated instance of a FMU. -- `tspan::Union{Tuple{Float64, Float64}, Nothing}=nothing`: Simulation-time-span as tuple (default = nothing: use default value from `fmu`'s model description or (0.0, 1.0)) - -- `tolerance::Union{Real, Nothing} = nothing`: tolerance for the solver (default = nothing: use default value from `fmu`'s model description or -if not available- default from DifferentialEquations.jl) -- `dt::Union{Real, Nothing} = nothing`: stepszie for the solver (default = nothing: use default value from `fmu`'s model description or -if not available- default from DifferentialEquations.jl) -- `solver = nothing`: Any Julia-supported ODE-solver (default = nothing: use DifferentialEquations.jl default solver) -- `customFx`: [deprecated] custom state derivative function ẋ=f(x,t) -- `recordValues::fmi2ValueReferenceFormat` = nothing: Array of variables (Strings or variableIdentifiers) to record. Results are returned as `DiffEqCallbacks.SavedValues` -- `recordEventIndicators::Union{AbstractArray{<:Integer, 1}, UnitRange{<:Integer}, Nothing} = nothing`: Array or Range of event indicators to record -- `recordEigenvalues::Bool=false`: compute and record eigenvalues -- `saveat = nothing`: Time points to save (interpolated) values at (default = nothing: save at each solver timestep) -- `x0::Union{AbstractArray{<:Real}, Nothing} = nothing`: initial fmu State (default = nothing: use current or default-initial fmu state) -- `setup::Union{Bool, Nothing} = nothing`: call fmi2SetupExperiment, fmi2EnterInitializationMode and fmi2ExitInitializationMode before each step (default = nothing: use value from `fmu`'s `FMU2ExecutionConfiguration`) -- `reset::Union{Bool, Nothing} = nothing`: call fmi2Reset before each step (default = nothing: use value from `fmu`'s `FMU2ExecutionConfiguration`) -- `instantiate::Union{Bool, Nothing} = nothing`: call fmi2Instantiate! before each step (default = nothing: use value from `fmu`'s `FMU2ExecutionConfiguration`) -- `freeInstance::Union{Bool, Nothing} = nothing`: call fmi2FreeInstance after each step (default = nothing: use value from `fmu`'s `FMU2ExecutionConfiguration`) -- `terminate::Union{Bool, Nothing} = nothing`: call fmi2Terminate after each step (default = nothing: use value from `fmu`'s `FMU2ExecutionConfiguration`) -- `inputValueReferences::fmi2ValueReferenceFormat = nothing`: Input variables (Strings or variableIdentifiers) to set at each simulation step -- `inputFunction = nothing`: Function to get values for the input variables at each simulation step. - - ## Pattern [`c`: current component, `u`: current state ,`t`: current time, returning array of values to be passed to `fmi2SetReal(..., inputValueReferences, inputFunction(...))`]: - - `inputFunction(t::fmi2Real)` - - `inputFunction(c::FMU2Component, t::fmi2Real)` - - `inputFunction(c::FMU2Component, u::Union{AbstractArray{fmi2Real,1}, Nothing})` - - `inputFunction(u::Union{AbstractArray{fmi2Real,1}, Nothing}, t::fmi2Real)` - - `inputFunction(c::FMU2Component, u::Union{AbstractArray{fmi2Real,1}, Nothing}, t::fmi2Real)` - -- `parameters::Union{Dict{<:Any, <:Any}, Nothing} = nothing`: Dict of parameter variables (strings or variableIdentifiers) and values (Real, Integer, Boolean, String) to set parameters during initialization -- `dtmax::Union{Real, Nothing} = nothing`: sets the maximum stepszie for the solver (default = nothing: use `(Simulation-time-span-length)/100.0`) -- `callbacksBefore = [], callbacksAfter = []`: functions that are to be called before and after internal time-event-, state-event- and step-event-callbacks are called -- `showProgress::Bool = true`: print simulation progressmeter in REPL -- `kwargs...`: keyword arguments that get passed onto the solvers solve call - -# Returns: -- If keyword `recordValues` has value `nothing`, a struct of type `ODESolution`. -- If keyword `recordValues` is set, a tuple of type `(ODESolution, DiffEqCallbacks.SavedValues)`. - -See also [`fmi2Simulate`](@ref), [`fmi2SimulateCS`](@ref). -""" -function fmi2SimulateME(fmu::FMU2, c::Union{FMU2Component, Nothing}=nothing, tspan::Union{Tuple{Float64, Float64}, Nothing}=nothing; - tolerance::Union{Real, Nothing} = nothing, - dt::Union{Real, Nothing} = nothing, - solver = nothing, - customFx = nothing, - recordValues::fmi2ValueReferenceFormat = nothing, - recordEventIndicators::Union{AbstractArray{<:Integer, 1}, UnitRange{<:Integer}, Nothing} = nothing, - recordEigenvalues::Bool=false, - saveat = nothing, - x0::Union{AbstractArray{<:Real}, Nothing} = nothing, - setup::Union{Bool, Nothing} = nothing, - reset::Union{Bool, Nothing} = nothing, - instantiate::Union{Bool, Nothing} = nothing, - freeInstance::Union{Bool, Nothing} = nothing, - terminate::Union{Bool, Nothing} = nothing, - inputValueReferences::fmi2ValueReferenceFormat = nothing, - inputFunction = nothing, - parameters::Union{Dict{<:Any, <:Any}, Nothing} = nothing, - dtmax::Union{Real, Nothing} = nothing, - callbacksBefore = [], - callbacksAfter = [], - showProgress::Bool = true, - kwargs...) - - @assert fmi2IsModelExchange(fmu) "fmi2SimulateME(...): This function supports Model Exchange FMUs only." - #@assert fmu.type == fmi2TypeModelExchange "fmi2SimulateME(...): This FMU supports Model Exchange, but was instantiated in CS mode. Use `fmiLoad(...; type=:ME)`." - - recordValues = prepareValueReference(fmu, recordValues) - inputValueReferences = prepareValueReference(fmu, inputValueReferences) - - savingValues = (length(recordValues) > 0) - savingEventIndicators = !isnothing(recordEventIndicators) - hasInputs = (length(inputValueReferences) > 0) - hasParameters = (parameters !== nothing) - hasStartState = (x0 !== nothing) - - t_start, t_stop = (tspan == nothing ? (nothing, nothing) : tspan) - - cbs = [] - - for cb in callbacksBefore - push!(cbs, cb) - end - - if t_start === nothing - t_start = fmi2GetDefaultStartTime(fmu.modelDescription) - - if t_start === nothing - t_start = 0.0 - @info "No `t_start` chosen, no `t_start` available in the FMU, auto-picked `t_start=0.0`." - end - end - - if t_stop === nothing - t_stop = fmi2GetDefaultStopTime(fmu.modelDescription) - - if t_stop === nothing - t_stop = 1.0 - @warn "No `t_stop` chosen, no `t_stop` availabel in the FMU, auto-picked `t_stop=1.0`." - end - end - - if tolerance === nothing - tolerance = fmi2GetDefaultTolerance(fmu.modelDescription) - # if no tolerance is given, pick auto-setting from DifferentialEquations.jl - end - - if dt === nothing - dt = fmi2GetDefaultStepSize(fmu.modelDescription) - # if no dt is given, pick auto-setting from DifferentialEquations.jl - end - - if dtmax === nothing - dtmax = (t_stop-t_start)/100.0 - end - - # input function handling - _inputFunction = nothing - if inputFunction != nothing - _inputFunction = FMU2InputFunction(inputFunction, inputValueReferences) - end - - # argument `tolerance=nothing` here, because ME-FMUs doesn't support tolerance control (no solver included) - # tolerance for the solver is set-up later in this function - inputs = nothing - if hasInputs - inputValues = eval!(_inputFunction, nothing, nothing, t_start) - inputs = Dict(inputValueReferences .=> inputValues) - end - - c, x0 = prepareSolveFMU(fmu, c, fmi2TypeModelExchange, instantiate, freeInstance, terminate, reset, setup, parameters, t_start, t_stop, nothing; x0=x0, inputs=inputs, handleEvents=FMI.handleEvents) - fmusol = c.solution - - # Zero state FMU: add dummy state - if c.fmu.isZeroState - x0 = [0.0] - end - - # from here on, we are in event mode, if `setup=false` this is the job of the user - #@assert c.state == fmi2ComponentStateEventMode "FMU needs to be in event mode after setup." - - # if x0 === nothing - # x0 = fmi2GetContinuousStates(c) - # x0_nom = fmi2GetNominalsOfContinuousStates(c) - # end - - # initial event handling - #handleEvents(c) - #fmi2EnterContinuousTimeMode(c) - - c.fmu.hasStateEvents = (c.fmu.modelDescription.numberOfEventIndicators > 0) - c.fmu.hasTimeEvents = (c.eventInfo.nextEventTimeDefined == fmi2True) - - setupODEProblem(c, x0, (t_start, t_stop); customFx=customFx, inputFunction=_inputFunction) - - progressMeter = nothing - if showProgress - progressMeter = ProgressMeter.Progress(1000; desc="Simulating ME-FMU ...", color=:blue, dt=1.0) #, barglyphs=ProgressMeter.BarGlyphs("[=> ]")) - ProgressMeter.update!(progressMeter, 0) # show it! - end - - # callback functions - - if c.fmu.hasTimeEvents && c.fmu.executionConfig.handleTimeEvents - timeEventCb = IterativeCallback((integrator) -> time_choice(c, integrator, t_start, t_stop), - (integrator) -> affectFMU!(c, integrator, 0, _inputFunction, fmusol), Float64; - initial_affect = (c.eventInfo.nextEventTime == t_start), - save_positions=(false,false)) - push!(cbs, timeEventCb) - end - - if c.fmu.hasStateEvents && c.fmu.executionConfig.handleStateEvents - - eventCb = VectorContinuousCallback((out, x, t, integrator) -> condition(c, out, x, t, integrator, _inputFunction), - (integrator, idx) -> affectFMU!(c, integrator, idx, _inputFunction, fmusol), - Int64(c.fmu.modelDescription.numberOfEventIndicators); - rootfind = RightRootFind, - save_positions=(false,false), - interp_points=fmu.executionConfig.rootSearchInterpolationPoints) - push!(cbs, eventCb) - end - - # use step callback always if we have inputs or need event handling (or just want to see our simulation progress) - if hasInputs || c.fmu.hasStateEvents || c.fmu.hasTimeEvents || showProgress - stepCb = FunctionCallingCallback((x, t, integrator) -> stepCompleted(c, x, t, integrator, _inputFunction, progressMeter, t_start, t_stop, fmusol); - func_everystep = true, - func_start = true) - push!(cbs, stepCb) - end - - if savingValues - dtypes = collect(fmi2DataTypeForValueReference(c.fmu.modelDescription, vr) for vr in recordValues) - fmusol.values = SavedValues(fmi2Real, Tuple{dtypes...}) - fmusol.valueReferences = copy(recordValues) - - savingCB = nothing - if saveat === nothing - savingCB = SavingCallback((u,t,integrator) -> saveValues(c, recordValues, u, t, integrator, _inputFunction), - fmusol.values) - else - savingCB = SavingCallback((u,t,integrator) -> saveValues(c, recordValues, u, t, integrator, _inputFunction), - fmusol.values, - saveat=saveat) - end - - push!(cbs, savingCB) - end - - if savingEventIndicators - dtypes = collect(fmi2Real for ei in recordEventIndicators) - fmusol.eventIndicators = SavedValues(fmi2Real, Tuple{dtypes...}) - fmusol.recordEventIndicators = copy(recordEventIndicators) - - savingCB = nothing - if saveat === nothing - savingCB = SavingCallback((u,t,integrator) -> saveEventIndicators(c, recordEventIndicators, u, t, integrator, _inputFunction), - fmusol.eventIndicators) - else - savingCB = SavingCallback((u,t,integrator) -> saveEventIndicators(c, recordEventIndicators, u, t, integrator, _inputFunction), - fmusol.eventIndicators, - saveat=saveat) - end - - push!(cbs, savingCB) - end - - if recordEigenvalues - dtypes = collect(Float64 for _ in 1:2*length(c.fmu.modelDescription.stateValueReferences)) - fmusol.eigenvalues = SavedValues(fmi2Real, Tuple{dtypes...}) - - savingCB = nothing - if saveat === nothing - savingCB = SavingCallback((u,t,integrator) -> saveEigenvalues(c, u, t, integrator, _inputFunction), - fmusol.eigenvalues) - else - savingCB = SavingCallback((u,t,integrator) -> saveEigenvalues(c, u, t, integrator, _inputFunction), - fmusol.eigenvalues, - saveat=saveat) - end - - push!(cbs, savingCB) - end - - for cb in callbacksAfter - push!(cbs, cb) - end - - # if auto_dt == true - # @assert solver !== nothing "fmi2SimulateME(...): `auto_dt=true` but no solver specified, this is not allowed." - # tmpIntegrator = init(c.problem, solver) - # dt = auto_dt_reset!(tmpIntegrator) - # end - - solveKwargs = Dict{Symbol, Any}() - - if dt !== nothing - solveKwargs[:dt] = dt - end - - if tolerance !== nothing - solveKwargs[:reltol] = tolerance - end - - if saveat !== nothing - solveKwargs[:saveat] = saveat - end - - if isnothing(solver) - fmusol.states = solve(c.problem; callback = CallbackSet(cbs...), dtmax=dtmax, solveKwargs..., kwargs...) - else - fmusol.states = solve(c.problem, solver; callback = CallbackSet(cbs...), dtmax=dtmax, solveKwargs..., kwargs...) - end - - fmusol.success = (fmusol.states.retcode == ReturnCode.Success) - - if !fmusol.success - @warn "FMU simulation failed with solver return code `$(fmusol.states.retcode)`, please check log for hints." - end - - # ZeroStateFMU: remove dummy state - if c.fmu.isZeroState - c.solution.states = nothing - end - - # cleanup progress meter - if showProgress - ProgressMeter.finish!(progressMeter) - end - - finishSolveFMU(fmu, c, freeInstance, terminate) - - return fmusol -end - -export fmi2SimulateME - -# function fmi2SimulateME(fmu::FMU2, -# c::Union{AbstractArray{<:Union{FMU2Component, Nothing}}, Nothing}=nothing, tspan::Union{Tuple{Float64, Float64}, Nothing}=nothing; -# x0::Union{AbstractArray{<:AbstractArray{<:Real}}, AbstractArray{<:Real}, Nothing} = nothing, -# parameters::Union{AbstractArray{<:Dict{<:Any, <:Any}}, Dict{<:Any, <:Any}, Nothing} = nothing, -# kwargs...) - -# return ThreadPool.foreach((c, x0, parameters) -> fmi2SimulateME(fmu, c; x0=x0, parameters=parameters, kwargs...), zip()) -# end - -############ Co-Simulation ############ - -""" - fmi2SimulateCS(c::FMU2Component, tspan::Union{Tuple{Float64, Float64}, Nothing}=nothing; kwargs...) - -Wrapper for `fmi2SimulateCS(fmu::FMU2, c::Union{FMU2Component, Nothing}, tspan::Union{Tuple{Float64, Float64}, Nothing}=nothing; kwargs...)` without a provided FMU2. -(FMU2 `fmu` is taken from `c`) -""" -function fmi2SimulateCS(c::FMU2Component, tspan::Union{Tuple{Float64, Float64}, Nothing}=nothing; kwargs...) - fmi2SimulateCS(c.fmu, c, tspan; kwargs...) -end - -""" - fmi2SimulateCS(fmu::FMU2, - c::Union{FMU2Component, Nothing}=nothing, - tspan::Union{Tuple{Float64, Float64}, Nothing}=nothing; - [tolerance::Union{Real, Nothing} = nothing, - dt::Union{Real, Nothing} = nothing, - recordValues::fmi2ValueReferenceFormat = nothing, - saveat = [], - setup::Union{Bool, Nothing} = nothing, - reset::Union{Bool, Nothing} = nothing, - instantiate::Union{Bool, Nothing} = nothing, - freeInstance::Union{Bool, Nothing} = nothing, - terminate::Union{Bool, Nothing} = nothing, - inputValueReferences::fmi2ValueReferenceFormat = nothing, - inputFunction = nothing, - showProgress::Bool=true, - parameters::Union{Dict{<:Any, <:Any}, Nothing} = nothing]) - -Simulate CS-FMU for the given simulation time interval. - -# Arguments -- `fmu::FMU2`: Mutable struct representing a FMU and all it instantiated instances. -- `c::Union{FMU2Component, Nothing}=nothing`: Mutable struct representing an instantiated instance of a FMU. -- `tspan::Union{Tuple{Float64, Float64}, Nothing}=nothing`: Simulation-time-span as tuple (default = nothing: use default value from `fmu`'s model description or (0.0, 1.0)) - -- `tolerance::Union{Real, Nothing} = nothing`: tolerance for the solver (default = nothing: use default value from `fmu`'s model description or 0.0) -- `dt::Union{Real, Nothing} = nothing`: stepszie for the solver (default = nothing: use default value from `fmu`'s model description or 1e-3) -- `recordValues::fmi2ValueReferenceFormat` = nothing: Array of variables (Strings or variableIdentifiers) to record. Results are returned as `DiffEqCallbacks.SavedValues` -- `saveat = nothing`: Time points to save values at (default = nothing: save at each communication timestep) -- `setup::Union{Bool, Nothing} = nothing`: call fmi2SetupExperiment, fmi2EnterInitializationMode and fmi2ExitInitializationMode before each step (default = nothing: use value from `fmu`'s `FMU2ExecutionConfiguration`) -- `reset::Union{Bool, Nothing} = nothing`: call fmi2Reset before each step (default = nothing: use value from `fmu`'s `FMU2ExecutionConfiguration`) -- `instantiate::Union{Bool, Nothing} = nothing`: call fmi2Reset before each step (default = nothing: use value from `fmu`'s `FMU2ExecutionConfiguration`) -- `freeInstance::Union{Bool, Nothing} = nothing`: call fmi2FreeInstance after each step (default = nothing: use value from `fmu`'s `FMU2ExecutionConfiguration`) -- `terminate::Union{Bool, Nothing} = nothing`: call fmi2Terminate after each step (default = nothing: use value from `fmu`'s `FMU2ExecutionConfiguration`) -- `inputValueReferences::fmi2ValueReferenceFormat = nothing`: Input variables (Strings or variableIdentifiers) to set at each communication step -- `inputFunction = nothing`: Function to get values for the input variables at each communication step. - - ## Pattern [`c`: current component, `t`: current time, returning array of values to be passed to `fmi2SetReal(..., inputValueReferences, inputFunction(...))`]: - - `inputFunction(t::fmi2Real)` - - `inputFunction(c::FMU2Component, t::fmi2Real)` - -- `showProgress::Bool = true`: print simulation progressmeter in REPL -- `parameters::Union{Dict{<:Any, <:Any}, Nothing} = nothing`: Dict of parameter variables (strings or variableIdentifiers) and values (Real, Integer, Boolean, String) to set parameters during initialization - -# Returns: -- `fmusol::FMU2Solution`, containing bool `fmusol.success` and if keyword `recordValues` is set, the saved values as `fmusol.values`. - -See also [`fmi2Simulate`](@ref), [`fmi2SimulateME`](@ref). -""" -function fmi2SimulateCS(fmu::FMU2, c::Union{FMU2Component, Nothing}=nothing, tspan::Union{Tuple{Float64, Float64}, Nothing}=nothing; - tolerance::Union{Real, Nothing} = nothing, - dt::Union{Real, Nothing} = nothing, - recordValues::fmi2ValueReferenceFormat = nothing, - saveat = [], - setup::Union{Bool, Nothing} = nothing, - reset::Union{Bool, Nothing} = nothing, - instantiate::Union{Bool, Nothing} = nothing, - freeInstance::Union{Bool, Nothing} = nothing, - terminate::Union{Bool, Nothing} = nothing, - inputValueReferences::fmi2ValueReferenceFormat = nothing, - inputFunction = nothing, - showProgress::Bool=true, - parameters::Union{Dict{<:Any, <:Any}, Nothing} = nothing) - - @assert fmi2IsCoSimulation(fmu) "fmi2SimulateCS(...): This function supports Co-Simulation FMUs only." - #@assert fmu.type == fmi2TypeCoSimulation "fmi2SimulateCS(...): This FMU supports Co-Simulation, but was instantiated in ME mode. Use `fmiLoad(...; type=:CS)`." - - # input function handling - inputValueReferences = prepareValueReference(fmu, inputValueReferences) - hasInputs = (length(inputValueReferences) > 0) - - _inputFunction = nothing - u = EMPTY_fmi2Real - u_refs = EMPTY_fmi2ValueReference - if hasInputs - _inputFunction = FMU2InputFunction(inputFunction, inputValueReferences) - u_refs = _inputFunction.vrs - end - - # outputs - y_refs = EMPTY_fmi2ValueReference - y = EMPTY_fmi2Real - if !isnothing(recordValues) - y_refs = prepareValueReference(fmu, recordValues) - y = zeros(fmi2Real, length(y_refs)) - end - - - t_start, t_stop = (tspan == nothing ? (nothing, nothing) : tspan) - - # pull default values from the model description - if not given by user - variableSteps = fmi2IsCoSimulation(fmu) && fmu.modelDescription.coSimulation.canHandleVariableCommunicationStepSize - t_start = t_start === nothing ? fmi2GetDefaultStartTime(fmu.modelDescription) : t_start - t_start = t_start === nothing ? 0.0 : t_start - t_stop = t_stop === nothing ? fmi2GetDefaultStopTime(fmu.modelDescription) : t_stop - t_stop = t_stop === nothing ? 1.0 : t_stop - tolerance = tolerance === nothing ? fmi2GetDefaultTolerance(fmu.modelDescription) : tolerance - tolerance = tolerance === nothing ? 0.0 : tolerance - dt = dt === nothing ? fmi2GetDefaultStepSize(fmu.modelDescription) : dt - dt = dt === nothing ? 1e-3 : dt - - inputs = nothing - if hasInputs - inputValues = eval!(_inputFunction, nothing, nothing, t_start) - inputs = Dict(inputValueReferences .=> inputValues) - end - c, _ = prepareSolveFMU(fmu, c, fmi2TypeCoSimulation, instantiate, freeInstance, terminate, reset, setup, parameters, t_start, t_stop, tolerance; inputs=inputs) - fmusol = c.solution - - # default setup - if length(saveat) == 0 - saveat = t_start:dt:t_stop - end - - # setup if no variable steps - if variableSteps == false - if length(saveat) >= 2 - dt = saveat[2] - saveat[1] - end - end - - t = t_start - - progressMeter = nothing - if showProgress - progressMeter = ProgressMeter.Progress(1000; desc="Simulating CS-FMU ...", color=:blue, dt=1.0) - ProgressMeter.update!(progressMeter, 0) # show it! - end - - first_step = true - - fmusol.values = SavedValues(Float64, Tuple{collect(Float64 for i in 1:length(y_refs))...} ) - fmusol.valueReferences = copy(y_refs) - - i = 1 - - while t < t_stop - if variableSteps - if length(saveat) > (i+1) - dt = saveat[i+1] - saveat[i] - else - dt = t_stop - t - end - end - - if !first_step - fmi2DoStep(c, dt; currentCommunicationPoint=t) - t = t + dt - i += 1 - else - first_step = false - end - - if hasInputs - u = eval!(_inputFunction, c, nothing, t) - end - - c(u=u, u_refs=u_refs, y=y, y_refs=y_refs) - - svalues = (y...,) - DiffEqCallbacks.copyat_or_push!(fmusol.values.t, i, t) - DiffEqCallbacks.copyat_or_push!(fmusol.values.saveval, i, svalues, Val{false}) - - if progressMeter !== nothing - ProgressMeter.update!(progressMeter, floor(Integer, 1000.0*(t-t_start)/(t_stop-t_start)) ) - end - end - - if progressMeter !== nothing - ProgressMeter.finish!(progressMeter) - end - - fmusol.success = true # ToDo: Check successful simulation! - - finishSolveFMU(fmu, c, freeInstance, terminate) - - return fmusol -end - -export fmi2SimulateCS - -##### CS & ME ##### - -""" - fmi2Simulate(c::FMU2Component, tspan::Union{Tuple{Float64, Float64}, Nothing}=nothing; kwargs...) - -Wrapper for `fmi2Simulate(fmu::FMU2, c::Union{FMU2Component, Nothing}, tspan::Union{Tuple{Float64, Float64}, Nothing}=nothing; kwargs...)` without a provided FMU2. -(FMU2 `fmu` is taken from `c`) -""" -function fmi2Simulate(c::FMU2Component, tspan::Union{Tuple{Float64, Float64}, Nothing}=nothing; kwargs...) - fmi2Simulate(c.fmu, c, tspan; kwargs...) -end - -# function (c::FMU2Component)(; t::Tuple{Float64, Float64}, kwargs...) -# fmi2Simulate(c, t; kwargs...) -# end - -# function (f::FMU2)(; t::Tuple{Float64, Float64}, kwargs...) -# fmi2Simulate(c.fmu, t; kwargs...) -# end - -""" - fmi2Simulate(args...) - -Starts a simulation of the `FMU2` for the matching type (`fmi2SimulateCS(args...)` or `fmi2SimulateME(args...)`); if both types are available, CS is preferred. - -See also [`fmi2SimulateCS`](@ref), [`fmi2SimulateME`](@ref). -""" -function fmi2Simulate(fmu::FMU2, c::Union{FMU2Component, Nothing}=nothing, tspan::Union{Tuple{Float64, Float64}, Nothing}=nothing; kwargs...) - - if fmu.type == fmi2TypeCoSimulation - return fmi2SimulateCS(fmu, c, tspan; kwargs...) - elseif fmu.type == fmi2TypeModelExchange - return fmi2SimulateME(fmu, c, tspan; kwargs...) - else - error(unknownFMUType) - end -end - -export fmi2Simulate \ No newline at end of file diff --git a/src/FMI3/sim.jl b/src/FMI3/sim.jl deleted file mode 100644 index f436f998..00000000 --- a/src/FMI3/sim.jl +++ /dev/null @@ -1,1261 +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. -# - -using DifferentialEquations, DifferentialEquations.DiffEqCallbacks -import DifferentialEquations.SciMLBase: RightRootFind, ReturnCode - -using FMIImport: fmi3EnterInitializationMode, fmi3ExitInitializationMode, fmi3UpdateDiscreteStates, fmi3GetContinuousStates, fmi3GetNominalsOfContinuousStates, fmi3SetContinuousStates, fmi3GetContinuousStateDerivatives! -using FMIImport.FMICore: fmi3StatusOK, fmi3TypeCoSimulation, fmi3TypeModelExchange -using FMIImport.FMICore: fmi3InstanceState, fmi3InstanceStateInstantiated, fmi3InstanceStateInitializationMode, fmi3InstanceStateEventMode, fmi3InstanceStateContinuousTimeMode, fmi3InstanceStateTerminated, fmi3InstanceStateError, fmi3InstanceStateFatal -using FMIImport: FMU3Solution, FMU3Event - -using FMIImport.FMICore.ChainRulesCore - -import ProgressMeter - -############ Model-Exchange ############ - -# Read next time event from FMU and provide it to the integrator -function time_choice(c::FMU3Instance, integrator, tStart, tStop) - #@info "TC" - - discreteStatesNeedUpdate, terminateSimulation, nominalsOfContinuousStatesChanged, valuesOfContinuousStatesChanged, nextEventTimeDefined, nextEventTime = fmi3UpdateDiscreteStates(c) - - if nextEventTimeDefined == fmi3True - - if nextEventTime >= tStart && nextEventTime <= tStop - return nextEventTime - else - # the time event is outside the simulation range! - @debug "Next time event @$(c.eventInfo.nextEventTime)s is outside simulation time range ($(tStart), $(tStop)), skipping." - return nothing - end - else - return nothing - end -end - -# Handles events and returns the values and nominals of the changed continuous states. -function handleEvents(c::FMU3Instance) - # @assert c.state == fmi3InstanceStateEventMode "handleEvents(...): Must be in event mode!" - - # trigger the loop - discreteStatesNeedUpdate = fmi3True - nominalsChanged = fmi3False - valuesChanged = fmi3False - nextEventTimeDefined = fmi3False - nextEventTime = 0.0 - - while discreteStatesNeedUpdate == fmi3True - - # TODO set inputs - discreteStatesNeedUpdate, terminateSimulation, nominalsOfContinuousStatesChanged, valuesOfContinuousStatesChanged, nextEventTimeDefined, nextEventTime = fmi3UpdateDiscreteStates(c) - - if c.state != fmi3InstanceStateEventMode - fmi3EnterEventMode(c, c.stepEvent, c.stateEvent, c.rootsFound, Csize_t(c.fmu.modelDescription.numberOfEventIndicators), c.timeEvent) - end - # TODO inputEvent handling - discreteStatesNeedUpdate = fmi3True - while discreteStatesNeedUpdate == fmi3True - # update discrete states - discreteStatesNeedUpdate, terminateSimulation, nominalsOfContinuousStatesChanged, valuesOfContinuousStatesChanged, nextEventTimeDefined, nextEventTime = fmi3UpdateDiscreteStates(c) - - if valuesOfContinuousStatesChanged == fmi3True - valuesChanged = true - end - - if nominalsOfContinuousStatesChanged == fmi3True - nominalsChanged = true - end - - if terminateSimulation == fmi3True - @error "fmi3UpdateDiscreteStates returned error!" - end - end - end - fmi3EnterContinuousTimeMode(c) - @debug "handleEvents(_, $(enterEventMode), $(exitInContinuousMode)): rootsFound: $(c.rootsFound) valuesChanged: $(valuesChanged) continuousStates: $(fmi3GetContinuousStates(c))", - return valuesChanged, nominalsChanged - -end - -# Returns the event indicators for an FMU. -function condition(c::FMU3Instance, out::AbstractArray{<:Real}, x, t, integrator, inputFunction, inputValues::AbstractArray{fmi3ValueReference}) - if inputFunction !== nothing - fmi3SetFloat64(c, inputValues, inputFunction(c, x, t)) - end - - @assert c.state == fmi3InstanceStateContinuousTimeMode "condition(...): Must be called in mode continuous time." - - fmi3SetContinuousStates(c, x) - fmi3SetTime(c, t) - if inputFunction !== nothing - fmi3SetFloat64(c, inputValues, inputFunction(c, x, t)) - end - - # TODO check implementation of fmi3GetEventIndicators! mit abstract array - fmi3GetEventIndicators!(c, out, UInt(length(out))) - - # if length(indicators) > 0 - # for i in 1:length(indicators) - # if c.z_prev[i] < 0 && indicators[i] >= 0 - # c.rootsFound[i] = 1 - # elseif c.z_prev[i] > 0 && indicators[i] <= 0 - # c.rootsFound[i] = -1 - # else - # c.rootsFound[i] = 0 - # end - # c.stateEvent |= (c.rootsFound[i] != 0) - # # c.z_prev[i] = indicators[i] - # end - # end - - return nothing -end - -# Handles the upcoming events. -function affectFMU!(c::FMU3Instance, integrator, idx, inputFunction, inputValues::Array{fmi3ValueReference}, solution::FMU3Solution) - - @assert c.state == fmi3InstanceStateContinuousTimeMode "affectFMU!(...): Must be in continuous time mode!" - - # there are fx-evaluations before the event is handled, reset the FMU state to the current integrator step - fmi3SetContinuousStates(c, integrator.u) - fmi3SetTime(c, integrator.t) - if inputFunction !== nothing - fmi3SetFloat64(c, inputValues, inputFunction(c, integrator.u, integrator.t)) - end - - fmi3EnterEventMode(c, c.stepEvent, c.stateEvent, c.rootsFound, Csize_t(c.fmu.modelDescription.numberOfEventIndicators), c.timeEvent) - - # Event found - handle it - handleEvents(c) - - left_x = nothing - right_x = nothing - - if c.eventInfo.valuesOfContinuousStatesChanged == fmi3True - left_x = integrator.u - right_x = fmi3GetContinuousStates(c) - @debug "affectFMU!(...): Handled event at t=$(integrator.t), new state is $(new_u)" - integrator.u = right_x - - u_modified!(integrator, true) - #set_proposed_dt!(integrator, 1e-10) - else - u_modified!(integrator, false) - @debug "affectFMU!(...): Handled event at t=$(integrator.t), no new state." - end - - if c.eventInfo.nominalsOfContinuousStatesChanged == fmi3True - x_nom = fmi3GetNominalsOfContinuousStates(c) - end - - ignore_derivatives() do - if idx != -1 # -1 no event, 0, time event, >=1 state event with indicator - e = FMU3Event(integrator.t, UInt64(idx), left_x, right_x) - push!(solution.events, e) - end - end - - #fmi3EnterContinuousTimeMode(c) -end - -# Does one step in the simulation. -function stepCompleted(c::FMU3Instance, x, t, integrator, inputFunction, inputValues::AbstractArray{fmi3ValueReference}, progressMeter, tStart, tStop, solution::FMU3Solution) - - @assert c.state == fmi3InstanceStateContinuousTimeMode "stepCompleted(...): Must be in continuous time mode." - #@info "Step completed" - if progressMeter !== nothing - stat = 1000.0*(t-tStart)/(tStop-tStart) - if !isnan(stat) - stat = floor(Integer, stat) - ProgressMeter.update!(progressMeter, stat) - end - end - - # if length(indicators) > 0 - # c.stateEvent = fmi3False - - # for i in 1:length(indicators) - # if c.z_prev[i] < 0 && indicators[i] >= 0 - # c.rootsFound[i] = 1 - # elseif c.z_prev[i] > 0 && indicators[i] <= 0 - # c.rootsFound[i] = -1 - # else - # c.rootsFound[i] = 0 - # end - # c.stateEvent |= (c.rootsFound[i] != 0) - # c.z_prev[i] = indicators[i] - # end - # end - (status, enterEventMode, terminateSimulation) = fmi3CompletedIntegratorStep(c, fmi3True) - - if terminateSimulation == fmi3True - @error "stepCompleted(...): FMU requested termination!" - end - - if enterEventMode == fmi3True - affectFMU!(c, integrator, -1, inputFunction, inputValues, solution) - else - if inputFunction !== nothing - fmi3SetFloat64(c, inputValues, inputFunction(c, x, t)) - end - end -end - -# save FMU values -function saveValues(c::FMU3Instance, recordValues, x, t, integrator, inputFunction, inputValues) - - @assert c.state == fmi3InstanceStateContinuousTimeMode "saveValues(...): Must be in continuous time mode." - - #x_old = fmi3GetContinuousStates(c) - #t_old = c.t - - fmi3SetContinuousStates(c, x) - fmi3SetTime(c, t) - if inputFunction !== nothing - fmi3SetFloat64(c, inputValues, inputFunction(c, x, t)) - end - - #fmi3SetContinuousStates(c, x_old) - #fmi3SetTime(c, t_old) - - return (fmi3GetFloat64(c, recordValues)...,) -end - -# Returns the state derivatives of the FMU. -function fx(c::FMU3Instance, - dx::AbstractArray{<:Real}, - x::AbstractArray{<:Real}, - p::Tuple, - t::Real) - - # if isa(t, ForwardDiff.Dual) - # t = ForwardDiff.value(t) - # end - @debug "fx($(x), _, $(t))" - fmi3SetTime(c, t) - fmi3SetContinuousStates(c, x) - dx = fmi3GetContinuousStateDerivatives(c) - - # if all(isa.(dx, ForwardDiff.Dual)) - # dx_tmp = collect(ForwardDiff.value(e) for e in dx) - # fmi3GetContinuousStateDerivatives!(c, dx_tmp) - # T, V, N = fd_eltypes(dx) - # dx[:] = collect(ForwardDiff.Dual{T, V, N}(dx_tmp[i], ForwardDiff.partials(dx[i]) ) for i in 1:length(dx)) - # else - # fmi3GetContinuousStateDerivatives!(c, dx) - # end - - # y, dx = FMIImport.eval!(c, dx, nothing, nothing, x, nothing, nothing, t) - - return dx -end - -# same function as in FMI2_sim.jl -function _fx_fd(comp, dx, x, p, t) - - ȧrgs = [] - args = [] - - push!(ȧrgs, NoTangent()) - push!(args, fx) - - push!(ȧrgs, NoTangent()) - push!(args, comp) - - T = nothing - V = nothing - - dx_set = length(dx) > 0 && all(isa.(dx, ForwardDiff.Dual)) - x_set = length(x) > 0 && all(isa.(x, ForwardDiff.Dual)) - p_set = length(p) > 0 && all(isa.(p, ForwardDiff.Dual)) - t_set = isa(t, ForwardDiff.Dual) - - if dx_set - T, V, N = fd_eltypes(dx) - push!(ȧrgs, collect(ForwardDiff.partials(e) for e in dx)) - push!(args, collect(ForwardDiff.value(e) for e in dx)) - #@info "dx_set num=$(length(dx)) partials=$(length(ForwardDiff.partials(dx[1])))" - else - push!(ȧrgs, NoTangent()) - push!(args, dx) - end - - if x_set - T, V, N = fd_eltypes(x) - push!(ȧrgs, collect(ForwardDiff.partials(e) for e in x)) - push!(args, collect(ForwardDiff.value(e) for e in x)) - #@info "x_set num=$(length(x)) partials=$(length(ForwardDiff.partials(x[1])))" - else - push!(ȧrgs, NoTangent()) - push!(args, x) - end - - if p_set - T, V, N = fd_eltypes(p) - push!(ȧrgs, collect(ForwardDiff.partials(e) for e in p)) - push!(args, collect(ForwardDiff.value(e) for e in p)) - else - push!(ȧrgs, NoTangent()) - push!(args, p) - end - - if t_set - T, V, N = fd_eltypes(t) - push!(ȧrgs, ForwardDiff.partials(t)) - push!(args, ForwardDiff.value(t)) - else - push!(ȧrgs, NoTangent()) - push!(args, t) - end - - ȧrgs = (ȧrgs...,) - args = (args...,) - - y, _, sdx, sx, sp, st = ChainRulesCore.frule(ȧrgs, args...) - - ys = [] - - #[collect( ForwardDiff.Dual{Tx, Vx, Nx}(y[i], ForwardDiff.partials(x_partials[i], t_partials[i])) for i in 1:length(y) )...] - for i in 1:length(y) - is = NoTangent() - - if dx_set - is = sdx[i]#.values - end - if x_set - is = sx[i]#.values - end - - if p_set - is = sp[i]#.values - end - if t_set - is = st[i]#.values - end - - #display("dx: $dx") - #display("sdx: $sdx") - - #partials = (isdx, isx, isp, ist) - - #display(partials) - - - #V = Float64 - #N = length(partials) - #display("$T $V $N") - - #display(is) - - @assert is != ZeroTangent() && is != NoTangent() "is: $(is)" - - push!(ys, ForwardDiff.Dual{T, V, N}(y[i], is ) ) # ForwardDiff.Partials{N, V}(partials) - end - - ys -end - -import FMIImport: fmi3VariabilityConstant, fmi3InitialApprox, fmi3InitialExact -function setBeforeInitialization(mv::FMIImport.fmi3Variable) - return mv.variability != fmi3VariabilityConstant && mv.initial ∈ (fmi3InitialApprox, fmi3InitialExact) -end - -import FMIImport: fmi3CausalityInput, fmi3CausalityParameter, fmi3VariabilityTunable -function setInInitialization(mv::FMIImport.fmi3Variable) - return mv.causality == fmi3CausalityInput || (mv.causality != fmi3CausalityParameter && mv.variability == fmi3VariabilityTunable) || (mv.variability != fmi3VariabilityConstant && mv.initial == fmi3InitialExact) -end - -function prepareFMU(fmu::FMU3, c::Union{Nothing, FMU3Instance}, type::fmi3Type, instantiate::Union{Nothing, Bool}, terminate::Union{Nothing, Bool}, reset::Union{Nothing, Bool}, setup::Union{Nothing, Bool}, parameters::Union{Dict{<:Any, <:Any}, Nothing}, t_start, t_stop, tolerance; - x0::Union{AbstractArray{<:Real}, Nothing}=nothing, inputFunction=nothing, inputValueReferences=nothing) - - if instantiate === nothing - instantiate = fmu.executionConfig.instantiate - 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 - - c = nothing - - # instantiate (hard) - if instantiate - if type == fmi3TypeCoSimulation - c = fmi3InstantiateCoSimulation!(fmu) - elseif type == fmi3TypeModelExchange - c = fmi3InstantiateModelExchange!(fmu) - else - c = fmi3InstantiateScheduledExecution!(fmu) - 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(fmu)` to prevent this message." - if type == fmi3TypeCoSimulation - c = fmi3InstantiateCoSimulation!(fmu) - elseif type == fmi3TypeModelExchange - c = fmi3InstantiateModelExchange!(fmu) - else - c = fmi3InstantiateScheduledExecution!(fmu) - end - end - end - end - - @assert c !== nothing "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) - # TODO this part is handled by fmi3EnterInitializationMode - # if setup - # retcode = fmi2SetupExperiment(c, t_start, t_stop; tolerance=tolerance) - # @assert retcode == fmi3StatusOK "fmi3Simulate(...): Setting up experiment failed with return code $(retcode)." - # end - - # parameters - if parameters !== nothing - retcodes = fmi3Set(c, collect(keys(parameters)), collect(values(parameters)); filter=setBeforeInitialization) - @assert all(retcodes .== fmi3StatusOK) "fmi3Simulate(...): Setting initial parameters failed with return code $(retcode)." - end - - # inputs - inputs = nothing - if inputFunction !== nothing && inputValueReferences !== nothing - # set inputs - inputs = Dict{fmi3ValueReference, Any}() - - inputValues = nothing - if hasmethod(inputFunction, Tuple{FMU3Instance, fmi3Float64}) # CS - inputValues = inputFunction(c, t_start) - else # ME - inputValues = inputFunction(c, nothing, t_start) - end - - for i in 1:length(inputValueReferences) - vr = inputValueReferences[i] - inputs[vr] = inputValues[i] - end - end - - # inputs - if inputs !== nothing - retcodes = fmi3Set(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 x0 !== nothing - #retcode = fmi3SetContinuousStates(c, x0) - #@assert retcode == fmi3StatusOK "fmi3Simulate(...): Setting initial state failed with return code $(retcode)." - retcodes = fmi3Set(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 = fmi3Set(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 = fmi3Set(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 = fmi3Set(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 - - if type == fmi3TypeModelExchange - if x0 === nothing - x0 = fmi3GetContinuousStates(c) - end - end - - return c, x0 -end - -function prepareFMU(fmu::Vector{FMU3}, c::Vector{Union{Nothing, FMU3Instance}}, type::Vector{fmi3Type}, 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) - - 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 c[i] !== nothing - if freeInstance - fmi3FreeInstance!(c[i]) - @debug "[AUTO-RELEASE INST]" - end - end - - if type[i] == fmi3TypeCoSimulation - c[i] = fmi3InstantiateCoSimulation!(fmu[i]) - elseif type[i] == fmi3TypeModelExchange - c[i] = fmi3InstantiateModelExchange!(fmu[i]) - else - c[i] = fmi3InstantiateScheduledExecution!(fmu[i]) - end - @debug "[NEW INST]" - else - if c[i] === nothing - c[i] = fmu[i].instances[end] - end - end - - # soft terminate (if necessary) - if terminate - retcode = fmi3Terminate(c[i]; soft=true) - @assert retcode == fmi3StatusOK "fmi3Simulate(...): Termination failed with return code $(retcode)." - end - - # soft reset (if necessary) - if reset - retcode = fmi3Reset(c[i]; soft=true) - @assert retcode == fmi3StatusOK "fmi3Simulate(...): 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 = fmi3EnterInitializationMode(c[i], t_start, t_stop; tolerance=tolerance) - @assert retcode == fmi3StatusOK "fmi3Simulate(...): Entering initialization mode failed with return code $(retcode)." - end - - if x0 !== nothing - if x0[i] !== nothing - retcode = fmi3SetContinuousStates(c[i], x0[i]) - @assert retcode == fmi3StatusOK "fmi3Simulate(...): Setting initial state failed with return code $(retcode)." - end - end - - if parameters !== nothing - if parameters[i] !== nothing - retcodes = fmi3Set(c[i], collect(keys(parameters[i])), collect(values(parameters[i])) ) - @assert all(retcodes .== fmi3StatusOK) "fmi3Simulate(...): Setting initial parameters failed with return code $(retcode)." - end - end - - if initFct !== nothing - initFct() - end - - # exit setup (hard) - if setup - retcode = fmi3ExitInitializationMode(c[i]) - @assert retcode == fmi3StatusOK "fmi3Simulate(...): Exiting initialization mode failed with return code $(retcode)." - end - - if type == fmi3TypeModelExchange - if x0 === nothing - if x0[i] === nothing - x0[i] = fmi3GetContinuousStates(c[i]) - end - end - end - end - - end # ignore_derivatives - - return c, x0 -end - -function finishFMU(fmu::FMU3, c::FMU3Instance, terminate::Union{Nothing, Bool}, freeInstance::Union{Nothing, Bool}) - - 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 - -""" - fmi3SimulateME(c::FMU3Instance, t_start::Union{Real, Nothing} = nothing, t_stop::Union{Real, Nothing} = nothing; kwargs...) - -Wrapper for `fmi3SimulateME(fmu::FMU3, c::Union{FMU3Instance, Nothing}, t_start::Union{Real, Nothing} = nothing, t_stop::Union{Real, Nothing} = nothing; kwargs...)` without a provided FMU3. -(FMU3 `fmu` is taken from `c`) -""" -function fmi3SimulateME(c::FMU3Instance, t_start::Union{Real, Nothing} = nothing, t_stop::Union{Real, Nothing} = nothing; kwargs...) - fmi3SimulateME(c.fmu, c, t_start, t_stop; kwargs...) -end - -# sets up the ODEProblem for simulating a ME-FMU -function setupODEProblem(c::FMU3Instance, x0::AbstractArray{fmi3Float64}, t_start::fmi3Float64, t_stop::fmi3Float64; p=(), customFx=nothing) - if customFx === nothing - customFx = (dx, x, p, t) -> fx(c, dx, x, p, t) - end - - p = () - c.problem = ODEProblem(customFx, x0, (t_start, t_stop), p,) - - return c.problem -end - -""" - fmi3SimulateME(fmu::FMU3, - c::Union{FMU3Instance, Nothing}=nothing, - t_start::Union{Real, Nothing} = nothing, - t_stop::Union{Real, Nothing} = nothing; - [tolerance::Union{Real, Nothing} = nothing, - dt::Union{Real, Nothing} = nothing, - solver = nothing, - customFx = nothing, - recordValues::fmi3ValueReferenceFormat = nothing, - saveat = nothing, - x0::Union{AbstractArray{<:Real}, Nothing} = nothing, - setup::Union{Bool, Nothing} = nothing, - reset::Union{Bool, Nothing} = nothing, - instantiate::Union{Bool, Nothing} = nothing, - freeInstance::Union{Bool, Nothing} = nothing, - terminate::Union{Bool, Nothing} = nothing, - inputValueReferences::fmi3ValueReferenceFormat = nothing, - inputFunction = nothing, - parameters::Union{Dict{<:Any, <:Any}, Nothing} = nothing, - dtmax::Union{Real, Nothing} = nothing, - callbacks = [], - showProgress::Bool = true, - kwargs...]) - -Simulate ME-FMU for the given simulation time interval. - -State- and Time-Events are handled correctly. - -# Arguments -- `fmu::FMU3`: Mutable struct representing a FMU and all it instantiated instances. -- `c::Union{FMU3Instance, Nothing}=nothing`: Mutable struct representing an instantiated instance of a FMU. -- `t_start::Union{Real, Nothing} = nothing`: Simulation-time-span start time (default = nothing: use default value from `fmu`'s model description or 0.0) -- `t_stop::Union{Real, Nothing} = nothing`: Simulation-time-span stop time (default = nothing: use default value from `fmu`'s model description or 1.0) - -- `tolerance::Union{Real, Nothing} = nothing`: tolerance for the solver (default = nothing: use default value from `fmu`'s model description or -if not available- default from DifferentialEquations.jl) -- `dt::Union{Real, Nothing} = nothing`: stepszie for the solver (default = nothing: use default value from `fmu`'s model description or -if not available- default from DifferentialEquations.jl) -- `solver = nothing`: Any Julia-supported ODE-solver (default = nothing: use DifferentialEquations.jl default solver) -- `customFx`: [deprecated] custom state derivative function ẋ=f(x,t) -- `recordValues::fmi3ValueReferenceFormat` = nothing: Array of variables (Strings or variableIdentifiers) to record. Results are returned as `DiffEqCallbacks.SavedValues` -- `saveat = nothing`: Time points to save (interpolated) values at (default = nothing: save at each solver timestep) -- `x0::Union{AbstractArray{<:Real}, Nothing} = nothing`: initial fmu State (default = nothing: use current or default-initial fmu state) -- `setup::Union{Bool, Nothing} = nothing`: call fmi3EnterInitializationMode and fmi3ExitInitializationMode before each step (default = nothing: use value from `fmu`'s `FMU3ExecutionConfiguration`) -- `reset::Union{Bool, Nothing} = nothing`: call fmi3Reset before each step (default = nothing: use value from `fmu`'s `FMU3ExecutionConfiguration`) -- `instantiate::Union{Bool, Nothing} = nothing`: call fmi3Instantiate! before each step (default = nothing: use value from `fmu`'s `FMU3ExecutionConfiguration`) -- `freeInstance::Union{Bool, Nothing} = nothing`: call fmi3FreeInstance after each step (default = nothing: use value from `fmu`'s `FMU3ExecutionConfiguration`) -- `terminate::Union{Bool, Nothing} = nothing`: call fmi3Terminate after each step (default = nothing: use value from `fmu`'s `FMU3ExecutionConfiguration`) -- `inputValueReferences::fmi3ValueReferenceFormat = nothing`: Input variables (Strings or variableIdentifiers) to set at each simulation step -- `inputFunction = nothing`: Function to get values for the input variables at each simulation step. - - ## Pattern [`c`: current instance, `u`: current state ,`t`: current time, returning array of values to be passed to `fmi3SetFloat64(..., inputValueReferences, inputFunction(...))`]: - - `inputFunction(t::fmi3Float64)` - - `inputFunction(c::FMU3Instance, t::fmi3Float64)` - - `inputFunction(c::FMU3Instance, u::Union{AbstractArray{fmi3Float64,1}, Nothing})` - - `inputFunction(u::Union{AbstractArray{fmi3Float64,1}, Nothing}, t::fmi3Float64)` - - `inputFunction(c::FMU3Instance, u::Union{AbstractArray{fmi3Float64,1}, Nothing}, t::fmi3Float64)` - -- `parameters::Union{Dict{<:Any, <:Any}, Nothing} = nothing`: Dict of parameter variables (strings or variableIdentifiers) and values (Real, Integer, Boolean, String) to set parameters during initialization -- `dtmax::Union{Real, Nothing} = nothing`: sets the maximum stepszie for the solver (default = nothing: use `(Simulation-time-span-length)/100.0`) -- `callbacks = []`: functions that are to be called at each solver time step -- `showProgress::Bool = true`: print simulation progressmeter in REPL -- `kwargs...`: keyword arguments that get passed onto the solvers solve call - -# Returns: -- If keyword `recordValues` has value `nothing`, a struct of type `ODESolution`. -- If keyword `recordValues` is set, a tuple of type `(ODESolution, DiffEqCallbacks.SavedValues)`. - -See also [`fmi3Simulate`](@ref), [`fmi3SimulateCS`](@ref), [`fmi3SimulateSE`](@ref). -""" -function fmi3SimulateME(fmu::FMU3, c::Union{FMU3Instance, Nothing}=nothing, t_start::Union{Real, Nothing} = nothing, t_stop::Union{Real, Nothing} = nothing; - tolerance::Union{Real, Nothing} = nothing, - dt::Union{Real, Nothing} = nothing, - solver = nothing, - customFx = nothing, - recordValues::fmi3ValueReferenceFormat = nothing, - saveat = nothing, - x0::Union{AbstractArray{<:Real}, Nothing} = nothing, - setup::Union{Bool, Nothing} = nothing, - reset::Union{Bool, Nothing} = nothing, - instantiate::Union{Bool, Nothing} = nothing, - freeInstance::Union{Bool, Nothing} = nothing, - terminate::Union{Bool, Nothing} = nothing, - inputValueReferences::fmi3ValueReferenceFormat = nothing, - inputFunction = nothing, - parameters::Union{Dict{<:Any, <:Any}, Nothing} = nothing, - dtmax::Union{Real, Nothing} = nothing, - callbacks = [], - showProgress::Bool = true, - kwargs...) - @warn "ME-simulation is not working properly right now!" - - @assert fmi3IsModelExchange(fmu) "fmi3SimulateME(...): This function supports Model Exchange FMUs only." - #@assert fmu.type == fmi3TypeModelExchange "fmi3SimulateME(...): This FMU supports Model Exchange, but was instantiated in CS mode. Use `fmiLoad(...; type=:ME)`." # TODO why not using this?? - - # input function handling - _inputFunction = nothing - if inputFunction !== nothing - if hasmethod(inputFunction, Tuple{fmi3Float64}) - _inputFunction = (c, u, t) -> inputFunction(t) - elseif hasmethod(inputFunction, Tuple{Union{FMU3Instance, Nothing}, fmi3Float64}) - _inputFunction = (c, u, t) -> inputFunction(c, t) - elseif hasmethod(inputFunction, Tuple{Union{FMU3Instance, Nothing}, AbstractArray{fmi3Float64,1}}) - _inputFunction = (c, u, t) -> inputFunction(c, u) - elseif hasmethod(inputFunction, Tuple{AbstractArray{fmi3Float64,1}, fmi3Float64}) - _inputFunction = (c, u, t) -> inputFunction(u, t) - else - _inputFunction = inputFunction - end - @assert hasmethod(_inputFunction, Tuple{FMU3Instance, Union{AbstractArray{fmi3Float64,1}, Nothing}, fmi3Float64}) "The given input function does not fit the needed input function pattern for ME-FMUs, which are: \n- `inputFunction(t::fmi3Float64)`\n- `inputFunction(comp::FMU3Instance, t::fmi3Float64)`\n- `inputFunction(comp::FMU3Instance, u::Union{AbstractArray{fmi3Float64,1}, Nothing})`\n- `inputFunction(u::Union{AbstractArray{fmi3Float64,1}, Nothing}, t::fmi3Float64)`\n- `inputFunction(comp::FMU3Instance, u::Union{AbstractArray{fmi3Float64,1}, Nothing}, t::fmi3Float64)`" - end - - recordValues = prepareValueReference(fmu, recordValues) - inputValueReferences = prepareValueReference(fmu, inputValueReferences) - - fmusol = FMU3Solution(fmu) - - savingValues = (length(recordValues) > 0) - hasInputs = (length(inputValueReferences) > 0) - hasParameters = (parameters !== nothing) - hasStartState = (x0 !== nothing) - - cbs = [] - - for cb in callbacks - push!(cbs, cb) - end - - if t_start === nothing - t_start = fmi3GetDefaultStartTime(fmu.modelDescription) - - if t_start === nothing - t_start = 0.0 - @info "No `t_start` chosen, no `t_start` available in the FMU, auto-picked `t_start=0.0`." - end - end - - if t_stop === nothing - t_stop = fmi3GetDefaultStopTime(fmu.modelDescription) - - if t_stop === nothing - t_stop = 1.0 - @warn "No `t_stop` chosen, no `t_stop` available in the FMU, auto-picked `t_stop=1.0`." - end - end - - if tolerance === nothing - tolerance = fmi3GetDefaultTolerance(fmu.modelDescription) - # if no tolerance is given, pick auto-setting from DifferentialEquations.jl - end - - if dt === nothing - dt = fmi3GetDefaultStepSize(fmu.modelDescription) - # if no dt is given, pick auto-setting from DifferentialEquations.jl - end - - if dtmax === nothing - dtmax = (t_stop-t_start)/100.0 - end - - # argument `tolerance=nothing` here, because ME-FMUs doesn't support tolerance control (no solver included) - # tolerance for the solver is set-up later in this function - c, x0 = prepareFMU(fmu, c, fmi3TypeModelExchange, instantiate, terminate, reset, setup, parameters, t_start, t_stop, nothing; x0=x0, inputFunction=_inputFunction, inputValueReferences=inputValueReferences) - - # from here on, we are in event mode, if `setup=false` this is the job of the user - #@assert c.state == fmi3InstanceStateEventMode "FMU needs to be in event mode after setup." - - # if x0 === nothing - # x0 = fmi3GetContinuousStates(c) - # x0_nom = fmi3GetNominalsOfContinuousStates(c) - # end - - # initial event handling - # fmi3EnterEventMode(c, c.stepEvent, c.stateEvent, c.rootsFound, Csize_t(c.fmu.modelDescription.numberOfEventIndicators), c.timeEvent) - handleEvents(c) - #fmi3EnterContinuousTimeMode(c) - - c.fmu.hasStateEvents = (c.fmu.modelDescription.numberOfEventIndicators > 0) - # c.fmu.hasTimeEvents = (c.eventInfo.nextEventTimeDefined == fmi2True) - c.fmu.hasTimeEvents = fmi3False - - setupODEProblem(c, x0, t_start, t_stop; customFx=customFx) - - progressMeter = nothing - if showProgress - progressMeter = ProgressMeter.Progress(1000; desc="Simulating ME-FMU ...", color=:blue, dt=1.0) #, barglyphs=ProgressMeter.BarGlyphs("[=> ]")) - ProgressMeter.update!(progressMeter, 0) # show it! - end - - # callback functions - - if c.fmu.hasTimeEvents - timeEventCb = IterativeCallback((integrator) -> time_choice(c, integrator, t_start, t_stop), - (integrator) -> affectFMU!(c, integrator, 0, _inputFunction, inputValueReferences, fmusol), Float64; - initial_affect = false, # (c.eventInfo.nextEventTime == t_start) - save_positions=(false,false)) - push!(cbs, timeEventCb) - end - - if c.fmu.hasStateEvents - - eventCb = VectorContinuousCallback((out, x, t, integrator) -> condition(c, out, x, t, integrator, _inputFunction, inputValueReferences), - (integrator, idx) -> affectFMU!(c, integrator, idx, _inputFunction, inputValueReferences, fmusol), - Int64(c.fmu.modelDescription.numberOfEventIndicators); - rootfind = RightRootFind, - save_positions=(false,false)) - push!(cbs, eventCb) - end - - # use step callback always if we have inputs or need event handling (or just want to see our simulation progress) - if hasInputs || c.fmu.hasStateEvents || c.fmu.hasTimeEvents || showProgress - stepCb = FunctionCallingCallback((x, t, integrator) -> stepCompleted(c, x, t, integrator, _inputFunction, inputValueReferences, progressMeter, t_start, t_stop, fmusol); - func_everystep = true, - func_start = true) - push!(cbs, stepCb) - end - - if savingValues - fmusol.values = SavedValues(Float64, Tuple{collect(Float64 for i in 1:length(recordValues))...}) - fmusol.valueReferences = copy(recordValues) - - if saveat === nothing - savingCB = SavingCallback((u,t,integrator) -> saveValues(c, recordValues, u, t, integrator, _inputFunction, inputValueReferences), - fmusol.values) - else - savingCB = SavingCallback((u,t,integrator) -> saveValues(c, recordValues, u, t, integrator, _inputFunction, inputValueReferences), - fmusol.values, - saveat=saveat) - end - - push!(cbs, savingCB) - end - - # if auto_dt == true - # @assert solver !== nothing "fmi2SimulateME(...): `auto_dt=true` but no solver specified, this is not allowed." - # tmpIntegrator = init(c.problem, solver) - # dt = auto_dt_reset!(tmpIntegrator) - # end - - solveKwargs = Dict{Symbol, Any}() - - if dt !== nothing - solveKwargs[:dt] = dt - end - - if tolerance !== nothing - solveKwargs[:reltol] = tolerance - end - - if saveat !== nothing - solveKwargs[:saveat] = saveat - end - - if solver === nothing - fmusol.states = solve(c.problem; callback = CallbackSet(cbs...), dtmax=dtmax, solveKwargs..., kwargs...) - else - fmusol.states = solve(c.problem, solver; callback = CallbackSet(cbs...), dtmax=dtmax, solveKwargs..., kwargs...) - end - - fmusol.success = (fmusol.states.retcode == ReturnCode.Success) - - # cleanup progress meter - if showProgress - ProgressMeter.finish!(progressMeter) - end - - finishFMU(fmu, c, terminate, freeInstance) - - return fmusol -end - -export fmi3SimulateME - -############ Co-Simulation ############ - -""" - fmi3SimulateCS(c::FMU3Instance, t_start::Union{Real, Nothing} = nothing, t_stop::Union{Real, Nothing} = nothing; kwargs...) - -Wrapper for `fmi3SimulateCS(fmu::FMU3, c::Union{FMU3Instance, Nothing}=nothing, t_start::Union{Real, Nothing} = nothing, t_stop::Union{Real, Nothing} = nothing; kwargs...)` without a provided FMU3. -(FMU3 `fmu` is taken from `c`) -""" -function fmi3SimulateCS(c::FMU3Instance, t_start::Union{Real, Nothing} = nothing, t_stop::Union{Real, Nothing} = nothing; kwargs...) - fmi3SimulateCS(c.fmu, c, t_start, t_stop; kwargs...) -end - -""" - fmi3SimulateCS(fmu::FMU3, - c::Union{FMU3Instance, Nothing}=nothing, - t_start::Union{Real, Nothing} = nothing, - t_stop::Union{Real, Nothing} = nothing; - [tolerance::Union{Real, Nothing} = nothing, - dt::Union{Real, Nothing} = nothing, - recordValues::fmi3ValueReferenceFormat = nothing, - saveat = [], - setup::Union{Bool, Nothing} = nothing, - reset::Union{Bool, Nothing} = nothing, - instantiate::Union{Bool, Nothing} = nothing, - freeInstance::Union{Bool, Nothing} = nothing, - terminate::Union{Bool, Nothing} = nothing, - inputValueReferences::fmi3ValueReferenceFormat = nothing, - inputFunction = nothing, - showProgress::Bool=true, - parameters::Union{Dict{<:Any, <:Any}, Nothing} = nothing]) - -Simulate CS-FMU for the given simulation time interval. - -# Arguments -- `fmu::FMU3`: Mutable struct representing a FMU and all it instantiated instances. -- `c::Union{FMU3Instance, Nothing}=nothing`: Mutable struct representing an instantiated instance of a FMU. -- `t_start::Union{Real, Nothing} = nothing`: Simulation-time-span start time (default = nothing: use default value from `fmu`'s model description or 0.0) -- `t_stop::Union{Real, Nothing} = nothing`: Simulation-time-span stop time (default = nothing: use default value from `fmu`'s model description or 1.0) - -- `tolerance::Union{Real, Nothing} = nothing`: tolerance for the solver (default = nothing: use default value from `fmu`'s model description or 0.0) -- `dt::Union{Real, Nothing} = nothing`: stepszie for the solver (default = nothing: use default value from `fmu`'s model description or 1e-3) -- `solver = nothing`: Any Julia-supported ODE-solver (default = nothing: use DifferentialEquations.jl default solver) -- `recordValues::fmi3ValueReferenceFormat` = nothing: Array of variables (Strings or variableIdentifiers) to record. Results are returned as `DiffEqCallbacks.SavedValues` -- `saveat = nothing`: Time points to save values at (default = nothing: save at each communication timestep) -- `setup::Union{Bool, Nothing} = nothing`: call fmi3EnterInitializationMode and fmi3ExitInitializationMode before each step (default = nothing: use value from `fmu`'s `FMU3ExecutionConfiguration`) -- `reset::Union{Bool, Nothing} = nothing`: call fmi3Reset before each step (default = nothing: use value from `fmu`'s `FMU3ExecutionConfiguration`) -- `instantiate::Union{Bool, Nothing} = nothing`: call fmi3Instantiate! before each step (default = nothing: use value from `fmu`'s `FMU3ExecutionConfiguration`) -- `freeInstance::Union{Bool, Nothing} = nothing`: call fmi3FreeInstance after each step (default = nothing: use value from `fmu`'s `FMU3ExecutionConfiguration`) -- `terminate::Union{Bool, Nothing} = nothing`: call fmi3Terminate after each step (default = nothing: use value from `fmu`'s `FMU3ExecutionConfiguration`) -- `inputValueReferences::fmi3ValueReferenceFormat = nothing`: Input variables (Strings or variableIdentifiers) to set at each communication step -- `inputFunction = nothing`: Function to get values for the input variables at each communication step. - - ## Pattern [`c`: current instance, `t`: current time, returning array of values to be passed to `fmi3SetFloat64(..., inputValueReferences, inputFunction(...))`]: - - `inputFunction(t::fmi3Float64)` - - `inputFunction(c::FMU3Instance, t::fmi3Float64)` - -- `showProgress::Bool = true`: print simulation progressmeter in REPL -- `parameters::Union{Dict{<:Any, <:Any}, Nothing} = nothing`: Dict of parameter variables (strings or variableIdentifiers) and values (Boolean, String, Float64, ...) to set parameters during initialization - -# Returns: -- `fmusol::FMU3Solution`, containing bool `fmusol.success` and if keyword `recordValues` is set, the saved values as `fmusol.values`. - -See also [`fmi3Simulate`](@ref), [`fmi3SimulateME`](@ref), [`fmi3SimulateSE`](@ref). -""" -function fmi3SimulateCS(fmu::FMU3, c::Union{FMU3Instance, Nothing}=nothing, t_start::Union{Real, Nothing} = nothing, t_stop::Union{Real, Nothing} = nothing; - tolerance::Union{Real, Nothing} = nothing, - dt::Union{Real, Nothing} = nothing, - recordValues::fmi3ValueReferenceFormat = nothing, - saveat = [], - setup::Union{Bool, Nothing} = nothing, - reset::Union{Bool, Nothing} = nothing, - instantiate::Union{Bool, Nothing} = nothing, - freeInstance::Union{Bool, Nothing} = nothing, - terminate::Union{Bool, Nothing} = nothing, - inputValueReferences::fmi3ValueReferenceFormat = nothing, - inputFunction = nothing, - showProgress::Bool=true, - parameters::Union{Dict{<:Any, <:Any}, Nothing} = nothing) - - @assert fmi3IsCoSimulation(fmu) "fmi3SimulateCS(...): This function supports Co-Simulation FMUs only." - - # input function handling - _inputFunction = nothing - if inputFunction != nothing - if hasmethod(inputFunction, Tuple{fmi3Float64}) - _inputFunction = (c, t) -> inputFunction(t) - else - _inputFunction = inputFunctiont - end - @assert hasmethod(_inputFunction, Tuple{FMU3Instance, fmi3Float64}) "The given input function does not fit the needed input function pattern for CS-FMUs, which are: \n- `inputFunction(t::fmi3Float64)`\n- `inputFunction(comp::FMU3Instance, t::fmi3Float64)`" - end - - fmusol = FMU3Solution(fmu) - - - recordValues = prepareValueReference(fmu, recordValues) - inputValueReferences = prepareValueReference(fmu, inputValueReferences) - hasInputs = (length(inputValueReferences) > 0) - - variableSteps = fmi3IsCoSimulation(fmu) && fmu.modelDescription.coSimulation.canHandleVariableCommunicationStepSize - - t_start = t_start === nothing ? fmi3GetDefaultStartTime(fmu.modelDescription) : t_start - t_start = t_start === nothing ? 0.0 : t_start - t_stop = t_stop === nothing ? fmi3GetDefaultStopTime(fmu.modelDescription) : t_stop - t_stop = t_stop === nothing ? 1.0 : t_stop - tolerance = tolerance === nothing ? fmi3GetDefaultTolerance(fmu.modelDescription) : tolerance - tolerance = tolerance === nothing ? 0.0 : tolerance - dt = dt === nothing ? fmi3GetDefaultStepSize(fmu.modelDescription) : dt - dt = dt === nothing ? 1e-3 : dt - - c, _ = prepareFMU(fmu, c, fmi3TypeCoSimulation, instantiate, terminate, reset, setup, parameters, t_start, t_stop, tolerance; inputFunction=_inputFunction, inputValueReferences=inputValueReferences) - - # default setup - if length(saveat) == 0 - saveat = t_start:dt:t_stop - end - - # setup if no variable steps - if variableSteps == false - if length(saveat) >= 2 - dt = saveat[2] - saveat[1] - end - end - - t = t_start - - record = length(recordValues) > 0 - - progressMeter = nothing - if showProgress - progressMeter = ProgressMeter.Progress(1000; desc="Simulating CS-FMU ...", color=:blue, dt=1.0) #, barglyphs=ProgressMeter.BarGlyphs("[=> ]")) - ProgressMeter.update!(progressMeter, 0) # show it! - end - - #numDigits = length(string(round(Integer, 1/dt))) - noSetFMUStatePriorToCurrentPoint = fmi3False - eventEncountered = fmi3False - terminateSimulation = fmi3False - earlyReturn = fmi3False - lastSuccessfulTime = fmi3Float64(0.0) - - if record - fmusol.values = SavedValues(Float64, Tuple{collect(Float64 for i in 1:length(recordValues))...} ) - fmusol.valueReferences = copy(recordValues) - - i = 1 - - svalues = (fmi3GetFloat64(c, recordValues)...,) - DiffEqCallbacks.copyat_or_push!(fmusol.values.t, i, t) - DiffEqCallbacks.copyat_or_push!(fmusol.values.saveval, i, svalues, Val{false}) - - while t < t_stop - if variableSteps - if length(saveat) > i - dt = saveat[i+1] - saveat[i] - else - dt = t_stop - saveat[i] - end - end - - if _inputFunction !== nothing - fmi3SetFloat64(fmu, inputValueReferences, _inputFunction(c, t)) - end - - fmi3DoStep!(fmu, t, dt, true, eventEncountered, terminateSimulation, earlyReturn, lastSuccessfulTime) - if eventEncountered == fmi3True - @warn "Event handling" - end - if terminateSimulation == fmi3True - @error "fmi3DoStep returned error!" - end - if earlyReturn == fmi3True - @warn "early Return" - end - t = t + dt #round(t + dt, digits=numDigits) - i += 1 - - svalues = (fmi3GetFloat64(c, recordValues)...,) - DiffEqCallbacks.copyat_or_push!(fmusol.values.t, i, t) - DiffEqCallbacks.copyat_or_push!(fmusol.values.saveval, i, svalues, Val{false}) - - if progressMeter !== nothing - ProgressMeter.update!(progressMeter, floor(Integer, 1000.0*(t-t_start)/(t_stop-t_start)) ) - end - end - - if progressMeter !== nothing - ProgressMeter.finish!(progressMeter) - end - - fmusol.success = true - - else - i = 1 - while t < t_stop - if variableSteps - if length(saveat) > i - dt = saveat[i+1] - saveat[i] - else - dt = t_stop - saveat[i] - end - end - - if _inputFunction !== nothing - fmi3SetFloat64(fmu, inputValues, _inputFunction(c, t)) - end - - fmi3DoStep!(fmu, t, dt, true, eventEncountered, terminateSimulation, earlyReturn, lastSuccessfulTime) - if eventEncountered == fmi3True - @warn "Event handling" - end - if terminateSimulation == fmi3True - @error "fmi3DoStep returned error!" - end - if earlyReturn == fmi3True - @warn "early Return" - end - t = t + dt #round(t + dt, digits=numDigits) - i += 1 - - if progressMeter !== nothing - ProgressMeter.update!(progressMeter, floor(Integer, 1000.0*(t-t_start)/(t_stop-t_start)) ) - end - end - - if progressMeter !== nothing - ProgressMeter.finish!(progressMeter) - end - - fmusol.success = true - end - - finishFMU(fmu, c, terminate, freeInstance) - - return fmusol -end - -export fmi3SimulateCS - -# TODO simulate ScheduledExecution -""" - fmi3SimulateSE(fmu::FMU3, - c::Union{FMU3Instance, Nothing}=nothing, - t_start::Union{Real, Nothing} = nothing, - t_stop::Union{Real, Nothing} = nothing; - [tolerance::Union{Real, Nothing} = nothing, - dt::Union{Real, Nothing} = nothing, - recordValues::fmi3ValueReferenceFormat = nothing, - saveat = [], - setup::Union{Bool, Nothing} = nothing, - reset::Union{Bool, Nothing} = nothing, - instantiate::Union{Bool, Nothing} = nothing, - freeInstance::Union{Bool, Nothing} = nothing, - terminate::Union{Bool, Nothing} = nothing, - inputValueReferences::fmi3ValueReferenceFormat = nothing, - inputFunction = nothing, - showProgress::Bool=true, - parameters::Union{Dict{<:Any, <:Any}, Nothing} = nothing]) - -Simulate SE-FMU; not yet implemented in library - -See also [`fmi3Simulate`](@ref), [`fmi3SimulateME`](@ref), [`fmi3SimulateCS`](@ref). -""" -function fmi3SimulateSE(fmu::FMU3, c::Union{FMU3Instance, Nothing}=nothing, t_start::Union{Real, Nothing} = nothing, t_stop::Union{Real, Nothing} = nothing; - tolerance::Union{Real, Nothing} = nothing, - dt::Union{Real, Nothing} = nothing, - recordValues::fmi3ValueReferenceFormat = nothing, - saveat = [], - setup::Union{Bool, Nothing} = nothing, - reset::Union{Bool, Nothing} = nothing, - instantiate::Union{Bool, Nothing} = nothing, - freeInstance::Union{Bool, Nothing} = nothing, - terminate::Union{Bool, Nothing} = nothing, - inputValueReferences::fmi3ValueReferenceFormat = nothing, - inputFunction = nothing, - showProgress::Bool=true, - parameters::Union{Dict{<:Any, <:Any}, Nothing} = nothing) @assert false "Not implemented" -end - -export fmi3SimulateSE - -##### CS & ME ##### - -""" - fmi3Simulate(c::FMU3Instance, t_start::Union{Real, Nothing} = nothing, t_stop::Union{Real, Nothing} = nothing; kwargs...) - -Wrapper for `fmi3Simulate(fmu::FMU3, c::Union{FMU3Instance, Nothing}=nothing, t_start::Union{Real, Nothing} = nothing, t_stop::Union{Real, Nothing} = nothing; kwargs...)` without a provided FMU3. -(FMU3 `fmu` is taken from `c`) -""" -function fmi3Simulate(c::FMU3Instance, t_start::Union{Real, Nothing} = nothing, t_stop::Union{Real, Nothing} = nothing; kwargs...) - fmi3Simulate(c.fmu, c, t_start, t_stop; kwargs...) -end - -""" - fmi3Simulate(args...) - -Starts a simulation of the `FMU3` for the matching type (`fmi3SimulateCS(args...)`, `fmi3SimulateME(args...)` or `fmi3SimulateSE(args...)`); if multiple types are available, CS is preferred over ME, over SE. - -See also [`fmi3SimulateCS`](@ref), [`fmi3SimulateME`](@ref), [`fmi3SimulateSE`](@ref). -""" -function fmi3Simulate(fmu::FMU3, c::Union{FMU3Instance, Nothing}=nothing, t_start::Union{Real, Nothing} = nothing, t_stop::Union{Real, Nothing} = nothing; kwargs...) - - if fmu.type == fmi3TypeCoSimulation - return fmi3SimulateCS(fmu, c, t_start, t_stop; kwargs...) - elseif fmu.type == fmi3TypeModelExchange - return fmi3SimulateME(fmu, c, t_start, t_stop; kwargs...) - elseif fmu.type == fmi3TypeScheduledExecution - return fmi3SimulateSE(fmu, c, t_start, t_stop; kwargs...) - else - error(unknownFMUType) - end -end - -export fmi3Simulate diff --git a/src/deprecated.jl b/src/deprecated.jl index 0e328a69..3c4bef58 100644 --- a/src/deprecated.jl +++ b/src/deprecated.jl @@ -3,8 +3,9 @@ # Licensed under the MIT license. See LICENSE file in the project root for details. # -function warnDeprecated(oldStr, newStr, additional="") - @warn "`$(oldStr)` is deprecated, use `$(newStr)` instead. $(additional)\n(this message is printed 3 times)." maxlog=3 +function warnDeprecated(oldStr, newStr, additional = "") + @warn "`$(oldStr)` is deprecated, use `$(newStr)` instead. $(additional)\n(this message is printed 3 times)." maxlog = + 3 end function fmi2Simulate(args...; kwargs...) @@ -20,7 +21,11 @@ end export fmiSimulate function fmi2SimulateME(args...; kwargs...) - warnDeprecated("fmi2SimulateME", "simulateME", "FMI version is determined automatically.") + warnDeprecated( + "fmi2SimulateME", + "simulateME", + "FMI version is determined automatically.", + ) simulateME(args...; kwargs...) end export fmi2SimulateME @@ -32,7 +37,11 @@ end export fmiSimulateME function fmi2SimulateCS(args...; kwargs...) - warnDeprecated("fmi2SimulateCS", "simulateCS", "FMI version is determined automatically.") + warnDeprecated( + "fmi2SimulateCS", + "simulateCS", + "FMI version is determined automatically.", + ) simulateCS(args...; kwargs...) end export fmi2SimulateCS @@ -95,4 +104,4 @@ function fmi3Info(args...; kwargs...) warnDeprecated("fmi3Info", "info", "FMI version is determined automatically.") info(args...; kwargs...) end -export fmi3Info \ No newline at end of file +export fmi3Info diff --git a/src/sim.jl b/src/sim.jl index 0d88856a..f3d4a1d7 100644 --- a/src/sim.jl +++ b/src/sim.jl @@ -53,7 +53,12 @@ You can force a specific simulation mode by calling [`simulateCS`](@ref), [`simu See also [`simulate`](@ref), [`simulateME`](@ref), [`simulateCS`](@ref), [`simulateSE`](@ref). """ -function simulate(fmu::FMU2, c::Union{FMU2Component, Nothing}=nothing, tspan::Union{Tuple{Float64, Float64}, Nothing}=nothing; kwargs...) +function simulate( + fmu::FMU2, + c::Union{FMU2Component,Nothing} = nothing, + tspan::Union{Tuple{Float64,Float64},Nothing} = nothing; + kwargs..., +) if fmu.type == fmi2TypeCoSimulation return simulateCS(fmu, c, tspan; kwargs...) @@ -63,7 +68,12 @@ function simulate(fmu::FMU2, c::Union{FMU2Component, Nothing}=nothing, tspan::Un error(unknownFMUType) end end -function simulate(fmu::FMU3, c::Union{FMU3Instance, Nothing}=nothing, tspan::Union{Tuple{Float64, Float64}, Nothing}=nothing; kwargs...) +function simulate( + fmu::FMU3, + c::Union{FMU3Instance,Nothing} = nothing, + tspan::Union{Tuple{Float64,Float64},Nothing} = nothing; + kwargs..., +) if fmu.type == fmi3TypeCoSimulation return simulateCS(fmu, c, tspan; kwargs...) @@ -75,8 +85,10 @@ function simulate(fmu::FMU3, c::Union{FMU3Instance, Nothing}=nothing, tspan::Uni error(unknownFMUType) end end -simulate(c::FMUInstance, tspan::Tuple{Float64, Float64}; kwargs...) = simulate(c.fmu, c, tspan; kwargs...) -simulate(fmu::FMU, tspan::Tuple{Float64, Float64}; kwargs...) = simulate(fmu, nothing, tspan; kwargs...) +simulate(c::FMUInstance, tspan::Tuple{Float64,Float64}; kwargs...) = + simulate(c.fmu, c, tspan; kwargs...) +simulate(fmu::FMU, tspan::Tuple{Float64,Float64}; kwargs...) = + simulate(fmu, nothing, tspan; kwargs...) export simulate """ @@ -125,43 +137,52 @@ State- and Time-Events are handled correctly. See also [`simulate`](@ref), [`simulateCS`](@ref), [`simulateSE`](@ref). """ -function simulateME(fmu::FMU, c::Union{FMUInstance, Nothing}, tspan::Union{Tuple{Float64, Float64}, Nothing}=nothing; +function simulateME( + fmu::FMU, + c::Union{FMUInstance,Nothing}, + tspan::Union{Tuple{Float64,Float64},Nothing} = nothing; solver = nothing, # [ToDo] type recordValues::fmi2ValueReferenceFormat = nothing, - recordEventIndicators::Union{AbstractArray{<:Integer, 1}, UnitRange{<:Integer}, Nothing} = nothing, - recordEigenvalues::Bool=false, + recordEventIndicators::Union{AbstractArray{<:Integer,1},UnitRange{<:Integer},Nothing} = nothing, + recordEigenvalues::Bool = false, saveat = nothing, # [ToDo] type - x0::Union{AbstractArray{<:Real}, Nothing} = nothing, - setup::Bool=fmu.executionConfig.setup, - reset::Bool=fmu.executionConfig.reset, - instantiate::Bool=fmu.executionConfig.instantiate, - freeInstance::Bool=fmu.executionConfig.freeInstance, - terminate::Bool=fmu.executionConfig.terminate, + x0::Union{AbstractArray{<:Real},Nothing} = nothing, + setup::Bool = fmu.executionConfig.setup, + reset::Bool = fmu.executionConfig.reset, + instantiate::Bool = fmu.executionConfig.instantiate, + freeInstance::Bool = fmu.executionConfig.freeInstance, + terminate::Bool = fmu.executionConfig.terminate, inputValueReferences::fmi2ValueReferenceFormat = nothing, inputFunction = nothing, - parameters::Union{Dict{<:Any, <:Any}, Nothing} = nothing, - callbacksBefore::AbstractVector=[], # [ToDo] type - callbacksAfter::AbstractVector=[], # [ToDo] type + parameters::Union{Dict{<:Any,<:Any},Nothing} = nothing, + callbacksBefore::AbstractVector = [], # [ToDo] type + callbacksAfter::AbstractVector = [], # [ToDo] type showProgress::Bool = true, - solveKwargs...) + solveKwargs..., +) @assert isModelExchange(fmu) "simulateME(...): This function supports Model Excahnge FMUs only." - + recordValues = prepareValueReference(fmu, recordValues) inputValueReferences = prepareValueReference(fmu, inputValueReferences) hasInputs = length(inputValueReferences) > 0 - solveKwargs = Dict{Symbol, Any}(solveKwargs...) + solveKwargs = Dict{Symbol,Any}(solveKwargs...) tspan = setupSolver!(fmu, tspan, solveKwargs) t_start, t_stop = tspan if !isnothing(saveat) - solveKwargs[:saveat] = saveat + solveKwargs[:saveat] = saveat end progressMeter = nothing - if showProgress - progressMeter = ProgressMeter.Progress(1000; desc="Simulating ME-FMU ...", color=:blue, dt=1.0) #, barglyphs=ProgressMeter.BarGlyphs("[=> ]")) + if showProgress + progressMeter = ProgressMeter.Progress( + 1000; + desc = "Simulating ME-FMU ...", + color = :blue, + dt = 1.0, + ) #, barglyphs=ProgressMeter.BarGlyphs("[=> ]")) ProgressMeter.update!(progressMeter, 0) # show it! end @@ -176,8 +197,16 @@ function simulateME(fmu::FMU, c::Union{FMUInstance, Nothing}, tspan::Union{Tuple inputValues = eval!(_inputFunction, nothing, nothing, t_start) inputs = Dict(inputValueReferences .=> inputValues) end - c, x0 = prepareSolveFMU(fmu, c, :ME; - parameters=parameters, t_start=t_start, t_stop=t_stop, x0=x0, inputs=inputs) + c, x0 = prepareSolveFMU( + fmu, + c, + :ME; + parameters = parameters, + t_start = t_start, + t_stop = t_stop, + x0 = x0, + inputs = inputs, + ) # Zero state FMU: add dummy state if c.fmu.isZeroState @@ -185,9 +214,20 @@ function simulateME(fmu::FMU, c::Union{FMUInstance, Nothing}, tspan::Union{Tuple end @assert !isnothing(x0) "x0 is nothing after prepare!" - - c.problem = setupODEProblem(c, x0, tspan; inputFunction=_inputFunction) - cbs = setupCallbacks(c, recordValues, recordEventIndicators, recordEigenvalues, _inputFunction, inputValueReferences, progressMeter, t_start, t_stop, saveat) + + c.problem = setupODEProblem(c, x0, tspan; inputFunction = _inputFunction) + cbs = setupCallbacks( + c, + recordValues, + recordEventIndicators, + recordEigenvalues, + _inputFunction, + inputValueReferences, + progressMeter, + t_start, + t_stop, + saveat, + ) #solveKwargs = Dict(solveKwargs...) #setupSolver(fmu, solveKwargs) @@ -217,13 +257,17 @@ function simulateME(fmu::FMU, c::Union{FMUInstance, Nothing}, tspan::Union{Tuple if isnothing(solver) c.solution.states = solve(c.problem; callback = CallbackSet(cbs...), solveKwargs...) else - c.solution.states = solve(c.problem, solver; callback = CallbackSet(cbs...), solveKwargs...) + c.solution.states = + solve(c.problem, solver; callback = CallbackSet(cbs...), solveKwargs...) end c.solution.success = (c.solution.states.retcode == ReturnCode.Success) - + if !c.solution.success - logWarning(fmu, "FMU simulation failed with solver return code `$(c.solution.states.retcode)`, please check log for hints.") + logWarning( + fmu, + "FMU simulation failed with solver return code `$(c.solution.states.retcode)`, please check log for hints.", + ) end # ZeroStateFMU: remove dummy state @@ -232,16 +276,18 @@ function simulateME(fmu::FMU, c::Union{FMUInstance, Nothing}, tspan::Union{Tuple end # cleanup progress meter - if showProgress + if showProgress ProgressMeter.finish!(progressMeter) end - finishSolveFMU(fmu, c; freeInstance=freeInstance, terminate=terminate) + finishSolveFMU(fmu, c; freeInstance = freeInstance, terminate = terminate) return c.solution end -simulateME(c::FMUInstance, tspan::Tuple{Float64, Float64}; kwargs...) = simulateME(c.fmu, c, tspan; kwargs...) -simulateME(fmu::FMU, tspan::Tuple{Float64, Float64}; kwargs...) = simulateME(fmu, nothing, tspan; kwargs...) +simulateME(c::FMUInstance, tspan::Tuple{Float64,Float64}; kwargs...) = + simulateME(c.fmu, c, tspan; kwargs...) +simulateME(fmu::FMU, tspan::Tuple{Float64,Float64}; kwargs...) = + simulateME(fmu, nothing, tspan; kwargs...) export simulateME ############ Co-Simulation ############ @@ -286,23 +332,27 @@ State- and Time-Events are handled internally by the FMU. See also [`simulate`](@ref), [`simulateME`](@ref), [`simulateSE`](@ref). """ -function simulateCS(fmu::FMU, c::Union{FMUInstance, Nothing}, tspan::Union{Tuple{Float64, Float64}, Nothing}=nothing; - tolerance::Union{Real, Nothing} = nothing, - dt::Union{Real, Nothing} = nothing, - recordValues::fmi2ValueReferenceFormat = nothing, - saveat = [], - setup::Bool=fmu.executionConfig.setup, - reset::Bool=fmu.executionConfig.reset, - instantiate::Bool=fmu.executionConfig.instantiate, - freeInstance::Bool=fmu.executionConfig.freeInstance, - terminate::Bool=fmu.executionConfig.terminate, - inputValueReferences::fmiValueReferenceFormat = nothing, - inputFunction = nothing, - showProgress::Bool=true, - parameters::Union{Dict{<:Any, <:Any}, Nothing} = nothing) +function simulateCS( + fmu::FMU, + c::Union{FMUInstance,Nothing}, + tspan::Union{Tuple{Float64,Float64},Nothing} = nothing; + tolerance::Union{Real,Nothing} = nothing, + dt::Union{Real,Nothing} = nothing, + recordValues::fmi2ValueReferenceFormat = nothing, + saveat = [], + setup::Bool = fmu.executionConfig.setup, + reset::Bool = fmu.executionConfig.reset, + instantiate::Bool = fmu.executionConfig.instantiate, + freeInstance::Bool = fmu.executionConfig.freeInstance, + terminate::Bool = fmu.executionConfig.terminate, + inputValueReferences::fmiValueReferenceFormat = nothing, + inputFunction = nothing, + showProgress::Bool = true, + parameters::Union{Dict{<:Any,<:Any},Nothing} = nothing, +) @assert isCoSimulation(fmu) "simulateCS(...): This function supports Co-Simulation FMUs only." - + # input function handling @debug "Simulating CS-FMU: Preparing input function ..." inputValueReferences = prepareValueReference(fmu, inputValueReferences) @@ -326,10 +376,12 @@ function simulateCS(fmu::FMU, c::Union{FMUInstance, Nothing}, tspan::Union{Tuple end t_start, t_stop = (tspan == nothing ? (nothing, nothing) : tspan) - + # pull default values from the model description - if not given by user @debug "Simulating CS-FMU: Pulling default values ..." - variableSteps = isCoSimulation(fmu) && isTrue(fmu.modelDescription.coSimulation.canHandleVariableCommunicationStepSize) + variableSteps = + isCoSimulation(fmu) && + isTrue(fmu.modelDescription.coSimulation.canHandleVariableCommunicationStepSize) t_start = t_start === nothing ? getDefaultStartTime(fmu.modelDescription) : t_start t_start = t_start === nothing ? 0.0 : t_start @@ -337,7 +389,8 @@ function simulateCS(fmu::FMU, c::Union{FMUInstance, Nothing}, tspan::Union{Tuple t_stop = t_stop === nothing ? getDefaultStopTime(fmu.modelDescription) : t_stop t_stop = t_stop === nothing ? 1.0 : t_stop - tolerance = tolerance === nothing ? getDefaultTolerance(fmu.modelDescription) : tolerance + tolerance = + tolerance === nothing ? getDefaultTolerance(fmu.modelDescription) : tolerance tolerance = tolerance === nothing ? 0.0 : tolerance dt = dt === nothing ? getDefaultStepSize(fmu.modelDescription) : dt @@ -351,9 +404,21 @@ function simulateCS(fmu::FMU, c::Union{FMUInstance, Nothing}, tspan::Union{Tuple end @debug "Simulating CS-FMU: Preparing solve ..." - c, _ = prepareSolveFMU(fmu, c, :CS; - instantiate=instantiate, freeInstance=freeInstance, terminate=terminate, reset=reset, setup=setup, parameters=parameters, - t_start=t_start, t_stop=t_stop, tolerance=tolerance, inputs=inputs) + c, _ = prepareSolveFMU( + fmu, + c, + :CS; + instantiate = instantiate, + freeInstance = freeInstance, + terminate = terminate, + reset = reset, + setup = setup, + parameters = parameters, + t_start = t_start, + t_stop = t_stop, + tolerance = tolerance, + inputs = inputs, + ) fmusol = c.solution # default setup @@ -362,8 +427,8 @@ function simulateCS(fmu::FMU, c::Union{FMUInstance, Nothing}, tspan::Union{Tuple end # setup if no variable steps - if variableSteps == false - if length(saveat) >= 2 + if variableSteps == false + if length(saveat) >= 2 dt = saveat[2] - saveat[1] end end @@ -371,14 +436,16 @@ function simulateCS(fmu::FMU, c::Union{FMUInstance, Nothing}, tspan::Union{Tuple t = t_start progressMeter = nothing - if showProgress - progressMeter = ProgressMeter.Progress(1000; desc="Sim. CS-FMU ...", color=:blue, dt=1.0) + if showProgress + progressMeter = + ProgressMeter.Progress(1000; desc = "Sim. CS-FMU ...", color = :blue, dt = 1.0) ProgressMeter.update!(progressMeter, 0) # show it! end first_step = true - fmusol.values = SavedValues(Float64, Tuple{collect(Float64 for i in 1:length(y_refs))...} ) + fmusol.values = + SavedValues(Float64, Tuple{collect(Float64 for i = 1:length(y_refs))...}) fmusol.valueReferences = copy(y_refs) i = 1 @@ -386,25 +453,25 @@ function simulateCS(fmu::FMU, c::Union{FMUInstance, Nothing}, tspan::Union{Tuple fmusol.success = true @debug "Starting simulation from $(t_start) to $(t_stop), variable steps: $(variableSteps)" - + while t < t_stop if variableSteps - if length(saveat) > (i+1) + if length(saveat) > (i + 1) dt = saveat[i+1] - saveat[i] - else + else dt = t_stop - t end end if !first_step - ret = doStep(c, dt; currentCommunicationPoint=t) + ret = doStep(c, dt; currentCommunicationPoint = t) if !isStatusOK(fmu, ret) - fmusol.success = false + fmusol.success = false end - t = t + dt + t = t + dt i += 1 else first_step = false @@ -414,14 +481,17 @@ function simulateCS(fmu::FMU, c::Union{FMUInstance, Nothing}, tspan::Union{Tuple u = eval!(_inputFunction, c, nothing, t) end - c(u=u, u_refs=u_refs, y=y, y_refs=y_refs) + c(u = u, u_refs = u_refs, y = y, y_refs = y_refs) svalues = (y...,) copyat_or_push!(fmusol.values.t, i, t) copyat_or_push!(fmusol.values.saveval, i, svalues, Val{false}) - if !isnothing(progressMeter) - ProgressMeter.update!(progressMeter, floor(Integer, 1000.0*(t-t_start)/(t_stop-t_start)) ) + if !isnothing(progressMeter) + ProgressMeter.update!( + progressMeter, + floor(Integer, 1000.0 * (t - t_start) / (t_stop - t_start)), + ) end end @@ -430,16 +500,18 @@ function simulateCS(fmu::FMU, c::Union{FMUInstance, Nothing}, tspan::Union{Tuple logWarning(fmu, "FMU simulation failed, please check log for hints.") end - if !isnothing(progressMeter) + if !isnothing(progressMeter) ProgressMeter.finish!(progressMeter) end - finishSolveFMU(fmu, c; freeInstance=freeInstance, terminate=terminate) + finishSolveFMU(fmu, c; freeInstance = freeInstance, terminate = terminate) return fmusol end -simulateCS(c::FMUInstance, tspan::Tuple{Float64, Float64}; kwargs...) = simulateCS(c.fmu, c, tspan; kwargs...) -simulateCS(fmu::FMU, tspan::Tuple{Float64, Float64}; kwargs...) = simulateCS(fmu, nothing, tspan; kwargs...) +simulateCS(c::FMUInstance, tspan::Tuple{Float64,Float64}; kwargs...) = + simulateCS(c.fmu, c, tspan; kwargs...) +simulateCS(fmu::FMU, tspan::Tuple{Float64,Float64}; kwargs...) = + simulateCS(fmu, nothing, tspan; kwargs...) export simulateCS # [TODO] simulate ScheduledExecution @@ -463,13 +535,23 @@ To be implemented ... See also [`simulate`](@ref), [`simulateME`](@ref), [`simulateCS`](@ref). """ -function simulateSE(fmu::FMU2, c::Union{FMU2Component, Nothing}, tspan::Union{Tuple{Float64, Float64}, Nothing}=nothing) +function simulateSE( + fmu::FMU2, + c::Union{FMU2Component,Nothing}, + tspan::Union{Tuple{Float64,Float64},Nothing} = nothing, +) @assert false "This is a FMI2-FMU, scheduled execution is not supported in FMI2." end -function simulateSE(fmu::FMU3, c::Union{FMU3Instance, Nothing}, tspan::Union{Tuple{Float64, Float64}, Nothing}=nothing) +function simulateSE( + fmu::FMU3, + c::Union{FMU3Instance,Nothing}, + tspan::Union{Tuple{Float64,Float64},Nothing} = nothing, +) # [ToDo] @assert false "Not implemented yet. Please open an issue if this is needed." end -simulateSE(c::FMUInstance, tspan::Tuple{Float64, Float64}; kwargs...) = simulateSE(c.fmu, c, tspan; kwargs...) -simulateSE(fmu::FMU, tspan::Tuple{Float64, Float64}; kwargs...) = simulateSE(fmu, nothing, tspan; kwargs...) -export simulateSE \ No newline at end of file +simulateSE(c::FMUInstance, tspan::Tuple{Float64,Float64}; kwargs...) = + simulateSE(c.fmu, c, tspan; kwargs...) +simulateSE(fmu::FMU, tspan::Tuple{Float64,Float64}; kwargs...) = + simulateSE(fmu, nothing, tspan; kwargs...) +export simulateSE diff --git a/test/eval.jl b/test/eval.jl index ae2f2813..b6b0db7a 100644 --- a/test/eval.jl +++ b/test/eval.jl @@ -2,9 +2,9 @@ using PkgEval using FMI using Test -config = Configuration(; julia="1.10", time_limit=120*60); +config = Configuration(; julia = "1.10", time_limit = 120 * 60); -package = Package(; name="FMI"); +package = Package(; name = "FMI"); @info "PkgEval" result = evaluate([config], [package]) diff --git a/test/runtests.jl b/test/runtests.jl index cbae3159..748ab0e2 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -27,20 +27,28 @@ for exec in FMU_EXECUTION_CONFIGURATIONS exec.assertOnWarning = true end -function getFMUStruct(modelname, mode, tool=ENV["EXPORTINGTOOL"], version=ENV["EXPORTINGVERSION"], fmiversion=ENV["FMIVERSION"], fmustruct=ENV["FMUSTRUCT"]; kwargs...) - +function getFMUStruct( + modelname, + mode, + tool = ENV["EXPORTINGTOOL"], + version = ENV["EXPORTINGVERSION"], + fmiversion = ENV["FMIVERSION"], + fmustruct = ENV["FMUSTRUCT"]; + kwargs..., +) + # choose FMU or FMUComponent if endswith(modelname, ".fmu") fmu = loadFMU(modelname; kwargs...) else - fmu = loadFMU(modelname, tool, version, fmiversion; kwargs...) + fmu = loadFMU(modelname, tool, version, fmiversion; kwargs...) end if fmustruct == "FMU" return fmu, fmu elseif fmustruct == "FMUCOMPONENT" - inst, _ = FMI.prepareSolveFMU(fmu, nothing, mode; loggingOn=true) + inst, _ = FMI.prepareSolveFMU(fmu, nothing, mode; loggingOn = true) @test !isnothing(inst) return inst, fmu @@ -52,7 +60,7 @@ end @testset "FMI.jl" begin if Sys.iswindows() || Sys.islinux() @info "Automated testing is supported on Windows/Linux." - + ENV["EXPORTINGTOOL"] = "Dymola" ENV["EXPORTINGVERSION"] = "2023x" @@ -65,12 +73,12 @@ end ENV["FMUSTRUCT"] = fmustruct @testset "Functions for $(ENV["FMUSTRUCT"])" begin - + @info "CS Simulation (sim_CS.jl)" @testset "CS Simulation" begin include("sim_CS.jl") end - + @info "ME Simulation (sim_ME.jl)" @testset "ME Simulation" begin include("sim_ME.jl") @@ -80,7 +88,7 @@ end @testset "SE Simulation" begin include("sim_SE.jl") end - + @info "Simulation FMU without states (sim_zero_state.jl)" @testset "Simulation FMU without states" begin include("sim_zero_state.jl") @@ -93,7 +101,7 @@ end # include("FMI2/performance.jl") # end # else - @info "Julia Version $(VERSION), skipping performance tests ..." + @info "Julia Version $(VERSION), skipping performance tests ..." #end end end @@ -104,12 +112,12 @@ end @testset "Method ambiguities" begin Aqua.test_ambiguities([FMI]) end - + @info "Aqua: Piracies" @testset "Piracies" begin Aqua.test_piracies(FMI) # ; broken = true) end - + @info "Aqua: Testing all (method ambiguities and piracies are tested separately)" Aqua.test_all(FMI; ambiguities = false, piracies = false) end diff --git a/test/sim_CS.jl b/test/sim_CS.jl index 819c48ca..ee34ad4c 100644 --- a/test/sim_CS.jl +++ b/test/sim_CS.jl @@ -13,11 +13,12 @@ t_start = 0.0 t_stop = 8.0 # test without recording values (just for completeness) -solution = simulateCS(fmuStruct, (t_start, t_stop); dt=1e-2) +solution = simulateCS(fmuStruct, (t_start, t_stop); dt = 1e-2) @test solution.success # test with recording values -solution = simulateCS(fmuStruct, (t_start, t_stop); dt=1e-2, recordValues=["mass.s", "mass.v"]) +solution = + simulateCS(fmuStruct, (t_start, t_stop); dt = 1e-2, recordValues = ["mass.s", "mass.v"]) @test solution.success @test length(solution.values.saveval) == t_start:1e-2:t_stop |> length @test length(solution.values.saveval[1]) == 2 @@ -32,8 +33,8 @@ v = collect(d[2] for d in solution.values.saveval) @test s[1] == 0.5 @test v[1] == 0.0 -@test isapprox(s[end], 0.509219; atol=1e-1) -@test isapprox(v[end], 0.314074; atol=1e-1) +@test isapprox(s[end], 0.509219; atol = 1e-1) +@test isapprox(v[end], 0.314074; atol = 1e-1) unloadFMU(fmu) @@ -41,18 +42,25 @@ unloadFMU(fmu) extForce_t! = function (t::Real, u::AbstractArray{<:Real}) u[1] = sin(t) -end +end -extForce_ct! = function (c::Union{FMUInstance, Nothing}, t::Real, u::AbstractArray{<:Real}) +extForce_ct! = function (c::Union{FMUInstance,Nothing}, t::Real, u::AbstractArray{<:Real}) u[1] = sin(t) -end +end fmustruct, fmu = getFMUStruct("SpringPendulumExtForce1D", :CS) for inpfct! in [extForce_ct!, extForce_t!] global solution - solution = simulateCS(fmustruct, (t_start, t_stop); dt=1e-2, recordValues=["mass.s", "mass.v"], inputValueReferences=["extForce"], inputFunction=inpfct!) + solution = simulateCS( + fmustruct, + (t_start, t_stop); + dt = 1e-2, + recordValues = ["mass.s", "mass.v"], + inputValueReferences = ["extForce"], + inputFunction = inpfct!, + ) @test solution.success @test length(solution.values.saveval) > 0 @test length(solution.values.t) > 0 diff --git a/test/sim_ME.jl b/test/sim_ME.jl index 05faa8c5..8d67e485 100644 --- a/test/sim_ME.jl +++ b/test/sim_ME.jl @@ -18,19 +18,24 @@ dtmax_inputs = 1e-3 rand_x0 = rand(2) kwargs = Dict(:dtmin => 1e-64, :abstol => 1e-8, :reltol => 1e-6, :dt => 1e-32) -solvers = [Tsit5(), Rodas5(autodiff=false)] # [Tsit5(), FBDF(autodiff=false), FBDF(autodiff=true), Rodas5(autodiff=false), Rodas5(autodiff=true)] +solvers = [Tsit5(), Rodas5(autodiff = false)] # [Tsit5(), FBDF(autodiff=false), FBDF(autodiff=true), Rodas5(autodiff=false), Rodas5(autodiff=true)] -extForce_t! = function(t::Real, u::AbstractArray{<:Real}) +extForce_t! = function (t::Real, u::AbstractArray{<:Real}) sense_setindex!(u, sin(t), 1) -end - -extForce_cxt! = function(c::Union{FMUInstance, Nothing}, x::Union{AbstractArray{<:Real}, Nothing}, t::Real, u::AbstractArray{<:Real}) +end + +extForce_cxt! = function ( + c::Union{FMUInstance,Nothing}, + x::Union{AbstractArray{<:Real},Nothing}, + t::Real, + u::AbstractArray{<:Real}, +) x1 = 0.0 - if x != nothing - x1 = x[1] + if x != nothing + x1 = x[1] end sense_setindex!(u, sin(t) * x1, 1) -end +end for solver in solvers @@ -42,12 +47,12 @@ for solver in solvers fmuStruct, fmu = getFMUStruct("SpringFrictionPendulum1D", :ME) - solution = simulateME(fmuStruct, (t_start, t_stop); solver=solver, kwargs...) + solution = simulateME(fmuStruct, (t_start, t_stop); solver = solver, kwargs...) @test length(solution.states.u) > 0 @test length(solution.states.t) > 0 - @test solution.states.t[1] == t_start - @test solution.states.t[end] == t_stop + @test solution.states.t[1] == t_start + @test solution.states.t[end] == t_stop # reference values from Simulation in Dymola2020x (Dassl) @test solution.states.u[1] == [0.5, 0.0] @@ -60,12 +65,12 @@ for solver in solvers ### test without recording values - solution = simulateME(fmuStruct, (t_start, t_stop); solver=solver, kwargs...) + solution = simulateME(fmuStruct, (t_start, t_stop); solver = solver, kwargs...) @test length(solution.states.u) > 0 @test length(solution.states.t) > 0 - @test solution.states.t[1] == t_start - @test solution.states.t[end] == t_stop + @test solution.states.t[1] == t_start + @test solution.states.t[end] == t_stop # reference values from Simulation in Dymola2020x (Dassl) @test solution.states.u[1] == [0.5, 0.0] @@ -73,50 +78,68 @@ for solver in solvers ### test with recording values (variable step record values) - solution = simulateME(fmuStruct, (t_start, t_stop); recordValues="mass.f", solver=solver, kwargs...) + solution = simulateME( + fmuStruct, + (t_start, t_stop); + recordValues = "mass.f", + solver = solver, + kwargs..., + ) dataLength = length(solution.states.u) @test dataLength > 0 @test length(solution.states.t) == dataLength @test length(solution.values.saveval) == dataLength @test length(solution.values.t) == dataLength - @test solution.states.t[1] == t_start - @test solution.states.t[end] == t_stop - @test solution.values.t[1] == t_start - @test solution.values.t[end] == t_stop + @test solution.states.t[1] == t_start + @test solution.states.t[end] == t_stop + @test solution.values.t[1] == t_start + @test solution.values.t[end] == t_stop # value/state getters @test solution.states.t == getTime(solution) - @test collect(s[1] for s in solution.values.saveval) == getValue(solution, 1; isIndex=true) - @test collect(u[1] for u in solution.states.u ) == getState(solution, 1; isIndex=true) - @test isapprox(getState(solution, 2; isIndex=true), getStateDerivative(solution, 1; isIndex=true); atol=1e-1) # tolerance is large, because Rosenbrock23 solution derivative is not that accurate (other solvers reach 1e-4 for this example) + @test collect(s[1] for s in solution.values.saveval) == + getValue(solution, 1; isIndex = true) + @test collect(u[1] for u in solution.states.u) == getState(solution, 1; isIndex = true) + @test isapprox( + getState(solution, 2; isIndex = true), + getStateDerivative(solution, 1; isIndex = true); + atol = 1e-1, + ) # tolerance is large, because Rosenbrock23 solution derivative is not that accurate (other solvers reach 1e-4 for this example) @info "Max error of solver polynominal derivative: $(max(abs.(getState(solution, 2; isIndex=true) .- getStateDerivative(solution, 1; isIndex=true))...))" # reference values from Simulation in Dymola2020x (Dassl) @test sum(abs.(solution.states.u[1] - [0.5, 0.0])) < 1e-4 @test sum(abs.(solution.states.u[end] - [1.05444, 1e-10])) < 0.01 @test abs(solution.values.saveval[1][1] - 0.75) < 1e-4 - @test sum(abs.(solution.values.saveval[end][1] - -0.54435 )) < 0.015 + @test sum(abs.(solution.values.saveval[end][1] - -0.54435)) < 0.015 ### test with recording values (fixed step record values) tData = t_start:0.1:t_stop - solution = simulateME(fmuStruct, (t_start, t_stop); recordValues="mass.f", saveat=tData, solver=solver, kwargs...) + solution = simulateME( + fmuStruct, + (t_start, t_stop); + recordValues = "mass.f", + saveat = tData, + solver = solver, + kwargs..., + ) @test length(solution.states.u) == length(tData) @test length(solution.states.t) == length(tData) @test length(solution.values.saveval) == length(tData) @test length(solution.values.t) == length(tData) - @test solution.states.t[1] == t_start - @test solution.states.t[end] == t_stop - @test solution.values.t[1] == t_start - @test solution.values.t[end] == t_stop + @test solution.states.t[1] == t_start + @test solution.states.t[end] == t_stop + @test solution.values.t[1] == t_start + @test solution.values.t[end] == t_stop # reference values from Simulation in Dymola2020x (Dassl) @test sum(abs.(solution.states.u[1] - [0.5, 0.0])) < 1e-4 @test sum(abs.(solution.states.u[end] - [1.05444, 1e-10])) < 0.01 @test abs(solution.values.saveval[1][1] - 0.75) < 1e-4 - @test sum(abs.(solution.values.saveval[end][1] - -0.54435 )) < 0.015 + @test sum(abs.(solution.values.saveval[end][1] - -0.54435)) < 0.015 unloadFMU(fmu) @@ -125,14 +148,22 @@ for solver in solvers fmuStruct, fmu = getFMUStruct("SpringPendulumExtForce1D", :ME) for inpfct! in [extForce_cxt!, extForce_t!] - - solution = simulateME(fmuStruct, (t_start, t_stop); inputValueReferences=["extForce"], inputFunction=inpfct!, solver=solver, dtmax=dtmax_inputs, kwargs...) # dtmax to force resolution + + solution = simulateME( + fmuStruct, + (t_start, t_stop); + inputValueReferences = ["extForce"], + inputFunction = inpfct!, + solver = solver, + dtmax = dtmax_inputs, + kwargs..., + ) # dtmax to force resolution @test length(solution.states.u) > 0 @test length(solution.states.t) > 0 - @test solution.states.t[1] == t_start + @test solution.states.t[1] == t_start @test solution.states.t[end] == t_stop - end + end # reference values `extForce_t` from Simulation in Dymola2020x (Dassl) @test solution.states.u[1] == [0.5, 0.0] @@ -146,13 +177,19 @@ for solver in solvers # there are issues with AD in Julia < 1.7.0 # ToDo: Fix Linux FMU if VERSION >= v"1.7.0" && !Sys.islinux() - solution = simulateME(fmuStruct, (t_start, t_stop); solver=solver, dtmax=dtmax_inputs, kwargs...) # dtmax to force resolution + solution = simulateME( + fmuStruct, + (t_start, t_stop); + solver = solver, + dtmax = dtmax_inputs, + kwargs..., + ) # dtmax to force resolution @test length(solution.states.u) > 0 @test length(solution.states.t) > 0 - @test solution.states.t[1] == t_start - @test solution.states.t[end] == t_stop + @test solution.states.t[1] == t_start + @test solution.states.t[end] == t_stop # reference values (no force) from Simulation in Dymola2020x (Dassl) @test solution.states.u[1] == [0.5, 0.0] @@ -165,15 +202,30 @@ for solver in solvers fmuStruct, fmu = getFMUStruct("SpringPendulumExtForce1D", :ME) - solution = simulateME(fmuStruct, (t_start, t_stop); saveat=tData, recordValues=:states, solver=solver, kwargs...) + solution = simulateME( + fmuStruct, + (t_start, t_stop); + saveat = tData, + recordValues = :states, + solver = solver, + kwargs..., + ) @test length(solution.states.u) == length(tData) @test length(solution.states.t) == length(tData) @test length(solution.values.saveval) == length(tData) @test length(solution.values.t) == length(tData) - @test isapprox(solution.states.t, solution.states.t; atol=1e-6) - @test isapprox(collect(u[1] for u in solution.states.u), collect(u[1] for u in solution.values.saveval); atol=1e-6) - @test isapprox(collect(u[2] for u in solution.states.u), collect(u[2] for u in solution.values.saveval); atol=1e-6) + @test isapprox(solution.states.t, solution.states.t; atol = 1e-6) + @test isapprox( + collect(u[1] for u in solution.states.u), + collect(u[1] for u in solution.values.saveval); + atol = 1e-6, + ) + @test isapprox( + collect(u[2] for u in solution.states.u), + collect(u[2] for u in solution.values.saveval); + atol = 1e-6, + ) unloadFMU(fmu) @@ -181,13 +233,14 @@ for solver in solvers fmuStruct, fmu = getFMUStruct("SpringFrictionPendulum1D", :ME) - solution = simulateME(fmuStruct, (t_start, t_stop); x0=rand_x0, solver=solver, kwargs...) + solution = + simulateME(fmuStruct, (t_start, t_stop); x0 = rand_x0, solver = solver, kwargs...) @test length(solution.states.u) > 0 @test length(solution.states.t) > 0 - @test solution.states.t[1] == t_start - @test solution.states.t[end] == t_stop + @test solution.states.t[1] == t_start + @test solution.states.t[end] == t_stop @test solution.states.u[1] == rand_x0 unloadFMU(fmu) -end \ No newline at end of file +end diff --git a/test/sim_SE.jl b/test/sim_SE.jl index 1a07977b..b41bb349 100644 --- a/test/sim_SE.jl +++ b/test/sim_SE.jl @@ -5,4 +5,4 @@ # testing different modes for SE (scheduled execution) mode -# [ToDo] coming soon \ No newline at end of file +# [ToDo] coming soon diff --git a/test/sim_zero_state.jl b/test/sim_zero_state.jl index 4ccd70da..8328873a 100644 --- a/test/sim_zero_state.jl +++ b/test/sim_zero_state.jl @@ -9,23 +9,30 @@ t_start = 0.0 t_stop = 8.0 solver = Tsit5() -inputFct! = function(t, u) +inputFct! = function (t, u) u[1] = sin(t) return nothing -end +end fmuStruct, fmu = getFMUStruct("IO", :ME) @test fmu.isZeroState # check if zero state is identified -solution = simulateME(fmuStruct, (t_start, t_stop); - solver=solver, - recordValues=["y_real"], # , "y_boolean", "y_integer"], # [ToDo] different types to record - inputValueReferences=["u_real"], # [ToDo] different types to set - inputFunction=inputFct!) +solution = simulateME( + fmuStruct, + (t_start, t_stop); + solver = solver, + recordValues = ["y_real"], # , "y_boolean", "y_integer"], # [ToDo] different types to record + inputValueReferences = ["u_real"], # [ToDo] different types to set + inputFunction = inputFct!, +) @test isnothing(solution.states) -@test solution.values.t[1] == t_start -@test solution.values.t[end] == t_stop -@test isapprox(collect(u[1] for u in solution.values.saveval), sin.(solution.values.t); atol=1e-6) +@test solution.values.t[1] == t_start +@test solution.values.t[end] == t_stop +@test isapprox( + collect(u[1] for u in solution.values.saveval), + sin.(solution.values.t); + atol = 1e-6, +) -unloadFMU(fmu) \ No newline at end of file +unloadFMU(fmu) diff --git a/test/unpack.jl b/test/unpack.jl index d007642f..b8a08a94 100644 --- a/test/unpack.jl +++ b/test/unpack.jl @@ -5,7 +5,8 @@ # test different unpacking path options for FMUs -pathToFMU = get_model_filename("SpringPendulum1D", ENV["EXPORTINGTOOL"], ENV["EXPORTINGVERSION"]) +pathToFMU = + get_model_filename("SpringPendulum1D", ENV["EXPORTINGTOOL"], ENV["EXPORTINGVERSION"]) # load FMU in temporary directory fmuStruct, myFMU = getFMUStruct(pathToFMU) @@ -15,6 +16,6 @@ fmiUnload(myFMU) # load FMU in source directory fmuDir = joinpath(splitpath(pathToFMU)[1:end-1]...) -fmuStruct, myFMU = getFMUStruct(pathToFMU; unpackPath=fmuDir) +fmuStruct, myFMU = getFMUStruct(pathToFMU; unpackPath = fmuDir) @test isfile(splitext(pathToFMU)[1] * ".zip") == true -@test isdir(splitext(pathToFMU)[1]) == true \ No newline at end of file +@test isdir(splitext(pathToFMU)[1]) == true