# Elmer

The Elmer backend: the typed solver stack, the mesh-name reconciliation a sif needs, and the backends that run Elmer.

## femforge.elmer

Elmer backend: the typed solver stack (`PhysicsConfig`), the mesh-name reconciliation a sif needs, and the backends that run Elmer.

Modules:

- **`config`** –

  Typed Elmer configuration: Pydantic models whose fields ARE their sif keywords, grouped by physics (`common`, `electromagnetism`, `thermal`, `mechanics`, `output`), plus the per-physics presets.
- **`mesh_names`** –

  Reconcile gmsh physical-group ids with the ids ElmerGrid assigns, read back from the `mesh.names` file it writes.
- **`physics`** –

  Discoverable vocabularies for the Elmer stage: the supported physics as an enum, not free strings.
- **`runtime`** –

  How Elmer runs: the native and Docker execution backends, the ElmerGrid commands they share, and the parsed solve.
- **`sif`** –

  Native writer for the Elmer simulation input file (.sif): pyelmer's API shape and output text, always LF-terminated.
- **`sim`** –

  Elmer .sif generator whose bodies and boundaries line up one-to-one with the gmsh physical and boundary groups.
- **`solver_log`** –

  Streaming ElmerSolver log parser (`SolverLogParser.feed`) plus the fixed-width `ConvergenceRenderer` for terminal display.
- **`variables`** –

  Elmer solver variable names: the single source for each string an emit site declares and a parser reads back.

Classes:

- **`CoilCurrentSolver`** –

  Elmer's `CoilSolver`: the divergence-conforming coil current distribution, the source stage of the `COIL_MAGNETOSTATICS` chain.
- **`Constants`** –

  The Elmer `Constants` block: each constant is an optional field carrying its sif keyword; a physics sets only the constants it needs, and an unset one is omitted.
- **`CoordinateSystem`** –

  Elmer's `Coordinate System`.
- **`ElectrostaticsSolver`** –

  SCAFFOLD: electrostatics (StatElecSolver). No nonlinear iteration.
- **`ExecSolver`** –

  When Elmer executes a solver (the `Exec Solver` keyword).
- **`GenericSolver`** –

  Escape hatch: an arbitrary Elmer solver block for anything the typed hierarchy doesn't model yet — set
- **`HeatSolver`** –

  SCAFFOLD: steady heat conduction. Wire-ready; its boundary conditions land on the adjacency-derived surface groups via the condition-driven boundary loop.
- **`IterativeMethod`** –

  The Krylov method for an iterative linear solve (`Linear System Iterative Method`).
- **`LinearSolverType`** –

  The `Linear System Solver` strategy: an iterative Krylov method or a direct factorization.
- **`LinearSystem`** –

  Mixin: the `Linear System *` settings for a solver that does a linear solve. Defaults are sensible iterative values; concrete solvers tune the iteration cap and tolerances they need.
- **`MagnetoDynamics2DSolver`** –

  Axisymmetric magnetostatics (MagnetoDynamics2D): the scalar azimuthal potential A\_phi under `Coordinate System = Axi Symmetric`.
- **`MagnetoDynamicsCalcFieldsSolver`** –

  Post-processing: recover B, H, fluxes and nodal forces from the computed A. Inherits `Linear System *` because
- **`MagnetoDynamicsSolver`** –

  3D magnetostatics (WhitneyAVSolver), with each magnet's drive entering as `Magnetization` on its material rather than as a body force.
- **`NonlinearSystem`** –

  Mixin: the `Nonlinear System *` (outer-iteration) convergence settings.
- **`OperatingPoint`** –

  The point at which a material's property functions are evaluated when stripping to Elmer's bare SI floats.
- **`PhysicsConfig`** –

  The fully populated, typed solver stack for one physics: ordered solvers plus simulation settings, activated constants and operating point.
- **`PhysicsSelection`** –

  Which physics the Elmer stage solves; `SimulationConfig` derives the solved stack from this selection and its frame.
- **`Preconditioning`** –

  The `Linear System Preconditioning` for an iterative solve.
- **`ResultOutputSolver`** –

  Write VTU output for ParaView.
- **`SaveScalarsSolver`** –

  SaveScalars (SaveData): export scalar reductions to `scalars.dat` and its `.names` sidecar.
- **`SimulationSettings`** –

  The Elmer `Simulation` block. Defaults are a 3D Cartesian steady-state run, used for magnetostatics and steady thermal.
- **`SimulationType`** –

  Elmer's `Simulation Type`.
- **`SolverBase`** –

  Base for a typed Elmer solver: its fields are its sif keywords.
- **`StatCurrentSolver`** –

  Static current conduction (Elmer's StatCurrentSolve): solves `Potential` from ∇·(σ∇φ) = 0, excited by per-phase
- **`SteadyState`** –

  Mixin: the `Steady State Convergence Tolerance` for a steady-state run.
- **`StressSolver`** –

  SCAFFOLD: linear elasticity (Elmer's StressSolve). Consumes `Youngs Modulus` and `Poisson Ratio` material
- **`TimesteppingMethod`** –

  Elmer's `Timestepping Method` (paired with `BDF Order` for BDF).
- **`MeshNames`** –

  The `name -> Elmer id` maps ElmerGrid records in `mesh.names`, one per section (one continuing id sequence: boundary ids start after the body ids).
- **`Physics`** –

  Which physics preset the sif writer wires; the string values double as the serialized spelling.
- **`ExecutionBackend`** –

  Runs the Elmer tools over local directories; a remote backend uploads from and downloads into the same paths.
- **`DockerElmer`** –

  Runs Elmer in `image`, femforge's Elmer image by default, built on first use; partitioned solves use the image's MUMPS.
- **`NativeElmer`** –

  Runs a native Elmer install; it is not partitioned, because native builds lack the MUMPS a partitioned solve needs.
- **`ElmerVariable`** –

  The Elmer variable names this project emits and reads back, one member per name.

Functions:

- **`physics_config`** –

  The fully-populated, typed solver stack for `physics`, keyed by an exhaustive `match` on the `Physics` enum (a new physics is a static obligation).
- **`prepare_for_mpi`** –

  Adjust a solver stack for a partitioned (MPI) solve: CoilSolver's serial-only umfpack Direct solve switches to MUMPS, and any other Direct solve raises.
- **`parse_mesh_names`** –

  Parse an ElmerGrid `mesh.names` file (`$ AIR__EXT = 1` lines under per-section headers) into its body and
- **`reconcile_ids`** –

  Overwrite each group's gmsh-tag `id` in place with the Elmer id ElmerGrid assigned to its name; returns the names `mesh.names` lacks.
- **`condition_physics`** –

  The physics whose authored conditions are *active* under `physics`: a coupled preset accepts its constituents'
- **`runtime_variables`** –

  The solved-field variable names a material property may depend on at runtime under `physics`. A property whose
- **`solve_case`** –

  Run ElmerSolver on `sim_dir` through `backend`, logging and collecting what its stream reports.
- **`elemental`** –

  The variable's DG counterpart: Elmer registers a solver's elemental field as `"<name> e"`.

Attributes:

- **`SifValue`** –

  An Elmer sif keyword value, copied verbatim into the sif.

### SifValue

```python
SifValue = str | int | float | bool
```

An Elmer sif keyword value, copied verbatim into the sif.

### CoilCurrentSolver

Bases: `SolverBase`, `LinearSystem`, `NonlinearSystem`, `SteadyState`

Elmer's `CoilSolver`: the divergence-conforming coil current distribution, the source stage of the `COIL_MAGNETOSTATICS` chain.

It acts only on `CoilDrive` bodies, because Elmer requires every body it covers to belong to a `Component`.

Config:

- `validate_assignment`: `True`
- `use_attribute_docstrings`: `True`

Fields:

- `steady_state_convergence_tolerance` (`SteadyStateConvToleranceField`)
- `nonlinear_system_convergence_tolerance` (`NonlinearConvToleranceField`)
- `nonlinear_system_max_iterations` (`NonlinearMaxIterationsField`)
- `nonlinear_system_newton_after_iterations` (`NonlinearNewtonAfterIterationsField`)
- `nonlinear_system_newton_after_tolerance` (`NonlinearNewtonAfterToleranceField`)
- `nonlinear_system_relaxation_factor` (`NonlinearRelaxationFactorField`)
- `nonlinear_system_convergence_measure` (`NonlinearConvMeasureField`)
- `linear_system_convergence_tolerance` (`LinearConvToleranceField`)
- `linear_system_preconditioning` (`LinearPreconditioningField`)
- `linear_system_residual_output` (`LinearResidualOutputField`)
- `linear_system_abort_not_converged` (`LinearAbortNotConvergedField`)
- `bicgstabl_polynomial_degree` (`BiCGStablPolynomialDegreeField`)
- `extra_keywords` (`dict[str, SifValue]`)
- `variable` (`VariableField`)
- `variable_dofs` (`VariableDofsField`)
- `stabilize` (`StabilizeField`)
- `kind` (`Literal['CoilCurrent']`)
- `equation` (`EquationField`)
- `procedure` (`ProcedureField`)
- `exec_solver` (`ExecSolverField`)
- `optimize_bandwidth` (`OptimizeBandwidthField`)
- `linear_system_solver` (`LinearSolverTypeField`)
- `linear_system_direct_method` (`LinearDirectMethodField`)
- `linear_system_iterative_method` (`LinearIterativeMethodField`)
- `linear_system_max_iterations` (`LinearMaxIterationsField`)
- `coil_closed` (`bool`)
- `normalize_coil_current` (`bool`)
- `calculate_elemental_fields` (`bool`)

#### kind

```python
kind: Literal['CoilCurrent'] = 'CoilCurrent'
```

Discriminator that names this solver, doubling as its sif Solver label.

#### coil\_closed

```python
coil_closed: bool = False
```

False: an open coil whose ends are the `Coil Start` / `Coil End` electrode boundaries.

#### normalize\_coil\_current

```python
normalize_coil_current: bool = False
```

Must stay OFF for a massive coil: it rescales |J| to a UNIFORM magnitude, which on a varying cross-section authors a non-conservative source that Jfix redistributes (silently reduced drive current in the winding). `Desired Coil Current` scaling happens regardless, so the total current is still exact with this off.

#### calculate\_elemental\_fields

```python
calculate_elemental_fields: bool = True
```

Export the DG `CoilCurrent e` field (what `Use Elemental CoilCurrent` reads); the nodal field stays too.

### Constants

Bases: `SifKeywordModel`

The Elmer `Constants` block: each constant is an optional field carrying its sif keyword; a physics sets only the constants it needs, and an unset one is omitted.

Fields:

- `extra_keywords` (`dict[str, SifValue]`)
- `permeability_of_vacuum` (`float | None`)
- `permittivity_of_vacuum` (`float | None`)
- `stefan_boltzmann` (`float | None`)

#### permeability\_of\_vacuum

```python
permeability_of_vacuum: float | None = None
```

`μ₀` `[H/m]`: Needed by the magnetostatic solver.

#### permittivity\_of\_vacuum

```python
permittivity_of_vacuum: float | None = None
```

`ε₀` `[F/m]`: Needed by the electrostatic solver.

#### stefan\_boltzmann

```python
stefan_boltzmann: float | None = None
```

`σ_SB` `[W/(m²·K⁴)]`: Needed for thermal radiation.

### CoordinateSystem

Bases: `StrEnum`

Elmer's `Coordinate System`.

Attributes:

- **`CARTESIAN`** –

  Cartesian coordinates.
- **`CYLINDRIC`** –

  Full cylindrical coordinates.
- **`CYLINDRIC_SYMMETRIC`** –

  2D cylindrically symmetric (r, z) coordinates, integrated per radian.
- **`AXI_SYMMETRIC`** –

  2D axisymmetric (r, z) coordinates, integrated per radian.
- **`POLAR`** –

  Polar coordinates.
- **`per_radian`** (`bool`) –

  Whether Elmer integrates domain integrals per radian here (the 2D symmetric systems), so lumped outputs need a 2π revolution factor.

#### CARTESIAN

```python
CARTESIAN = 'Cartesian'
```

Cartesian coordinates.

#### CYLINDRIC

```python
CYLINDRIC = 'Cylindric'
```

Full cylindrical coordinates.

#### CYLINDRIC\_SYMMETRIC

```python
CYLINDRIC_SYMMETRIC = 'Cylindric Symmetric'
```

2D cylindrically symmetric (r, z) coordinates, integrated per radian.

#### AXI\_SYMMETRIC

```python
AXI_SYMMETRIC = 'Axi Symmetric'
```

2D axisymmetric (r, z) coordinates, integrated per radian.

#### POLAR

```python
POLAR = 'Polar'
```

Polar coordinates.

#### per\_radian

```python
per_radian: bool
```

Whether Elmer integrates domain integrals per radian here (the 2D symmetric systems), so lumped outputs need a 2π revolution factor.

### ElectrostaticsSolver

Bases: `SolverBase`, `LinearSystem`, `SteadyState`

SCAFFOLD: electrostatics (StatElecSolver). No nonlinear iteration.

Fields:

- `linear_system_solver` (`LinearSolverTypeField`)
- `linear_system_direct_method` (`LinearDirectMethodField`)
- `linear_system_iterative_method` (`LinearIterativeMethodField`)
- `linear_system_preconditioning` (`LinearPreconditioningField`)
- `linear_system_residual_output` (`LinearResidualOutputField`)
- `linear_system_abort_not_converged` (`LinearAbortNotConvergedField`)
- `bicgstabl_polynomial_degree` (`BiCGStablPolynomialDegreeField`)
- `extra_keywords` (`dict[str, SifValue]`)
- `variable_dofs` (`VariableDofsField`)
- `exec_solver` (`ExecSolverField`)
- `optimize_bandwidth` (`OptimizeBandwidthField`)
- `kind` (`Literal['Electrostatics']`)
- `equation` (`EquationField`)
- `procedure` (`ProcedureField`)
- `variable` (`VariableField`)
- `stabilize` (`StabilizeField`)
- `steady_state_convergence_tolerance` (`SteadyStateConvToleranceField`)
- `linear_system_max_iterations` (`LinearMaxIterationsField`)
- `linear_system_convergence_tolerance` (`LinearConvToleranceField`)
- `calculate_electric_field` (`bool`)

#### kind

```python
kind: Literal['Electrostatics'] = 'Electrostatics'
```

Discriminator that names this solver, doubling as its sif Solver label.

#### calculate\_electric\_field

```python
calculate_electric_field: bool = True
```

Recover E from the potential.

### ExecSolver

Bases: `StrEnum`

When Elmer executes a solver (the `Exec Solver` keyword).

Attributes:

- **`ALWAYS`** –

  Run in the normal solve sequence.
- **`BEFORE_SIMULATION`** –

  Run once before the simulation.
- **`AFTER_SIMULATION`** –

  Run once after the simulation.
- **`BEFORE_TIMESTEP`** –

  Run at the start of each timestep.
- **`AFTER_TIMESTEP`** –

  Run at the end of each timestep.
- **`NEVER`** –

  Never run.

#### ALWAYS

```python
ALWAYS = 'Always'
```

Run in the normal solve sequence.

#### BEFORE\_SIMULATION

```python
BEFORE_SIMULATION = 'Before simulation'
```

Run once before the simulation.

#### AFTER\_SIMULATION

```python
AFTER_SIMULATION = 'After simulation'
```

Run once after the simulation.

#### BEFORE\_TIMESTEP

```python
BEFORE_TIMESTEP = 'Before timestep'
```

Run at the start of each timestep.

#### AFTER\_TIMESTEP

```python
AFTER_TIMESTEP = 'After timestep'
```

Run at the end of each timestep.

#### NEVER

```python
NEVER = 'Never'
```

Never run.

### GenericSolver

Bases: `SolverBase`, `LinearSystem`, `NonlinearSystem`, `SteadyState`

Escape hatch: an arbitrary Elmer solver block for anything the typed hierarchy doesn't model yet — set `equation`/`procedure` and author the rest through `extra_keywords` (plus the shared typed knobs).

Fields:

- `steady_state_convergence_tolerance` (`SteadyStateConvToleranceField`)
- `nonlinear_system_convergence_tolerance` (`NonlinearConvToleranceField`)
- `nonlinear_system_max_iterations` (`NonlinearMaxIterationsField`)
- `nonlinear_system_newton_after_iterations` (`NonlinearNewtonAfterIterationsField`)
- `nonlinear_system_newton_after_tolerance` (`NonlinearNewtonAfterToleranceField`)
- `nonlinear_system_relaxation_factor` (`NonlinearRelaxationFactorField`)
- `nonlinear_system_convergence_measure` (`NonlinearConvMeasureField`)
- `linear_system_solver` (`LinearSolverTypeField`)
- `linear_system_direct_method` (`LinearDirectMethodField`)
- `linear_system_iterative_method` (`LinearIterativeMethodField`)
- `linear_system_max_iterations` (`LinearMaxIterationsField`)
- `linear_system_convergence_tolerance` (`LinearConvToleranceField`)
- `linear_system_preconditioning` (`LinearPreconditioningField`)
- `linear_system_residual_output` (`LinearResidualOutputField`)
- `linear_system_abort_not_converged` (`LinearAbortNotConvergedField`)
- `bicgstabl_polynomial_degree` (`BiCGStablPolynomialDegreeField`)
- `extra_keywords` (`dict[str, SifValue]`)
- `equation` (`EquationField`)
- `procedure` (`ProcedureField`)
- `variable` (`VariableField`)
- `variable_dofs` (`VariableDofsField`)
- `exec_solver` (`ExecSolverField`)
- `stabilize` (`StabilizeField`)
- `optimize_bandwidth` (`OptimizeBandwidthField`)
- `kind` (`Literal['Generic']`)

#### kind

```python
kind: Literal['Generic'] = 'Generic'
```

Discriminator that names this solver, doubling as its sif Solver label.

### HeatSolver

Bases: `SolverBase`, `LinearSystem`, `NonlinearSystem`, `SteadyState`

SCAFFOLD: steady heat conduction. Wire-ready; its boundary conditions land on the adjacency-derived surface groups via the condition-driven boundary loop.

Fields:

- `nonlinear_system_newton_after_iterations` (`NonlinearNewtonAfterIterationsField`)
- `nonlinear_system_newton_after_tolerance` (`NonlinearNewtonAfterToleranceField`)
- `nonlinear_system_relaxation_factor` (`NonlinearRelaxationFactorField`)
- `nonlinear_system_convergence_measure` (`NonlinearConvMeasureField`)
- `linear_system_iterative_method` (`LinearIterativeMethodField`)
- `linear_system_convergence_tolerance` (`LinearConvToleranceField`)
- `linear_system_residual_output` (`LinearResidualOutputField`)
- `linear_system_abort_not_converged` (`LinearAbortNotConvergedField`)
- `bicgstabl_polynomial_degree` (`BiCGStablPolynomialDegreeField`)
- `extra_keywords` (`dict[str, SifValue]`)
- `exec_solver` (`ExecSolverField`)
- `kind` (`Literal['HeatSolver']`)
- `equation` (`EquationField`)
- `procedure` (`ProcedureField`)
- `variable` (`VariableField`)
- `variable_dofs` (`VariableDofsField`)
- `stabilize` (`StabilizeField`)
- `optimize_bandwidth` (`OptimizeBandwidthField`)
- `linear_system_solver` (`LinearSolverTypeField`)
- `linear_system_direct_method` (`LinearDirectMethodField`)
- `steady_state_convergence_tolerance` (`SteadyStateConvToleranceField`)
- `nonlinear_system_convergence_tolerance` (`NonlinearConvToleranceField`)
- `nonlinear_system_max_iterations` (`NonlinearMaxIterationsField`)
- `linear_system_max_iterations` (`LinearMaxIterationsField`)
- `linear_system_preconditioning` (`LinearPreconditioningField`)

#### kind

```python
kind: Literal['HeatSolver'] = 'HeatSolver'
```

Discriminator that names this solver, doubling as its sif Solver label.

### IterativeMethod

Bases: `StrEnum`

The Krylov method for an iterative linear solve (`Linear System Iterative Method`).

Attributes:

- **`BICGSTAB`** –

  Biconjugate gradient stabilized.
- **`BICGSTABL`** –

  BiCGStab(l), of degree `bicgstabl_polynomial_degree`.
- **`CG`** –

  Conjugate gradient, for symmetric positive-definite systems.
- **`CGS`** –

  Conjugate gradient squared.
- **`GMRES`** –

  Generalized minimal residual.
- **`GCR`** –

  Generalized conjugate residual.
- **`TFQMR`** –

  Transpose-free quasi-minimal residual.

#### BICGSTAB

```python
BICGSTAB = 'BiCGStab'
```

Biconjugate gradient stabilized.

#### BICGSTABL

```python
BICGSTABL = 'BiCGStabl'
```

BiCGStab(l), of degree `bicgstabl_polynomial_degree`.

#### CG

```python
CG = 'CG'
```

Conjugate gradient, for symmetric positive-definite systems.

#### CGS

```python
CGS = 'CGS'
```

Conjugate gradient squared.

#### GMRES

```python
GMRES = 'GMRES'
```

Generalized minimal residual.

#### GCR

```python
GCR = 'GCR'
```

Generalized conjugate residual.

#### TFQMR

```python
TFQMR = 'TFQMR'
```

Transpose-free quasi-minimal residual.

### LinearSolverType

Bases: `StrEnum`

The `Linear System Solver` strategy: an iterative Krylov method or a direct factorization.

Attributes:

- **`ITERATIVE`** –

  A Krylov iteration, chosen by `linear_system_iterative_method`.
- **`DIRECT`** –

  A direct factorization, chosen by `linear_system_direct_method`.

#### ITERATIVE

```python
ITERATIVE = 'Iterative'
```

A Krylov iteration, chosen by `linear_system_iterative_method`.

#### DIRECT

```python
DIRECT = 'Direct'
```

A direct factorization, chosen by `linear_system_direct_method`.

### LinearSystem

Bases: `BaseModel`

Mixin: the `Linear System *` settings for a solver that does a linear solve. Defaults are sensible iterative values; concrete solvers tune the iteration cap and tolerances they need.

Config:

- `use_attribute_docstrings`: `True`

Fields:

- `linear_system_solver` (`LinearSolverTypeField`)
- `linear_system_direct_method` (`LinearDirectMethodField`)
- `linear_system_iterative_method` (`LinearIterativeMethodField`)
- `linear_system_max_iterations` (`LinearMaxIterationsField`)
- `linear_system_convergence_tolerance` (`LinearConvToleranceField`)
- `linear_system_preconditioning` (`LinearPreconditioningField`)
- `linear_system_residual_output` (`LinearResidualOutputField`)
- `linear_system_abort_not_converged` (`LinearAbortNotConvergedField`)
- `bicgstabl_polynomial_degree` (`BiCGStablPolynomialDegreeField`)

### MagnetoDynamics2DSolver

Bases: `SolverBase`, `LinearSystem`, `NonlinearSystem`, `SteadyState`

Axisymmetric magnetostatics (MagnetoDynamics2D): the scalar azimuthal potential A\_phi under `Coordinate System = Axi Symmetric`.

The mesh lies flat in the XY plane (Z = 0), with X the radial distance from the symmetry axis (X \>= 0) and Y the position along it.

Fields:

- `nonlinear_system_newton_after_iterations` (`NonlinearNewtonAfterIterationsField`)
- `nonlinear_system_newton_after_tolerance` (`NonlinearNewtonAfterToleranceField`)
- `linear_system_iterative_method` (`LinearIterativeMethodField`)
- `linear_system_max_iterations` (`LinearMaxIterationsField`)
- `linear_system_convergence_tolerance` (`LinearConvToleranceField`)
- `linear_system_preconditioning` (`LinearPreconditioningField`)
- `linear_system_residual_output` (`LinearResidualOutputField`)
- `linear_system_abort_not_converged` (`LinearAbortNotConvergedField`)
- `bicgstabl_polynomial_degree` (`BiCGStablPolynomialDegreeField`)
- `extra_keywords` (`dict[str, SifValue]`)
- `exec_solver` (`ExecSolverField`)
- `stabilize` (`StabilizeField`)
- `kind` (`Literal['MagnetoDynamics2D']`)
- `equation` (`EquationField`)
- `procedure` (`ProcedureField`)
- `variable` (`VariableField`)
- `variable_dofs` (`VariableDofsField`)
- `optimize_bandwidth` (`OptimizeBandwidthField`)
- `nonlinear_system_max_iterations` (`NonlinearMaxIterationsField`)
- `nonlinear_system_convergence_tolerance` (`NonlinearConvToleranceField`)
- `nonlinear_system_convergence_measure` (`NonlinearConvMeasureField`)
- `nonlinear_system_relaxation_factor` (`NonlinearRelaxationFactorField`)
- `linear_system_solver` (`LinearSolverTypeField`)
- `linear_system_direct_method` (`LinearDirectMethodField`)
- `steady_state_convergence_tolerance` (`SteadyStateConvToleranceField`)

#### kind

```python
kind: Literal['MagnetoDynamics2D'] = 'MagnetoDynamics2D'
```

Discriminator that names this solver, doubling as its sif Solver label.

### MagnetoDynamicsCalcFieldsSolver

Bases: `SolverBase`, `LinearSystem`

Post-processing: recover B, H, fluxes and nodal forces from the computed A. Inherits `Linear System *` because nodal-field recovery solves an (SPD, hence CG) mass-matrix system — without a linear solver Elmer aborts.

Fields:

- `linear_system_solver` (`LinearSolverTypeField`)
- `linear_system_direct_method` (`LinearDirectMethodField`)
- `linear_system_convergence_tolerance` (`LinearConvToleranceField`)
- `linear_system_preconditioning` (`LinearPreconditioningField`)
- `linear_system_residual_output` (`LinearResidualOutputField`)
- `linear_system_abort_not_converged` (`LinearAbortNotConvergedField`)
- `bicgstabl_polynomial_degree` (`BiCGStablPolynomialDegreeField`)
- `extra_keywords` (`dict[str, SifValue]`)
- `variable` (`VariableField`)
- `variable_dofs` (`VariableDofsField`)
- `exec_solver` (`ExecSolverField`)
- `stabilize` (`StabilizeField`)
- `kind` (`Literal['MagnetoDynamicsCalcFields']`)
- `equation` (`EquationField`)
- `procedure` (`ProcedureField`)
- `optimize_bandwidth` (`OptimizeBandwidthField`)
- `linear_system_iterative_method` (`LinearIterativeMethodField`)
- `linear_system_max_iterations` (`LinearMaxIterationsField`)
- `potential_variable` (`str | None`)
- `average_within_materials` (`bool | None`)
- `calculate_magnetic_field_strength` (`bool`)
- `calculate_magnetic_flux_density` (`bool`)
- `calculate_nodal_forces` (`bool`)
- `calculate_nodal_fields` (`bool`)
- `calculate_joule_heating` (`bool | None`)
- `calculate_electric_field` (`bool | None`)
- `calculate_magnetic_vector_potential` (`bool | None`)
- `separate_magnetic_energy` (`bool | None`)
- `calculate_relative_permeability` (`bool | None`)
- `calculate_elemental_fields` (`bool`)

#### kind

```python
kind: Literal['MagnetoDynamicsCalcFields'] = 'MagnetoDynamicsCalcFields'
```

Discriminator that names this solver, doubling as its sif Solver label.

#### potential\_variable

```python
potential_variable: str | None = None
```

The vector-potential variable to post-process; None lets the solver pick up the (default-named) MgDyn variable.

#### average\_within\_materials

```python
average_within_materials: bool | None = True
```

Average recovered fields within each material so the B/H jump at a magnet surface is resolved cleanly.

#### calculate\_magnetic\_field\_strength

```python
calculate_magnetic_field_strength: bool = True
```

Recover H.

#### calculate\_magnetic\_flux\_density

```python
calculate_magnetic_flux_density: bool = True
```

Recover B.

#### calculate\_nodal\_forces

```python
calculate_nodal_forces: bool = True
```

Recover nodal forces (drives the motor-force optimisation).

#### calculate\_nodal\_fields

```python
calculate_nodal_fields: bool = False
```

Recover nodal field values.

#### calculate\_joule\_heating

```python
calculate_joule_heating: bool | None = None
```

Recover the eddy/Joule loss field. Identically zero for a steady real-valued solve — set False there: the zero-RHS projection it triggers when a Current Density body force is present segfaults CalcFields.

#### calculate\_electric\_field

```python
calculate_electric_field: bool | None = None
```

Recover E = -dA/dt - grad V. Identically zero at steady state — set False for the chained coil solve, where its zero-RHS projection is what segfaults CalcFields after the B components.

#### calculate\_magnetic\_vector\_potential

```python
calculate_magnetic_vector_potential: bool | None = None
```

Project the edge-element av onto a nodal A field; None keeps the solver default.

#### separate\_magnetic\_energy

```python
separate_magnetic_energy: bool | None = None
```

Export the magnetic field energy as SaveScalars-visible scalars (the ElmerCSC mgdyn\_bh recipe) — the energy-method inductance observable: L\_eff = 2W/I².

#### calculate\_relative\_permeability

```python
calculate_relative_permeability: bool | None = None
```

Recover the local relative permeability; the mgdyn\_bh energy recipe enables it alongside the energy export.

#### calculate\_elemental\_fields

```python
calculate_elemental_fields: bool = True
```

Recover elemental field values.

### MagnetoDynamicsSolver

Bases: `SolverBase`, `LinearSystem`, `NonlinearSystem`, `SteadyState`

3D magnetostatics (WhitneyAVSolver), with each magnet's drive entering as `Magnetization` on its material rather than as a body force.

Leave `variable` unset, so the far-field boundary condition can still name `AV {e}`.

Fields:

- `steady_state_convergence_tolerance` (`SteadyStateConvToleranceField`)
- `nonlinear_system_convergence_measure` (`NonlinearConvMeasureField`)
- `linear_system_solver` (`LinearSolverTypeField`)
- `linear_system_direct_method` (`LinearDirectMethodField`)
- `extra_keywords` (`dict[str, SifValue]`)
- `variable` (`VariableField`)
- `variable_dofs` (`VariableDofsField`)
- `exec_solver` (`ExecSolverField`)
- `stabilize` (`StabilizeField`)
- `kind` (`Literal['MagnetoDynamics']`)
- `equation` (`EquationField`)
- `procedure` (`ProcedureField`)
- `optimize_bandwidth` (`OptimizeBandwidthField`)
- `nonlinear_system_convergence_tolerance` (`NonlinearConvToleranceField`)
- `nonlinear_system_max_iterations` (`NonlinearMaxIterationsField`)
- `nonlinear_system_newton_after_iterations` (`NonlinearNewtonAfterIterationsField`)
- `nonlinear_system_newton_after_tolerance` (`NonlinearNewtonAfterToleranceField`)
- `nonlinear_system_relaxation_factor` (`NonlinearRelaxationFactorField`)
- `linear_system_iterative_method` (`LinearIterativeMethodField`)
- `bicgstabl_polynomial_degree` (`BiCGStablPolynomialDegreeField`)
- `linear_system_preconditioning` (`LinearPreconditioningField`)
- `linear_system_max_iterations` (`LinearMaxIterationsField`)
- `linear_system_convergence_tolerance` (`LinearConvToleranceField`)
- `linear_system_abort_not_converged` (`LinearAbortNotConvergedField`)
- `linear_system_residual_output` (`LinearResidualOutputField`)
- `fix_input_current_density` (`bool | None`)
- `use_elemental_coilcurrent` (`bool | None`)

#### kind

```python
kind: Literal['MagnetoDynamics'] = 'MagnetoDynamics'
```

Discriminator that names this solver, doubling as its sif Solver label.

#### fix\_input\_current\_density

```python
fix_input_current_density: bool | None = None
```

Project the source J onto a divergence-free field before the AV solve: the discrete J is not exactly solenoidal edge-element-wise, and an unfixed source makes the gauge-non-unique system inconsistent. None omits the keyword.

#### use\_elemental\_coilcurrent

```python
use_elemental_coilcurrent: bool | None = None
```

Read the source J from CoilSolver's *elemental* (DG) field instead of a nodal Body Force: nodal J is Galerkin-averaged across the conductor-air interface and silently dilutes the drive \~2x on thin traces. Requires a CoilSolver in the stack; None omits the keyword.

### NonlinearSystem

Bases: `BaseModel`

Mixin: the `Nonlinear System *` (outer-iteration) convergence settings.

Fields:

- `nonlinear_system_convergence_tolerance` (`NonlinearConvToleranceField`)
- `nonlinear_system_max_iterations` (`NonlinearMaxIterationsField`)
- `nonlinear_system_newton_after_iterations` (`NonlinearNewtonAfterIterationsField`)
- `nonlinear_system_newton_after_tolerance` (`NonlinearNewtonAfterToleranceField`)
- `nonlinear_system_relaxation_factor` (`NonlinearRelaxationFactorField`)
- `nonlinear_system_convergence_measure` (`NonlinearConvMeasureField`)

### OperatingPoint

Bases: `BaseModel`

The point at which a material's property functions are evaluated when stripping to Elmer's bare SI floats. `Static` properties ignore it; temperature-dependent properties consume it via `as_mapping`.

Config:

- `arbitrary_types_allowed`: `True`
- `validate_assignment`: `True`
- `use_attribute_docstrings`: `True`

Fields:

- `temperature` (`Temperature`)

#### temperature

```python
temperature: Temperature
```

`T` `[K]`: Evaluation temperature for material property functions.

#### as\_mapping

```python
as_mapping() -> dict[str, Quantity]
```

The ephemeral name-\>quantity mapping the property functions consume (`prop(**at)`).

### PhysicsConfig

Bases: `BaseModel`

The fully populated, typed solver stack for one physics: ordered solvers plus simulation settings, activated constants and operating point.

Config:

- `arbitrary_types_allowed`: `True`
- `validate_assignment`: `True`
- `use_attribute_docstrings`: `True`

Fields:

- `solvers` (`list[SolverUnion]`)
- `simulation` (`SimulationSettings`)
- `constants` (`Constants`)
- `operating_point` (`OperatingPoint`)

#### solvers

```python
solvers: list[SolverUnion]
```

The ordered concrete solver instances Elmer runs for this physics.

#### simulation

```python
simulation: SimulationSettings
```

The Elmer Simulation block (run type, coordinate system, output files).

#### constants

```python
constants: Constants
```

The physical constants this physics activates.

#### operating\_point

```python
operating_point: OperatingPoint
```

Where material property functions are evaluated when stripping to SI.

#### solver

```python
solver(kind: type[SolverT]) -> SolverT
```

The live solver instance of type `kind` in this stack; raises `KeyError` when it has none.

#### check\_frame

```python
check_frame(frame: Frame) -> None
```

Raise when this stack's coordinate system disagrees with `frame`: per-radian exactly when the frame is axisymmetric.

### PhysicsSelection

Bases: `ConfigArea`

Which physics the Elmer stage solves; `SimulationConfig` derives the solved stack from this selection and its frame.

Fields:

- `selected` (`Physics`)
- `overrides` (`PhysicsConfig | None`)

Validators:

- `selectable` → `selected`

#### selected

```python
selected: Physics = Physics.MAGNETOSTATICS
```

The physics as authored (magnetostatics / thermal / ...); the config's frame decides the form it solves as.

#### overrides

```python
overrides: PhysicsConfig | None = None
```

The authored solver stack for this run (see `SimulationConfig.customize_physics`); `None` solves the preset. Part of the config, so authored settings feed the run identity (`cache_key`) and the manifest.

#### selectable

```python
selectable(selected: Physics) -> Physics
```

Reject the internal axisymmetric stack.

#### check\_frame

```python
check_frame(frame: Frame) -> None
```

Raise when `selected` has no form under `frame` or authored `overrides` disagree with it.

### Preconditioning

Bases: `StrEnum`

The `Linear System Preconditioning` for an iterative solve.

Attributes:

- **`NONE`** –

  No preconditioning.
- **`DIAGONAL`** –

  Diagonal (Jacobi) scaling.
- **`ILU0`** –

  Incomplete LU without fill-in.
- **`ILU1`** –

  Incomplete LU with fill level 1.
- **`ILU2`** –

  Incomplete LU with fill level 2.
- **`ILU`** –

  Incomplete LU without an explicit fill level.
- **`ILUT`** –

  Incomplete LU with threshold dropping.

#### NONE

```python
NONE = 'None'
```

No preconditioning.

#### DIAGONAL

```python
DIAGONAL = 'Diagonal'
```

Diagonal (Jacobi) scaling.

#### ILU0

```python
ILU0 = 'ILU0'
```

Incomplete LU without fill-in.

#### ILU1

```python
ILU1 = 'ILU1'
```

Incomplete LU with fill level 1.

#### ILU2

```python
ILU2 = 'ILU2'
```

Incomplete LU with fill level 2.

#### ILU

```python
ILU = 'ILU'
```

Incomplete LU without an explicit fill level.

#### ILUT

```python
ILUT = 'ILUT'
```

Incomplete LU with threshold dropping.

### ResultOutputSolver

Bases: `SolverBase`

Write VTU output for ParaView.

Fields:

- `extra_keywords` (`dict[str, SifValue]`)
- `variable` (`VariableField`)
- `variable_dofs` (`VariableDofsField`)
- `stabilize` (`StabilizeField`)
- `optimize_bandwidth` (`OptimizeBandwidthField`)
- `kind` (`Literal['ResultOutputSolver']`)
- `equation` (`EquationField`)
- `procedure` (`ProcedureField`)
- `exec_solver` (`ExecSolverField`)
- `vtu_format` (`bool`)
- `output_file_name` (`str`)
- `save_geometry_ids` (`bool`)
- `saved_scalar_fields` (`list[str] | None`)
- `saved_vector_fields` (`list[str] | None`)
- `discontinuous_bodies` (`bool | None`)

#### kind

```python
kind: Literal['ResultOutputSolver'] = 'ResultOutputSolver'
```

Discriminator that names this solver, doubling as its sif Solver label.

#### vtu\_format

```python
vtu_format: bool = True
```

Write VTU (ParaView) output.

#### output\_file\_name

```python
output_file_name: str = 'File "case"'
```

Stem of the written VTU (Elmer emits e.g. `case_t0001.vtu`); pinned so the name never depends on Elmer defaults.

#### save\_geometry\_ids

```python
save_geometry_ids: bool = True
```

Tag output cells with their gmsh geometry ids.

#### saved\_scalar\_fields

```python
saved_scalar_fields: list[str] | None = None
```

Scalar fields to save (each a full sif RHS, e.g. `'String "joule heating"'`); None saves every active variable.

#### saved\_vector\_fields

```python
saved_vector_fields: list[str] | None = None
```

Vector fields to save; None saves every active variable.

#### discontinuous\_bodies

```python
discontinuous_bodies: bool | None = None
```

Average elemental fields body-wise onto a minimal discontinuous node set, so the VTU keeps cross-body discontinuities.

### SaveScalarsSolver

Bases: `SolverBase`

SaveScalars (SaveData): export scalar reductions to `scalars.dat` and its `.names` sidecar.

Its `diffusive flux` operator integrates each `Save Scalars = True` electrode's terminal current, the `I` in `R = ΔV / I`.

Fields:

- `extra_keywords` (`dict[str, SifValue]`)
- `variable` (`VariableField`)
- `variable_dofs` (`VariableDofsField`)
- `stabilize` (`StabilizeField`)
- `optimize_bandwidth` (`OptimizeBandwidthField`)
- `kind` (`Literal['SaveScalars']`)
- `equation` (`EquationField`)
- `procedure` (`ProcedureField`)
- `exec_solver` (`ExecSolverField`)
- `save_component_results` (`bool | None`)
- `parallel_reduce` (`bool | None`)
- `filename` (`str`)
- `variable_1` (`str | None`)
- `operator_1` (`str | None`)
- `coefficient_1` (`str | None`)
- `variable_2` (`str | None`)
- `operator_2` (`str | None`)
- `variable_3` (`str | None`)
- `operator_3` (`str | None`)

#### kind

```python
kind: Literal['SaveScalars'] = 'SaveScalars'
```

Discriminator that names this solver, doubling as its sif Solver label.

#### save\_component\_results

```python
save_component_results: bool | None = None
```

Also export the scalars arising from Component sections.

#### parallel\_reduce

```python
parallel_reduce: bool | None = None
```

Reduce a partitioned run's output to one `scalars.dat` instead of per-rank `.dat.<rank>` files.

#### filename

```python
filename: str = '"scalars.dat"'
```

The .dat the scalars land in (the run parser reads it, or its per-rank `.dat.<rank>` siblings, via the `.names` sidecar).

#### variable\_1

```python
variable_1: str | None = ElmerVariable.POTENTIAL
```

The variable the operator reduces.

#### operator\_1

```python
operator_1: str | None = 'diffusive flux'
```

The reduction: boundary integral of the conjugate flux of `variable_1`.

#### coefficient\_1

```python
coefficient_1: str | None = 'Electric Conductivity'
```

The material coefficient weighting the flux (σ ∂φ/∂n integrates to a current).

#### variable\_2

```python
variable_2: str | None = ElmerVariable.JOULE_HEATING
```

Second reduction: the Joule loss field (requires `Calculate Joule Heating`).

#### operator\_2

```python
operator_2: str | None = 'int'
```

Volume integral: total dissipated power P (W), the flux-independent cross-check R = ΔV²/P.

#### variable\_3

```python
variable_3: str | None = None
```

Third reduction; the coupled joule-heating stack sets `Temperature` here.

#### operator\_3

```python
operator_3: str | None = None
```

Reduction for `variable_3`; the coupled stack uses `max` (peak temperature).

### SimulationSettings

Bases: `SifKeywordModel`

The Elmer `Simulation` block. Defaults are a 3D Cartesian steady-state run, used for magnetostatics and steady thermal.

Fields:

- `extra_keywords` (`dict[str, SifValue]`)
- `max_output_level` (`int`)
- `coordinate_system` (`CoordinateSystem`)
- `coordinate_mapping` (`str`)
- `simulation_type` (`SimulationType`)
- `steady_state_max_iterations` (`int`)
- `output_intervals` (`int`)
- `timestepping_method` (`TimesteppingMethod`)
- `bdf_order` (`int`)
- `solver_input_file` (`str`)
- `post_file` (`str | None`)
- `output_file` (`str | None`)

#### max\_output\_level

```python
max_output_level: int = 5
```

Verbosity of ElmerSolver's console output (0-quiet … higher-noisier).

#### coordinate\_system

```python
coordinate_system: CoordinateSystem = CoordinateSystem.CARTESIAN
```

The simulation coordinate system.

#### coordinate\_mapping

```python
coordinate_mapping: str = '1 2 3'
```

Axis permutation for the coordinate system.

#### simulation\_type

```python
simulation_type: SimulationType = SimulationType.STEADY_STATE
```

Steady-state, transient, or scanning run.

#### steady\_state\_max\_iterations

```python
steady_state_max_iterations: int = 1
```

Max steady-state (coupled-system) iterations.

#### output\_intervals

```python
output_intervals: int = 1
```

Write results every N steps.

#### timestepping\_method

```python
timestepping_method: TimesteppingMethod = TimesteppingMethod.BDF
```

Time integration scheme.

#### bdf\_order

```python
bdf_order: int = 1
```

Order of the BDF timestepping method.

#### solver\_input\_file

```python
solver_input_file: str = 'case.sif'
```

Name of the generated .sif.

#### post\_file

```python
post_file: str | None = None
```

ParaView/VTU post-processing output file; None (the default) leaves VTU output to the ResultOutputSolver — setting both trips Elmer's AddVtuOutputSolverHack and its log spam.

#### output\_file

```python
output_file: str | None = None
```

ElmerSolver restart/result file; None (the default) skips it — nothing downstream reads it and it grows to multi-GB on large solves. Set a name only when a restart is wanted.

### SimulationType

Bases: `StrEnum`

Elmer's `Simulation Type`.

Attributes:

- **`STEADY_STATE`** –

  A steady-state solve.
- **`TRANSIENT`** –

  A time-dependent solve.
- **`SCANNING`** –

  A sweep of steady-state solves over a scanned parameter.

#### STEADY\_STATE

```python
STEADY_STATE = 'Steady state'
```

A steady-state solve.

#### TRANSIENT

```python
TRANSIENT = 'Transient'
```

A time-dependent solve.

#### SCANNING

```python
SCANNING = 'Scanning'
```

A sweep of steady-state solves over a scanned parameter.

### SolverBase

Bases: `SifKeywordModel`

Base for a typed Elmer solver: its fields are its sif keywords.

Fields:

- `extra_keywords` (`dict[str, SifValue]`)
- `kind` (`str`)
- `equation` (`EquationField`)
- `procedure` (`ProcedureField`)
- `variable` (`VariableField`)
- `variable_dofs` (`VariableDofsField`)
- `exec_solver` (`ExecSolverField`)
- `stabilize` (`StabilizeField`)
- `optimize_bandwidth` (`OptimizeBandwidthField`)

#### kind

```python
kind: str
```

Discriminator that names this solver, doubling as its sif Solver label.

#### required\_material\_keys

```python
required_material_keys: frozenset[ElmerMaterialKeyword] = frozenset()
```

The material keywords every body this solver acts on must supply, checked by `Sim.validate`.

#### conductor\_only

```python
conductor_only: bool = False
```

Restrict this solver's equation to conductor bodies: solving a conduction variable over dielectrics too puts \~21 decades of σ contrast into one matrix — singular for a direct factorization, a stall for preconditioned CG.

### StatCurrentSolver

Bases: `SolverBase`, `LinearSystem`, `NonlinearSystem`, `SteadyState`

Static current conduction (Elmer's StatCurrentSolve): solves `Potential` from ∇·(σ∇φ) = 0, excited by per-phase `FixedPotential` terminal pairs. The nonlinear block is inert while σ is Static.

Fields:

- `nonlinear_system_newton_after_iterations` (`NonlinearNewtonAfterIterationsField`)
- `nonlinear_system_newton_after_tolerance` (`NonlinearNewtonAfterToleranceField`)
- `nonlinear_system_relaxation_factor` (`NonlinearRelaxationFactorField`)
- `nonlinear_system_convergence_measure` (`NonlinearConvMeasureField`)
- `linear_system_preconditioning` (`LinearPreconditioningField`)
- `linear_system_residual_output` (`LinearResidualOutputField`)
- `linear_system_abort_not_converged` (`LinearAbortNotConvergedField`)
- `bicgstabl_polynomial_degree` (`BiCGStablPolynomialDegreeField`)
- `extra_keywords` (`dict[str, SifValue]`)
- `exec_solver` (`ExecSolverField`)
- `kind` (`Literal['StatCurrent']`)
- `equation` (`EquationField`)
- `procedure` (`ProcedureField`)
- `variable` (`VariableField`)
- `variable_dofs` (`VariableDofsField`)
- `stabilize` (`StabilizeField`)
- `optimize_bandwidth` (`OptimizeBandwidthField`)
- `steady_state_convergence_tolerance` (`SteadyStateConvToleranceField`)
- `nonlinear_system_convergence_tolerance` (`NonlinearConvToleranceField`)
- `nonlinear_system_max_iterations` (`NonlinearMaxIterationsField`)
- `linear_system_solver` (`LinearSolverTypeField`)
- `linear_system_direct_method` (`LinearDirectMethodField`)
- `linear_system_iterative_method` (`LinearIterativeMethodField`)
- `linear_system_max_iterations` (`LinearMaxIterationsField`)
- `linear_system_convergence_tolerance` (`LinearConvToleranceField`)
- `calculate_volume_current` (`bool`)
- `calculate_joule_heating` (`bool`)

#### kind

```python
kind: Literal['StatCurrent'] = 'StatCurrent'
```

Discriminator that names this solver, doubling as its sif Solver label.

#### calculate\_volume\_current

```python
calculate_volume_current: bool = True
```

Recover the volume current density J = -σ∇φ.

#### calculate\_joule\_heating

```python
calculate_joule_heating: bool = True
```

Recover the Joule loss density σ|∇φ|² (the heat-solve coupling source).

### SteadyState

Bases: `BaseModel`

Mixin: the `Steady State Convergence Tolerance` for a steady-state run.

Fields:

- `steady_state_convergence_tolerance` (`SteadyStateConvToleranceField`)

### StressSolver

Bases: `SolverBase`, `LinearSystem`, `NonlinearSystem`, `SteadyState`

SCAFFOLD: linear elasticity (Elmer's StressSolve). Consumes `Youngs Modulus` and `Poisson Ratio` material keywords; the latter is not yet stored on MechanicalProperties.

Fields:

- `nonlinear_system_newton_after_iterations` (`NonlinearNewtonAfterIterationsField`)
- `nonlinear_system_newton_after_tolerance` (`NonlinearNewtonAfterToleranceField`)
- `nonlinear_system_relaxation_factor` (`NonlinearRelaxationFactorField`)
- `nonlinear_system_convergence_measure` (`NonlinearConvMeasureField`)
- `linear_system_solver` (`LinearSolverTypeField`)
- `linear_system_direct_method` (`LinearDirectMethodField`)
- `linear_system_iterative_method` (`LinearIterativeMethodField`)
- `linear_system_convergence_tolerance` (`LinearConvToleranceField`)
- `linear_system_preconditioning` (`LinearPreconditioningField`)
- `linear_system_residual_output` (`LinearResidualOutputField`)
- `linear_system_abort_not_converged` (`LinearAbortNotConvergedField`)
- `bicgstabl_polynomial_degree` (`BiCGStablPolynomialDegreeField`)
- `extra_keywords` (`dict[str, SifValue]`)
- `exec_solver` (`ExecSolverField`)
- `stabilize` (`StabilizeField`)
- `kind` (`Literal['StressSolver']`)
- `equation` (`EquationField`)
- `procedure` (`ProcedureField`)
- `variable` (`VariableField`)
- `variable_dofs` (`VariableDofsField`)
- `optimize_bandwidth` (`OptimizeBandwidthField`)
- `steady_state_convergence_tolerance` (`SteadyStateConvToleranceField`)
- `nonlinear_system_convergence_tolerance` (`NonlinearConvToleranceField`)
- `nonlinear_system_max_iterations` (`NonlinearMaxIterationsField`)
- `linear_system_max_iterations` (`LinearMaxIterationsField`)
- `calculate_stresses` (`bool`)

#### kind

```python
kind: Literal['StressSolver'] = 'StressSolver'
```

Discriminator that names this solver, doubling as its sif Solver label.

#### calculate\_stresses

```python
calculate_stresses: bool = True
```

Recover the stress tensor from the displacement.

### TimesteppingMethod

Bases: `StrEnum`

Elmer's `Timestepping Method` (paired with `BDF Order` for BDF).

Attributes:

- **`BDF`** –

  Backward differentiation formula, of order `bdf_order`.
- **`NEWMARK`** –

  The Newmark-beta method.
- **`RUNGE_KUTTA`** –

  A Runge-Kutta method.

#### BDF

```python
BDF = 'BDF'
```

Backward differentiation formula, of order `bdf_order`.

#### NEWMARK

```python
NEWMARK = 'Newmark'
```

The Newmark-beta method.

#### RUNGE\_KUTTA

```python
RUNGE_KUTTA = 'Runge-Kutta'
```

A Runge-Kutta method.

### MeshNames

```python
MeshNames(bodies: dict[str, int], boundaries: dict[str, int])
```

The `name -> Elmer id` maps ElmerGrid records in `mesh.names`, one per section (one continuing id sequence: boundary ids start after the body ids).

Attributes:

- **`bodies`** (`dict[str, int]`) –

  Body name to Elmer body id.
- **`boundaries`** (`dict[str, int]`) –

  Boundary name to Elmer boundary id.

#### bodies

```python
bodies: dict[str, int]
```

Body name to Elmer body id.

#### boundaries

```python
boundaries: dict[str, int]
```

Boundary name to Elmer boundary id.

### Physics

Bases: `StrEnum`

Which physics preset the sif writer wires; the string values double as the serialized spelling.

Methods:

- **`in_frame`** –

  The physics this selection solves as under `frame`; raises when it has no form there.

Attributes:

- **`MAGNETOSTATICS`** –

  The static magnetic field; it solves as `MAGNETOSTATICS_2D` under `frame=Axisymmetric()`.
- **`MAGNETOSTATICS_2D`** –

  Axisymmetric magnetostatics on the r–z half-plane with MagnetoDynamics2D, which also takes `CurrentDensity` coil drives.
- **`THERMAL`** –

  Steady-state heat conduction.
- **`ELECTROSTATICS`** –

  The electrostatic potential and field.
- **`CURRENT_CONDUCTION`** –

  Static current conduction: the potential-driven current density through the conductors.
- **`JOULE_HEATING`** –

  Current conduction coupled to heat conduction: the steady temperature its Joule losses produce.
- **`COIL_MAGNETOSTATICS`** –

  Current conduction chained into magnetostatics: the field of a coil's own solved current distribution.
- **`LINEAR_ELASTICITY`** –

  Linear elastic stress and strain.

#### MAGNETOSTATICS

```python
MAGNETOSTATICS = 'magnetostatics'
```

The static magnetic field; it solves as `MAGNETOSTATICS_2D` under `frame=Axisymmetric()`.

#### MAGNETOSTATICS\_2D

```python
MAGNETOSTATICS_2D = 'magnetostatics_2d'
```

Axisymmetric magnetostatics on the r–z half-plane with MagnetoDynamics2D, which also takes `CurrentDensity` coil drives.

#### THERMAL

```python
THERMAL = 'thermal'
```

Steady-state heat conduction.

#### ELECTROSTATICS

```python
ELECTROSTATICS = 'electrostatics'
```

The electrostatic potential and field.

#### CURRENT\_CONDUCTION

```python
CURRENT_CONDUCTION = 'current_conduction'
```

Static current conduction: the potential-driven current density through the conductors.

#### JOULE\_HEATING

```python
JOULE_HEATING = 'joule_heating'
```

Current conduction coupled to heat conduction: the steady temperature its Joule losses produce.

#### COIL\_MAGNETOSTATICS

```python
COIL_MAGNETOSTATICS = 'coil_magnetostatics'
```

Current conduction chained into magnetostatics: the field of a coil's own solved current distribution.

#### LINEAR\_ELASTICITY

```python
LINEAR_ELASTICITY = 'linear_elasticity'
```

Linear elastic stress and strain.

#### in\_frame

```python
in_frame(frame: Frame) -> Physics
```

The physics this selection solves as under `frame`; raises when it has no form there.

### ExecutionBackend

Bases: `Protocol`

Runs the Elmer tools over local directories; a remote backend uploads from and downloads into the same paths.

Methods:

- **`grid_convert`** –

  Convert the gmsh mesh `mesh_dir/msh_name` into Elmer mesh files plus `elmergrid.log` in `out_dir`; raises `CommandError` on failure.
- **`grid_partition`** –

  Split the Elmer mesh dir `mesh_dir` into `partitioning.<n_parts>` inside it; raises `CommandError` on failure.
- **`solve`** –

  Run ElmerSolver on the case in `sim_dir`, reading only inside it; a failed solve returns its nonzero exit instead of raising.

Attributes:

- **`partitioned`** (`bool`) –

  Whether a solve with more than one MPI rank is trustworthy here.

#### partitioned

```python
partitioned: bool
```

Whether a solve with more than one MPI rank is trustworthy here.

#### grid\_convert

```python
grid_convert(mesh_dir: Path, msh_name: str, *, out_dir: Path, scale: float | None = None, autoclean: bool = False, keep_mesh_dir: bool = False, on_line: LineSink | None = None, timeout_s: float | None = None, cancel: CancelToken | None = None) -> CommandResult
```

Convert the gmsh mesh `mesh_dir/msh_name` into Elmer mesh files plus `elmergrid.log` in `out_dir`; raises `CommandError` on failure.

Parameters:

- **`mesh_dir`** (`Path`) –

  The directory holding the `.msh`.
- **`msh_name`** (`str`) –

  The `.msh` file name.
- **`out_dir`** (`Path`) –

  Where the mesh files and the log land.
- **`scale`** (`float | None`, default: `None` ) –

  A factor applied to every coordinate; None keeps them.
- **`autoclean`** (`bool`, default: `False` ) –

  Run ElmerGrid's `-autoclean` pass.
- **`keep_mesh_dir`** (`bool`, default: `False` ) –

  Keep the mesh files in ElmerGrid's `<stem>/` dir instead of moving them into `out_dir`.
- **`on_line`** (`LineSink | None`, default: `None` ) –

  Receives each output line.
- **`timeout_s`** (`float | None`, default: `None` ) –

  Seconds before the conversion is stopped; None waits for it.
- **`cancel`** (`CancelToken | None`, default: `None` ) –

  Stops the conversion once it is set.

#### grid\_partition

```python
grid_partition(mesh_dir: Path, n_parts: int, *, halobc: bool, on_line: LineSink | None = None, timeout_s: float | None = None, cancel: CancelToken | None = None) -> CommandResult
```

Split the Elmer mesh dir `mesh_dir` into `partitioning.<n_parts>` inside it; raises `CommandError` on failure.

Parameters:

- **`mesh_dir`** (`Path`) –

  The Elmer mesh dir itself; a backend that addresses it by name needs that name dot-free.
- **`n_parts`** (`int`) –

  The partition count.
- **`halobc`** (`bool`) –

  Add the halo elements that boundary-terminated coil electrodes need.
- **`on_line`** (`LineSink | None`, default: `None` ) –

  Receives each output line.
- **`timeout_s`** (`float | None`, default: `None` ) –

  Seconds before the partitioning is stopped; None waits for it.
- **`cancel`** (`CancelToken | None`, default: `None` ) –

  Stops the partitioning once it is set.

#### solve

```python
solve(sim_dir: Path, *, n_procs: int = 1, on_line: LineSink | None = None, timeout_s: float | None = None, cancel: CancelToken | None = None) -> CommandResult
```

Run ElmerSolver on the case in `sim_dir`, reading only inside it; a failed solve returns its nonzero exit instead of raising.

Parameters:

- **`sim_dir`** (`Path`) –

  The case directory, which receives `elmersolver.log` and the solver's outputs.
- **`n_procs`** (`int`, default: `1` ) –

  The MPI rank count.
- **`on_line`** (`LineSink | None`, default: `None` ) –

  Receives each output line.
- **`timeout_s`** (`float | None`, default: `None` ) –

  Seconds before the solve is stopped; None waits for it.
- **`cancel`** (`CancelToken | None`, default: `None` ) –

  Stops the solve once it is set.

### DockerElmer

```python
DockerElmer(image: DockerImage | None = None, *, docker: DockerCli | None = None)
```

Bases: `ExecutionBackend`

Runs Elmer in `image`, femforge's Elmer image by default, built on first use; partitioned solves use the image's MUMPS.

Methods:

- **`grid_convert`** –

  Convert the gmsh mesh `mesh_dir/msh_name` in a container into Elmer mesh files plus `elmergrid.log` in `out_dir`.
- **`grid_partition`** –

  Split the Elmer mesh dir `mesh_dir` into `partitioning.<n_parts>` inside it, in a container.
- **`solve`** –

  Run ElmerSolver on the case in `sim_dir` in a container, under `mpirun` when `n_procs` exceeds one.

Attributes:

- **`partitioned`** (`bool`) –

  True: the image's MUMPS makes a multi-rank solve trustworthy.

#### partitioned

```python
partitioned: bool
```

True: the image's MUMPS makes a multi-rank solve trustworthy.

#### grid\_convert

```python
grid_convert(mesh_dir: Path, msh_name: str, *, out_dir: Path, scale: float | None = None, autoclean: bool = False, keep_mesh_dir: bool = False, on_line: LineSink | None = None, timeout_s: float | None = None, cancel: CancelToken | None = None) -> CommandResult
```

Convert the gmsh mesh `mesh_dir/msh_name` in a container into Elmer mesh files plus `elmergrid.log` in `out_dir`.

Parameters:

- **`mesh_dir`** (`Path`) –

  The directory holding the `.msh`.
- **`msh_name`** (`str`) –

  The `.msh` file name.
- **`out_dir`** (`Path`) –

  Where the mesh files and the log land.
- **`scale`** (`float | None`, default: `None` ) –

  A factor applied to every coordinate; None keeps them.
- **`autoclean`** (`bool`, default: `False` ) –

  Run ElmerGrid's `-autoclean` pass.
- **`keep_mesh_dir`** (`bool`, default: `False` ) –

  Keep the mesh files in ElmerGrid's `<stem>/` dir instead of moving them into `out_dir`.
- **`on_line`** (`LineSink | None`, default: `None` ) –

  Receives each output line.
- **`timeout_s`** (`float | None`, default: `None` ) –

  Seconds before the conversion is stopped; None waits for it.
- **`cancel`** (`CancelToken | None`, default: `None` ) –

  Stops the conversion once it is set.

#### grid\_partition

```python
grid_partition(mesh_dir: Path, n_parts: int, *, halobc: bool, on_line: LineSink | None = None, timeout_s: float | None = None, cancel: CancelToken | None = None) -> CommandResult
```

Split the Elmer mesh dir `mesh_dir` into `partitioning.<n_parts>` inside it, in a container.

Parameters:

- **`mesh_dir`** (`Path`) –

  The Elmer mesh dir itself.
- **`n_parts`** (`int`) –

  The partition count.
- **`halobc`** (`bool`) –

  Add the halo elements that boundary-terminated coil electrodes need.
- **`on_line`** (`LineSink | None`, default: `None` ) –

  Receives each output line.
- **`timeout_s`** (`float | None`, default: `None` ) –

  Seconds before the partitioning is stopped; None waits for it.
- **`cancel`** (`CancelToken | None`, default: `None` ) –

  Stops the partitioning once it is set.

#### solve

```python
solve(sim_dir: Path, *, n_procs: int = 1, on_line: LineSink | None = None, timeout_s: float | None = None, cancel: CancelToken | None = None) -> CommandResult
```

Run ElmerSolver on the case in `sim_dir` in a container, under `mpirun` when `n_procs` exceeds one.

Parameters:

- **`sim_dir`** (`Path`) –

  The case directory, which receives `elmersolver.log` and the solver's outputs.
- **`n_procs`** (`int`, default: `1` ) –

  The MPI rank count.
- **`on_line`** (`LineSink | None`, default: `None` ) –

  Receives each output line.
- **`timeout_s`** (`float | None`, default: `None` ) –

  Seconds before the solve is stopped; None waits for it.
- **`cancel`** (`CancelToken | None`, default: `None` ) –

  Stops the solve once it is set.

### NativeElmer

```python
NativeElmer(tools: ElmerToolsLocations | None = None)
```

Bases: `ExecutionBackend`

Runs a native Elmer install; it is not partitioned, because native builds lack the MUMPS a partitioned solve needs.

Methods:

- **`grid_convert`** –

  Convert the gmsh mesh `mesh_dir/msh_name` with the native ElmerGrid into Elmer mesh files plus `elmergrid.log` in `out_dir`.
- **`grid_partition`** –

  Split the Elmer mesh dir `mesh_dir` into `partitioning.<n_parts>` inside it with the native ElmerGrid.
- **`solve`** –

  Run the native ElmerSolver on the case in `sim_dir`, serially when the MPI build is missing.

Attributes:

- **`tools`** (`ElmerToolsLocations`) –

  The native Elmer executables.
- **`partitioned`** (`bool`) –

  False: a native build lacks the MUMPS a partitioned solve needs.

#### tools

```python
tools: ElmerToolsLocations
```

The native Elmer executables.

#### partitioned

```python
partitioned: bool
```

False: a native build lacks the MUMPS a partitioned solve needs.

#### grid\_convert

```python
grid_convert(mesh_dir: Path, msh_name: str, *, out_dir: Path, scale: float | None = None, autoclean: bool = False, keep_mesh_dir: bool = False, on_line: LineSink | None = None, timeout_s: float | None = None, cancel: CancelToken | None = None) -> CommandResult
```

Convert the gmsh mesh `mesh_dir/msh_name` with the native ElmerGrid into Elmer mesh files plus `elmergrid.log` in `out_dir`.

Parameters:

- **`mesh_dir`** (`Path`) –

  The directory holding the `.msh`.
- **`msh_name`** (`str`) –

  The `.msh` file name.
- **`out_dir`** (`Path`) –

  Where the mesh files and the log land.
- **`scale`** (`float | None`, default: `None` ) –

  A factor applied to every coordinate; None keeps them.
- **`autoclean`** (`bool`, default: `False` ) –

  Run ElmerGrid's `-autoclean` pass.
- **`keep_mesh_dir`** (`bool`, default: `False` ) –

  Keep the mesh files in ElmerGrid's `<stem>/` dir instead of moving them into `out_dir`.
- **`on_line`** (`LineSink | None`, default: `None` ) –

  Receives each output line.
- **`timeout_s`** (`float | None`, default: `None` ) –

  Seconds before the conversion is stopped; None waits for it.
- **`cancel`** (`CancelToken | None`, default: `None` ) –

  Stops the conversion once it is set.

#### grid\_partition

```python
grid_partition(mesh_dir: Path, n_parts: int, *, halobc: bool, on_line: LineSink | None = None, timeout_s: float | None = None, cancel: CancelToken | None = None) -> CommandResult
```

Split the Elmer mesh dir `mesh_dir` into `partitioning.<n_parts>` inside it with the native ElmerGrid.

Parameters:

- **`mesh_dir`** (`Path`) –

  The Elmer mesh dir itself.
- **`n_parts`** (`int`) –

  The partition count.
- **`halobc`** (`bool`) –

  Add the halo elements that boundary-terminated coil electrodes need.
- **`on_line`** (`LineSink | None`, default: `None` ) –

  Receives each output line.
- **`timeout_s`** (`float | None`, default: `None` ) –

  Seconds before the partitioning is stopped; None waits for it.
- **`cancel`** (`CancelToken | None`, default: `None` ) –

  Stops the partitioning once it is set.

#### solve

```python
solve(sim_dir: Path, *, n_procs: int = 1, on_line: LineSink | None = None, timeout_s: float | None = None, cancel: CancelToken | None = None) -> CommandResult
```

Run the native ElmerSolver on the case in `sim_dir`, serially when the MPI build is missing.

Parameters:

- **`sim_dir`** (`Path`) –

  The case directory, which receives `elmersolver.log` and the solver's outputs.
- **`n_procs`** (`int`, default: `1` ) –

  The MPI rank count.
- **`on_line`** (`LineSink | None`, default: `None` ) –

  Receives each output line.
- **`timeout_s`** (`float | None`, default: `None` ) –

  Seconds before the solve is stopped; None waits for it.
- **`cancel`** (`CancelToken | None`, default: `None` ) –

  Stops the solve once it is set.

### ElmerVariable

Bases: `StrEnum`

The Elmer variable names this project emits and reads back, one member per name.

VTU and SaveScalars names come back lowercased, so match them with `member.lower()`.

Attributes:

- **`POTENTIAL`** –

  The conduction/electrostatic scalar potential (also the Dirichlet keyword of `FixedPotential`).
- **`TEMPERATURE`** –

  The heat-solve variable. Keep Elmer's spelling: runtime material properties emit `Variable Temperature`.
- **`DISPLACEMENT`** –

  The elasticity primary variable.
- **`JOULE_HEATING`** –

  Fixed by Elmer: created internally by StatCurrentSolve's `Calculate Joule Heating`.
- **`MAGNETIC_FLUX_DENSITY`** –

  Fixed by Elmer: created internally by MagnetoDynamicsCalcFields.
- **`MAGNETIC_FIELD_ENERGY`** –

  Fixed by Elmer: the CalcFields `Separate Magnetic Energy` observable's log/scalar name.
- **`EDDY_CURRENT_POWER`** –

  Fixed by Elmer: the CalcFields eddy-loss `res:` scalar (harmonic/transient solves; absent in statics).

#### POTENTIAL

```python
POTENTIAL = 'Potential'
```

The conduction/electrostatic scalar potential (also the Dirichlet keyword of `FixedPotential`). NOT renameable: coupled HeatSolve resolves the electric solution by this exact name for `Joule Heat = True` — any other name silently zeroes the heat source.

#### TEMPERATURE

```python
TEMPERATURE = 'Temperature'
```

The heat-solve variable. Keep Elmer's spelling: runtime material properties emit `Variable Temperature`.

#### DISPLACEMENT

```python
DISPLACEMENT = 'Displacement'
```

The elasticity primary variable.

#### JOULE\_HEATING

```python
JOULE_HEATING = 'Joule Heating'
```

Fixed by Elmer: created internally by StatCurrentSolve's `Calculate Joule Heating`.

#### MAGNETIC\_FLUX\_DENSITY

```python
MAGNETIC_FLUX_DENSITY = 'Magnetic Flux Density'
```

Fixed by Elmer: created internally by MagnetoDynamicsCalcFields.

#### MAGNETIC\_FIELD\_ENERGY

```python
MAGNETIC_FIELD_ENERGY = 'Magnetic Field Energy'
```

Fixed by Elmer: the CalcFields `Separate Magnetic Energy` observable's log/scalar name.

#### EDDY\_CURRENT\_POWER

```python
EDDY_CURRENT_POWER = 'Eddy Current Power'
```

Fixed by Elmer: the CalcFields eddy-loss `res:` scalar (harmonic/transient solves; absent in statics).

### physics\_config

```python
physics_config(physics: Physics) -> PhysicsConfig
```

The fully-populated, typed solver stack for `physics`, keyed by an exhaustive `match` on the `Physics` enum (a new physics is a static obligation).

### prepare\_for\_mpi

```python
prepare_for_mpi(config: PhysicsConfig) -> None
```

Adjust a solver stack for a partitioned (MPI) solve: CoilSolver's serial-only umfpack Direct solve switches to MUMPS, and any other Direct solve raises.

### parse\_mesh\_names

```python
parse_mesh_names(text: str) -> MeshNames
```

Parse an ElmerGrid `mesh.names` file (`$ AIR__EXT = 1` lines under per-section headers) into its body and boundary `name -> id` maps; anything before the first recognized header, and any malformed line, is ignored.

### reconcile\_ids

```python
reconcile_ids(names: MeshNames, physical_groups: Iterable[_IdentifiedGroup], boundary_groups: Iterable[_IdentifiedGroup]) -> list[str]
```

Overwrite each group's gmsh-tag `id` in place with the Elmer id ElmerGrid assigned to its name; returns the names `mesh.names` lacks.

Parameters:

- **`names`** (`MeshNames`) –

  The ids parsed from `mesh.names`.
- **`physical_groups`** (`Iterable[_IdentifiedGroup]`) –

  The body groups to renumber.
- **`boundary_groups`** (`Iterable[_IdentifiedGroup]`) –

  The boundary groups to renumber.

### condition\_physics

```python
condition_physics(physics: Physics) -> frozenset[Physics]
```

The physics whose authored conditions are *active* under `physics`: a coupled preset accepts its constituents' conditions too, so the same tags drive both the standalone and the coupled solves without re-authoring.

### runtime\_variables

```python
runtime_variables(physics: Physics) -> frozenset[str]
```

The solved-field variable names a material property may depend on at runtime under `physics`. A property whose parameter names are all in this set is emitted as a variable-dependent expression on the local solved field; otherwise it falls back to its operating-point value.

### solve\_case

```python
solve_case(backend: ExecutionBackend, sim_dir: Path, *, n_procs: int = 1, targets: ConvergenceTargets | None = None, convergence_jsonl: bool = True, on_event: Callable[[SolverEvent], None] | None = None, on_line: LineSink | None = None, timeout_s: float | None = None, cancel: CancelToken | None = None) -> SolveRun
```

Run ElmerSolver on `sim_dir` through `backend`, logging and collecting what its stream reports.

A failed solve returns its nonzero exit; `run.parse` grades the unchanged `elmersolver.log`.

Parameters:

- **`backend`** (`ExecutionBackend`) –

  Where ElmerSolver runs.
- **`sim_dir`** (`Path`) –

  The case directory.
- **`n_procs`** (`int`, default: `1` ) –

  The MPI rank count.
- **`targets`** (`ConvergenceTargets | None`, default: `None` ) –

  The sif-authored iteration caps and tolerances the logged convergence lines report against.
- **`convergence_jsonl`** (`bool`, default: `True` ) –

  Write each convergence sample to `convergence.jsonl` in `sim_dir`.
- **`on_event`** (`Callable[[SolverEvent], None] | None`, default: `None` ) –

  Receives each parsed solver event.
- **`on_line`** (`LineSink | None`, default: `None` ) –

  Receives each raw output line.
- **`timeout_s`** (`float | None`, default: `None` ) –

  Seconds before the solve is stopped; None waits for it.
- **`cancel`** (`CancelToken | None`, default: `None` ) –

  Stops the solve once it is set.

### elemental

```python
elemental(variable: ElmerVariable | str) -> str
```

The variable's DG counterpart: Elmer registers a solver's elemental field as `"<name> e"`.
