MOOSE-Wrapped Apps

Overview

MOOSE has been designed so that coupling to existing codes is a relatively straight-forward process. If your existing code is relatively modular you may not be required to make any intrusive changes in order to get it talking to MOOSE. This provides an attractive solution for developers where creating a new code from scratch may not be feasible, or where existing codes contain capabilities that are well outside the scope of MOOSE's normal capabilities. This page describes the extension points and directions for creating a new "thin wrapper" application that will allow your application to plug in and work with existing MOOSE-based and other MOOSE-wrapped codes.

Wrapping Interfaces

There are only a handful of interfaces for wrapping an existing code. Each of those interfaces is enumerated and described in detail here:

  1. "External" App object

  2. ExternalProblem object

  3. "External" Mesh object

  4. TimeStepper object(s)

  5. (optional) "External" Executioner object

These wrapping interfaces represent the minimum set of objects that need to be developed in order to couple an existing application to other MOOSE-based or MOOSE-wrapped application. However, since the wrapped app is a MOOSEApp, any of the normal MOOSE systems can be extended to provide additional functionality or capabilities within the combined simulation. Examples would include new Outputs, new Postprocessors, new AuxKernels, etc. to better visualize and understand the behavior of the combined simulations.

External App

The External App is the normal MooseApp-derived object that gets created whenever a developer creates (or "storks") a new application. The App object is the container for every other object in your simulation and provides the necessary encapsulation, which makes the MultiApp system possible. For the most part the skeleton implementation generated by stork script is sufficient for many normal simulation runs. However, two important methods need to be implemented for complete "fully-coupled" Multiapp functionality. Namely the backup() and restore() methods.

The backup method should return a shared_ptr object containing a Backup object for the external application. This object is just a wrapper for one or more binary blobs containing the state data for the external application. Ideally, the application could restore it's complete internal state from the Backup blob if asked to do so.

  /**
   * Backs up the application to the folder \p folder_base
   *
   * @return The files that are written in the backup
   */
  std::vector<std::filesystem::path> backup(const std::filesystem::path & folder_base);
  /**
   * Backs up the application memory in a Backup.
   *
   * @return The backup
   */
  std::unique_ptr<Backup> backup();
(framework/include/base/MooseApp.h)

The restore method should is the complementary method to the backup method. The Application is called with a Backup object, which is then read and used to reconstruct the state of External Application. These two methods can be used by the MOOSE framework to resolve one or more steps so that Picard or fixed-point iterations may be taken to tightly converge a simulation step.

   * @param folder_base The backup folder base
   * @param for_restart Whether this restoration is explicitly for the first restoration of restart
   * data
   *
   * You must call finalizeRestore() after this in order to finalize the restoration.
   * The restore process is kept open in order to restore additional data after
   * the initial restore (that is, the restoration of data that has already been declared).
   */
  void restore(const std::filesystem::path & folder_base, const bool for_restart);
(framework/include/base/MooseApp.h)

ExternalProblem-derived object

The ExternalProblem object contains the extra interface function necessary for creating and maintaining current solution information on an "external" Mesh object. Specifically, the ExternalProblem creates a final override for the solve() method that triggers three different callbacks:


syncSolutions(Direction::TO_EXTERNAL_APP);
externalSolve();
syncSolutions(Direction::FROM_EXTERNAL_APP);

This calling sequence ensures that update-to-date information on the Mesh can be pulled into the External Application and used for the solve. After the External solve has completed, another callback to send data back from the External Application to the Mesh is made. This allows MOOSE to either read from or write to the field variables on the ExternalMesh and ensure that they are accurate and update to date at all times. Additionally, the native representation of the external application's data in the ExternalMesh ensure that no additional code to transfer information among the active applications in a simulation needs to be written, tested, and verified. For example, a sampling or projection transfer can be used between two meshes with discretization without the need to write the projection operation in a custom made transfer, which would only be applicable to the specific external app being targeted.

In order for MOOSE to understand which variables should be maintained on the ExternalMesh, another overridable method was created that is called during the problem setup to assist the adding the necessary AuxVariables to the simulation. Generally, the list of which variables are needed for a specific coupling is relatively static. However, the developer may find it convenient to supply a varying list of available parameters through normal Problem construction.

  /**
   * Method called to add AuxVariables to the simulation. These variables would be the fields
   * that should either be saved out with the MOOSE-formatted solutions or available for
   * transfer to variables in Multiapp simulations.
   */
  virtual void addExternalVariables() {}
(framework/include/problems/ExternalProblem.h)

"external" Mesh

The external mesh is the MOOSE-native mesh, which holds the geometry and field information for the external application. There is no "external mesh" object in the MOOSE framework. The external mesh is just a normal MooseMesh-derived object that must be created in the coupling application to hold the geometric information for the external code being coupled in. As with other MeshMesh-derived objects, the main method that must be implemented is the build() method. Normally a user must query their own application to reconstruct the geometry in the ExternalMesh.

Figure 1: ExternalMesh illustration

TimeStepper object

The TimeStepper system in MOOSE is designed to allow full control of time step selection without requiring the user to implement all of complex details of a "Transient" execution strategy. For many types of coupling, the developer should consider starting with a custom TimeStepper object to tie the external application and MOOSE together. The TimeStepper object works in concert with the ExternalProblem to execute the solver steps on the external application.

A simplified call sequence, looks like this:


TimeStepper::preStep();
TimeStepper::computeDT();
ExternalProblem::externalSolve();
TimeStepper::converged();
TimeStepper::postStep();

Each of these calls can be overridden with just the creation of two new derived objects with enough flexibility and functionality for coupling to many external applications.

"external" Executioner

If the coupling developer requires ultimate flexibility, the Transient executioner can be extended to override existing simulation execution behaviors. This would potentially allow the developer to replace almost every part of the execution control of coupled simulation. However a small number of APIs can be overridden with very little impact on MOOSE's behavior. See the Transient Executioner for details.