Main Concept
For certain multiscale scenarios, having an adaptivity strategy that groups the micro simulations into active and inactive simulations may be insufficient. Alternatively, a hierarchy of micro-scale models, for example, reduced order models (ROMs) can be used. The model adaptivity functionality allows for the definition of multiple model fidelities and the switching between them at run-time.
Iterative Process
Without model adaptivity, the Micro Manager calls the solve(micro_sims_input, dt) routine of all active simulations
and copies their output to their closest similar inactive counterparts.
With model adaptivity, there is an iterative process, because a model may not be sufficiently accurate (given the current input).
The call to solve(micro_sims_input, dt) leads to the following logic:
self._model_adaptivity_controller.initialise_solve()
active_sim_ids = None
if self._is_adaptivity_on:
active_sim_ids = self._adaptivity_controller.get_active_sim_local_ids()
output = None
while self._model_adaptivity_controller.should_iterate():
self._model_adaptivity_controller.switch_models(
self._mesh_vertex_coords,
self._t,
micro_sims_input,
output,
self._micro_sims,
active_sim_ids,
)
output = solve_variant(micro_sims_input, dt)
self._model_adaptivity_controller.check_convergence(
self._mesh_vertex_coords,
self._t,
micro_sims_input,
output,
self._micro_sims,
active_sim_ids,
)
self._model_adaptivity_controller.finalise_solve()
return output
Here, after initialization and active sim acquisition, models will be switched, evaluated and checked for convergence
as long as the switching_function contains values other than 0.
Model evaluation - in the call solve_variant(micro_sims_input, dt) - is delegated to the regular
(non-model-adaptive) micro_sim_solve(micro_sims_input, dt) method.
Interfaces
class MicroSimulation: # Name is fixed
def __init__(self, sim_id):
"""
Constructor of class MicroSimulation.
Parameters
----------
sim_id : int
ID of the simulation instance, that the Micro Manager has set for it.
"""
def initialize(self) -> dict:
"""
Initialize the micro simulation and return initial data which will be used in computing adaptivity before the first time step.
Defining this function is OPTIONAL.
Returns
-------
initial_data : dict
Dictionary with names of initial data as keys and the initial data itself as values.
"""
def solve(self, macro_data: dict, dt: float) -> dict:
"""
Solve one time step of the micro simulation for transient problems or solve until steady state for steady-state problems.
Parameters
----------
macro_data : dict
Dictionary with names of macro data as keys and the data as values.
dt : float
Current time step size.
Returns
-------
micro_data : dict
Dictionary with names of micro data as keys and the updated micro data a values.
"""
def set_state(self, state):
"""
Set the state of the micro simulation.
"""
def get_state(self):
"""
Return the state of the micro simulation.
"""
def output(self):
"""
This function writes output of the micro simulation in some form.
It will be called with frequency set by configuration option `simulation_params: micro_output_n`
This function is *optional*.
"""
For this the default MicroSimulation still serves as the model interface, while the (set)|(get)_state() methods
are called to transfer internal model parameters from one to another.
The list of provided models is interpreted in decreasing fidelity order. In other words, the first one
is likely to be the full order model, while subsequent ones are ROMs.
def switching_function(
resolutions: np.ndarray,
locations: np.ndarray,
t: float,
inputs: list[dict],
prev_output: dict,
active: np.ndarray,
) -> np.ndarray:
"""
Switching interface function, use as reference
Parameters
----------
resolutions : np.array - shape(N,)
Array with resolution information as get_sim_class_resolution would return for a sim obj.
locations : np.array - shape(N,D)
Array with gaussian points for all sims. D is the mesh dimension.
t : float
Current time in simulation.
inputs : list[dict]
List of input objects.
prev_output : [None, dict-like]
Contains the outputs of the previous model evaluation.
active : np.array - shape(N,)
Bool array indicating whether the model is active or not.
"""
return np.zeros_like(resolutions)
The switching of models is governed by the switching_function.
The output is expected to be a np.ndarray of shape (N,) and is interpreted in the following manner:
| Value | Action |
|---|---|
| 0 | No resolution change |
| -1 | Increase model fidelity by one (go back one in list) |
| 1 | Decrease model fidelity by one (go one ahead in list) |