Creating a Simulation Class
The Simulation class is the central component of your Sim4Life plugin. It serves as the container for all simulation settings and provides the core functionality for your physics model.
Simulation Class Structure
The Simulation class inherits from SimulationBase and implements several key methods:
class Simulation(SimulationBase):
"""
Central model class representing a custom physics simulation.
This class serves as the main container for all simulation settings and components,
including setup parameters, material properties, boundary conditions, and solver parameters.
"""
@classmethod
def get_simulation_type_name(cls) -> str:
"""Display name for this simulation type in the Sim4Life interface."""
return "My Physics Simulation"
@classmethod
def get_simulation_description(cls) -> str:
"""Brief description of what this simulation type does."""
return "A simulation for solving custom physics problems"
@classmethod
def get_simulation_icon(cls) -> str:
"""Icon path for this simulation type in the UI."""
return "icons/XSimulatorUI/new_simulation.ico"
def __init__(
self,
parent: "TreeItem",
sim_desc: str = "Simulation",
sim_notes: str = "Solves physics equations",
) -> None:
"""Initialize a new simulation instance with default settings."""
super().__init__(parent, sim_desc, sim_notes)
# Add a help button to the properties panel
self._properties.Add("help_button", create_help_button())
# Schedule the help button connection
asyncio.get_event_loop().call_soon(self._connect_help)
# Note: Using call_soon ensures that the help button connection does not interfere with the initialization process, especially after deserialization from file.
def on_initialize_settings(self) -> None:
"""Create all the settings components for this simulation."""
self._setup_settings = setup_settings.SetupSettings(self)
self._material_settings = material_settings.Materials(self)
self._boundary_settings = boundary_settings.Boundaries(self)
self._grid_settings = grid_settings.GridSettings(self)
self._solver_settings = solver_settings.SolverSettings(self)
# Add any other settings components specific to your physics model
Required Methods
Your Simulation class must implement several required methods:
Class Methods for Identification
@classmethod
def get_simulation_type_name(cls) -> str:
"""Returns the display name used in the Sim4Life interface."""
return "My Physics Simulation"
@classmethod
def get_simulation_description(cls) -> str:
"""Returns a brief description of this simulation type."""
return "A simulation for solving custom physics problems"
@classmethod
def get_simulation_icon(cls) -> str:
"""Returns the path to the icon used in the UI."""
return "icons/XSimulatorUI/new_simulation.ico"
These methods provide basic identification and display information for your simulation type in the Sim4Life interface.
Initialization and Setting Creation
def __init__(self, parent: "TreeItem", sim_desc: str = "Simulation", sim_notes: str = "Notes") -> None:
"""Initializes a new simulation instance with default settings."""
super().__init__(parent, sim_desc, sim_notes)
# Add properties, buttons, etc.
def on_initialize_settings(self) -> None:
"""Creates all the settings objects for this simulation."""
# Create settings components
self._setup_settings = setup_settings.SetupSettings(self)
self._material_settings = material_settings.Materials(self)
# ...other settings...
The on_initialize_settings method is called during initialization to set up all the settings components that make up your simulation.
Properties and Getters
@property
def setup_settings(self) -> setup_settings.SetupSettings:
return self._setup_settings
@property
def material_settings(self) -> material_settings.Materials:
return self._material_settings
# ...other property getters...
These property getters provide access to the various settings components of your simulation.
Solver and Extractor Methods
def register_extractor(self) -> pp.PythonModuleAlgorithm:
"""
Registers a post-processing extractor for simulation results.
Returns:
A PostPro algorithm that can extract and process simulation results
"""
return pp.PythonModuleAlgorithm(
"my_package_name.model.simulation_extractor",
0, # number of inputs
1, # number of outputs
)
def solver_backend(self) -> tuple[SolverBackend, config_type_t | None]:
"""
Specifies which type of solver backend to use for running this simulation.
Returns:
A tuple containing the solver backend type and optional configuration
"""
return SolverBackend.PROCESS, None
def get_solver_src(self) -> str:
"""
Returns the Python module path to the solver implementation.
Returns:
The fully qualified module name for the solver implementation
"""
return "my_package_name.solver.driver"
These methods provide the connections to your solver implementation and result extraction.
Note: At present, only the subprocess backend is supported. This means your solver must have all required third-party libraries available after the plugin is installed via pip. In the future, a remote execution backend using Docker containerization will be introduced to simplify dependency management and execution.
Validation and API Model Conversion
def validate(self) -> bool:
"""
Validates that the simulation configuration is complete and ready to run.
Returns:
True if the simulation is valid and ready to run, False otherwise
"""
self.clear_status_recursively()
# Perform validation checks
# ...
return True
def as_api_model(self) -> api_models.Simulation:
"""
Converts the simulation model to a format suitable for the solver API.
Returns:
A solver API model representation of this simulation
"""
if not self.validate():
raise RuntimeError("Validation failed")
# Convert settings to API model
# ...
return api_models.Simulation(
setup_settings=self._setup_settings.as_api_model(),
# ...other settings...
)
These methods handle validation before running the simulation and conversion to the format needed by your solver.
Adding Help Information
It's a good practice to provide help information for your simulation:
def _connect_help(self):
"""Connects the help button click event to the help display handler."""
help_button = self._properties.help_button
assert isinstance(help_button, xc.PropertyPushButton)
help_button.OnClicked.Connect(self._display_help)
def _display_help(self) -> None:
"""Displays help information when the help button is clicked."""
title = "My Physics Simulation"
text = (
"This simulation solves the physics equation using numerical methods."
"\n\n"
"For more information, please refer to the documentation."
)
display_help(title, text)
Example: Complete Simulation Class
Here's a complete example of a simulation class for a hypothetical wave equation solver:
class WaveSimulation(SimulationBase):
"""
Central model class representing a wave equation simulation.
This class simulates wave propagation in various media, solving the
wave equation: ∂²u/∂t² = c² ∇²u
"""
@classmethod
def get_simulation_type_name(cls) -> str:
return "Wave Equation"
@classmethod
def get_simulation_description(cls) -> str:
return "Simulation of wave propagation in various media"
@classmethod
def get_simulation_icon(cls) -> str:
return "icons/XSimulatorUI/wave_simulation.ico"
def __init__(
self,
parent: "TreeItem",
sim_desc: str = "Wave Simulation",
sim_notes: str = "Solves ∂²u/∂t² = c² ∇²u",
) -> None:
super().__init__(parent, sim_desc, sim_notes)
self._properties.Add("help_button", create_help_button())
asyncio.get_event_loop().call_soon(self._connect_help)
def on_initialize_settings(self) -> None:
self._setup_settings = setup_settings.SetupSettings(self)
self._material_settings = material_settings.Materials(self)
self._boundary_settings = boundary_settings.Boundaries(self)
self._grid_settings = grid_settings.GridSettings(self)
self._time_settings = time_settings.TimeSettings(self) # Time-domain specific
self._solver_settings = solver_settings.SolverSettings(self)
self._initial_conditions = initial_conditions.InitialConditions(self) # Wave-specific
# ... Properties, validation, etc. ...
Note: Using call_soon ensures that the help button connection does not interfere with the initialization process, especially after deserialization from file.
Common Patterns and Best Practices
When creating your simulation class:
- Organize settings logically: Group related settings together in logical collections
- Provide clear validation: Implement thorough validation to catch configuration errors
- Include helpful descriptions: Make sure all properties and UI elements have clear descriptions
- Document physics equations: Include the governing equations in comments and help text
- Prioritize extensibility: Design your simulation to be easily extendable for future features