Source code for fdscore.ers_ops

"""Algebraic post-processing for compatible extreme-response spectra.

This module provides safe combination utilities for
:class:`fdscore.types.ERSResult` objects that already share the same
engineering interpretation, oscillator grid, and compatibility
signature.
"""

from __future__ import annotations

from copy import deepcopy
from typing import Any, Sequence

import numpy as np

from .types import ERSResult
from .validate import ValidationError, assert_ers_compatible


def _copy_meta(ers: ERSResult) -> dict[str, Any]:
    return deepcopy(ers.meta or {})


def _copy_provenance(ers: ERSResult) -> dict[str, Any]:
    return deepcopy((ers.meta or {}).get("provenance", {}))


[docs] def envelope_ers(results: Sequence[ERSResult]) -> ERSResult: """Compute a pointwise envelope across compatible ERS results. Parameters ---------- results : sequence of ERSResult Response spectra to combine. All inputs must share the same frequency grid and ERS compatibility signature. Returns ------- ERSResult Envelope spectrum whose response at each oscillator frequency is the maximum of the corresponding input values. Notes ----- Compatibility is enforced internally, so the function will reject inputs that mix different response metrics, peak conventions, oscillator assumptions, or incompatible frequency grids. The returned result preserves the reference grid and stores provenance metadata recording the number and origin of contributing spectra. """ if len(results) == 0: raise ValidationError("results must not be empty.") ref = results[0] for other in results[1:]: assert_ers_compatible(ref, other) response = np.asarray(ref.response, dtype=float).copy() for ers in results[1:]: response_i = np.asarray(ers.response, dtype=float) if response_i.shape != response.shape: raise ValidationError("All ERS response arrays must match the reference shape.") response = np.maximum(response, response_i) meta = _copy_meta(ref) meta["provenance"] = { "source": "envelope_ers", "n_inputs": int(len(results)), "inputs": [ { "index": int(i), "provenance": _copy_provenance(ers), } for i, ers in enumerate(results) ], } return ERSResult(f=np.asarray(ref.f, dtype=float), response=response, meta=meta)