# ----------------------------------------------------------------------------------------
#
# 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 a set of tools to perform the analysis of a set of Fisher matrices.
"""
import copy
import math
# ***************************************************************************************
import os
import numpy as np
from cosmicfishpie.analysis import fisher_derived as fd
from cosmicfishpie.analysis import fisher_matrix as fm
from cosmicfishpie.analysis import fisher_operations as fo
from cosmicfishpie.analysis import utilities as fu
# ***************************************************************************************
[docs]
class CosmicFish_FisherAnalysis:
"""
This class takes care of handeling a set of Fisher matrices with plotting in mind.
This class is meant to hold a list of Fisher matrices and have defined on this list
a set of vectorized operations.
For now no caching is implemented.
:ivar fisher_list: list of Fisher matrices :class:`cosmicfish_pylib.fisher_matrix.fisher_matrix`
:ivar fisher_name_list: list of names of the Fisher matrices. The names are used as the unique
identifier of the Fisher matrix. No double name is enforced.
"""
# -----------------------------------------------------------------------------------
# class getters:
[docs]
def get_fisher_list(self):
""":returns: the list fisher matrices."""
return self.fisher_list
[docs]
def get_fisher_name_list(self):
""":returns: the list fisher matrices names. These are the unique identifiers of the list."""
return self.fisher_name_list
# -----------------------------------------------------------------------------------
def __init__(
self, fisher_list=None, fisher_path=None, search_fisher_guess=False, with_derived=True
):
"""
**CosmicFish_FisherAnalysis class constructor**. The constructor builds the Fisher matrices list.
If all the arguments of the constructor are None then the Fisher list will be created and will be empty.
If this is initialized with fisher_list then this will constitute the base Fisher set.
if this is initialized with fisher_path then the code will search the desired path for Fisher matrices.
If initialized with both fisher_list and fisher_path then both will be added.
:param fisher_list: Fisher matrix or list of Fisher matrices.
:type fisher_list: :class:`cosmicfish_pylib.fisher_matrix.fisher_matrix` or :class:`list` of :class:`cosmicfish_pylib.fisher_matrix.fisher_matrix`
:param fisher_path: path or list of paths to search for Fisher matrices.
:type fisher_path: :class:`string` or :class:`list` of :class:`string`
:param search_fisher_guess: wether to guess the Fisher matrix name or not.
Guessing assumes that the Fisher matrices have 'fisher_matrix' and '.dat' in the name as happens
with Fisher matrices produced with CosmicFish.
:type search_fisher_guess: :class:`bool`
:param with_derived: wether to search for derived Fisher matrices to add to the base Fisher that are found.
:type with_derived: :class:`bool`
"""
# create class instances:
self.feedback = 1
self.fisher_list = []
self.fisher_name_list = []
# check wether we have to do something:
if not (fisher_list is None and fisher_path is None):
# do the initialization from fisher_list:
if fisher_list is not None:
self.add_fisher_matrix(fisher_list)
# do the initialization from path:
if fisher_path is not None:
self.search_fisher_path(
fisher_path=fisher_path,
search_fisher_guess=search_fisher_guess,
with_derived=with_derived,
)
# -----------------------------------------------------------------------------------
def __del__(self):
"""
CosmicFish_FisherAnalysis class destructor.
Makes sure everything is gone when called.
"""
self.fisher_list[:] = []
self.fisher_name_list[:] = []
# -----------------------------------------------------------------------------------
[docs]
def search_fisher_path(self, fisher_path, search_fisher_guess=False, with_derived=True):
"""
Searches a path for fisher matrices.
Will detect wether fisher_path contains directly the paths to the Fisher files or folder.
If a list of folders is passed all the folders will be searched, first for Fisher matrices
then for derived Fisher matrices.
:param fisher_path: path or list of paths to search for Fisher matrices. If this contains Fisher matrices files those are imported as well.
:type fisher_path: :class:`string` or :class:`list` of :class:`string`
:param search_fisher_guess: wether to guess the Fisher matrix name or not.
Guessing assumes that the Fisher matrices have 'fisher_matrix' and '.dat' in the name as happens
with Fisher matrices produced with CosmicFish.
:type search_fisher_guess: :class:`bool`
:param with_derived: wether to search for derived Fisher matrices to add to the base Fisher that are found.
:type with_derived: :class:`bool`
"""
fisher_path = copy.deepcopy(fu.make_list(fisher_path))
# explore all paths:
fisher_files = []
derived_fisher_files = []
for path in fisher_path:
if os.path.isfile(path):
fisher_files.append(path)
derived_fisher_files.append(path)
continue
if search_fisher_guess:
# search for all files with fisher_matrix in the name and fisher_matrix_derived in the name
# this is the standard cosmicfish output and should be safe if you are using cosmicfish.
# get the file list:
files = [
os.path.join(path, f)
for f in os.listdir(path)
if os.path.isfile(os.path.join(path, f))
]
# filter the list by guessing the names to get Fisher matrices:
for f in files:
f_temp = os.path.basename(f)
if "fisher_matrix" in f_temp and ".dat" in f_temp and "_derived" not in f_temp:
fisher_files.append(f)
# filter the list by guessing the names to get derived Fisher
# matrices:
for f in files:
f_temp = os.path.basename(f)
if "fisher_matrix" in f_temp and "_derived" in f_temp and ".dat" in f_temp:
derived_fisher_files.append(f)
else:
# search for all the files and try to import them as fisher matrices then as derived fishers
# get the file list:
files = [
os.path.join(path, f)
for f in os.listdir(path)
if os.path.isfile(os.path.join(path, f))
]
for f in files:
fisher_files.append(f)
derived_fisher_files.append(f)
# import fisher matrices:
for file in fisher_files:
if self.feedback > 1:
print("Trying to import: " + file + " as a Fisher matrix: ", end=" ")
try:
self.fisher_list.append(fm.fisher_matrix(file_name=file))
self.fisher_name_list.append(self.fisher_list[-1].name)
if self.feedback > 1:
print("SUCCESS")
if self.feedback == 1:
print("Imported as a Fisher matrix: " + file)
# remove from the derived fishers if necessary:
derived_fisher_files.remove(file)
except Exception as e:
if self.feedback > 1:
print("FAIL")
print(("Exception: ", e))
# import derived fisher matrices:
if with_derived:
for file in derived_fisher_files:
# get the derived matrix:
if self.feedback > 1:
print("Trying to import: " + file + " as a derived Fisher matrix: ", end=" ")
try:
fisher_derived = fd.fisher_derived(file_name=file)
if self.feedback > 1:
print("SUCCESS")
except BaseException:
fisher_derived = None
if self.feedback > 1:
print("FAIL")
# add derived to the fisher matrix when possible:
if fisher_derived is not None:
for i in range(len(self.fisher_name_list)):
if self.feedback > 1:
print(
"Trying to add derived from: "
+ fisher_derived.name
+ " to the Fisher matrix: "
+ self.fisher_list[i].name,
end=" ",
)
try:
self.fisher_list[i] = fisher_derived.add_derived(
fisher_matrix=self.fisher_list[i], preserve_input=True
)
self.fisher_name_list[i] = self.fisher_list[i].name
if self.feedback > 1:
print("SUCCESS")
if self.feedback == 1:
print(
"Added derived parameters from: "
+ fisher_derived.name
+ " to Fisher matrix: "
+ self.fisher_list[i].name
)
except BaseException:
if self.feedback > 1:
print("FAIL")
# -----------------------------------------------------------------------------------
[docs]
def add_fisher_matrix(self, fisher):
"""
Add a set of Fisher matrices to the already existing set.
Rejects Fisher matrices if the name is double i.e. the name is the unique
identifier of the Fisher matrix.
Checks wether the elements that are passed are really Fisher matrices.
:param fisher_list: Fisher matrix or list of Fisher matrices.
:type fisher_list: :class:`cosmicfish_pylib.fisher_matrix.fisher_matrix` or :class:`list` of :class:`cosmicfish_pylib.fisher_matrix.fisher_matrix`
"""
fisher_in = copy.deepcopy(fu.make_list(fisher))
#
for i in fisher_in:
# check wether all the input elements are Fisher matrices:
if not isinstance(i, fm.fisher_matrix):
raise ValueError("The input element is not a Fisher matrix of type fisher_matrix.")
else:
# get the name of the Fisher matrix:
name_temp = copy.deepcopy(i.name)
# check wether it is already an element of the list:
if name_temp in self.fisher_name_list:
raise ValueError("A Fisher matrix with name " + name_temp + " already exists.")
else:
# add it to the Fisher list:
self.fisher_name_list.append(name_temp)
self.fisher_list.append(copy.deepcopy(i))
# -----------------------------------------------------------------------------------
[docs]
def get_fisher_matrix(self, names=None):
"""
Returns the list of Fisher matrices corresponding to the given names.
:param names: names of the Fisher matrices.
:type names: a :class:`string` or a :class:`list` of :class:`string`
:returns: a list containing the desired Fisher matrices. Notice if the name is not found
in the list no error is risen and and the entrance is just ignored.
:rtype: a :class:`list` of :class:`cosmicfish_pylib.fisher_matrix.fisher_matrix`
"""
# process names:
if names is None:
names_temp = self.fisher_name_list
else:
names_temp = [i for i in fu.make_list(names) if i in self.fisher_name_list]
# get the fishers:
fisher_list = []
for name in names_temp:
for fish in self.fisher_list:
if fish.name == name:
fisher_list.append(fish)
return fisher_list
# -----------------------------------------------------------------------------------
[docs]
def delete_fisher_matrix(self, names=None):
"""
Delete the fisher matrix or the fisher matrices in names from the Fisher list.
:param names: names of the Fisher matrices to delete.
:type names: a :class:`string` or a :class:`list` of :class:`string`
"""
# process names:
if names is None:
names_temp = self.fisher_name_list
else:
names_temp = [i for i in fu.make_list(names) if i in self.fisher_name_list]
# get names to delete:
names_to_delete = [i for i in names_temp if i in self.fisher_name_list]
# get indeces to delete:
indexes_to_delete = [i for i, s in enumerate(self.fisher_name_list) if s in names_to_delete]
# delete them. We have to use this way because fisher_matrix might not
# have a delete method and we do not want to move in memory the arrays.
for i in range(len(indexes_to_delete)):
self.fisher_name_list.pop(indexes_to_delete[i] - i)
self.fisher_list.pop(indexes_to_delete[i] - i)
# -----------------------------------------------------------------------------------
[docs]
def get_parameter_list(self, names=None):
"""
Returns the list of parameter names of all the matrices identified in names.
:param names: names of the Fisher matrices.
:type names: a :class:`string` or a :class:`list` of :class:`string`
:returns: a list containing the parameter names.
:rtype: a :class:`list` of :class:`string`
"""
# process names:
if names is None:
names_temp = self.fisher_name_list
else:
names_temp = [i for i in fu.make_list(names) if i in self.fisher_name_list]
# get the parameter names:
parameter_names = []
for i in self.get_fisher_matrix(names_temp):
for j in i.param_names:
if j not in parameter_names:
parameter_names.append(j)
return parameter_names
# -----------------------------------------------------------------------------------
[docs]
def reshuffle(self, params, names=None, update_names=True):
"""
Reshuffles all the Fisher matrices.
:param params: parameters to reshuffle.
:type params: a :class:`string` or a :class:`list` of :class:`string`
:param names: names of the Fisher matrices.
:type names: a :class:`string` or a :class:`list` of :class:`string`
:returns: a new Fisher list with the reshuffled Fishers
:rtype: a :class:`cosmicfish_pylib.fisher_plot_analysis.CosmicFish_FisherAnalysis`
"""
# process names:
if names is None:
names_temp = self.fisher_name_list
else:
names_temp = [i for i in fu.make_list(names) if i in self.fisher_name_list]
# create the empty Fisher list:
temp = CosmicFish_FisherAnalysis()
# get the parameter names:
for fish in self.get_fisher_matrix(names_temp):
parameters = [par for par in params if par in fish.get_param_names()]
if len(parameters) == 0:
continue # skip the element if it has no parameters
temp.add_fisher_matrix(fisher=fo.reshuffle(fish, parameters, update_names=update_names))
return temp
# -----------------------------------------------------------------------------------
[docs]
def marginalise(self, params, names=None, update_names=True):
"""
Marginalise all the Fisher matrices over all the parameters that are not in params.
:param params: list of names of the parameters of the output Fisher matrix,
in the order that will appear in the output Fisher matrix. All other parameters
will be marginalized over.
:type params: a :class:`string` or a :class:`list` of :class:`string`
:param names: names of the Fisher matrices.
:type names: a :class:`string` or a :class:`list` of :class:`string`
:returns: a new Fisher list with the reshuffled Fishers
:rtype: a :class:`cosmicfish_pylib.fisher_plot_analysis.CosmicFish_FisherAnalysis`
"""
# process names:
if names is None:
names_temp = copy.deepcopy(self.fisher_name_list)
else:
names_temp = copy.deepcopy(
[i for i in fu.make_list(names) if i in self.fisher_name_list]
)
# create the empty Fisher list:
temp = CosmicFish_FisherAnalysis()
# get the parameter names:
for fish in self.get_fisher_matrix(names_temp):
parameters = [par for par in params if par in fish.get_param_names()]
if len(parameters) > 0:
temp.add_fisher_matrix(
fisher=fo.marginalise(fish, parameters, update_names=update_names)
)
return temp
# -----------------------------------------------------------------------------------
[docs]
def get_parameter_latex_names(self, names=None):
"""
Returns a dictionary mapping parameter names and latex parameter names.
:param names: names of the Fisher matrices.
:type names: a :class:`string` or a :class:`list` of :class:`string`
:returns: a dictionary containing parameter names and the LaTeX parameter names.
:rtype: :class:`dict`
"""
# process names:
if names is None:
names_temp = self.fisher_name_list
else:
names_temp = [i for i in fu.make_list(names) if i in self.fisher_name_list]
# get the parameter names:
# get the names in latex:
latex_names = {}
for name in self.get_parameter_list(names_temp):
for fish in self.get_fisher_matrix(names_temp):
try:
latex_names[name] = fish.get_param_name_latex(name)
except BaseException:
continue
if name in latex_names:
continue
return latex_names
[docs]
def compare_fisher_results(
self,
parstomarg=None,
fisher_list=None,
export_json_path=None,
append=False,
overwrite=False,
extra_metadata=None,
):
"""
Compare and print results from Fisher matrices.
This method analyzes each Fisher matrix in the list, calculating and printing
various statistics including the Figure of Merit (FoM) and parameter constraints.
Parameters
----------
parstomarg : list of str, optional
List of parameter names to marginalize over when computing the FoM. If None,
the first two parameters of each Fisher matrix are used (independently per matrix).
fisher_list : list of FisherMatrix objects, optional
List of Fisher matrices to analyze. If None, uses the instance's fisher_list.
export_json_path : str, optional
If provided, a JSON file with the collected statistics is written here.
append : bool, default False
If True and export_json_path exists, merge/append current run results to the
existing JSON (keyed by Fisher name, overwriting duplicates for that name only).
overwrite : bool, default False
If True and export_json_path exists, the file is overwritten (takes precedence
over append). Ignored if append=True.
extra_metadata : dict, optional
Additional metadata to store at top-level of JSON output.
Notes
-----
This method modifies the instance's fisher_list if a new list is provided.
Returns the structured results list (same objects written to JSON when requested).
Prints
------
- Fisher matrix name
- Figure of Merit (FoM) for chosen marginalized parameters
- Per-parameter: fiducial, 1-sigma error, percent error
JSON Schema (export_json_path)
------------------------------
The JSON output has the following structure::
{
"metadata": {
"timestamp": "ISO-8601 str",
"parstomarg_user_provided": true,
"n_fishers": 3,
...extra_metadata
},
"results": [
{
"name": "FisherName",
"FoM": {
"parameters": ["p1", "p2", ...],
"value": 123.45
},
"parameters": [
{
"name": "param1",
"fiducial": 1.0,
"sigma": 0.1,
"percent_error": 10.0
},
...
]
},
...
]
}
"""
# Lazy imports to avoid adding module-level dependencies if unused
import datetime as _dt
import json
import os
if fisher_list is not None:
self.fisher_list = fisher_list
results_struct = []
for _, fish in enumerate(self.fisher_list):
print("----")
print("Fisher Name: ", fish.name)
# Use a fresh list per Fisher to avoid mutating the caller-supplied list across iterations
local_pars_to_marg = (
list(parstomarg) if parstomarg is not None else fish.get_param_names()[:2]
)
fiww = fo.marginalise(fish, local_pars_to_marg)
try:
det_val = fiww.determinant()
deFoM = float(math.sqrt(det_val)) if det_val >= 0 else float("nan")
except Exception:
deFoM = float("nan")
parstomarg_str = ",".join(local_pars_to_marg)
print(f"Fisher FoM in {parstomarg_str}: {deFoM:.3f}")
sigmas = fish.get_confidence_bounds()
fidus = fish.get_param_fiducial()
parnames = fish.get_param_names()
param_entries = []
for jj, par in enumerate(parnames):
fidu = float(fidus[jj])
abs_sig = float(abs(sigmas[jj]))
perc_err = (
float(100 * abs(sigmas[jj] / fidus[jj])) if fidus[jj] != 0 else float("inf")
)
print(
f"Parameter {par:<10s}, "
f"fiducial: {fidu:>8.3f}, "
f"1-sigma error: {abs_sig:>8.4f}, "
f"percent error: {perc_err:>8.1f}%"
)
param_entries.append(
{
"name": par,
"fiducial": fidu,
"sigma": abs_sig,
"percent_error": perc_err,
}
)
results_struct.append(
{
"name": fish.name,
"FoM": {"parameters": local_pars_to_marg, "value": deFoM},
"parameters": param_entries,
}
)
if export_json_path is not None:
# Prepare metadata
meta = {
# Timezone-aware UTC timestamp (avoid deprecated utcnow); normalized to trailing Z
"timestamp": _dt.datetime.now(_dt.timezone.utc)
.replace(microsecond=0)
.isoformat()
.replace("+00:00", "Z"),
"n_fishers": len(results_struct),
}
if extra_metadata:
try:
meta.update({k: v for k, v in extra_metadata.items()})
except Exception:
pass
out_obj = {"metadata": meta, "results": results_struct}
# Merge logic if append
if append and not overwrite and os.path.isfile(export_json_path):
try:
with open(export_json_path, "r") as f:
existing = json.load(f)
except Exception:
existing = {"metadata": {}, "results": []}
# Index by name for merging
existing_index = {r.get("name"): r for r in existing.get("results", [])}
for r in results_struct:
existing_index[r["name"]] = r # overwrite / add
merged_results = list(existing_index.values())
# Update metadata (keep history in a list if present)
history = existing.get("metadata", {}).get("history", [])
history.append(existing.get("metadata", {}))
out_obj["metadata"]["history"] = history[-10:] # keep last 10 entries
out_obj["results"] = merged_results
# Overwrite if requested or default behavior
if not append or overwrite or not os.path.isfile(export_json_path):
try:
os.makedirs(os.path.dirname(export_json_path), exist_ok=True)
except Exception:
pass
try:
with open(export_json_path, "w") as f:
json.dump(out_obj, f, indent=2)
print(f"[compare_fisher_results] JSON exported to: {export_json_path}")
except Exception as e: # pragma: no cover - best effort
print(f"[compare_fisher_results] Failed to write JSON: {e}")
return results_struct
# -----------------------------------------------------------------------------------
[docs]
def compute_plot_range(
self,
params=None,
confidence_level=0.6827,
range_factors={"default": 1.0},
names=None,
nice=True,
dimensions=1,
):
"""
Function that computes a meaningfull plot range for the plots involving the specified parameters
and the specified Fisher names.
:param params: name of the parameter or list of names of parameters.
:type params: a :class:`string` or a :class:`list` of :class:`string`
:param confidence_level: (optional) Confidence Level of the bounds. Default 68%.
:type confidence_level: :class:`float`
:param names: names of the Fisher matrices.
:type names: a :class:`string` or a :class:`list` of :class:`string`
:param nice: wether the number is properly rounded to be nice.
:type nice: :class:`bool`
:returns: a dictionary of name and bounds
:rtype: :class:`dict`
"""
# process names:
if names is None:
names_temp = self.fisher_name_list
else:
names_temp = [i for i in fu.make_list(names) if i in self.fisher_name_list]
# process parameters:
total_paramnames_list = self.get_parameter_list(names_temp)
if params is None:
params_temp = total_paramnames_list
else:
params_temp = [i for i in fu.make_list(params) if i in total_paramnames_list]
# get the fishers:
fisher_temp_list = self.get_fisher_matrix(names_temp)
# compute the confidence coefficient:
confidence_coefficient = fu.confidence_coefficient(confidence_level, dimensions=dimensions)
# get the ranges:
rangefinal = []
for i in params_temp:
lower_bound = []
upper_bound = []
for j in fisher_temp_list:
# get the index of the parameter:
try:
index = j.get_param_index(i)
except BaseException:
continue
# get sigma and fiducial:
sigma = j.get_fisher_inverse()[index, index]
sigma = math.sqrt(sigma)
fiducial = j.get_fiducial(i)
# apply rescaling factor
resc_def = range_factors["default"]
# try to find rescaling factor for given parameter, otherwise
# return default
resc = range_factors.get(i, resc_def)
# create sigmarang
sigmarang = confidence_coefficient * sigma * resc
# apply a coefficient to safeguard:
if not nice:
sigma = sigmarang
# store the values:
if nice:
lower_bound.append(
fu.significant_digits((fiducial - sigmarang, sigmarang), mode=2, digits=1)
)
upper_bound.append(
fu.significant_digits((fiducial + sigmarang, sigmarang), mode=0, digits=1)
)
else:
lower_bound.append(fiducial - sigmarang)
upper_bound.append(fiducial + sigmarang)
# decide what to use:
upper_bound = np.array(upper_bound)
lower_bound = np.array(lower_bound)
rangefinal.append([float(str(np.amin(lower_bound))), float(str(np.amax(upper_bound)))])
dict = {}
for i, j in zip(params_temp, range(len(params_temp))):
dict[i] = rangefinal[j]
return dict
# -----------------------------------------------------------------------------------
[docs]
def compute_gaussian(
self,
params=None,
confidence_level=0.6827,
names=None,
num_points=100,
normalized=False,
nice_bounds=True,
):
"""
Function that computes the (1D) gaussian distribution of a given parameter.
Returns a dictionary with all the meaningul information about the gaussian.
:param params: name of the parameter or list of names of parameters.
:type params: a :class:`string` or a :class:`list` of :class:`string`
:param confidence_level: (optional) Confidence Level of the bounds. Default 68%.
:type confidence_level: :class:`float`
:param names: names of the Fisher matrices.
:type names: a :class:`string` or a :class:`list` of :class:`string`
:param num_points: number of (x,y) points.
:type num_points: :class:`int`
:param normalized: wether the distribution is normalized or not.
:type normalized: :class:`bool`
:param nice_bounds: wether the x range is properly rounded to be nice or not.
:type nice_bounds: :class:`bool`
:returns: a dictionary mapping name and parameter to a tuple of: [x, y, [fiducial,sigma]]
:rtype: :class:`dict`
"""
# process names:
if names is None:
names_temp = self.fisher_name_list
else:
names_temp = [i for i in fu.make_list(names) if i in self.fisher_name_list]
# process parameters:
total_paramnames_list = self.get_parameter_list(names_temp)
if params is None:
params_temp = total_paramnames_list
else:
params_temp = [i for i in fu.make_list(params) if i in total_paramnames_list]
# get the fishers:
fisher_temp_list = self.get_fisher_matrix(names_temp)
# compute the confidence coefficient:
confidence_coefficient = fu.confidence_coefficient(confidence_level, dimensions=1)
# get the plot ranges:
plot_ranges = self.compute_plot_range(
params=params_temp,
confidence_level=confidence_level,
names=names_temp,
nice=nice_bounds,
dimensions=1,
)
# get the distributions:
gaussian_distro = {}
for par1 in params_temp:
dict_names = {}
for mat, name in zip(fisher_temp_list, names_temp):
# get the index of the parameter:
try:
index = mat.get_param_index(par1)
except BaseException:
dict_names[name] = [np.array([0.0]), np.array([0.0]), [0.0, 0.0]]
continue
# get sigma and fiducial:
sigma = math.sqrt(mat.get_fisher_inverse()[index, index])
fiducial = mat.get_fiducial(par1)
# get optimal x:
if nice_bounds:
lower_bound = plot_ranges[par1][0]
upper_bound = plot_ranges[par1][1]
else:
lower_bound = fiducial - confidence_coefficient * sigma
upper_bound = fiducial + confidence_coefficient * sigma
# nice_points_fact = 0.33 #factor to increase the range of plotted line in x when the plot range restricts the gaussian too much
# num_points = int(3*num_points)
# x_points = np.linspace( lower_bound-abs(nice_points_fact*lower_bound), upper_bound+abs(nice_points_fact*upper_bound), num_points )
x_points = np.linspace(lower_bound, upper_bound, num_points)
# get y:
if normalized:
y_points = np.array(
[
math.exp(-((x - fiducial) ** 2) / (2.0 * sigma**2))
/ (sigma * math.sqrt(2.0 * math.pi))
for x in x_points
]
)
else:
y_points = np.array(
[math.exp(-((x - fiducial) ** 2) / (2.0 * sigma**2)) for x in x_points]
)
dict_names[name] = [x_points, y_points, [fiducial, sigma]]
gaussian_distro[par1] = dict_names
return gaussian_distro
# -----------------------------------------------------------------------------------
[docs]
def compute_ellipse(
self, params1=None, params2=None, confidence_level=0.6827, names=None, num_points=100
):
"""
Function that computes the (2D) ellipses for a given parameters combination.
Returns a dictionary with all the meaningul information about the ellipses.
:param params1: name of the first parameter or list of names of parameters.
:type params1: a :class:`string` or a :class:`list` of :class:`string`
:param params2: name of the second parameter or list of names of parameters.
:type params3: a :class:`string` or a :class:`list` of :class:`string`
:param confidence_level: (optional) Confidence Level of the bounds. Default 68%.
:type confidence_level: :class:`float`
:param names: names of the Fisher matrices.
:type names: a :class:`string` or a :class:`list` of :class:`string`
:param num_points: number of (x,y) points.
:type num_points: :class:`int`
:returns: a dictionary mapping name and parameters to a tuple of: [x, y, [fiducial_x, fiducial_y, coeff_a, coeff_b, theta_0]]
:rtype: :class:`dict`
"""
# process names:
if names is None:
names_temp = self.fisher_name_list
else:
names_temp = [i for i in fu.make_list(names) if i in self.fisher_name_list]
# process parameters:
total_paramnames_list = self.get_parameter_list(names_temp)
if params1 is None:
params_temp_1 = total_paramnames_list
else:
params_temp_1 = [i for i in fu.make_list(params1) if i in total_paramnames_list]
if params2 is None:
params_temp_2 = total_paramnames_list
else:
params_temp_2 = [i for i in fu.make_list(params2) if i in total_paramnames_list]
# get the fishers:
fisher_temp_list = self.get_fisher_matrix(names_temp)
# compute the confidence coefficient:
confidence_coefficient = fu.confidence_coefficient(confidence_level, dimensions=2)
# print("2D confidence coefficients: ", confidence_coefficient)
# get the distributions:
gaussian_distro = {}
for par1 in params_temp_1:
dict_names_1 = {}
for par2 in params_temp_2:
dict_names_2 = {}
for mat, name in zip(fisher_temp_list, names_temp):
# get the index of the parameter:
try:
index_1 = mat.get_param_index(par1)
index_2 = mat.get_param_index(par2)
except BaseException:
dict_names_2[name] = [
np.array([0.0]),
np.array([0.0]),
[0.0, 0.0, 0.0, 0.0, 0.0],
]
continue
# get sigmas:
sigma_x = mat.get_fisher_inverse()[index_1, index_1]
sigma_y = mat.get_fisher_inverse()[index_2, index_2]
sigma_xy = mat.get_fisher_inverse()[index_1, index_2]
# get fiducial:
fiducial_x = mat.get_fiducial(par1)
fiducial_y = mat.get_fiducial(par2)
# compute the ellipse coefficients:
coeff_a = confidence_coefficient * math.sqrt(
(sigma_x + sigma_y) / 2.0
+ math.sqrt((sigma_x - sigma_y) ** 2 / 4.0 + sigma_xy**2)
)
coeff_b = confidence_coefficient * math.sqrt(
(sigma_x + sigma_y) / 2.0
- math.sqrt((sigma_x - sigma_y) ** 2 / 4.0 + sigma_xy**2)
)
theta_0 = math.atan2((2.0 * sigma_xy), (sigma_x - sigma_y)) / 2.0
# generate the ellipses
angles = np.linspace(0, 2.0 * math.pi, num_points)
x_points = np.array(
[
+coeff_a * math.cos(theta) * math.cos(theta_0)
- coeff_b * math.sin(theta) * math.sin(theta_0)
+ fiducial_x
for theta in angles
]
)
y_points = np.array(
[
+coeff_a * math.cos(theta) * math.sin(theta_0)
+ coeff_b * math.sin(theta) * math.cos(theta_0)
+ fiducial_y
for theta in angles
]
)
# save the result:
dict_names_2[name] = [
x_points,
y_points,
[fiducial_x, fiducial_y, coeff_a, coeff_b, theta_0],
]
dict_names_1[par2] = dict_names_2
gaussian_distro[par1] = dict_names_1
return gaussian_distro
# -----------------------------------------------------------------------------------
# ***************************************************************************************