Source code for curepy.container.ancillary_parameter

"""Container for ancillary parameter information"""

import numpy as np
from typing import Optional
import punpy
import comet_maths as cm
import warnings
import curepy.utilities.utilities as util
from scipy.linalg import block_diag


[docs] class AncillaryParameter: def __init__( self, b: Optional[list] = None, u_b: Optional[list] = None, corr_b: Optional[list] = None, corr_between_b: Optional[np.ndarray] = None, b_samples: Optional[np.ndarray] = None, b_MC_steps: Optional[int] = None, ) -> None: """ Container for ancillary (forward-model) parameter data and uncertainties. :param b: List of ancillary parameter arrays. :param u_b: List of uncertainty arrays for each ancillary parameter. :param corr_b: List of error-correlation specifications for each ancillary parameter. Each element may be ``None``, ``"rand"``, ``"syst"``, or a square correlation matrix. :param corr_between_b: Correlation matrix between the different ancillary parameters. :param b_samples: Pre-computed Monte Carlo samples for the ancillary parameters. :param b_MC_steps: Number of Monte Carlo steps to use when generating ancillary parameter samples. """ self.b = None self.u_b = None self.corr_b = None self.corr_between_b = None if b is not None: self._format_ancillary_data(b, u_b, corr_b, corr_between_b) self.b_MC_steps = b_MC_steps self.b_samples = b_samples def _format_ancillary_data( self, b: list, u_b: Optional[list], corr_b: Optional[list], corr_between_b: Optional[np.ndarray], ) -> None: """ Validate and format ancillary parameter inputs into numpy arrays. Converts scalar values to 1-D arrays, handles ragged (non-rectangular) parameter lists, and formats correlation matrices via :func:`~curepy.utilities.utilities.format_correlation`. :param b: List of ancillary parameter arrays. :param u_b: List of uncertainty arrays for each ancillary parameter, or ``None``. :param corr_b: List of error-correlation specifications, or ``None``. :param corr_between_b: Correlation matrix between ancillary parameters, or ``None``. """ multiple_b = False if b is not None: for i in range(len(b)): if not hasattr(b[i], "__len__"): b[i] = np.array([b[i]]) else: b[i] = np.array(b[i]) try: self.b = np.array(b) except: multiple_b = True self.b = util.to_ragged_array(b) if u_b is not None: for i in range(len(u_b)): if u_b[i] is None: u_b[i] = np.zeros_like(self.b[i]) elif not hasattr(u_b[i], "__len__"): u_b[i] = np.array([u_b[i]]) else: u_b[i] = np.array(u_b[i]) if multiple_b: self.u_b = util.to_ragged_array(u_b) else: self.u_b = u_b if corr_b is not None: if multiple_b: self.corr_b = util.to_ragged_array( [ util.format_correlation(self.b[i], corr_b[i]) for i in range(self.b.shape[0]) ] ) else: self.corr_b = util.format_correlation(self.b, corr_b) if corr_between_b is not None: self.corr_between_b = np.array(corr_between_b)
[docs] def generate_b_samples(self): """ Generate Monte Carlo samples for the ancillary parameters. Uses :class:`punpy.MCPropagation` to draw samples from the joint distribution defined by ``b``, ``u_b``, ``corr_b``, and ``corr_between_b``. If ``b`` is ``None`` the resulting ``b_samples`` attribute is set to ``None``. If ``b_samples`` has already been provided it is left unchanged. """ if self.b is None: self.b_samples = None else: if self.b_samples is not None: self.b_samples = self.b_samples else: if self.u_b is not None: prop = punpy.MCPropagation(self.b_MC_steps) if self.b_samples is None: self.b_samples = prop.generate_MC_sample( self.b, self.u_b, self.corr_b, self.corr_between_b, ) else: self.b_samples = self.b
[docs] def calculate_b_cov(self) -> Optional[np.ndarray]: """ Calculate the full covariance matrix for all ancillary parameters. Constructs per-parameter correlation matrices, flattens and combines them with ``corr_between_b`` (if set) using ``comet_maths.calculate_flattened_corr``. Returns ``None`` if insufficient data is available and emits a warning. :returns: Combined covariance matrix for all ancillary parameters, or ``None`` if it cannot be computed. """ if ( self.b is None and self.u_b is None and (self.corr_b is None or self.corr_between_b is None) ): warnings.warn( "b, u_b, and a correlation matrix must be defined to calculate a covariance matrix" ) return None else: if self.corr_b is not None: for i in range(len(self.corr_b)): if self.corr_b[i] is None: self.corr_b[i] = np.eye(len(self.b[i])) if len(set([len(corr) for corr in self.corr_b])) == 1: total_corr = cm.calculate_flattened_corr( corrs=[corr for corr in self.corr_b], corr_between=( self.corr_between_b if self.corr_between_b is not None else np.eye(len(self.b)) ), ) else: total_corr = block_diag(*[corr for corr in self.corr_b]) warnings.warn( "Correlation matrices for each b have different shapes. Assuming no correlation between different b values." ) return cm.convert_corr_to_cov( total_corr, np.hstack([b.flatten() for b in self.u_b]) ) else: warnings.warn( "Correlation matrix must be defined to calculate covariance" ) return None