Source code for smefit.coefficients

# -*- coding: utf-8 -*-
import numpy as np
import pandas as pd


[docs] class Coefficient: """ Coefficient object Parameters ---------- name: str name of the operator corresponding to the Wilson coefficient minimum : float minimum value maximum : float maximum value value : float, optional best value. If None set to random between minimum and maximum constrain : dict, bool - if False, the parameter is free, default option - if True, the parameter is fixed to the given value - if dict the parameter is fixed to a function of other coefficients """ def __init__(self, name, minimum, maximum, value=None, constrain=False): self.name = name self.minimum = minimum self.maximum = maximum # determine if the parameter is free self.is_free = False self.constrain = None if constrain is False: if value is not None: raise ValueError( f"Wilson Coefficient {self.name} is free, but a value is specified" ) self.is_free = True elif constrain is True: if value is None: raise ValueError( f"Wilson Coefficient {self.name} is fixed, but no value is specified" ) elif isinstance(constrain, dict): value = 0.0 self.constrain = [self.build_additive_factor_dict(constrain)] elif isinstance(constrain, list): value = 0.0 self.constrain = [ self.build_additive_factor_dict(fact_dict) for fact_dict in constrain ] # if no value is already there, the parameter is free self.value = value if value is None: self.value = np.random.uniform(low=minimum, high=maximum)
[docs] @staticmethod def build_additive_factor_dict(constrain): r""" Build the dictionary for each additive factor appearing in the constrain: .. :math: \prod_{i=1} a_{i} c_{i}^{n_{i}} Parameters ---------- constrain: dict dict object with the form {'c1': a1, 'c2': [a2,n2], ...} Returns ------- factor_dict: dict dict object with the form {'c1': [a1,1], 'c2': [a2,n2], ...} """ factor_dict = {} # loop on free parameters appearing in the factor for key, factor in constrain.items(): if isinstance(factor, (int, float)): factor_dict[key] = np.array([factor, 1]) else: factor = np.array(factor) # if factor has not 2 elements or is not a number raise error if factor.size > 2 or factor.dtype not in [int, float]: raise ValueError(f"Unknown specified constrain {constrain}") factor_dict[key] = factor return factor_dict
def __repr__(self): return self.name def __eq__(self, coeff_other): return self.name == coeff_other.name def __lt__(self, coeff_other): return self.name < coeff_other.name def __add__(self, coeff_other): self.value += coeff_other.value return self
[docs] def update_constrain(self, inv_rotation): """Update the constrain when a new basis is chosen. Only linear constrain are supported. Parameters ---------- inv_rotation: pd.DataFrame rotation matrix from the original basis to the new_basis """ # loop on the sum and simplify the constrain old_coeffs = [(*factor.keys(),)[0] for factor in self.constrain] old_factors = [(*factor.values(),)[0][0] for factor in self.constrain] rot = inv_rotation[old_coeffs] new_constrain = (rot * old_factors).sum(axis=1) new_constrain = new_constrain[new_constrain != 0] self.constrain = [{k: v} for k, v in new_constrain.items()]
[docs] class CoefficientManager: """ Coefficient objcts manager Parameters ---------- input_array: np.ndarray or list list of `smefit.coefficients.Coefficient` instances """ def __init__(self, input_array): # all the numerical informations are stored into a DataFrame self._table = pd.DataFrame( np.array( [[o.value, o.minimum, o.maximum] for o in input_array], dtype=float ), columns=["value", "minimum", "maximum"], ) self._table.index = np.array([o.name for o in input_array], dtype=str) self.is_free = np.array([o.is_free for o in input_array], dtype=bool) # NOTE: this will not be updated. self._objlist = input_array @property def name(self): return np.array(self._table.index, dtype=str) @property def value(self): return np.array(self._table.value.values, dtype=float) @property def minimum(self): return self._table.minimum.values @property def maximum(self): return self._table.maximum.values @property def size(self): return self._table.shape[0]
[docs] @classmethod def from_dict(cls, coefficient_config): """ Create a coefficientManager from a dictionary Parameters ---------- coefficient_config : dict coefficients configuration dictionary Returns ------- coefficient_manager: `smefit.coefficients.CoefficientManager` instance of the class """ elements = [] for name, property_dict in coefficient_config.items(): constrain = ( property_dict["constrain"] if "constrain" in property_dict else False ) elements.append( Coefficient( name, property_dict["min"], property_dict["max"], constrain=constrain, value=property_dict.get("value", None), ) ) # make sure elements are sorted by names return cls(np.unique(elements))
def __getitem__(self, idx): # TODO: shall it return the object list element? # in that case it has to be updated if isinstance(idx, int): return self._table.iloc[idx] return self._table.loc[idx] @property def free_parameters(self): """Returns the table containing only free parameters""" return self._table[self.is_free]
[docs] def set_free_parameters(self, value): """Set the values of the free parmaters""" self._table.iloc[self.is_free, 0] = value
[docs] def set_constraints(self): r""" Sets constraints between coefficients according to the coefficient.constrain information: .. :math: c_{m} = \sum_{i=1} \prod_{j=1}^{N_i} a_{i,j} c_{i,j}^{n_{i,j}} """ # loop pn fixed coefficients for coefficient_fixed in self._objlist[np.invert(self.is_free)]: # skip coefficient fixed to a single value if coefficient_fixed.constrain is None: continue temp = 0.0 for add_factor_dict in coefficient_fixed.constrain: free_dofs = [ self._table.at[fixed_name, "value"] for fixed_name in add_factor_dict ] # matrix with multiplicative factors and exponents fact_exp = np.array((*add_factor_dict.values(),), dtype=float) temp += np.prod(fact_exp[:, 0] * np.power(free_dofs, fact_exp[:, 1])) self._table.at[coefficient_fixed.name, "value"] = temp
[docs] def update_constrain(self, inv_rotation): r"""Update the constraints according to rotation matrix. Only linear constrain are supported. Parameters ---------- inv_rotation: pd.DataFrame rotation matrix from the original basis to the new_basis """ for coefficient_fixed in self._objlist[~self.is_free]: # skip coefficient fixed to a single value if coefficient_fixed.constrain is None: continue coefficient_fixed.update_constrain(inv_rotation)