Source code for popclass.posterior

"""
Utilities for creating the posterior and inference data objects for interfacing
with ``popclass``' classification function.
"""
import copy

import numpy as np


[docs] class InferenceData: """ ``popclass`` version of an object containing the inference data for classification, including posterior samples and prior density. Similar to ``popclass.Posterior``, but is intended to include prior information to be passed to the classifier. """ def __init__(self, posterior, prior_density): """ Initialize the InferenceData object. Args: posterior (popclass.Posterior): A posterior object in popclass formatting convention, containing posterior samples of the shape (number of samples, number of parameters) prior_density (array-like): 1D array representing the prior density with an expected shape of (number of samples,) """ self.posterior = posterior self.prior_density = prior_density
[docs] class Posterior: """ ``popclass`` object containing the user's posterior information. This object can either be initialized from data arrays, or come from outside libraries in a compatible format. Acceptable formats from outside sources are listed below. **Supported Formats**: * ArViz * BAGLE (Microlensing specific, see below) """ def __init__(self, samples, parameter_labels): """ Initialize posterior object. Args: samples (array-like): Posterior samples with a shape of (number of samples, number of parameters). Rows correspond to individual samples drawn from the posterior distribution, and columns correspond to specific parameters. parameter_labels (list[str]): List of strings representing the labels of the parameters. There should be an equal number of labels to columns in samples representing individual parameters (i.e. the number of parameters). Raises: ValueError: if the number of parameters is not less than the number of samples. """ testnan = np.isnan(samples) if True in testnan: raise ValueError("Posterior samples cannot be NaN") # Check that number of samples > number of parameters if samples.shape[0] <= samples.shape[1]: raise ValueError( "Number of samples must be greater than number of parameters!" ) self.parameter_labels = parameter_labels self.samples = samples
[docs] def marginal(self, parameter_list): """ Get marginal distribution for some ordered subset of parameters in ``Posterior`` Args: parameter_list (list[str]): List of parameters for generating a marginal distribution. Should be a subset of ``Posterior.parameter_labels()``. Returns: New instance of the ``Posterior`` object only containing samples determined and ordered by `parameter_list`. Raises: ValueError: if the number of parameters is not less than the number of samples. """ _1, id_arr_labels, id_arr_list = np.intersect1d( self.parameter_labels, parameter_list, return_indices=True ) marginal = copy.deepcopy(self) marginal.parameter_labels = list([parameter_list[i] for i in id_arr_list]) marginal.samples = self.samples[:, id_arr_labels] # Shape check if marginal.samples.shape[0] <= marginal.samples.shape[1]: raise ValueError( "Number of samples in marginal array must be greater than number of parameters!" ) return marginal
@property def parameters(self): """ Defines an ordered list of parameters for the ``Posterior`` object. Returns: parameters (list [str]): Ordered list of parameters in the ``Posterior`` object. """ return self.parameter_labels
[docs] def to_inference_data(self, prior_density): """ Go from the ``Posterior`` object to a new ``InferenceData`` object. Args: posterior_object (popclass.Posterior): Either a popclass ``Posterior`` or ``Posterior.marginal() `` object. prior_density (array-like): 1D array representing the prior density with an expected shape of (number of samples,). Prior density corresponds to samples in posterior_object, as the number of entries must match the number of rows in the posterior samples array. Returns: popclass.InferenceData: An ``InferenceData`` object that contains all the information needed to pass to a classifier. """ return InferenceData(posterior=self, prior_density=prior_density)
[docs] @classmethod def from_arviz(cls, arviz_posterior_object): """ Utility to convert an ArViz posterior object directly to popclass posterior object. Args: arviz_posterior_object (arviz.InferenceData): InferenceData from an ArViz run. Returns: popclass.Posterior: A ``popclass.Posterior`` object generated from the ArViz posterior. Raises: ValueError: if the number of parameters is not less than the number of samples. """ labels = list(arviz_posterior_object.posterior.data_vars.keys()) samples = arviz_posterior_object.posterior.to_dataarray().to_numpy().squeeze() samples_array = np.array(samples).swapaxes(0, 1) # Shape check if samples_array.shape[0] <= samples_array.shape[1]: raise ValueError( "Number of samples in arviz array must be greater than number of parameters!" ) return cls(samples_array, labels)
[docs] @classmethod def from_pymultinest(cls, pymultinest_analyzer_object, parameter_labels): """ Utility to convert a PyMultiNest posterior to a popclass posterior object. Args: pymultinest_analyzer_object: Analyzer object from PyMultiNest parameter_labels (list[str]): Ordered list of parameters. Should correspond to the order of parameters in ``pymultinest_analyzer_object``. Returns: popclass.Posterior: A ``Posterior`` object with samples from the PyMultiNest analysis. Raises: ValueError: if the number of parameters is not less than the number of samples. """ samples = pymultinest_analyzer_object.get_equal_weighted_posterior() # Shape check if samples.shape[0] <= samples.shape[1]: raise ValueError( "Number of samples in pymultinest array must be greater than number of parameters!" ) return Posterior(samples, parameter_labels)
# def convert_dynesty(dynesty_posterior_object, parameter_labels) -> Posterior: # """ # function should convert dynesty posterior object to our definition of Posterior. # """ # # samples = dynesty_posterior_object.results('samples') # # weights = dynesty_posterior_object.results('logwt') # samples = dynesty_posterior_object.sample_equal() # # return Posterior(samples, parameter_labels)