MOOSE FMU Interface
The MOOSEFMU defines the Moose2FMU Python base class which contains the boilerplate needed to wrap a MOOSE simulation as a Functional Mock-up Unit (FMU) using the FMI 2.0 standard. Users only need to implement their own __init__ and do_step methods when deriving from Moose2FMU.
Initialization Parameters
Moose2FMU accepts a handful of keyword arguments that configure how the underlying MOOSE simulation is launched and synchronized. The example FMU tests in test/tests/controls/moose_fmu pass these parameters as FMI start values, so you can see them in action in run_fmu.py and the simulate_moose_fmu helper inside moose_fmu_tester.py:
flag: Optional user-defined synchronization flag (see Synchronization and data retrieval flags) that supplements the defaultINITIAL,MULTIAPP_FIXED_POINT_BEGIN, andMULTIAPP_FIXED_POINT_ENDsignals.moose_command: Full command string used to launch the MOOSE executable and input file (for example, including MPI launchers when needed).server_name: Name of theMooseControlserver block to connect to.max_retries: Number of times helper utilities will poll the control server before aborting.dt_tolerance: Permitted synchronization tolerance between FMU time and the MOOSE simulation clock.
def __init__(
self,
*args,
flag: str = "",
moose_command: str = "",
server_name: str = "web_server",
max_retries: int = 5,
dt_tolerance: Real = 1e-3,
**kwargs,
):
"""Initialize the base Moose2FMU wrapper and default configuration."""
super().__init__(*args, **kwargs, logging_add_standard_categories=True)
# Per-instance logging uses a hierarchical "<module>.<class>" name keyed
# off the concrete (possibly subclassed) type:
# base_logger -- module-scoped parent. Setting its level or attaching
# handlers configures every class logger in that module
# at once.
# self.logger -- the per-class child the instance actually logs
# through, so each record is tagged with the concrete
# class name and any Moose2FMU subclass automatically
# gets its own distinct logger.
base_logger = logging.getLogger(self.__class__.__module__)
self.logger = base_logger.getChild(self.__class__.__name__)
self.logger.info("Moose2FMU initialized successfully.")
# Caller-supplied configuration (constructor kwargs / FMI start values):
# flag -- extra sync flag, merged with the built-in defaults
# moose_command -- command line that launches the MOOSE executable
# server_name -- MooseControl server block this FMU attaches to
# max_retries -- control-server poll attempts before giving up
# dt_tolerance -- allowed FMU-vs-MOOSE clock mismatch when syncing
self.flag: str = flag
self.moose_command: str = moose_command
self.server_name: str = server_name
self.max_retries: int = max_retries
self.dt_tolerance: Real = dt_tolerance
# Experiment/runtime state, set later (start/stop/tolerance by the FMI
# host via setup_experiment, moose_time during stepping):
# moose_time -- latest MOOSE clock captured during sync; also an output
# start_time -- experiment start time
# stop_time -- experiment stop time
# tolerance -- integrator tolerance requested by the host
self.moose_time: Real = 0.0
self.start_time: Real = 0.0
self.stop_time: Real = 0.0
self.tolerance: Real = 1.0e-3
(moose/python/moosefmu/moose2fmu.py)Because Moose2FMU derives from pythonfmu's Fmi2Slave, it also inherits a default_experiment attribute. Assign it a DefaultExperiment instance (from pythonfmu.default_experiment import DefaultExperiment) when the FMU should advertise a different start_time, stop_time, step_size, or tolerance. These values are written into the FMU's modelDescription.xml as the <DefaultExperiment> element and serve as hints to the importing co-simulation tool. Note that the step size is fixed when the FMU is built, so changing it requires rebuilding the FMU.
Runtime helpers
Moose2FMU provides a small collection of convenience methods that simplify interacting with a running MOOSE simulation:
set_controllable_realandset_controllable_vectorpush new controllable values to theWebServerControlsystem. Values are cached to avoid redundant updates; passforce=True`` to resend a value that was already applied.sync_moose_fmu_to_target_timesteps the running MOOSE simulation forward until the MOOSE clock reaches the FMU's target time (current_time, the communication point requested by the co-simulation master indo_step) to withindt_tolerance. It is the MOOSE clock that is advanced to catch up with the FMU, not the other way around, because MOOSE is a high-fidelity physics solver that usually integrates with smaller internal time steps than the FMU's communication step; it therefore takes one or more of its own steps to reach each FMU communication point (the base class requiresmoose_dt <= step_size). At each synchronization flag the method reads MOOSE's time and, if the target has not been reached, acknowledges the flag and lets MOOSE continue to its next step. It returns the matched MOOSE time and the flag that was active. Additional synchronization flags can be supplied via the optionalallowed_flagsargument.ensure_control_listeningverifies the control socket is ready before any postprocessors or reporters are queried.get_postprocessor_valueandget_reporter_valuewait for a specific synchronization flag and return the requested quantity.get_flag_with_retriesnormalizes retry logic when polling MOOSE for the next synchronization flag.
Together these helpers reduce the boilerplate needed inside a custom do_step implementation and make it easier to write robust coupling logic.
Synchronization and data retrieval flags
Moose2FMU coordinates with the running MOOSE simulation through named MooseControl flags. To simplify typical multiapp workflows the base class waits for a set of synchronization signals by default:
INITIALandMULTIAPP_FIXED_POINT_BEGINare consumed before attempting to advance the coupled simulation.MULTIAPP_FIXED_POINT_BEGINis the first flag raised immediately after the new time step has been computed, so listening for it gives the FMU the earliest opportunity to synchronize simulation time and FMU time.MULTIAPP_FIXED_POINT_ENDis consumed before any reporter or postprocessor values are retrieved. In a typical execution order controls run ahead of reporters and postprocessors, and those objects usually execute atTIMESTEP_END. TheMULTIAPP_FIXED_POINT_ENDflag fires right afterTIMESTEP_END, ensuring the data pulled from MOOSE reflects the updated state of the current time step.
Any flag string supplied through the flag initialization argument is merged into these defaults, and the higher-level helpers such as set_controllable_real, set_controllable_vector, get_reporter_value, and get_postprocessor_value also accept per-call flag arguments. Passing custom flags in these locations allows FMUs to react to user-defined synchronization points while still benefiting from the built-in defaults. Refer to SetupInterface for additional details on MOOSE execute flags.
Creating a Custom FMU
from moosefmu import Moose2FMU
class CustomMoose(Moose2FMU):
def __init__(self, **kwargs):
super().__init__(**kwargs)
# register additional inputs/outputs here
def do_step(
self,
current_time: float,
step_size: float,
no_set_fmu_state_prior: bool = False,
) -> bool:
# advance the MOOSE simulation and update outputs
return True
A typical step method will synchronize with MOOSE and make use of the helper utilities described above:
def do_step(self, current_time: float, step_size: float, **_kwargs) -> bool:
moose_time, signal = self.sync_moose_fmu_to_target_time(current_time, step_size)
if moose_time is None:
return False
if signal == "MULTIAPP_FIXED_POINT_END":
diffused = self.get_postprocessor_value(signal, "diffused", current_time)
if diffused is None:
return False
self.diffused = diffused
self.set_controllable_real("BCs/boundary", 1.0)
return True
Building the FMU
Save the custom class in a Python file and use pythonfmu to build the FMU:
pythonfmu build MooseTest.py
The resulting .fmu file can then be imported into any standard-compliant co-simulation environment.
Testing the FMU
Sample helper scripts in test/tests/controls/moose_fmu demonstrate how to exercise a generated FMU with pyfmi and show the initialization parameters in context:
run_fmu.pyrunsMooseTest.fmuin a stand-alone mode using manualdo_stepcalls and prints the time,moose_timeanddiffusedoutputs.run_fmu_connection.pyshows how to coupleMooseTest.fmuwith another FMU (e.g.,Dahlquist.fmu) and change boundary conditions during the simulation.run_fmu_step_by_step.pywalks through the FMU initialization sequence (load_fmu → setup_experiment → enter_initialization_mode → set(...) → exit_initialization_mode), making it clear where each start value is applied.
To try the examples:
cd test/tests/controls/moose_fmu
python run_fmu.py # stand‑alone Moose FMU
python run_fmu_connection.py # coupled FMUs example