Skip to content

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 module-attribute

SifValue = str | int | float | bool

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

CoilCurrentSolver pydantic-model

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 pydantic-field

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

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

coil_closed pydantic-field

coil_closed: bool = False

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

normalize_coil_current pydantic-field

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 pydantic-field

calculate_elemental_fields: bool = True

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

Constants pydantic-model

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:

permeability_of_vacuum pydantic-field

permeability_of_vacuum: float | None = None

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

permittivity_of_vacuum pydantic-field

permittivity_of_vacuum: float | None = None

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

stefan_boltzmann pydantic-field

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 class-attribute instance-attribute

CARTESIAN = 'Cartesian'

Cartesian coordinates.

CYLINDRIC class-attribute instance-attribute

CYLINDRIC = 'Cylindric'

Full cylindrical coordinates.

CYLINDRIC_SYMMETRIC class-attribute instance-attribute

CYLINDRIC_SYMMETRIC = 'Cylindric Symmetric'

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

AXI_SYMMETRIC class-attribute instance-attribute

AXI_SYMMETRIC = 'Axi Symmetric'

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

POLAR class-attribute instance-attribute

POLAR = 'Polar'

Polar coordinates.

per_radian property

per_radian: bool

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

ElectrostaticsSolver pydantic-model

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 pydantic-field

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

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

calculate_electric_field pydantic-field

calculate_electric_field: bool = True

Recover E from the potential.

ExecSolver

Bases: StrEnum

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

Attributes:

ALWAYS class-attribute instance-attribute

ALWAYS = 'Always'

Run in the normal solve sequence.

BEFORE_SIMULATION class-attribute instance-attribute

BEFORE_SIMULATION = 'Before simulation'

Run once before the simulation.

AFTER_SIMULATION class-attribute instance-attribute

AFTER_SIMULATION = 'After simulation'

Run once after the simulation.

BEFORE_TIMESTEP class-attribute instance-attribute

BEFORE_TIMESTEP = 'Before timestep'

Run at the start of each timestep.

AFTER_TIMESTEP class-attribute instance-attribute

AFTER_TIMESTEP = 'After timestep'

Run at the end of each timestep.

NEVER class-attribute instance-attribute

NEVER = 'Never'

Never run.

GenericSolver pydantic-model

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 pydantic-field

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

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

HeatSolver pydantic-model

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 pydantic-field

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 class-attribute instance-attribute

BICGSTAB = 'BiCGStab'

Biconjugate gradient stabilized.

BICGSTABL class-attribute instance-attribute

BICGSTABL = 'BiCGStabl'

BiCGStab(l), of degree bicgstabl_polynomial_degree.

CG class-attribute instance-attribute

CG = 'CG'

Conjugate gradient, for symmetric positive-definite systems.

CGS class-attribute instance-attribute

CGS = 'CGS'

Conjugate gradient squared.

GMRES class-attribute instance-attribute

GMRES = 'GMRES'

Generalized minimal residual.

GCR class-attribute instance-attribute

GCR = 'GCR'

Generalized conjugate residual.

TFQMR class-attribute instance-attribute

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 class-attribute instance-attribute

ITERATIVE = 'Iterative'

A Krylov iteration, chosen by linear_system_iterative_method.

DIRECT class-attribute instance-attribute

DIRECT = 'Direct'

A direct factorization, chosen by linear_system_direct_method.

LinearSystem pydantic-model

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 pydantic-model

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 pydantic-field

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

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

MagnetoDynamicsCalcFieldsSolver pydantic-model

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:

kind pydantic-field

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

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

potential_variable pydantic-field

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 pydantic-field

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 pydantic-field

calculate_magnetic_field_strength: bool = True

Recover H.

calculate_magnetic_flux_density pydantic-field

calculate_magnetic_flux_density: bool = True

Recover B.

calculate_nodal_forces pydantic-field

calculate_nodal_forces: bool = True

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

calculate_nodal_fields pydantic-field

calculate_nodal_fields: bool = False

Recover nodal field values.

calculate_joule_heating pydantic-field

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 pydantic-field

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 pydantic-field

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 pydantic-field

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 pydantic-field

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 pydantic-field

calculate_elemental_fields: bool = True

Recover elemental field values.

MagnetoDynamicsSolver pydantic-model

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 pydantic-field

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

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

fix_input_current_density pydantic-field

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 pydantic-field

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 pydantic-model

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 pydantic-model

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 pydantic-field

temperature: Temperature

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

as_mapping

as_mapping() -> dict[str, Quantity]

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

Source code in src/femforge/elmer/config/common/operating_point.py
def as_mapping(self) -> dict[str, Quantity]:
    """The ephemeral name->quantity mapping the property functions consume (``prop(**at)``)."""
    return {"temperature": self.temperature}

PhysicsConfig pydantic-model

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 pydantic-field

solvers: list[SolverUnion]

The ordered concrete solver instances Elmer runs for this physics.

simulation pydantic-field

simulation: SimulationSettings

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

constants pydantic-field

constants: Constants

The physical constants this physics activates.

operating_point pydantic-field

operating_point: OperatingPoint

Where material property functions are evaluated when stripping to SI.

solver

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

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

Source code in src/femforge/elmer/config/common/physics_config.py
def solver(self, kind: type[SolverT]) -> SolverT:
    """The live solver instance of type ``kind`` in this stack; raises ``KeyError`` when it has none."""
    for spec in self.solvers:
        if isinstance(spec, kind):
            return spec
    raise KeyError(f"No {kind.__name__} in this physics' solver stack ({[type(s).__name__ for s in self.solvers]}).")

check_frame

check_frame(frame: Frame) -> None

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

Source code in src/femforge/elmer/config/common/physics_config.py
def check_frame(self, frame: Frame) -> None:
    """Raise when this stack's coordinate system disagrees with ``frame``: per-radian exactly when the frame is axisymmetric."""
    coordinate_system: CoordinateSystem = self.simulation.coordinate_system
    axisymmetric: bool = isinstance(frame, Axisymmetric)

    if coordinate_system.per_radian == axisymmetric:
        return

    raise ValueError(f"The solver stack's Coordinate System '{coordinate_system.value}' cannot solve frame={type(frame).__name__}(); re-customize the physics after setting the frame.")

PhysicsSelection pydantic-model

Bases: ConfigArea

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

Fields:

Validators:

selected pydantic-field

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

overrides pydantic-field

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 pydantic-validator

selectable(selected: Physics) -> Physics

Reject the internal axisymmetric stack.

Source code in src/femforge/elmer/config/common/selection.py
@field_validator("selected")
@classmethod
def selectable(cls, selected: Physics) -> Physics:
    """Reject the internal axisymmetric stack."""
    if selected is Physics.MAGNETOSTATICS_2D:
        raise ValueError(f"Physics '{selected.value}' is not selectable: select '{Physics.MAGNETOSTATICS.value}' with frame=Axisymmetric().")

    return selected

check_frame

check_frame(frame: Frame) -> None

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

Source code in src/femforge/elmer/config/common/selection.py
def check_frame(self, frame: Frame) -> None:
    """Raise when ``selected`` has no form under ``frame`` or authored ``overrides`` disagree with it."""
    _ = self.selected.in_frame(frame)

    if self.overrides is not None:
        self.overrides.check_frame(frame)

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 class-attribute instance-attribute

NONE = 'None'

No preconditioning.

DIAGONAL class-attribute instance-attribute

DIAGONAL = 'Diagonal'

Diagonal (Jacobi) scaling.

ILU0 class-attribute instance-attribute

ILU0 = 'ILU0'

Incomplete LU without fill-in.

ILU1 class-attribute instance-attribute

ILU1 = 'ILU1'

Incomplete LU with fill level 1.

ILU2 class-attribute instance-attribute

ILU2 = 'ILU2'

Incomplete LU with fill level 2.

ILU class-attribute instance-attribute

ILU = 'ILU'

Incomplete LU without an explicit fill level.

ILUT class-attribute instance-attribute

ILUT = 'ILUT'

Incomplete LU with threshold dropping.

ResultOutputSolver pydantic-model

Bases: SolverBase

Write VTU output for ParaView.

Fields:

kind pydantic-field

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

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

vtu_format pydantic-field

vtu_format: bool = True

Write VTU (ParaView) output.

output_file_name pydantic-field

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 pydantic-field

save_geometry_ids: bool = True

Tag output cells with their gmsh geometry ids.

saved_scalar_fields pydantic-field

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 pydantic-field

saved_vector_fields: list[str] | None = None

Vector fields to save; None saves every active variable.

discontinuous_bodies pydantic-field

discontinuous_bodies: bool | None = None

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

SaveScalarsSolver pydantic-model

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:

kind pydantic-field

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

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

save_component_results pydantic-field

save_component_results: bool | None = None

Also export the scalars arising from Component sections.

parallel_reduce pydantic-field

parallel_reduce: bool | None = None

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

filename pydantic-field

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 pydantic-field

variable_1: str | None = ElmerVariable.POTENTIAL

The variable the operator reduces.

operator_1 pydantic-field

operator_1: str | None = 'diffusive flux'

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

coefficient_1 pydantic-field

coefficient_1: str | None = 'Electric Conductivity'

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

variable_2 pydantic-field

variable_2: str | None = ElmerVariable.JOULE_HEATING

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

operator_2 pydantic-field

operator_2: str | None = 'int'

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

variable_3 pydantic-field

variable_3: str | None = None

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

operator_3 pydantic-field

operator_3: str | None = None

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

SimulationSettings pydantic-model

Bases: SifKeywordModel

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

Fields:

max_output_level pydantic-field

max_output_level: int = 5

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

coordinate_system pydantic-field

The simulation coordinate system.

coordinate_mapping pydantic-field

coordinate_mapping: str = '1 2 3'

Axis permutation for the coordinate system.

simulation_type pydantic-field

Steady-state, transient, or scanning run.

steady_state_max_iterations pydantic-field

steady_state_max_iterations: int = 1

Max steady-state (coupled-system) iterations.

output_intervals pydantic-field

output_intervals: int = 1

Write results every N steps.

timestepping_method pydantic-field

timestepping_method: TimesteppingMethod = TimesteppingMethod.BDF

Time integration scheme.

bdf_order pydantic-field

bdf_order: int = 1

Order of the BDF timestepping method.

solver_input_file pydantic-field

solver_input_file: str = 'case.sif'

Name of the generated .sif.

post_file pydantic-field

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 pydantic-field

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 class-attribute instance-attribute

STEADY_STATE = 'Steady state'

A steady-state solve.

TRANSIENT class-attribute instance-attribute

TRANSIENT = 'Transient'

A time-dependent solve.

SCANNING class-attribute instance-attribute

SCANNING = 'Scanning'

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

SolverBase pydantic-model

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 pydantic-field

kind: str

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

required_material_keys class-attribute

required_material_keys: frozenset[ElmerMaterialKeyword] = frozenset()

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

conductor_only class-attribute

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 pydantic-model

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 pydantic-field

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

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

calculate_volume_current pydantic-field

calculate_volume_current: bool = True

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

calculate_joule_heating pydantic-field

calculate_joule_heating: bool = True

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

SteadyState pydantic-model

Bases: BaseModel

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

Fields:

  • steady_state_convergence_tolerance (SteadyStateConvToleranceField)

StressSolver pydantic-model

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 pydantic-field

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

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

calculate_stresses pydantic-field

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 class-attribute instance-attribute

BDF = 'BDF'

Backward differentiation formula, of order bdf_order.

NEWMARK class-attribute instance-attribute

NEWMARK = 'Newmark'

The Newmark-beta method.

RUNGE_KUTTA class-attribute instance-attribute

RUNGE_KUTTA = 'Runge-Kutta'

A Runge-Kutta method.

MeshNames dataclass

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 instance-attribute

bodies: dict[str, int]

Body name to Elmer body id.

boundaries instance-attribute

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 class-attribute instance-attribute

MAGNETOSTATICS = 'magnetostatics'

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

MAGNETOSTATICS_2D class-attribute instance-attribute

MAGNETOSTATICS_2D = 'magnetostatics_2d'

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

THERMAL class-attribute instance-attribute

THERMAL = 'thermal'

Steady-state heat conduction.

ELECTROSTATICS class-attribute instance-attribute

ELECTROSTATICS = 'electrostatics'

The electrostatic potential and field.

CURRENT_CONDUCTION class-attribute instance-attribute

CURRENT_CONDUCTION = 'current_conduction'

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

JOULE_HEATING class-attribute instance-attribute

JOULE_HEATING = 'joule_heating'

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

COIL_MAGNETOSTATICS class-attribute instance-attribute

COIL_MAGNETOSTATICS = 'coil_magnetostatics'

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

LINEAR_ELASTICITY class-attribute instance-attribute

LINEAR_ELASTICITY = 'linear_elasticity'

Linear elastic stress and strain.

in_frame

in_frame(frame: Frame) -> Physics

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

Source code in src/femforge/elmer/physics.py
def in_frame(self, frame: Frame) -> Physics:
    """The physics this selection solves as under ``frame``; raises when it has no form there."""
    match frame, self:
        case Solid(), Physics.MAGNETOSTATICS_2D:
            raise ValueError(f"Physics '{self.value}' is the axisymmetric stack: select '{Physics.MAGNETOSTATICS.value}' with frame=Axisymmetric().")
        case Solid(), _:
            return self
        case Axisymmetric(), Physics.MAGNETOSTATICS | Physics.MAGNETOSTATICS_2D:
            return Physics.MAGNETOSTATICS_2D
        case Axisymmetric(), Physics.THERMAL:
            return Physics.THERMAL
        case Axisymmetric(), _:
            raise ValueError(f"Physics '{self.value}' has no axisymmetric form; solve it with frame=Solid().")

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 property

partitioned: bool

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

grid_convert

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.

Source code in src/femforge/elmer/runtime/backend.py
def grid_convert(
    self,
    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.

    Args:
        mesh_dir: The directory holding the ``.msh``.
        msh_name: The ``.msh`` file name.
        out_dir: Where the mesh files and the log land.
        scale: A factor applied to every coordinate; None keeps them.
        autoclean: Run ElmerGrid's ``-autoclean`` pass.
        keep_mesh_dir: Keep the mesh files in ElmerGrid's ``<stem>/`` dir instead of moving them into ``out_dir``.
        on_line: Receives each output line.
        timeout_s: Seconds before the conversion is stopped; None waits for it.
        cancel: Stops the conversion once it is set.
    """
    ...

grid_partition

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.

Source code in src/femforge/elmer/runtime/backend.py
def grid_partition(
    self,
    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.

    Args:
        mesh_dir: The Elmer mesh dir itself; a backend that addresses it by name needs that name dot-free.
        n_parts: The partition count.
        halobc: Add the halo elements that boundary-terminated coil electrodes need.
        on_line: Receives each output line.
        timeout_s: Seconds before the partitioning is stopped; None waits for it.
        cancel: Stops the partitioning once it is set.
    """
    ...

solve

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.

Source code in src/femforge/elmer/runtime/backend.py
def solve(
    self,
    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.

    Args:
        sim_dir: The case directory, which receives ``elmersolver.log`` and the solver's outputs.
        n_procs: The MPI rank count.
        on_line: Receives each output line.
        timeout_s: Seconds before the solve is stopped; None waits for it.
        cancel: Stops the solve once it is set.
    """
    ...

DockerElmer

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.

Source code in src/femforge/elmer/runtime/docker.py
def __init__(self, image: DockerImage | None = None, *, docker: DockerCli | None = None) -> None:
    """Run in ``image`` (femforge's Elmer image when None) through ``docker`` (a fresh client when None)."""
    self.image: DockerImage = image if image is not None else ELMER_IMAGE.image()
    self.docker: DockerCli = docker if docker is not None else DockerCli()

partitioned property

partitioned: bool

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

grid_convert

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.

Source code in src/femforge/elmer/runtime/docker.py
def grid_convert(
    self,
    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``.

    Args:
        mesh_dir: The directory holding the ``.msh``.
        msh_name: The ``.msh`` file name.
        out_dir: Where the mesh files and the log land.
        scale: A factor applied to every coordinate; None keeps them.
        autoclean: Run ElmerGrid's ``-autoclean`` pass.
        keep_mesh_dir: Keep the mesh files in ElmerGrid's ``<stem>/`` dir instead of moving them into ``out_dir``.
        on_line: Receives each output line.
        timeout_s: Seconds before the conversion is stopped; None waits for it.
        cancel: Stops the conversion once it is set.
    """
    out_dir.mkdir(parents=True, exist_ok=True)
    result: CommandResult = self._run(
        [ELMERGRID, *grid_convert_args(msh_name, scale=scale, autoclean=autoclean)],
        mount=mesh_dir,
        workdir=CASE_DIR,
        log_path=out_dir / BundleFile.ELMERGRID_LOG,
        on_line=on_line,
        timeout_s=timeout_s,
        cancel=cancel,
    )

    return finish_grid_convert(
        result,
        mesh_dir,
        msh_name,
        out_dir=out_dir,
        keep_mesh_dir=keep_mesh_dir,
    )

grid_partition

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.

Source code in src/femforge/elmer/runtime/docker.py
def grid_partition(
    self,
    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.

    Args:
        mesh_dir: The Elmer mesh dir itself.
        n_parts: The partition count.
        halobc: Add the halo elements that boundary-terminated coil electrodes need.
        on_line: Receives each output line.
        timeout_s: Seconds before the partitioning is stopped; None waits for it.
        cancel: Stops the partitioning once it is set.
    """
    # The container addresses the mesh dir by its mount point, whose name is dot-free whatever the host directory is called.
    case: PurePosixPath = PurePosixPath(CASE_DIR)
    result: CommandResult = self._run(
        [ELMERGRID, *grid_partition_args(case.name, n_parts, halobc=halobc)],
        mount=mesh_dir,
        workdir=str(case.parent),
        log_path=mesh_dir / BundleFile.PARTITION_LOG,
        on_line=on_line,
        timeout_s=timeout_s,
        cancel=cancel,
    )

    return check_partitions(result, mesh_dir, n_parts)

solve

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.

Source code in src/femforge/elmer/runtime/docker.py
def solve(
    self,
    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.

    Args:
        sim_dir: The case directory, which receives ``elmersolver.log`` and the solver's outputs.
        n_procs: The MPI rank count.
        on_line: Receives each output line.
        timeout_s: Seconds before the solve is stopped; None waits for it.
        cancel: Stops the solve once it is set.
    """
    args: list[str] = [MPIRUN, OVERSUBSCRIBE, "-np", str(n_procs), ELMERSOLVER_MPI] if n_procs > 1 else [ELMERSOLVER]

    return self._run(
        args,
        mount=sim_dir,
        workdir=CASE_DIR,
        log_path=sim_dir / BundleFile.SOLVER_LOG,
        on_line=on_line,
        timeout_s=timeout_s,
        cancel=cancel,
    )

NativeElmer

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.

Source code in src/femforge/elmer/runtime/native.py
def __init__(self, tools: ElmerToolsLocations | None = None) -> None:
    """Run ``tools``, or the install [`locate_elmer_tools`][] finds on first use."""
    self._tools: ElmerToolsLocations | None = tools

tools property

tools: ElmerToolsLocations

The native Elmer executables.

partitioned property

partitioned: bool

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

grid_convert

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.

Source code in src/femforge/elmer/runtime/native.py
def grid_convert(
    self,
    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``.

    Args:
        mesh_dir: The directory holding the ``.msh``.
        msh_name: The ``.msh`` file name.
        out_dir: Where the mesh files and the log land.
        scale: A factor applied to every coordinate; None keeps them.
        autoclean: Run ElmerGrid's ``-autoclean`` pass.
        keep_mesh_dir: Keep the mesh files in ElmerGrid's ``<stem>/`` dir instead of moving them into ``out_dir``.
        on_line: Receives each output line.
        timeout_s: Seconds before the conversion is stopped; None waits for it.
        cancel: Stops the conversion once it is set.
    """
    out_dir.mkdir(parents=True, exist_ok=True)
    result: CommandResult = run_streaming(
        [self.tools.ElmerGrid, *grid_convert_args(msh_name, scale=scale, autoclean=autoclean)],
        cwd=mesh_dir,
        log_path=out_dir / BundleFile.ELMERGRID_LOG,
        on_line=on_line,
        timeout_s=timeout_s,
        cancel=cancel,
    )

    return finish_grid_convert(
        result,
        mesh_dir,
        msh_name,
        out_dir=out_dir,
        keep_mesh_dir=keep_mesh_dir,
    )

grid_partition

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.

Source code in src/femforge/elmer/runtime/native.py
def grid_partition(
    self,
    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.

    Args:
        mesh_dir: The Elmer mesh dir itself.
        n_parts: The partition count.
        halobc: Add the halo elements that boundary-terminated coil electrodes need.
        on_line: Receives each output line.
        timeout_s: Seconds before the partitioning is stopped; None waits for it.
        cancel: Stops the partitioning once it is set.
    """
    result: CommandResult = run_streaming(
        [self.tools.ElmerGrid, *grid_partition_args(mesh_dir.name, n_parts, halobc=halobc)],
        cwd=mesh_dir.parent,
        log_path=mesh_dir / BundleFile.PARTITION_LOG,
        on_line=on_line,
        timeout_s=timeout_s,
        cancel=cancel,
    )

    return check_partitions(result, mesh_dir, n_parts)

solve

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.

Source code in src/femforge/elmer/runtime/native.py
def solve(
    self,
    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.

    Args:
        sim_dir: The case directory, which receives ``elmersolver.log`` and the solver's outputs.
        n_procs: The MPI rank count.
        on_line: Receives each output line.
        timeout_s: Seconds before the solve is stopped; None waits for it.
        cancel: Stops the solve once it is set.
    """
    tools: ElmerToolsLocations = self.tools
    command: list[str] = [tools.ElmerSolver, BundleFile.SIF]
    if n_procs > 1 and tools.mpiexec is not None and tools.ElmerSolver_mpi is not None:
        command = [tools.mpiexec, "-n", str(n_procs), tools.ElmerSolver_mpi, BundleFile.SIF]
    elif n_procs > 1:
        log.bind(n_procs=n_procs).warning("ElmerSolver_mpi or mpiexec is not installed; running the solve serially.")

    return run_streaming(
        command,
        cwd=sim_dir,
        log_path=sim_dir / BundleFile.SOLVER_LOG,
        on_line=on_line,
        timeout_s=timeout_s,
        cancel=cancel,
    )

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 class-attribute instance-attribute

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 class-attribute instance-attribute

TEMPERATURE = 'Temperature'

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

DISPLACEMENT class-attribute instance-attribute

DISPLACEMENT = 'Displacement'

The elasticity primary variable.

JOULE_HEATING class-attribute instance-attribute

JOULE_HEATING = 'Joule Heating'

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

MAGNETIC_FLUX_DENSITY class-attribute instance-attribute

MAGNETIC_FLUX_DENSITY = 'Magnetic Flux Density'

Fixed by Elmer: created internally by MagnetoDynamicsCalcFields.

MAGNETIC_FIELD_ENERGY class-attribute instance-attribute

MAGNETIC_FIELD_ENERGY = 'Magnetic Field Energy'

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

EDDY_CURRENT_POWER class-attribute instance-attribute

EDDY_CURRENT_POWER = 'Eddy Current Power'

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

physics_config

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).

Source code in src/femforge/elmer/config/common/presets.py
def physics_config(physics: Physics) -> PhysicsConfig:
    """The fully-populated, typed solver stack for ``physics``, keyed by an exhaustive ``match`` on the [`Physics`][femforge.elmer.physics.Physics] enum (a new physics is a static obligation)."""
    match physics:
        case Physics.MAGNETOSTATICS:
            return PhysicsConfig(
                solvers=[MagnetoDynamicsSolver(), MagnetoDynamicsCalcFieldsSolver(), bare_save_scalars(), ResultOutputSolver()],
                simulation=SimulationSettings(),
                constants=Constants(permeability_of_vacuum=PERMEABILITY_OF_VACUUM),
                operating_point=OperatingPoint(),
            )
        case Physics.MAGNETOSTATICS_2D:
            # What MAGNETOSTATICS solves as under frame=Axisymmetric(), on the 2D cross-section the frame maps the model
            # onto (X = radial >= 0, Y = along the axis). CalcFields pairs with the 2D solver (Potential Variable pins
            # the scalar A_phi); nodal fields stay ON — turning them off also suppresses the nodal force variable the
            # force reduction reads.
            return PhysicsConfig(
                solvers=[
                    MagnetoDynamics2DSolver(),
                    MagnetoDynamicsCalcFieldsSolver(
                        potential_variable=f'String "{ElmerVariable.POTENTIAL}"',
                        calculate_nodal_fields=True,
                    ),
                    bare_save_scalars(),
                    ResultOutputSolver(),
                ],
                simulation=SimulationSettings(coordinate_system=CoordinateSystem.AXI_SYMMETRIC),
                constants=Constants(permeability_of_vacuum=PERMEABILITY_OF_VACUUM),
                operating_point=OperatingPoint(),
            )
        case Physics.THERMAL:
            # The one SaveScalars reduction is the peak temperature — Result.temperature_max, the thermal-rating observable.
            return PhysicsConfig(
                solvers=[
                    HeatSolver(),
                    SaveScalarsSolver(variable_1=ElmerVariable.TEMPERATURE, operator_1="max", coefficient_1=None, variable_2=None, operator_2=None),
                    ResultOutputSolver(),
                ],
                simulation=SimulationSettings(),
                constants=Constants(stefan_boltzmann=STEFAN_BOLTZMANN),
                operating_point=OperatingPoint(),
            )
        case Physics.CURRENT_CONDUCTION:
            return PhysicsConfig(
                solvers=[StatCurrentSolver(), SaveScalarsSolver(), ResultOutputSolver()],
                simulation=SimulationSettings(),
                constants=Constants(),
                operating_point=OperatingPoint(),
            )
        case Physics.JOULE_HEATING:
            # Coupled StatCurrentSolve -> HeatSolve; one-way while sigma is Static, the iteration head-room is for a future two-way sigma(T).
            return PhysicsConfig(
                solvers=[
                    StatCurrentSolver(),
                    HeatSolver(),
                    SaveScalarsSolver(variable_3=ElmerVariable.TEMPERATURE, operator_3="max"),
                    ResultOutputSolver(),
                ],
                simulation=SimulationSettings(steady_state_max_iterations=10),
                constants=Constants(stefan_boltzmann=STEFAN_BOLTZMANN),
                operating_point=OperatingPoint(),
            )
        case Physics.COIL_MAGNETOSTATICS:
            # Chained CoilSolver -> WhitneyAV (one-way: J is independent of A). The AV source is CoilSolver's ELEMENTAL
            # coil current — a nodal Volume Current bridge silently dilutes the drive ~2x on thin conductors.
            return PhysicsConfig(
                solvers=[
                    # Before Simulation: the source must exist before the AV assembly reads it.
                    CoilCurrentSolver(),
                    # Jfix stays ON even with the elemental source: the DG coil current is not exactly solenoidal
                    # edge-element-wise, and without the projection the singular curl-curl solve diverges.
                    MagnetoDynamicsSolver(fix_input_current_density=True, use_elemental_coilcurrent=True),
                    # Potential Variable is pinned (typed String) so CalcFields latches onto the edge-element av; nodal forces
                    # off (meaningless without magnets); Separate Magnetic Energy prints "Magnetic Field Energy" to the log.
                    # H recovery off: μr ≡ 1 in these solves (H = B/μ0), and skipping it drops a 3-component CG
                    # recovery plus a quarter of the VTU on the big L meshes.
                    MagnetoDynamicsCalcFieldsSolver(
                        potential_variable='String "av"',
                        calculate_magnetic_field_strength=False,
                        calculate_magnetic_vector_potential=False,
                        calculate_relative_permeability=True,
                        calculate_nodal_forces=False,
                        separate_magnetic_energy=True,
                    ),
                    bare_save_scalars(),
                    # The pipeline reads only B back (energy cross-check, field sampling), so the VTU saves just that.
                    ResultOutputSolver(saved_vector_fields=[f'String "{elemental(ElmerVariable.MAGNETIC_FLUX_DENSITY)}"']),
                ],
                simulation=SimulationSettings(),
                constants=Constants(permeability_of_vacuum=PERMEABILITY_OF_VACUUM),
                operating_point=OperatingPoint(),
            )
        case Physics.ELECTROSTATICS:
            return PhysicsConfig(
                solvers=[ElectrostaticsSolver(), ResultOutputSolver()],
                simulation=SimulationSettings(),
                constants=Constants(permittivity_of_vacuum=PERMITTIVITY_OF_VACUUM),
                operating_point=OperatingPoint(),
            )
        # SCAFFOLD physics: the solver stack is complete, but the per-body wiring in Sim is a marked stub.
        case Physics.LINEAR_ELASTICITY:
            return PhysicsConfig(
                solvers=[StressSolver(), ResultOutputSolver()],
                simulation=SimulationSettings(),
                constants=Constants(),
                operating_point=OperatingPoint(),
            )

prepare_for_mpi

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.

Source code in src/femforge/elmer/config/common/physics_config.py
def 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."""
    for solver in config.solvers:
        if isinstance(solver, CoilCurrentSolver):
            solver.linear_system_direct_method = "MUMPS"
        elif isinstance(solver, LinearSystem) and solver.linear_system_solver is LinearSolverType.DIRECT:
            raise ValueError(f"{type(solver).__name__} uses a Direct linear solve, which is unvalidated under partitioning — run it serially (mpi_processes=1).")

parse_mesh_names

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.

Source code in src/femforge/elmer/mesh_names.py
def 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.
    """
    bodies: dict[str, int] = {}
    boundaries: dict[str, int] = {}
    current: dict[str, int] | None = None

    for raw in text.splitlines():
        line: str = raw.strip()
        if line.startswith("!"):
            lowered: str = line.lower()
            if _BODIES_HEADER in lowered:
                current = bodies
            elif _BOUNDARIES_HEADER in lowered:
                current = boundaries
            continue

        if current is None or not line.startswith("$") or "=" not in line:
            continue

        name, _, value = line[1:].partition("=")
        try:
            current[name.strip()] = int(value.strip())
        except ValueError:
            continue  # a non-integer rhs isn't an id mapping; skip it rather than fail the whole parse.

    return MeshNames(bodies=bodies, boundaries=boundaries)

reconcile_ids

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.

Source code in src/femforge/elmer/mesh_names.py
def 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.

    Args:
        names: The ids parsed from ``mesh.names``.
        physical_groups: The body groups to renumber.
        boundary_groups: The boundary groups to renumber.
    """
    missing: list[str] = []
    for group in physical_groups:
        if group.name in names.bodies:
            group.id = names.bodies[group.name]
        else:
            missing.append(group.name)
    for group in boundary_groups:
        if group.name in names.boundaries:
            group.id = names.boundaries[group.name]
        else:
            missing.append(group.name)
    return missing

condition_physics

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.

Source code in src/femforge/elmer/physics.py
def 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.
    """
    if physics is Physics.JOULE_HEATING:
        return frozenset({Physics.JOULE_HEATING, Physics.CURRENT_CONDUCTION, Physics.THERMAL})
    if physics is Physics.COIL_MAGNETOSTATICS:
        # CURRENT_CONDUCTION conditions (FixedPotential electrodes) are deliberately NOT activated: this stack has no
        # Potential variable (the source stage is CoilSolver, not StatCurrent), so shared electrode tags stay inert here.
        return frozenset({Physics.COIL_MAGNETOSTATICS, Physics.MAGNETOSTATICS})
    if physics is Physics.MAGNETOSTATICS_2D:
        # The same Magnetization tags drive the 3D and the axisymmetric solves without re-authoring.
        return frozenset({Physics.MAGNETOSTATICS_2D, Physics.MAGNETOSTATICS})
    return frozenset({physics})

runtime_variables

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.

Source code in src/femforge/elmer/physics.py
def 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.
    """
    if physics in (Physics.THERMAL, Physics.JOULE_HEATING):
        return frozenset({"temperature"})
    return frozenset()

solve_case

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.

Source code in src/femforge/elmer/runtime/solve.py
def 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``.

    Args:
        backend: Where ElmerSolver runs.
        sim_dir: The case directory.
        n_procs: The MPI rank count.
        targets: The sif-authored iteration caps and tolerances the logged convergence lines report against.
        convergence_jsonl: Write each convergence sample to ``convergence.jsonl`` in ``sim_dir``.
        on_event: Receives each parsed solver event.
        on_line: Receives each raw output line.
        timeout_s: Seconds before the solve is stopped; None waits for it.
        cancel: Stops the solve once it is set.
    """
    parser = SolverLogParser()
    # One renderer per solve: its first-relc progress baseline must not leak into the next solve.
    renderer = ConvergenceRenderer(targets)
    samples: list[ConvergenceSample] = []
    messages: list[SolverMessage] = []
    stream: IO[str] | None = (sim_dir / BundleFile.CONVERGENCE).open("w", encoding="utf-8") if convergence_jsonl else None

    def handle(line: str) -> None:
        if on_line is not None:
            on_line(line)

        event: SolverEvent | None = parser.feed(line)
        if event is None:
            return

        if isinstance(event, ConvergenceSample):
            samples.append(event)
            if stream is not None:
                record: dict[str, object] = {
                    "wall_time": event.wall_time,
                    "solver": event.solver,
                    "loop": event.loop,
                    "iter": event.iteration,
                    "nrm": event.norm,
                    "relc": event.relative_change,
                }
                _ = stream.write(json.dumps(record) + "\n")
                stream.flush()
        elif isinstance(event, SolverMessage):
            messages.append(event)

        if on_event is not None:
            on_event(event)

        if isinstance(event, ConvergenceSample):
            log.info(renderer.render(event))
        elif isinstance(event, SolverMessage):
            (log.warning if event.severity == "warning" else log.error)(event.text)

    try:
        result: CommandResult = backend.solve(
            sim_dir,
            n_procs=n_procs,
            on_line=handle,
            timeout_s=timeout_s,
            cancel=cancel,
        )
    finally:
        if stream is not None:
            stream.close()

    return SolveRun(result=result, samples=samples, messages=messages)

elemental

elemental(variable: ElmerVariable | str) -> str

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

Source code in src/femforge/elmer/variables.py
def elemental(variable: ElmerVariable | str) -> str:
    """The variable's DG counterpart: Elmer registers a solver's elemental field as ``"<name> e"``."""
    return f"{variable} e"