Source code for pystra.active_learning.results
"""Immutable records separating surrogate diagnostics and sampling error."""
from dataclasses import dataclass
import numpy as np
from scipy.stats import norm
from ._validation import _positive_integer
__all__ = ["ReliabilityEstimate", "LearningStep", "ActiveLearningResult"]
[docs]
@dataclass(frozen=True)
class ReliabilityEstimate:
"""Conditional failure estimate returned by a reliability estimator.
``sampling_cov`` quantifies sampling error conditional on the fitted
surrogate; it excludes surrogate bias. Infinity denotes unresolved
precision. ``sampling_interval`` is either None or a (lower, upper) pair
at ``confidence_level``. Its calculation belongs to the estimator, as do
importance weights and corrections for dependent samples. ``method``
names that calculation; ``sampling_dependence`` is 'independent' or
'dependent'. ``n_samples`` counts surrogate classifications, not true
limit-state evaluations.
``converged`` means the estimator reached its target event, separately
from satisfying a requested CoV. ``status`` explains incomplete sampling.
``diagnostics`` retains immutable method-specific records (SubsetRun for
replicated subset simulation). Incomplete estimates must not be accepted
as successful reliability results.
"""
failure_probability: float
sampling_cov: float
sampling_interval: tuple | None
confidence_level: float | None
n_samples: int
method: str
sampling_dependence: str
converged: bool = True
status: str = "completed"
diagnostics: tuple = ()
def __post_init__(self):
if (
not np.isfinite(self.failure_probability)
or not 0 <= self.failure_probability <= 1
):
raise ValueError("failure_probability must be finite and in [0, 1]")
if np.isnan(self.sampling_cov) or self.sampling_cov < 0:
raise ValueError("sampling_cov must be nonnegative or infinity")
_positive_integer(self.n_samples, "n_samples", 2)
if not isinstance(self.method, str) or not self.method.strip():
raise ValueError("method must describe the sampling calculation")
if self.sampling_dependence not in ("independent", "dependent"):
raise ValueError("sampling_dependence must be 'independent' or 'dependent'")
if not isinstance(self.converged, bool):
raise ValueError("converged must be a bool")
object.__setattr__(self, "diagnostics", tuple(self.diagnostics))
if self.sampling_interval is None:
if self.confidence_level is not None:
raise ValueError("confidence_level requires a sampling_interval")
else:
interval = tuple(float(value) for value in self.sampling_interval)
if len(interval) != 2 or not 0 <= interval[0] <= interval[1] <= 1:
raise ValueError("sampling_interval must be an ordered pair in [0, 1]")
if self.confidence_level is None or not 0 < self.confidence_level < 1:
raise ValueError("confidence_level must be in (0, 1)")
object.__setattr__(self, "sampling_interval", interval)
@property
def beta(self) -> float:
"""Normal-equivalent index; infinite at probability zero or one."""
return float(-norm.ppf(self.failure_probability))
[docs]
@dataclass(frozen=True)
class LearningStep:
"""One fit's probability diagnostics and cumulative true evaluation count.
``probability_band`` is the ascending pair of probabilities for
mean + 2*std <= 0 and mean - 2*std <= 0. The fixed MC workflow uses
candidate proportions; adaptive estimators supply their own measure.
``beta_band`` transforms those
endpoints into ascending reliability indices. These are surrogate
sensitivity diagnostics, not confidence intervals or true Pf bounds.
``learning_satisfied`` is the selection policy's threshold flag, before
applying the stopping policy. The default requires an interior exploratory
failure probability, under the estimator's sampling measure.
``estimation_converged`` and optional ``sampling_cov`` record the
exploratory estimator's completion and precision, not the final sample.
``bootstrap_probability_band`` is the min/max of actual replicate Pf
estimates on fixed IID normal enrichment, when requested. It need not
enclose the full-design mean prediction and is not a confidence interval.
"""
failure_probability: float
learning_score: float
n_limit_state_evaluations: int
probability_band: tuple
beta_band: tuple
learning_satisfied: bool
estimation_converged: bool = True
sampling_cov: float | None = None
bootstrap_probability_band: tuple | None = None
def __post_init__(self):
if self.bootstrap_probability_band is not None:
band = tuple(float(value) for value in self.bootstrap_probability_band)
if len(band) != 2 or not 0 <= band[0] <= band[1] <= 1:
raise ValueError("bootstrap_probability_band must be ordered in [0, 1]")
object.__setattr__(self, "bootstrap_probability_band", band)
@property
def beta(self) -> float:
"""Normal-equivalent index of the candidate probability."""
return float(-norm.ppf(self.failure_probability))
[docs]
@dataclass(frozen=True)
class ActiveLearningResult:
"""Snapshot including unfinished estimates, diagnostics and stopping status.
``estimate`` identifies the final estimator and its uncertainty semantics.
Probability, beta, sampling diagnostics and n_estimation are exposed as
convenient properties of that record. ``converged`` means the configured
stopping and sampling criteria passed; it does not guarantee accuracy or
discovery of all failure regions. History contains exploratory probability
diagnostics, separate from the independent final estimate.
"""
estimate: ReliabilityEstimate
converged: bool
status: str
n_limit_state_evaluations: int
history: tuple
@property
def failure_probability(self) -> float:
"""Final estimator's conditional failure probability."""
return self.estimate.failure_probability
@property
def beta(self) -> float:
"""Normal-equivalent index of the final probability."""
return self.estimate.beta
@property
def sampling_cov(self) -> float:
"""Final estimator's conditional sampling coefficient of variation."""
return self.estimate.sampling_cov
@property
def sampling_interval(self) -> tuple | None:
"""Final estimator's conditional sampling interval, if available."""
return self.estimate.sampling_interval
@property
def n_estimation(self) -> int:
"""Number of surrogate classifications used by the final estimator."""
return self.estimate.n_samples