Source code for nusa.post

"""Post-processing helpers for NuSA analysis results."""

from __future__ import annotations

import numpy as np

from .result import StaticResult


def _require_result(result):
    if not isinstance(result, StaticResult):
        raise TypeError("post-processing requires a StaticResult")


_ELEMENT_ALIASES = {
    "sxx": "stress_xx",
    "syy": "stress_yy",
    "sxy": "stress_xy",
    "exx": "strain_xx",
    "eyy": "strain_yy",
    "exy": "strain_xy",
}

_NODAL_ALIASES = {
    **_ELEMENT_ALIASES,
    "usum": "displacement_magnitude",
    "seqv": "von_mises_stress",
}


[docs] def element_field(result, name): """Return one canonical scalar element field in result order.""" _require_result(result) name = _ELEMENT_ALIASES.get(name, name) records = result.element_results if not records: return np.empty(0, dtype=float) if any(name not in record for record in records): raise ValueError(f"Element field {name!r} is not available") return np.array([record[name] for record in records], dtype=float)
def _displacement_component(result, name): try: component = result.displacement_dofs.index(name) except ValueError: return None width = len(result.displacement_dofs) values = result.displacements.reshape(-1, width) return values[:, component].copy()
[docs] def nodal_field(result, name, recovery="average"): """Return a nodal field from primary or recovered result quantities. Continuum element fields are recovered to nodes using an arithmetic average of adjacent element values. This is intentionally the first, explicit recovery policy; richer recovery methods can be added later. """ _require_result(result) name = _NODAL_ALIASES.get(name, name) direct = _displacement_component(result, name) if direct is not None: return direct if name == "displacement_magnitude": ux = _displacement_component(result, "ux") uy = _displacement_component(result, "uy") if ux is None and uy is None: raise ValueError("Displacement magnitude is not available") if ux is None: ux = np.zeros_like(uy) if uy is None: uy = np.zeros_like(ux) return np.sqrt(ux**2 + uy**2) if name == "von_mises_stress": sxx = nodal_field(result, "stress_xx", recovery=recovery) syy = nodal_field(result, "stress_yy", recovery=recovery) sxy = nodal_field(result, "stress_xy", recovery=recovery) return np.sqrt(sxx**2 - sxx * syy + syy**2 + 3.0 * sxy**2) if recovery != "average": raise ValueError( f"Unknown recovery method {recovery!r}; expected 'average'" ) element_values = element_field(result, name) totals = np.zeros(len(result.node_labels), dtype=float) counts = np.zeros(len(result.node_labels), dtype=int) for value, connectivity in zip(element_values, result.connectivity): for node_index in connectivity: totals[node_index] += value counts[node_index] += 1 if np.any(counts == 0): raise ValueError("Cannot recover a nodal field for unconnected nodes") return totals / counts