# ----------------------------------------------------------------------------------------
#
# This file is part of CosmicFish.
#
# Copyright (C) 2015-2017 by the CosmicFish authors
#
# The CosmicFish code is free software;
# You can use it, redistribute it, and/or modify it under the terms
# of the GNU General Public License as published by the Free Software Foundation;
# either version 3 of the License, or (at your option) any later version.
# The full text of the license can be found in the file LICENSE at
# the top level of the CosmicFish distribution.
#
# ----------------------------------------------------------------------------------------
"""
:synopsis: Module that contains the fisher_matrix class and the operations defined on it.
The idea is that of creating a Fisher matrix safely, with all operations being
well defined and safe-guarded once the input matrix is accepted as a Fisher
matrix.
"""
# ***************************************************************************************
import copy
import os
from pathlib import Path
import numpy as np
from cosmicfishpie.analysis import utilities as fu
# ***************************************************************************************
[docs]
class fisher_matrix:
"""
This class contains the relevant code to define a fisher matrix
and basic operations on it.
:ivar fisher_cutoff: cutoff for the spectrum of the Fisher matrix. Parameter of the class. Starts at 10**(-9) but, if needed, is fixed during computations.
:ivar fisher_spectrum: maximum condition number allowed for the Fisher matrix. Worse constrained modes that go above this value will be flattened to this value.
:ivar fisher_matrix: numpy array with the fisher matrix. Passed to the constructor of by file.
:ivar path: absolute path of the input Fisher matrix. Computed at initialization if passing a file or just an empty string.
:ivar name: name of the input Fisher matrix. Computed at initialization if passing a file or just an empty string.
:ivar indir: absoulte path of the directory of the input Fisher matrix. Computed at initialization if passing a file or just an empty string.
:ivar num_params: number of parameters of the input Fisher matrix. Computed at initialization.
:ivar fisher_eigenvalues: eigenvalues of the input Fisher matrix. Computed at initialization or by the PCA function.
:ivar fisher_eigenvectors: eigenvectors of the input Fisher matrix. Computed at initialization or by the PCA function.
:ivar fisher_matrix_inv: inverse of the input Fisher matrix. Computed at initialization or by inverse_fisher_matrix.
:ivar param_names: name of the parameters of the input Fisher matrix. Passed to the constructor of by file.
:ivar param_names_latex: LaTeX name of the parameters of the Fisher matrix. Passed to the constructor of by file.
:ivar param_fiducial: numpy array with the values of the fiducial parameters. Passed to the constructor of by file.
:ivar param_names_dict: a dictionary that maps parameter names to numbers and vice versa.
.. automethod:: cosmicfishpie.analysis.fisher_matrix.fisher_matrix.__add__
.. automethod:: cosmicfishpie.analysis.fisher_matrix.fisher_matrix.__eq__
.. automethod:: cosmicfishpie.analysis.fisher_matrix.fisher_matrix.__ne__
"""
# -----------------------------------------------------------------------------------
# class getters:
[docs]
def get_fisher_matrix(self):
""":returns: the fisher matrix as a numpy array."""
return self.fisher_matrix
[docs]
def get_fisher_eigenvalues(self):
""":returns: the eigenvalues of the Fisher matrix as a numpy array."""
return self.fisher_eigenvalues
[docs]
def get_fisher_eigenvectors(self):
""":returns: the eigenvectors of the Fisher matrix."""
return self.fisher_eigenvectors
[docs]
def get_fisher_inverse(self):
""":returns: the inverse of the Fisher matrix."""
return self.fisher_matrix_inv
[docs]
def get_param_names(self):
""":returns: the parameter names of the Fisher matrix."""
return self.param_names
[docs]
def get_param_names_latex(self):
""":returns: the parameter names, in LaTeX format of the Fisher matrix."""
return self.param_names_latex
[docs]
def get_param_fiducial(self):
""":returns: the fiducial values of the parameters of the Fisher matrix."""
return self.param_fiducial
[docs]
def get_param_names_fiducial_dict(self):
""":returns: a dictionary with parameter names as keys and fiducial values as values."""
return dict(zip(self.param_names, self.param_fiducial))
# -----------------------------------------------------------------------------------
# advanced class getters:
[docs]
def get_param_name(self, number):
"""
Returns the name of the parameter corresponding to the given number.
:param number: number of the parameter or list of numbers. Notice that parameters are numbered starting from 1.
:type number: :class:`int` or a :class:`list` of :class:`int`
:returns: the name or a list of names of the parameters.
:rtype: :class:`string` or a :class:`list` of :class:`string`.
"""
if isinstance(number, int):
return self.param_names_dict[number]
elif len(number) > 1:
return [self.param_names_dict[i] for i in number]
[docs]
def get_param_index(self, name):
"""
Returns the index of a parameter as specified by his name. Notice that indices starts at 0.
:param name: input name or list of names of the parameters.
:type name: :class:`string` or a :class:`list` of :class:`string`
:returns: the index of the parameter or a list of numbers.
:rtype: :class:`int` or a :class:`list` of :class:`int`
"""
if isinstance(name, str):
return self.param_names_dict[name] - 1
elif len(name) > 1:
return [self.param_names_dict[i] - 1 for i in name]
[docs]
def get_param_number(self, name):
"""
Returns the number of a parameter as specified by his name.
Notice this differs from get_param_index becasue number = index+1.
:param name: input name or list of names of the parameters.
:type name: :class:`string` or a :class:`list` of :class:`string`
:returns: the index of the parameter or a list of numbers.
:rtype: :class:`int` or a :class:`list` of :class:`int`
"""
if isinstance(name, str):
return self.param_names_dict[name]
elif len(name) > 1:
return [self.param_names_dict[i] for i in name]
[docs]
def get_param_name_latex(self, name):
"""
Returns the Latex name of the parameter called name.
:param name: input name or list of names of the parameters.
:type name: :class:`string` or a :class:`list` of :class:`string`
:returns: the Latex name or a list of Latex names corresponding to the parameter names.
:rtype: :class:`string` or a :class:`list` of :class:`string`.
"""
if isinstance(name, str):
return self.param_names_latex[self.get_param_index(name)]
elif len(name) > 1:
return [self.param_names_latex[i] for i in self.get_param_index(name)]
[docs]
def get_fiducial(self, name):
"""
Returns the fiducial of the parameter called name.
:param name: input name or list of names of the parameters.
:type name: :class:`string` or a :class:`list` of :class:`string`
:returns: the fiducial or a list of fiducials corresponding to the parameter names.
:rtype: :class:`float` or a :class:`list` of :class:`float`.
"""
if isinstance(name, str):
return self.param_fiducial[self.get_param_index(name)]
elif len(name) > 1:
return [self.param_fiducial[i] for i in self.get_param_index(name)]
# -----------------------------------------------------------------------------------
def __init__(
self,
fisher_matrix=None,
param_names=None,
param_names_latex=None,
fiducial=None,
file_name=None,
name="",
):
"""
**fisher_matrix class constructor**. The constructor will read from file the Fisher matrix if it is initialized
with the name of a file (and the file exists). Otherwise it will read the matrix and the
parameter names as passed by the user.
:param fisher_matrix: array containing the input Fisher matrix. You can initialize a Fisher matrix
passing a matrix or by filename.
:type fisher_matrix: 2D :class:`list` or :class:`numpy.array`
:param param_names: names of the parameters of the Fisher matrix. If initialized from file it will
read them if a file file_name.paramnames is found. If a file is found and param_names are given to
the constructor the latter will be used. If param_names is None when itialized from
python it will just use some defaults names (p1, p2,...).
:type param_names: :class:`list` of :class:`string`
:param param_names_latex: LaTeX names of the parameters of the Fisher matrix. If initialized from file it will
read them if a file file_name.paramnames is found. If a file is found and param_names_latex are given to
the constructor the latter will be used. If it is none when itialized from
python it will just use some defaults names (p1, p2,...).
:type param_names_latex: :class:`list` of :class:`string`
:param fiducial: values of the fiducial parameters. If initialized from file it will have the value found in .paramnames.
If not found on a file it will be zero. Passing it to the constructor overwrites the values.
:type fiducial: :class:`list` of :class:`float` or :class:`numpy.array`
:param file_name: name of the file (and path) of the input Fisher matrix.
:type file_name: :class:`string`
"""
# check that the input is legal:
if fisher_matrix is None and file_name is None:
raise ValueError(
"Error in initializing the Fisher matrix: fisher_matrix and file_name are both None."
)
# initialize all the objects:
self.fisher_cutoff = 10 ** (-9)
self.fisher_spectrum = 10 ** (10)
self.fisher_matrix = np.array([])
self.file_name = file_name
self.path = ""
self.name = name
self.indir = ""
self.num_params = 0
self.fisher_eigenvalues = np.array([])
self.fisher_eigenvectors = np.array([[]])
self.fisher_matrix_inv = np.array([[]])
self.param_names = [""]
self.param_names_latex = [""]
self.param_fiducial = np.array([])
self.param_names_dict = {}
# initialize the Fisher matrix:
if fisher_matrix is None:
# initialize from file:
self.fisher_matrix = np.loadtxt(file_name)
# get the file name and path:
self.path = os.path.abspath(file_name)
self.name = os.path.splitext(os.path.basename(file_name))[0]
self.indir = os.path.dirname(self.path)
else:
# read the fisher matrix from input:
self.fisher_matrix = np.array(fisher_matrix)
self.name = name
# protection against 1D Fisher matrices
if np.ndim(self.fisher_matrix) == 0:
self.fisher_matrix = np.array([[self.fisher_matrix]])
elif np.ndim(self.fisher_matrix) == 1:
self.fisher_matrix = np.array([self.fisher_matrix])
# check if the Fisher matrix is symmetric:
try:
self.check_symmetric()
except ValueError as ve:
print("Exception: ", ve)
print("Symmetrizing matrix... ")
self.make_symmetric()
# protection against matrices with zeros
zeros_row = np.where(~self.fisher_matrix.any(axis=1))[0] # find rows with zeros
zeros_col = np.where(~self.fisher_matrix.any(axis=0))[0] # find cols with zeros
if zeros_col.any() and zeros_col.any():
print("Columns and Rows with zeros will be deleted")
temp_mat = np.delete(self.fisher_matrix, zeros_row, axis=0)
temp_mat = np.delete(temp_mat, zeros_col, axis=1)
self.fisher_matrix = np.copy(temp_mat)
# get the number of parameters:
self.num_params = self.fisher_matrix.shape[0]
# load the parameter names:
if param_names is None:
try:
self.load_paramnames_from_file()
except ValueError:
self.param_names = ["p" + str(i + 1) for i in range(self.num_params)]
self.param_names_latex = ["p" + str(i + 1) for i in range(self.num_params)]
self.param_fiducial = np.array([0.0 for i in self.param_names])
else:
self.param_names = copy.deepcopy(param_names)
self.param_names_latex = copy.deepcopy(param_names)
self.param_fiducial = np.array([0.0 for i in self.param_names])
# over write the fiducial if it is given:
if param_names_latex is not None:
self.param_names_latex = copy.deepcopy(param_names_latex)
if fiducial is not None:
self.param_fiducial = copy.deepcopy(fiducial)
self.param_fiducial = np.array(self.param_fiducial)
# check the validity:
if len(self.param_names) != self.num_params:
raise ValueError("The input param_names has not " + str(self.num_params) + " elements.")
if len(self.param_names_latex) != self.num_params:
raise ValueError(
"The input param_names_latex has not " + str(self.num_params) + " elements."
)
if len(self.param_fiducial) != self.num_params:
raise ValueError(
"The input param_fiducial has not " + str(self.num_params) + " elements."
)
# create a dictionary of param names:
self.param_names_dict = {}
for i in range(len(self.param_names)):
self.param_names_dict[i + 1] = self.param_names[i]
self.param_names_dict[self.param_names[i]] = i + 1
# do PCA and store results:
(self.fisher_eigenvalues, self.fisher_eigenvectors) = self.PCA()
# protect against degenerate parameters:
# self.protect_degenerate()
# invert the Fisher matrix and store the result:
self.fisher_matrix_inv = self.inverse_fisher_matrix()
# -----------------------------------------------------------------------------------
[docs]
def load_paramnames_from_file(self, file_name=None):
"""
Loads the paramnames array from a file.
:param file_name: (optional) file name and path of the parameter names file.
If file_name is None this reads the file self.name+.paramnames.
"""
if file_name is None:
name = os.path.join(self.indir, self.name + ".paramnames")
else:
name = file_name
# check input:
if not os.path.isfile(name):
raise ValueError("No .paramnames file found at: " + name)
# parse the param names:
self.param_names = []
self.param_names_latex = []
self.param_fiducial = []
with open(name) as f:
for line in f:
if line[0] != "#" and line[1] != "#":
split_line = [i.strip() for i in line.split(" ")]
split_line = [i for i in split_line if i != ""]
self.param_names.append(split_line[0].strip())
if len(split_line) == 1:
# latex and fiducial missing:
self.param_names_latex.append(split_line[0].strip())
self.param_fiducial.append(0.0)
elif len(split_line) == 2:
# one of the two is missing:
try:
temp = float(split_line[1].strip())
self.param_fiducial.append(temp)
self.param_names_latex.append(split_line[0].strip())
except BaseException:
temp = 0.0
self.param_fiducial.append(temp)
self.param_names_latex.append(split_line[1].strip())
elif len(split_line) >= 3:
# nothing is missing:
self.param_names_latex.append(split_line[1].strip())
self.param_fiducial.append(float(split_line[2].strip()))
self.param_fiducial = np.array(self.param_fiducial)
# print("DEBUG: ", self.param_names_latex)
# check the validity of the param names:
if len(self.param_names) != self.num_params:
raise ValueError(
"Error in load_paramnames_from_file: wrong number of parameters in the .paramnames file"
)
# -----------------------------------------------------------------------------------
[docs]
def save_paramnames_to_file(self, file_name=None):
"""
Saves the paramnames to a file.
:param file_name: (optional) file name and path of the parameter names file.
If file_name is None this saves the file self.name+.paramnames.
"""
if file_name is None:
name = self.indir + "/" + self.name + ".paramnames"
else:
name = file_name
# open the output file:
out_file = open(name, "w")
# write the header:
out_file.write("#\n")
out_file.write("# This file contains the parameter names for a Fisher matrix.\n")
out_file.write("#\n")
# write the parameters:
if sum(self.get_param_fiducial()) == 0:
print("** warning: all fiducials are set to zero in .paramnames file")
for ind in range(self.num_params):
param_name = self.get_param_name(ind + 1)
out_file.write(
str(param_name)
+ " "
+ str(self.get_param_name_latex(param_name))
+ " "
+ str(self.get_fiducial(param_name))
+ "\n"
)
# close the output file:
out_file.close()
# -----------------------------------------------------------------------------------
[docs]
def save_to_file(self, file_name, simple_header=False, file_format=".txt"):
"""Saves the fisher matrix to a file. Notice that the file name has to be specified
to avoid overwriting an existing fisher matrix.
:param file_name: file name and path of the output fisher matrix.
The file extension gets automatically added as is not needed.
"""
# save the param name file:
file_name = Path(file_name)
if file_name.suffix in [".txt", ".dat", ".mat"]:
file_name = str(file_name.with_suffix(""))
else:
file_name = str(file_name)
self.save_paramnames_to_file(file_name=file_name + ".paramnames")
# open the output file:
out_file = open(file_name + file_format, "w")
# write the header:
if not simple_header:
out_file.write("#\n")
out_file.write(
"# This file contains a Fisher matrix created with the CosmicFish code.\n"
)
out_file.write("#\n")
out_file.write("# The parameters of this Fisher matrix are:\n")
out_file.write("#\n")
# write the param names commented:
for ind in range(self.num_params):
param_name = self.get_param_name(ind + 1)
out_file.write(
"#"
+ " "
+ str(ind + 1)
+ " "
+ str(param_name)
+ " "
+ str(self.get_param_name_latex(param_name))
+ " "
+ str(self.get_fiducial(param_name))
+ "\n"
)
out_file.write("#\n")
elif simple_header:
parnamsarr = [self.get_param_name(ind + 1) for ind in range(self.num_params)]
out_file.write("# " + " ".join(parnamsarr))
out_file.write("\n")
# write the fisher matrix:
fisher_matrix = self.get_fisher_matrix()
for i in range(self.num_params):
for j in range(self.num_params):
out_file.write(str(format(fisher_matrix[i, j], ".16E")) + " ")
out_file.write("\n")
# close the output file:
out_file.close()
# -----------------------------------------------------------------------------------
[docs]
def __add__(self, other):
"""
Addition operator (+). Safeguarded agains adding Fisher matrices with different parameters
and different fiducials.
The addition will add parameters with the same name and append parameters with different names.
Notice that if a parameter is in one of the two Fisher matrices but not in the other it will be
assumed independent from the other.
"""
# get the new param names and fiducial:
param_names_new = copy.deepcopy(self.param_names)
param_names_latex_new = copy.deepcopy(self.param_names_latex)
param_fiducial_new = copy.deepcopy(self.param_fiducial)
rtol = 5e-4 # relative tolerance when adding Fishers of different fiducials
# check the parameters of the other Fisher matrix:
for i in other.param_names:
# if the parameter is in common check that they have the same
# fiducial value:
if i in self.param_names_dict:
ind1 = self.param_names_dict[i] - 1
ind2 = other.param_names_dict[i] - 1
if not np.allclose(
self.param_fiducial[ind1], other.param_fiducial[ind2], rtol=1e-6
):
print(
"Warning in addition: parameter "
+ str(i)
+ " has different fiducials: "
+ str(self.param_fiducial[ind1])
+ " and "
+ str(other.param_fiducial[ind2])
)
print("Accepted relative tolerance: " + str(rtol))
if not np.allclose(
self.param_fiducial[ind1], other.param_fiducial[ind2], rtol=rtol
):
raise ValueError(
"Error in addition: parameter "
+ str(i)
+ " has different fiducials: "
+ str(self.param_fiducial[ind1])
+ " and "
+ str(other.param_fiducial[ind2])
)
# otherwise we add them:
else:
ind = other.param_names_dict[i] - 1
param_names_new.append(i)
param_names_latex_new.append(other.param_names_latex[ind])
param_fiducial_new = np.append(param_fiducial_new, other.param_fiducial[ind])
# initialize an empty matrix:
num_param_new = len(param_names_new)
new_matrix = np.zeros([num_param_new, num_param_new])
# fill the new matrix:
for i in range(num_param_new):
for j in range(num_param_new):
# get the new parameters name at each entry:
x = param_names_new[i]
y = param_names_new[j]
# try to see if we have a corresponding entry on the first
# matrix:
try:
x1 = self.param_names_dict[x] - 1
y1 = self.param_names_dict[y] - 1
fact_1 = self.fisher_matrix[x1, y1]
except BaseException:
fact_1 = 0.0
# try to see if we have a corresponding entry on the second
# matrix:
try:
x1 = other.param_names_dict[x] - 1
y1 = other.param_names_dict[y] - 1
fact_2 = other.fisher_matrix[x1, y1]
except BaseException:
fact_2 = 0.0
# write the entrance of the new matrix:
new_matrix[i, j] = fact_1 + fact_2
# create the new Fisher matrix:
fisher_new = fisher_matrix(
fisher_matrix=new_matrix,
param_names=param_names_new,
param_names_latex=param_names_latex_new,
fiducial=param_fiducial_new,
)
fisher_new.name = self.name + "_" + other.name
fisher_new.path = self.path
fisher_new.indir = self.indir
return fisher_new
# -----------------------------------------------------------------------------------
[docs]
def __eq__(self, other):
"""
Equality check operator (==). Ensures equality in all properties of the Fisher matrix.
Notice that also name, path and indir are checked.
"""
try:
return_value = (
isinstance(other, fisher_matrix)
and np.allclose(self.fisher_cutoff, other.fisher_cutoff)
and np.allclose(self.fisher_matrix, other.fisher_matrix)
and self.path == other.path
and self.name == other.name
and self.indir == other.indir
and self.num_params == other.num_params
and np.allclose(self.fisher_eigenvalues, other.fisher_eigenvalues)
and np.allclose(self.fisher_eigenvectors, other.fisher_eigenvectors)
and np.allclose(self.fisher_matrix_inv, other.fisher_matrix_inv)
and self.param_names == other.param_names
and self.param_names_latex == other.param_names_latex
and np.allclose(self.param_fiducial, other.param_fiducial)
and self.param_names_dict == other.param_names_dict
)
except BaseException:
return_value = False
return return_value
# -----------------------------------------------------------------------------------
[docs]
def __ne__(self, other):
"""
Non-equality operator (!=). Simply implemented as the inverse of the equality operator.
"""
return not self.__eq__(other)
# -----------------------------------------------------------------------------------
[docs]
def inverse_fisher_matrix(self):
"""
Invert the Fisher matrix.
:returns: a matrix containing the inverse of the Fisher matrix.
:rtype: :class:`numpy.array`
"""
return np.linalg.inv(np.array(self.fisher_matrix))
# -----------------------------------------------------------------------------------
[docs]
def check_symmetric(self):
"""
Assert if the Fisher matrix is symmetric or not
:returns: a :class:`bool`
:rtype: :class:`bool`
"""
symm_bool = np.allclose(
self.fisher_matrix, np.transpose(self.fisher_matrix), rtol=1e-03, atol=1e-06
)
if not symm_bool:
raise ValueError(
"The input Fisher matrix {} is not equal to its transpose".format(self.name)
)
return symm_bool
[docs]
def make_symmetric(self, method=None):
"""
Transforms a non-symmetric matrix into a symmetric matrix
"""
m = self.fisher_matrix
if method is None:
# method = 'average'
method = "tril"
if method == "average":
sym = (1 / 2) * (m.T + m)
elif method == "tril":
sym = np.tril(m) + np.triu(m, 1)
self.fisher_matrix = sym
[docs]
def PCA(self):
"""
This function performs the principal component analysis of the Fisher matrix returning its eigenvalues and its eigenvectors.
As of now it just works just as a wrapper for numpy.
:returns: a :class:`list` with (eigenvalues, eigenvectors) as :class:`numpy.array`.
:rtype: :class:`list` of :class:`numpy.array`
"""
w, v = np.linalg.eigh(self.fisher_matrix)
return (w, v)
# -----------------------------------------------------------------------------------
[docs]
def determinant(self):
"""
This function returns the determinant of the Fisher matrix.
:return: a :class:`float` with the determinant of the Fisher matrix.
"""
return np.linalg.det(self.fisher_matrix)
# -----------------------------------------------------------------------------------
[docs]
def protect_degenerate(self, cache=True):
"""
Protects the Fisher matrix against degeneracies. Modifies the spectrum to ensure that
the absolute value of the eigenvalues is bounded. This will make the Fisher matrix
strictly positive definite. It will modify the magnitude of the worst constrained
parameter combinations without modifying the degeneracies directions.
:param cache: (optional) wether to use cached results or compute everything again
:type cache: bool
"""
# check cache:
if not cache:
(self.fisher_eigenvalues, self.fisher_eigenvectors) = self.PCA()
# get the eigenvalues and eigenvactors:
fisher_eigenvectors = self.fisher_eigenvectors
#
redo_PCA = False
# sometime the cutoff has to be adjusted if the condition number is too
# large:
minimum_spectrum = np.amin(np.abs(self.fisher_eigenvalues))
maximum_spectrum = np.amax(np.abs(self.fisher_eigenvalues))
if np.isclose(minimum_spectrum, 0.0):
condition_number = maximum_spectrum / self.fisher_cutoff
else:
condition_number = maximum_spectrum / minimum_spectrum
if np.abs(condition_number) > self.fisher_spectrum:
self.fisher_cutoff = maximum_spectrum / self.fisher_spectrum
# cycle through the eigenvalues
for i in range(len(self.fisher_eigenvalues)):
# detect very small eigenvalues
if self.fisher_eigenvalues[i] < self.fisher_cutoff:
# tell the code to redo PCA:
redo_PCA = True
# define the perturbation to the eigenvalue
self.fisher_eigenvalues[i] = self.fisher_cutoff
# modify the Fisher matrix if necessary:
if redo_PCA:
# write:
temp = np.diag(self.fisher_eigenvalues)
# get the Fisher:
self.fisher_matrix = np.dot(fisher_eigenvectors, np.dot(temp, fisher_eigenvectors.T))
# redo PCA:
(self.fisher_eigenvalues, self.fisher_eigenvectors) = self.PCA()
# -----------------------------------------------------------------------------------
[docs]
def get_confidence_bounds(self, confidence_level=0.6827, marginal=True, cache=False):
"""
Computes the marginal 1D confidence bounds on the Fisher parameters
:param confidence_level: (optional) C.L. of the bounds. Default 68%.
:type confidence_level: :class:`float` in [0,1]
:param cache: (optional) wether to use cached results or compute everything again
:type cache: bool
"""
# check input:
if confidence_level < 0.0 or confidence_level > 1.0:
raise ValueError("Invalid confidence level. Legal input is between 0 and 1.")
# invert the Fisher matrix:
if cache:
fisher_matrix_inv_temp = self.fisher_matrix_inv
else:
fisher_matrix_inv_temp = self.inverse_fisher_matrix()
# compute the coefficient that correspond to the desired confidence
# level:
coefficient = fu.confidence_coefficient(confidence_level)
if marginal:
errors = np.sqrt(np.diagonal(fisher_matrix_inv_temp))
else:
errors = np.sqrt(1.0 / np.diagonal(self.fisher_matrix))
# compute the result:
return coefficient * errors
# -----------------------------------------------------------------------------------
# class setters:
[docs]
def set_fisher_matrix(self, fisher_matrix):
"""
Function sets a new fisher matrix substituting the old one. Notice that
this will reset parameter names, latex parameter names and fiducial values.
:param fisher_matrix: :class:`numpy.array` containing the input Fisher matrix.
:type fisher_matrix: :class:`numpy.array`
"""
# initialize the Fisher matrix:
temp_fisher_matrix = np.array(fisher_matrix)
# protection against 1D Fisher matrices
if np.ndim(temp_fisher_matrix) == 0:
temp_fisher_matrix = np.array([[temp_fisher_matrix]])
elif np.ndim(temp_fisher_matrix) == 1:
temp_fisher_matrix = np.array([temp_fisher_matrix])
# check if the Fisher matrix is symmetric:
# accept the fisher matrix and start doing computations:
self.fisher_matrix = np.array(temp_fisher_matrix)
try:
self.check_symmetric()
except ValueError as ve:
print("Exception: ", ve)
print("Symmetrizing matrix... ")
self.make_symmetric()
# get the number of parameters:
self.num_params = self.fisher_matrix.shape[0]
# load the parameter names:
self.param_names = ["p" + str(i + 1) for i in range(self.num_params)]
self.param_names_latex = ["p" + str(i + 1) for i in range(self.num_params)]
self.param_fiducial = np.array([0.0 for i in self.param_names])
# re-create a dictionary of param names:
self.param_names_dict = {}
for i in range(len(self.param_names)):
self.param_names_dict[i + 1] = self.param_names[i]
self.param_names_dict[self.param_names[i]] = i + 1
# do PCA and store results:
(self.fisher_eigenvalues, self.fisher_eigenvectors) = self.PCA()
# protect against degenerate parameters:
# self.protect_degenerate( )
# invert the Fisher matrix and store the result:
self.fisher_matrix_inv = self.inverse_fisher_matrix()
[docs]
def set_param_names(self, param_names):
"""
Function sets a new list of param names substituting the old one. Notice that
latex parameter names will be reset.
:param param_names: list containing the new parameter names.
:type param_names: :class:`list` of :class:`string`
"""
# check the input:
if len(param_names) != self.num_params:
raise ValueError(
"set_param_names: the input param_names has not "
+ str(self.num_params)
+ " elements."
)
# accept input:
self.param_names = copy.deepcopy(param_names)
# overwrite the latex parameter names:
self.param_names_latex = copy.deepcopy(param_names)
# re-create a dictionary of param names:
self.param_names_dict = {}
for i in range(len(self.param_names)):
self.param_names_dict[i + 1] = self.param_names[i]
self.param_names_dict[self.param_names[i]] = i + 1
[docs]
def translate_param_names(self, trans_param_dict):
"""
Function substituting individual param names, effectively translating
between names.
:param trans_param_dict: dict containing the equivalence between names
:type trans_param_dict: :class:`dict` of :class:`string`
"""
new_names = [trans_param_dict.get(pp, pp) for pp in self.param_names]
# overwrite the latex parameter names:
self.param_names = copy.deepcopy(new_names)
# re-create a dictionary of param names:
self.param_names_dict = {}
for i in range(len(self.param_names)):
self.param_names_dict[i + 1] = self.param_names[i]
self.param_names_dict[self.param_names[i]] = i + 1
[docs]
def set_param_names_latex(self, param_names_latex):
"""
Function sets a new list of LaTeX param names substituting the old one.
:param param_names_latex: list containing the new LaTeX parameter names.
:type param_names: :class:`list` of :class:`string`
"""
# check the input:
if len(param_names_latex) != self.num_params:
raise ValueError(
"set_param_names_latex: the input param_names_latex has not "
+ str(self.num_params)
+ " elements."
)
# accept input:
self.param_names_latex = copy.deepcopy(param_names_latex)
[docs]
def set_fiducial(self, fiducial):
"""
Function sets a new fiducial substituting the old one.
:param fiducial: list containing the new fiducial.
:type param_names: :class:`list` of :class:`float`
"""
# check the input:
if len(fiducial) != self.num_params:
raise ValueError(
"set_fiducial: the input fiducial has not " + str(self.num_params) + " elements."
)
# accept input:
self.param_fiducial = np.array(fiducial)
# -----------------------------------------------------------------------------------
# ***************************************************************************************