Small update: The second example works. I had to change the “communication interval” in Simulink from auto to 0.01 to make it work.
In addition, I added these lines to copy the FMU file to the desired directory:
# Save the FMU to the output directory
output_dir = joinpath(@__DIR__, "..", "output")
mkpath(output_dir) # Create output directory if it doesn't exist
fmu_filename = basename(fmu.fmu_path)
fmu_source = fmu.fmu_path
if isfile(fmu_source)
fmu_dest = joinpath(output_dir, fmu_filename)
cp(fmu_source, fmu_dest, force=true)
println("✅ FMU copied to: $fmu_dest")
end
Further, the directory with the FMU file must be in the Matlab path.
The first example also works now, but the example in the documentation is broken. This code works:
using FMUGeneration
using OrdinaryDiffEq
lv_expr = :(function (dx, x, u, p, t)
x1, x2 = x
u1, u2 = u
alpha, beta, gamma, delta = p
dx[1] = alpha * x1 - beta * x1 * x2 + u1
dx[2] = delta * x1 * x2 - gamma * x2 - u2
return nothing # In-place modification
end);
initial_states = [1.0, 1.0]
initial_inputs = [0.01, 0.02]
default_parameters = [2.0, 1.875, 2.0, 1.875]
tspan = (0.0, 10.0)
param_names = ["alpha", "beta", "gamma", "delta"]
input_names = ["u_1" , "u_2"]
state_names = ["x_1" , "x_2"]
output_names = ["y_1", "y_2"] # Different names to avoid conflict with states
# Define observable function to compute outputs
observable_expr = :(function (x, u, p, t)
# Return the states as individual outputs (y_1 = x_1, y_2 = x_2)
y1 = x[1]
y2 = x[2]
return [y1, y2]
end)
fmu = JuliaFMU(
# REQUIRED ARGUMENTS
# We specify the FMU name
"lotka-volterra",
# We specify the FMI version
FMI_V3, # or v2,
# We specify the FMU operating types supported
[FMI_MODELEXCHANGE, FMI_COSIMULATION];
# OPTIONAL ARGUMENTS
# We optionally specify the default time-space of the FMU operation
default_tspan = tspan,
# We optionally specify the recommended step size
# default_stepsize = 1e-3,
# We optionally specify the recommended solver tolerance
# default_tolerance = 1e-6,
# Metadata: inputs, parameters and states respectively
inputs = [
(name=input_names[i], start=initial_inputs[i]) for i in 1:length(input_names)
],
parameters = [
(name=param_names[i], start=default_parameters[i]) for i in 1:length(param_names)
],
states = [
(name=state_names[i], start=initial_states[i]) for i in 1:length(state_names)
],
outputs = [
(name=output_names[i],) for i in 1:length(output_names)
],
# We define the dependencies required for the FMU. Here, we need OrdinaryDiffEq for the solver used to run the FMU in CS mode.
dependencies = @deps([OrdinaryDiffEq]),
# We define the ODE function expression with the signature `(dx, u, p, t) -> begin ... end` where the function is expected to be inplace. Here, it is the `double_pendulum_expr` defined earlier. This is an essential kwarg for all ME FMUs.
ode_function = lv_expr,
# We optionally define function to compute the outputs with the signature `(x, u, p, t) -> outs`.
observables_function = observable_expr,
# We specify which solver to use for cosimulation. We default to `OrdinaryDiffEq.AutoTsit5(OrdinaryDiffEq.FBDF())` if not provided.
cosimulator_solver = :(OrdinaryDiffEq.AutoTsit5(OrdinaryDiffEq.FBDF())),
# If we had inputs, we would also had to specify it like below:
# inputs = [
# (name="input_1", start=1.0),
# ...
# ],
# If we had to initialize out states in a specific manner, we could also that like below:
# state_initializer = :((x, u, p, t) -> initialize_x)
# We could also optionally provide integrator options:
# cosimulator_integrator_options=(abstol=1e-6, reltol=1e-6),
# We could also optionally provide objects from user space needed for the FMU dynamics to operate
# objects=@objects([test_obj]),
# We optionally define the number of threads to start the FMU with to capitalize on multi-threaded acceleration of matmul operations. This is only relevant if your computation is heavy on matrix operations.
# n_octavian_threads = 4
)
# Save the FMU to the output directory
output_dir = joinpath(@__DIR__, "..", "output")
mkpath(output_dir) # Create output directory if it doesn't exist
fmu_filename = basename(fmu.fmu_path)
fmu_source = fmu.fmu_path
if isfile(fmu_source)
fmu_dest = joinpath(output_dir, fmu_filename)
cp(fmu_source, fmu_dest, force=true)
println("✅ FMU copied to: $fmu_dest")
end
Key changes:
- do not use greek letters, only ascii
- add an output function (Simulink crashes if there is no output function)
- fix the ODE definition. It did not modify
dx
in-place.