API Models
API models are the data transfer objects that connect your plugin's UI with the solver. They provide a clean, serializable representation of the simulation configuration that can be passed to the solver process.
Purpose of API Models
API models serve several important purposes:
- Data Transfer: They facilitate communication between the UI and the solver
- Clean Interface: They provide a simplified representation of simulation parameters
- Validation: They enforce data types and constraints
- Serialization: They enable easy conversion to/from JSON
Location and Structure
API models are defined in your plugin's solver/driver/api_models.py file. They use dataclasses and dataclasses_json to create serializable data objects:
from dataclasses import dataclass
from enum import Enum
from dataclasses_json import dataclass_json
class LogLevel(Enum):
"""Logging verbosity levels."""
INFO = "info"
DEBUG = "debug"
ERROR = "error"
@dataclass_json
@dataclass
class SetupSettings:
"""General simulation setup parameters."""
log_level: str = LogLevel.INFO.value
@dataclass_json
@dataclass
class MaterialSettings:
"""Material properties and domain extents."""
xmin: float
xmax: float
ymin: float
ymax: float
zmin: float
zmax: float
thermal_conductivity: float = 1.0
# ... other data classes ...
@dataclass_json
@dataclass
class Simulation:
"""Complete simulation configuration."""
setup_settings: SetupSettings
material_settings: list[MaterialSettings]
# ... other settings ...
Using Dataclasses
Python's dataclass decorator automatically adds special methods like __init__ and __repr__ to your classes. When combined with dataclass_json, they also gain serialization capabilities:
@dataclass_json # Add JSON serialization
@dataclass # Create a data class
class GridSettings:
"""Computational grid parameters."""
dx: float = 1.0
dy: float = 1.0
dz: float = 1.0
JSON Serialization
The dataclass_json decorator adds methods for converting to and from JSON:
# Create an instance
grid = GridSettings(dx=0.5, dy=0.5, dz=0.5)
# Convert to JSON string
json_str = grid.to_json() # '{"dx": 0.5, "dy": 0.5, "dz": 0.5}'
# Create from JSON string
restored_grid = GridSettings.from_json(json_str)
Type Validation
Use type hints and default values to enforce data types and constraints:
@dataclass_json
@dataclass
class SolverSettings:
"""Numerical solver parameters."""
solver_method: str = "jacobi"
tolerance: float = 1e-6
max_iter: int = 1000
Enums for Constants
Use enum classes for constants like modes, types, or predefined options:
class DirichletFace(Enum):
"""Domain boundary faces with fixed values."""
XMIN = "xmin"
XMAX = "xmax"
YMIN = "ymin"
YMAX = "ymax"
ZMIN = "zmin"
ZMAX = "zmax"
@dataclass_json
@dataclass
class BoundarySettings:
"""Boundary condition specification."""
face: str = DirichletFace.XMIN.value
temperature: float = 0.0
Nested Data Structures
Complex models can include nested data structures or lists:
@dataclass_json
@dataclass
class Simulation:
"""Complete simulation configuration."""
setup_settings: SetupSettings
material_settings: list[MaterialSettings]
source_settings: list[SourceSettings]
boundary_settings: list[BoundarySettings]
grid_settings: GridSettings
solver_settings: SolverSettings
Connection to UI Model
In your Simulation class (the UI model), implement an as_api_model method to convert the UI model to an API model:
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 materials
api_materials: list[api_models.MaterialSettings] = []
for material in self._material_settings.elements:
api_materials.append(material.as_api_model())
# Convert sources
api_sources: list[api_models.SourceSettings] = []
for source in self._source_settings.elements:
api_sources.append(source.as_api_model())
# Convert boundaries
api_boundaries: list[api_models.BoundarySettings] = []
for boundary in self._boundary_settings.elements:
api_boundaries.append(boundary.as_api_model())
# Create complete API model
return api_models.Simulation(
setup_settings=self._setup_settings.as_api_model(),
material_settings=api_materials,
source_settings=api_sources,
boundary_settings=api_boundaries,
grid_settings=self._grid_settings.as_api_model(),
solver_settings=self._solver_settings.as_api_model(),
)
Example API Models
Here's a complete example of API models for a heat conduction simulation:
from dataclasses import dataclass
from enum import Enum
from dataclasses_json import dataclass_json
class LogLevel(Enum):
"""Logging verbosity levels."""
INFO = "info"
DEBUG = "debug"
ERROR = "error"
@dataclass_json
@dataclass
class SetupSettings:
"""General simulation setup parameters."""
log_level: str = LogLevel.INFO.value
@dataclass_json
@dataclass
class MaterialSettings:
"""Material properties and domain extents."""
xmin: float
xmax: float
ymin: float
ymax: float
zmin: float
zmax: float
thermal_conductivity: float = 1.0
@dataclass_json
@dataclass
class SourceSettings:
"""Heat source properties and location."""
x: float
y: float
z: float
volumetric_heat_source: float = 100.0
class DirichletFace(Enum):
"""Domain boundary faces with fixed values."""
XMIN = "xmin"
XMAX = "xmax"
YMIN = "ymin"
YMAX = "ymax"
ZMIN = "zmin"
ZMAX = "zmax"
@dataclass_json
@dataclass
class BoundarySettings:
"""Boundary condition specification."""
face: str = DirichletFace.XMIN.value
temperature: float = 0.0
@dataclass_json
@dataclass
class GridSettings:
"""Computational grid parameters."""
dx: float = 1.0
dy: float = 1.0
dz: float = 1.0
class SolverMethod(Enum):
"""Numerical solution methods."""
JACOBI = "jacobi"
@dataclass_json
@dataclass
class SolverSettings:
"""Numerical solver parameters."""
solver_method: str = SolverMethod.JACOBI.value
tolerance: float = 1e-6
max_iter: int = 1000
@dataclass_json
@dataclass
class Simulation:
"""Complete simulation configuration."""
setup_settings: SetupSettings
material_settings: list[MaterialSettings]
source_settings: list[SourceSettings]
boundary_settings: list[BoundarySettings]
grid_settings: GridSettings
solver_settings: SolverSettings
Best Practices
When designing API models:
- Keep them simple: Include only the data needed by the solver
- Use meaningful defaults: Provide sensible default values where appropriate
- Document carefully: Add docstrings to explain the purpose of each class and field
- Validate inputs: Add validation in the solver to ensure the API models contain valid data
- Version explicitly: If you need to change API models between versions, consider versioning