Skip to content

Config

The composed simulation config that ties the mesh, the domain and the Elmer physics together, and the solve frame it maps the authored model into.

femforge.config

The composed simulation config: the composition root that ties the gmsh mesh, the domain and the Elmer physics areas together.

Classes:

  • RunMeta –

    Searchable run metadata: a description and free key=value tags, recorded verbatim in the bundle manifest and

  • SimulationConfig –

    The single config spanning the whole pipeline, one area model per concern; solved_physics / resolved_stack give what the run solves under frame.

RunMeta pydantic-model

Bases: ConfigArea

Searchable run metadata: a description and free key=value tags, recorded verbatim in the bundle manifest and NEVER part of the run identity — editing them re-labels a run without re-solving it.

Config:

  • arbitrary_types_allowed: True
  • use_attribute_docstrings: True

Fields:

description pydantic-field

description: str | None = None

One line on what this run is for (shown in the driver log, searchable via run.find_runs).

tags pydantic-field

tags: TagDict

Free key=value labels (the same tag-dict shape the geometry uses); find_runs matches by subset.

matches

matches(selector: Mapping[str, str]) -> bool

Whether these tags satisfy every selector pair (the same containment rule as EntityTag.matches).

Source code in src/femforge/config.py
def matches(self, selector: Mapping[str, str]) -> bool:
    """Whether these tags satisfy every selector pair (the same containment rule as ``EntityTag.matches``)."""
    return all(self.tags.get(key) == value for key, value in selector.items())

SimulationConfig pydantic-model

Bases: ConfigArea

The single config spanning the whole pipeline, one area model per concern; solved_physics / resolved_stack give what the run solves under frame.

Config:

  • validate_assignment: True

Fields:

Validators:

geometry pydantic-field

geometry: SerializeAsAny[GeometryArea] | None = None

Optional parametric geometry: when set, the driver calls its build_step into the run directory before meshing and repoints mesh.step at it — the STEP is derived output, and the run identity comes from these parameters rather than an authored file.

frame pydantic-field

frame: Frame

How the authored model maps onto the solve: as-is in 3D, or its axisymmetric r–z half-plane.

mesh pydantic-field

mesh: MeshConfig

The gmsh mesh settings: STEP source, global element size, gmsh options.

domain pydantic-field

domain: DomainConfig

Materials + per-region tags.

physics pydantic-field

Which physics the Elmer stage solves.

meta pydantic-field

meta: RunMeta

Searchable description + tags; excluded from cache_key, so labeling never re-identifies a run.

frame_fits_inputs pydantic-validator

frame_fits_inputs(frame: Frame, info: ValidationInfo) -> Frame

On reassignment, reject a frame the domain or the physics selection cannot solve.

Source code in src/femforge/config.py
@field_validator("frame")
@classmethod
def frame_fits_inputs(cls, frame: Frame, info: ValidationInfo) -> Frame:
    """On reassignment, reject a ``frame`` the domain or the physics selection cannot solve."""
    domain: object = info.data.get("domain")
    physics: object = info.data.get("physics")

    if isinstance(domain, DomainConfig):
        domain.check_frame(frame)

    if isinstance(physics, PhysicsSelection):
        physics.check_frame(frame)

    return frame

domain_fits_frame pydantic-validator

domain_fits_frame(domain: DomainConfig, info: ValidationInfo) -> DomainConfig

Reject conditions the frame cannot solve.

Source code in src/femforge/config.py
@field_validator("domain")
@classmethod
def domain_fits_frame(cls, domain: DomainConfig, info: ValidationInfo) -> DomainConfig:
    """Reject conditions the frame cannot solve."""
    frame: object = info.data.get("frame")

    if isinstance(frame, Solid | Axisymmetric):
        domain.check_frame(frame)

    return domain

physics_fits_frame pydantic-validator

physics_fits_frame(physics: PhysicsSelection, info: ValidationInfo) -> PhysicsSelection

Reject a selection the frame cannot solve.

Source code in src/femforge/config.py
@field_validator("physics")
@classmethod
def physics_fits_frame(cls, physics: PhysicsSelection, info: ValidationInfo) -> PhysicsSelection:
    """Reject a selection the frame cannot solve."""
    frame: object = info.data.get("frame")

    if isinstance(frame, Solid | Axisymmetric):
        physics.check_frame(frame)

    return physics

solved_physics

solved_physics() -> Physics

The physics the selection solves as under frame (MAGNETOSTATICS becomes MAGNETOSTATICS_2D when axisymmetric).

Source code in src/femforge/config.py
def solved_physics(self) -> Physics:
    """The physics the selection solves as under ``frame`` (``MAGNETOSTATICS`` becomes ``MAGNETOSTATICS_2D`` when axisymmetric)."""
    return self.physics.selected.in_frame(self.frame)

resolved_stack

resolved_stack() -> PhysicsConfig

The solver stack the run solves: the authored physics.overrides, else the solved physics' preset in frame.

Source code in src/femforge/config.py
def resolved_stack(self) -> PhysicsConfig:
    """The solver stack the run solves: the authored ``physics.overrides``, else the solved physics' preset in ``frame``."""
    if self.physics.overrides is not None:
        return self.physics.overrides

    return solved_stack(self.physics.selected, self.frame)

customize_physics

customize_physics() -> PhysicsConfig

Materialize the resolved stack into physics.overrides (idempotent) and return it for typed editing.

Source code in src/femforge/config.py
def customize_physics(self) -> PhysicsConfig:
    """Materialize the resolved stack into ``physics.overrides`` (idempotent) and return it for typed editing."""
    if self.physics.overrides is None:
        self.physics.overrides = solved_stack(self.physics.selected, self.frame)

    return self.physics.overrides

femforge.common.frame

The solve frame: how an authored model maps onto the frame Elmer solves in.

Classes:

  • Axis –

    A line through origin along direction, in the authored model's frame.

  • Solid –

    Solve the authored 3D model as-is.

  • Axisymmetric –

    Solve the r–z half-plane of a model symmetric about axis: X = radial, Y = along the axis, Z = 0.

Attributes:

  • ALIGNMENT_TOLERANCE –

    [-]: The largest transverse share of a unit direction still counted as parallel to an axis.

ALIGNMENT_TOLERANCE module-attribute

ALIGNMENT_TOLERANCE = 1e-06

[-]: The largest transverse share of a unit direction still counted as parallel to an axis.

Axis pydantic-model

Bases: ConfigArea

A line through origin along direction, in the authored model's frame.

Config:

  • frozen: True

Fields:

origin pydantic-field

origin: Vec3Field

[mm]: A point on the axis.

direction pydantic-field

direction: DirectionField

[-]: The axis direction; a scaled direction is the same axis.

label property

label: str

Z or -X for a coordinate axis through the origin, else its direction and origin.

along

along(direction: Vec3) -> float | None

The cosine between direction and this axis (±1), or None when they are not parallel within ALIGNMENT_TOLERANCE.

Source code in src/femforge/common/frame.py
def along(self, direction: Vec3) -> float | None:
    """The cosine between ``direction`` and this axis (±1), or None when they are not parallel within ``ALIGNMENT_TOLERANCE``."""
    unit: Vec3 = direction.normalized()
    cosine: float = unit.dot(self.direction)
    transverse: float = (unit - self.direction * cosine).magnitude()

    if transverse > ALIGNMENT_TOLERANCE:
        return None

    return cosine

Solid pydantic-model

Bases: ConfigArea

Solve the authored 3D model as-is.

Config:

  • frozen: True

Fields:

kind pydantic-field

kind: Literal['solid'] = 'solid'

Discriminator that names this frame in a serialized config.

label property

label: str

A short human-readable name.

model_dim property

model_dim: Literal[2, 3]

The mesh dimension this frame solves on.

Axisymmetric pydantic-model

Bases: ConfigArea

Solve the r–z half-plane of a model symmetric about axis: X = radial, Y = along the axis, Z = 0.

Config:

  • frozen: True

Fields:

Validators:

kind pydantic-field

kind: Literal['axisymmetric'] = 'axisymmetric'

Discriminator that names this frame in a serialized config.

axis pydantic-field

axis: Axis = Axis.Z

The authored model's axis of symmetry.

radial pydantic-field

radial: DirectionField | None = None

[-]: The direction from the axis toward the sectioned half-plane, stored as its unit part perpendicular to the axis; None picks the world axis least aligned with the symmetry axis.

label property

label: str

A short human-readable name.

model_dim property

model_dim: Literal[2, 3]

The mesh dimension this frame solves on.

perpendicular pydantic-validator

perpendicular(radial: Direction | None, info: ValidationInfo) -> Direction | None

Reject an axis-parallel radial and store its unit component perpendicular to the axis.

Source code in src/femforge/common/frame.py
@field_validator("radial")
@classmethod
def perpendicular(cls, radial: Direction | None, info: ValidationInfo) -> Direction | None:
    """Reject an axis-parallel radial and store its unit component perpendicular to the axis."""
    axis: object = info.data.get("axis")

    if radial is None or not isinstance(axis, Axis):
        return radial

    cosine: float = radial.dot(axis.direction)

    # A stored radial is already perpendicular; projecting it again would shift its last digit on every reload.
    if abs(cosine) <= DIRECTION_SLACK:
        return radial

    across: Vec3 = radial - axis.direction * cosine

    if across.magnitude() <= ALIGNMENT_TOLERANCE:
        raise ValueError(f"The radial direction {radial} is parallel to the symmetry axis {axis.direction}.")

    return Direction(across.x, across.y, across.z)