Source code for femorph_solver.estimators._problem
"""ProblemSpec — per-solve feature vector."""
from __future__ import annotations
from dataclasses import dataclass
[docs]
@dataclass(frozen=True)
class ProblemSpec:
"""Per-solve feature vector fed to the estimator.
Attributes
----------
n_dof
Number of free DOFs after BC reduction. The dominant
feature — factor cost scales ~``n_dof^(4/3)`` on 3D meshes
with nested dissection, solve cost ~``n_dof × log(n_dof)``.
n_modes
Modes requested. eigsh iteration count scales roughly
linearly with this at fixed tolerance.
nnz
Non-zeros in the reduced K (upper triangle). Captures
mesh connectivity density — a 3D plate has denser fill
per DOF than a 1D beam.
linear_solver
Backend name. ``mkl_direct`` vs ``pardiso`` vs ``cholmod``
have measurably different factor + solve constants; we
regress them separately rather than lumping.
eigen_solver
Eigen backend — ARPACK vs LOBPCG iterate differently.
"""
n_dof: int
n_modes: int
nnz: int = 0
linear_solver: str = "auto"
eigen_solver: str = "arpack"
#: Optional, set to True if the caller plans to use OOC. The
#: estimator knows OOC is ~3.5× the in-core wall and ~0.78× the
#: peak RSS (from TA-1 measurements) and applies that factor
#: post-prediction.
ooc: bool = False
[docs]
@classmethod
def from_model(
cls,
model,
n_modes: int,
*,
linear_solver: str = "auto",
eigen_solver: str = "arpack",
ooc: bool = False,
) -> ProblemSpec:
"""Extract a ProblemSpec from a :class:`~femorph_solver.Model`.
Uses the model's ``_dof_layout`` to count DOFs (cheap —
doesn't factor K). ``nnz`` is not computed here (would
require a full K assembly); the caller can supply it if
they have a cached matrix.
"""
# _dof_layout returns (node_nums, layout_dict, N) where N
# is the total DOF count before BC reduction. Actual free
# DOF count depends on prescribed BCs — approximate as
# ``N - len(prescribed)``.
_, _, n_dof_full = model._dof_layout()
try:
prescribed = model._prescribed_dofs()
n_dof_free = max(1, n_dof_full - len(prescribed))
except Exception:
n_dof_free = n_dof_full
return cls(
n_dof=n_dof_free,
n_modes=n_modes,
linear_solver=linear_solver,
eigen_solver=eigen_solver,
ooc=ooc,
)