"""High-level API for programmatic control of a PyFEM analysis.
This module provides the `PyFEMAPI` class which encapsulates the complete
calculation machinery (input reader, solver, output manager) and exposes a
step-wise interface so callers can advance the analysis one step at a time or
drive the entire run to completion.
Usage:
api = PyFEMAPI('mycase.pro')
while api.is_active:
api.step()
results = api.get_results()
"""
from __future__ import annotations
from pathlib import Path
from typing import Any, Optional, Tuple, Union
from pyfem.io.InputReader import InputRead
from pyfem.io.InputReader import InputReader
from pyfem.io.OutputManager import OutputManager
from pyfem.solvers.Solver import Solver
[docs]
class PyFEMAPI:
"""Programmatic API to run PyFEM analyses step-by-step.
The class can be constructed with either:
- a path to a `.pro` file (string or Path), OR
- a pre-built `(props, globdat)` tuple returned by `InputRead`.
Methods:
step(): advance one solver/output cycle
run_all(): run until `globdat.active` becomes False
is_active: boolean property exposing `globdat.active`
get_results(): return a light-weight results dict (placeholder)
close(): finalize and free resources
"""
def __init__(self, props: Union[str, Path, Tuple[Any, Any]]) -> None:
"""Initialize API and internal components.
Args:
props: Either the path to a `.pro` file (or file basename), or a
tuple `(props, globdat)` as returned by `InputRead`.
"""
if isinstance(props, tuple) and len(props) == 2:
self.props, self.globdat = props
else:
# Accept either a filename string or Path; InputRead expects the
# pro filename as first argument.
fname = str(props)
self.props, self.globdat = InputRead(fname)
# Instantiate solver and output manager
self.solver = Solver(self.props, self.globdat)
self.output = OutputManager(self.props, self.globdat)
@property
def isActive(self) -> bool:
return bool(getattr(self.globdat, 'active', False))
[docs]
def step(self , nCyc: int = 1 ) -> None:
"""Perform a single solver step (by default) followed by output processing ."""
for iCyc in range(nCyc):
if not self.is_active:
return
self.solver.run(self.props, self.globdat)
self.output.run(self.props, self.globdat)
[docs]
def runAll(self) -> None:
"""Run steps until the analysis completes."""
while self.is_active:
self.step()
[docs]
def getResults(self) -> Any:
"""Return a lightweight results container.
This is a small convenience wrapper; consumers can directly inspect
`self.globdat` for detailed state if needed.
"""
return {
'active': self.is_active,
'globdat': self.globdat,
'props': self.props,
}
[docs]
def close(self) -> None:
"""Finalize the analysis and close global data resources."""
try:
self.globdat.close()
except Exception:
# Best-effort close; do not propagate to callers
pass
[docs]
def run(props: Union[str, Path, Tuple[Any, Any]]) -> Any:
"""Convenience function mirroring the old `run` behavior used in scripts.
Creates a `PyFEMAPI` instance, runs the analysis to completion and returns
the results dictionary.
"""
api = PyFEMAPI(props)
api.run_all()
return api.get_results()