From 992abff11d18465cd4f38361026066d07ca889c4 Mon Sep 17 00:00:00 2001 From: Mark Nestor Costantini Date: Sun, 14 Apr 2024 10:09:10 +0100 Subject: [PATCH 01/35] added new pineparser from nnpdf and legacy option to load_fktable function --- validphys2/src/validphys/commondataparser.py | 2 + validphys2/src/validphys/fkparser.py | 10 +- validphys2/src/validphys/pineparser.py | 218 +++++++++++++++++++ 3 files changed, 228 insertions(+), 2 deletions(-) create mode 100644 validphys2/src/validphys/pineparser.py diff --git a/validphys2/src/validphys/commondataparser.py b/validphys2/src/validphys/commondataparser.py index ab2cbaf8c..02a89bb8c 100644 --- a/validphys2/src/validphys/commondataparser.py +++ b/validphys2/src/validphys/commondataparser.py @@ -13,6 +13,8 @@ from validphys.core import peek_commondata_metadata from validphys.coredata import CommonData +EXT = "pineappl.lz4" + def load_commondata(spec): """ Load the data corresponding to a CommonDataSpec object. diff --git a/validphys2/src/validphys/fkparser.py b/validphys2/src/validphys/fkparser.py index e2e4077db..da4779235 100644 --- a/validphys2/src/validphys/fkparser.py +++ b/validphys2/src/validphys/fkparser.py @@ -29,6 +29,7 @@ import pandas as pd from validphys.coredata import FKTableData, CFactorData +from validphys.pineparser import pineappl_reader @@ -53,8 +54,13 @@ class GridInfo: def load_fktable(spec): """Load the data corresponding to a FKSpec object. The cfactors will be applied to the grid.""" - with open_fkpath(spec.fkpath) as handle: - tabledata = parse_fktable(handle) + if spec.legacy: + with open_fkpath(spec.fkpath) as handle: + tabledata = parse_fktable(handle) + + else: + tabledata = pineappl_reader(spec) + if not spec.cfactors: return tabledata diff --git a/validphys2/src/validphys/pineparser.py b/validphys2/src/validphys/pineparser.py new file mode 100644 index 000000000..fc4847a05 --- /dev/null +++ b/validphys2/src/validphys/pineparser.py @@ -0,0 +1,218 @@ +""" + Loader for the pineappl-based FKTables + + The FKTables for pineappl have ``pineappl.lz4`` and can be utilized + directly with the ``pineappl`` cli as well as read with ``pineappl.fk_table`` +""" + +import logging + +import numpy as np +import pandas as pd + +from validphys.commondataparser import EXT +from validphys.coredata import FKTableData + +log = logging.getLogger(__name__) + + +def _pinelumi_to_columns(pine_luminosity, hadronic): + """Makes the pineappl luminosity into the column indices of a dataframe + These corresponds to the indices of a flattened (14x14) matrix for hadronic observables + and the non-zero indices of the 14-flavours for DIS + + Parameters + ---------- + pine_luminosity: list(tuple(int)) + list with a pair of flavours per channel + hadronic: bool + flag for hadronic / DIS observables + + Returns + ------- + list(int): list of labels for the columns + """ + evol_basis_pids = tuple( + [22, 100, 21, 200] + + [200 + n**2 - 1 for n in range(2, 6 + 1)] + + [100 + n**2 - 1 for n in range(2, 6 + 1)] + ) + flav_size = len(evol_basis_pids) + columns = [] + if hadronic: + for i, j in pine_luminosity: + idx = evol_basis_pids.index(i) + jdx = evol_basis_pids.index(j) + columns.append(flav_size * idx + jdx) + else: + # The proton might come from both sides + try: + columns = [evol_basis_pids.index(i) for _, i in pine_luminosity] + except ValueError: + columns = [evol_basis_pids.index(i) for i, _ in pine_luminosity] + return columns + + +def pineappl_reader(fkspec): + """ + Receives a fkspec, which contains the path to the fktables that are to be read by pineappl + as well as metadata that fixes things like conversion factors or apfelcomb flag. + The fkspec contains also the cfactors which are applied _directly_ to each of the fktables. + + The output of this function is an instance of FKTableData which can be generated from reading + several FKTable files which get concatenated on the ndata (bin) axis. + + For more information on the reading of pineappl tables: + https://pineappl.readthedocs.io/en/latest/modules/pineappl/pineappl.html#pineappl.pineappl.PyFkTable + + About the reader: + Each pineappl table is a 4-dimensional grid with: + (ndata, active channels, x1, x2) + for DIS grids x2 will contain one single number. + The luminosity channels are given in a (flav1, flav2) format and thus need to be converted + to the 1-D index of a (14x14) luminosity tensor in order to put in the form of a dataframe. + + All grids in pineappl are constructed with the exact same xgrid, + the active channels can vary and so when grids are concatenated for an observable + the gaps are filled with 0s. + + The pineappl grids are such that obs = sum_{bins} fk * f (*f) * bin_w + so in order to use them together with old-style grids (obs = sum_{bins} fk * xf (*xf)) + it is necessary to remove the factor of x and the normalization of the bins. + + About apfelcomb flags in yamldb files: + old commondata files and old grids have over time been through various iterations while remaining compatibility between each other, + and fixes and hacks have been incorporated in one or another + for the new theory to be compatible with old commpondata it is necessary + to keep track of said hacks (and to apply conversion factors when required) + NOTE: both conversion factors and apfelcomb flags will be eventually removed. + + Returns + ------- + validphys.coredata.FKTableData + an FKTableData object containing all necessary information to compute predictions + """ + from pineappl.fk_table import FkTable + + pines = [] + for fk_path in fkspec.fkpath: + try: + pines.append(FkTable.read(fk_path)) + except BaseException as e: + # Catch absolutely any error coming from pineappl, give some info and immediately raise + log.error(f"Fatal error reading {fk_path}") + raise e + + cfactors = fkspec.load_cfactors() + + # Extract metadata from the first grid + pine_rep = pines[0] + + # Is it hadronic? (at the moment only hadronic and DIS are considered) + hadronic = pine_rep.key_values()["initial_state_1"] == pine_rep.key_values()["initial_state_2"] + # Sanity check (in case at some point we start fitting things that are not protons) + if hadronic and pine_rep.key_values()["initial_state_1"] != "2212": + raise ValueError( + "pineappl_reader is not prepared to read a hadronic fktable with no protons!" + ) + Q0 = np.sqrt(pine_rep.muf2()) + xgrid = np.array([]) + for pine in pines: + xgrid = np.union1d(xgrid, pine.x_grid()) + xi = np.arange(len(xgrid)) + protected = False + + # Process the shifts and normalizations (if any), + # shifts is a dictionary with {fktable_name: shift_value} + # normalization instead {fktable_name: normalization to apply} + # since this parser doesn't know about operations, we need to convert it to a list + # then we just iterate over the fktables and apply the shift in the right order + shifts = fkspec.metadata.shifts + normalization_per_fktable = fkspec.metadata.normalization + fknames = [i.name.replace(f".{EXT}", "") for i in fkspec.fkpath] + if cfactors is not None: + cfactors = dict(zip(fknames, cfactors)) + + # fktables in pineapplgrid are for obs = fk * f while previous fktables were obs = fk * xf + # prepare the grid all tables will be divided by + if hadronic: + xdivision = np.prod(np.meshgrid(xgrid, xgrid), axis=0) + else: + xdivision = xgrid[:, np.newaxis] + + partial_fktables = [] + ndata = 0 + for fkname, p in zip(fknames, pines): + # Start by reading possible cfactors if cfactor is not empty + cfprod = 1.0 + if cfactors is not None: + for cfac in cfactors.get(fkname, []): + cfprod *= cfac.central_value + + # Read the table, remove bin normalization and apply cfactors + raw_fktable = (cfprod * p.table().T / p.bin_normalizations()).T + n = raw_fktable.shape[0] + + # Apply possible per-fktable fixes + if shifts is not None: + ndata += shifts.get(fkname, 0) + + if normalization_per_fktable is not None: + raw_fktable = raw_fktable * normalization_per_fktable.get(fkname, 1.0) + + # Add empty points to ensure that all fktables share the same x-grid upon convolution + missing_x_points = np.setdiff1d(xgrid, p.x_grid(), assume_unique=True) + for x_point in missing_x_points: + miss_index = list(xgrid).index(x_point) + raw_fktable = np.insert(raw_fktable, miss_index, 0.0, axis=2) + if hadronic: + raw_fktable = np.insert(raw_fktable, miss_index, 0.0, axis=3) + # Check conversion factors and remove the x* from the fktable + raw_fktable *= fkspec.metadata.conversion_factor / xdivision + + # Create the multi-index for the dataframe + # for optimized pineappls different grids can potentially have different indices + # so they need to be indexed separately and then concatenated only at the end + lumi_columns = _pinelumi_to_columns(p.lumi(), hadronic) + lf = len(lumi_columns) + data_idx = np.arange(ndata, ndata + n) + if hadronic: + idx = pd.MultiIndex.from_product([data_idx, xi, xi], names=["data", "x1", "x2"]) + else: + idx = pd.MultiIndex.from_product([data_idx, xi], names=["data", "x"]) + + # Now concatenate (data, x1, x2) and move the flavours to the columns + df_fktable = raw_fktable.swapaxes(0, 1).reshape(lf, -1).T + partial_fktables.append(pd.DataFrame(df_fktable, columns=lumi_columns, index=idx)) + + ndata += n + + # Finallly concatenate all fktables, sort by flavours and fill any holes + sigma = pd.concat(partial_fktables, sort=True, copy=False).fillna(0.0) + + # Check whether this is a 1-point normalization fktable and, if that's the case, protect! + if fkspec.metadata.operation == "RATIO" and len(pines) == 1: + # it _might_ be, check whether it is the divisor fktable + divisor = fkspec.metadata.FK_tables[-1][0] + name = fkspec.fkpath[0].name.replace(f".{EXT}", "") + + if np.allclose(sigma.loc[1:], 0.0): + # Old denominator fktables were filled with 0s beyond the first point + # and they needed to be post-processed to repeat the same point many time + # Instead, drop everything beyond the 1st point (used 0:0 to keep the same kind of df) + sigma = sigma.loc[0:0] + ndata = 1 + + if ndata == 1: + # There's no doubt + protected = divisor == name + + return FKTableData( + sigma=sigma, + ndata=ndata, + Q0=Q0, + metadata=fkspec.metadata, + hadronic=hadronic, + xgrid=xgrid, + protected=protected, + ) From c33b94124556295db35b11089a2a586d9b1bdaac Mon Sep 17 00:00:00 2001 From: Mark Nestor Costantini Date: Sun, 14 Apr 2024 10:55:33 +0100 Subject: [PATCH 02/35] added get_np_fkatable method to FKTableData --- validphys2/src/validphys/coredata.py | 44 ++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/validphys2/src/validphys/coredata.py b/validphys2/src/validphys/coredata.py index 4b8c3cd3d..42c4dd95b 100644 --- a/validphys2/src/validphys/coredata.py +++ b/validphys2/src/validphys/coredata.py @@ -97,6 +97,50 @@ def with_cuts(self, cuts): newsigma = self.sigma.loc[cuts] return dataclasses.replace(self, ndata=newndata, sigma=newsigma) + def get_np_fktable(self): + """Returns the fktable as a dense numpy array that can be directly + manipulated with numpy + + The return shape is: + (ndata, nx, nbasis) for DIS + (ndata, nx, nx, nbasis) for hadronic + where nx is the length of the xgrid + and nbasis the number of flavour contributions that contribute + """ + # Read up the shape of the output table + ndata = self.ndata + nx = len(self.xgrid) + nbasis = self.sigma.shape[1] + + if ndata == 0: + if self.hadronic: + return np.zeros((ndata, nbasis, nx, nx)) + return np.zeros((ndata, nbasis, nx)) + + # Make the dataframe into a dense numpy array + + # First get the data index out of the way + # this is necessary because cuts/shifts and for performance reasons + # otherwise we will be putting things in a numpy array in very awkward orders + ns = self.sigma.unstack(level=("data",), fill_value=0) + x1 = ns.index.get_level_values(0) + + if self.hadronic: + x2 = ns.index.get_level_values(1) + fk_raw = np.zeros((nx, nx, ns.shape[1])) + fk_raw[x2, x1, :] = ns.values + + # The output is (ndata, basis, x1, x2) + fktable = fk_raw.reshape((nx, nx, nbasis, ndata)).T + else: + fk_raw = np.zeros((nx, ns.shape[1])) + fk_raw[x1, :] = ns.values + + # The output is (ndata, basis, x1) + fktable = fk_raw.reshape((nx, nbasis, ndata)).T + + return fktable + @dataclasses.dataclass(eq=False) class CFactorData: From ccd7f14892bb42d12ad41d328d1b6c59d7393bed Mon Sep 17 00:00:00 2001 From: Mark Nestor Costantini Date: Sun, 14 Apr 2024 10:56:26 +0100 Subject: [PATCH 03/35] added legacy and metadata args to FKTableSpec --- validphys2/src/validphys/core.py | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/validphys2/src/validphys/core.py b/validphys2/src/validphys/core.py index 0772d948a..d2d1e0ec4 100644 --- a/validphys2/src/validphys/core.py +++ b/validphys2/src/validphys/core.py @@ -572,12 +572,27 @@ def __str__(self): return self.name class FKTableSpec(TupleComp): - def __init__(self, fkpath, cfactors, use_fixed_predictions=False, fixed_predictions_path=None): + def __init__(self, fkpath, cfactors, use_fixed_predictions=False, fixed_predictions_path=None, metadata=None): self.fkpath = fkpath - self.cfactors = cfactors + self.cfactors = cfactors if cfactors is not None else [] + self.legacy = False self.use_fixed_predictions = use_fixed_predictions self.fixed_predictions_path = fixed_predictions_path - super().__init__(fkpath, cfactors) + + if not isinstance(fkpath, (tuple, list)): + self.legacy = True + else: + fkpath = tuple(fkpath) + + self.metadata = metadata + + # For non-legacy theory, add the metadata since it defines how the theory is to be loaded + # and thus, it should also define the hash of the class + if not self.legacy: + super().__init__(fkpath, cfactors, self.metadata) + else: + super().__init__(fkpath, cfactors) + #NOTE: We cannot do this because Fkset owns the fktable, and trying #to reuse the loaded one fails after it gets deleted. From 2e641b0bf646229c218a2ad34e25b87fe476e4b4 Mon Sep 17 00:00:00 2001 From: Mark Nestor Costantini Date: Sun, 14 Apr 2024 10:56:50 +0100 Subject: [PATCH 04/35] first version of new fktable parser --- validphys2/src/validphys/n3fit_data_utils.py | 50 ++++++++++++++++++-- 1 file changed, 45 insertions(+), 5 deletions(-) diff --git a/validphys2/src/validphys/n3fit_data_utils.py b/validphys2/src/validphys/n3fit_data_utils.py index 243fdce0e..93a6b5433 100644 --- a/validphys2/src/validphys/n3fit_data_utils.py +++ b/validphys2/src/validphys/n3fit_data_utils.py @@ -5,7 +5,7 @@ """ import numpy as np import yaml -from validphys.fkparser import parse_cfactor +from validphys.fkparser import parse_cfactor, load_fktable from validphys.coredata import CFactorData @@ -62,6 +62,40 @@ def fk_parser(fk, is_hadronic=False): # reshape fktable = fktable_array.reshape(shape_out) + dict_out = { + "ndata": ndata, + "nbasis": nbasis, + "nonzero": nbasis, + "basis": basis, + "nx": nx, + "xgrid": xgrid, + "fktable": fktable, + } + + return dict_out + +def new_fk_parser(fkspec, is_hadronic=False): + """ + # Arguments: + - `fkspec`: fkspec object + + # Return: + - `dict_out`: dictionary with all information about the fktable + - 'xgrid' + - 'nx' + - 'ndata' + - 'basis' + - 'fktable' + """ + fktable = load_fktable(fkspec) + ndata = fktable.ndata + xgrid = fktable.xgrid + # n of active flavours + nbasis = len(fktable.sigma.columns) + basis = fktable.sigma.columns.to_numpy() + nx = len(xgrid) + fktable = fktable.get_np_fktable() + dict_out = { "ndata": ndata, "nbasis": nbasis, @@ -73,6 +107,7 @@ def fk_parser(fk, is_hadronic=False): } return dict_out + def parse_simu_parameters_names_CF(simu_parameters_names_CF, simu_parameters_linear_combinations, cuts): """ Returns a dictionary containing the bsm k-factor corrections @@ -151,9 +186,12 @@ def common_data_reader_dataset(dataset_c, dataset_spec): cuts = dataset_spec.cuts how_many = dataset_c.GetNSigma() dict_fktables = [] - for i in range(how_many): - fktable = dataset_c.GetFK(i) - dict_fktables.append(fk_parser(fktable, dataset_c.IsHadronic())) + for fkspec in dataset_spec.fkspecs: + dict_fktables.append(new_fk_parser(fkspec, dataset_c.IsHadronic())) + + # for i in range(how_many): + # fktable = dataset_c.GetFK(i) + # dict_fktables.append(fk_parser(fktable, dataset_c.IsHadronic())) dataset_dict = { "fktables": dict_fktables, @@ -194,7 +232,9 @@ def positivity_reader(pos_spec): pos_c = pos_spec.load() ndata = pos_c.GetNData() - parsed_set = [fk_parser(pos_c, pos_c.IsHadronic())] + # assuming that all positivity sets have only one fktable + # parsed_set = [fk_parser(pos_c, pos_c.IsHadronic())] + parsed_set = [new_fk_parser(pos_spec.fkspecs[0], pos_c.IsHadronic())] pos_sets = [ { From e27f7eeca25042932a747eda0bda45c0bf9923d2 Mon Sep 17 00:00:00 2001 From: Mark Nestor Costantini Date: Sun, 14 Apr 2024 14:59:21 +0100 Subject: [PATCH 05/35] added luminosity mapping method to FKTableData --- validphys2/src/validphys/coredata.py | 18 ++++++++++++++++++ validphys2/src/validphys/n3fit_data_utils.py | 12 ++++++------ 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/validphys2/src/validphys/coredata.py b/validphys2/src/validphys/coredata.py index 42c4dd95b..fb9038239 100644 --- a/validphys2/src/validphys/coredata.py +++ b/validphys2/src/validphys/coredata.py @@ -140,6 +140,24 @@ def get_np_fktable(self): fktable = fk_raw.reshape((nx, nbasis, ndata)).T return fktable + + + @property + def luminosity_mapping(self): + """Return the flavour combinations that contribute to the fktable + in the form of a single array + + The return shape is: + (nbasis,) for DIS + (nbasis*2,) for hadronic + """ + basis = self.sigma.columns.to_numpy() + if self.hadronic: + ret = np.zeros(14 * 14, dtype=bool) + ret[basis] = True + basis = np.array(np.where(ret.reshape(14, 14))).T.reshape(-1) + return basis + @dataclasses.dataclass(eq=False) diff --git a/validphys2/src/validphys/n3fit_data_utils.py b/validphys2/src/validphys/n3fit_data_utils.py index 93a6b5433..aa1c38a7e 100644 --- a/validphys2/src/validphys/n3fit_data_utils.py +++ b/validphys2/src/validphys/n3fit_data_utils.py @@ -87,14 +87,14 @@ def new_fk_parser(fkspec, is_hadronic=False): - 'basis' - 'fktable' """ - fktable = load_fktable(fkspec) - ndata = fktable.ndata - xgrid = fktable.xgrid + fktable_data = load_fktable(fkspec) + ndata = fktable_data.ndata + xgrid = fktable_data.xgrid # n of active flavours - nbasis = len(fktable.sigma.columns) - basis = fktable.sigma.columns.to_numpy() + basis = fktable_data.luminosity_mapping + nbasis = len(basis) nx = len(xgrid) - fktable = fktable.get_np_fktable() + fktable = fktable_data.get_np_fktable() dict_out = { "ndata": ndata, From 145d5289be1a5072c520737822d4839bc4bb21f4 Mon Sep 17 00:00:00 2001 From: Mark Nestor Costantini Date: Sun, 14 Apr 2024 15:03:55 +0100 Subject: [PATCH 06/35] xgrid reshape --- validphys2/src/validphys/n3fit_data_utils.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/validphys2/src/validphys/n3fit_data_utils.py b/validphys2/src/validphys/n3fit_data_utils.py index aa1c38a7e..bb25f4c3f 100644 --- a/validphys2/src/validphys/n3fit_data_utils.py +++ b/validphys2/src/validphys/n3fit_data_utils.py @@ -89,11 +89,18 @@ def new_fk_parser(fkspec, is_hadronic=False): """ fktable_data = load_fktable(fkspec) ndata = fktable_data.ndata - xgrid = fktable_data.xgrid + xgrid_flat = fktable_data.xgrid + nx = len(xgrid_flat) + + if is_hadronic: + xgrid = xgrid_flat.reshape(1, nx) + else: + xgrid = xgrid_flat.reshape(1, nx) + # n of active flavours basis = fktable_data.luminosity_mapping nbasis = len(basis) - nx = len(xgrid) + fktable = fktable_data.get_np_fktable() dict_out = { From 89fd055d972680bd7864b95c3be64d85a4418d98 Mon Sep 17 00:00:00 2001 From: Mark Nestor Costantini Date: Sun, 14 Apr 2024 15:39:20 +0100 Subject: [PATCH 07/35] include cuts when loading fk table, seems to works for DIS --- validphys2/src/validphys/n3fit_data_utils.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/validphys2/src/validphys/n3fit_data_utils.py b/validphys2/src/validphys/n3fit_data_utils.py index bb25f4c3f..c3ca324b9 100644 --- a/validphys2/src/validphys/n3fit_data_utils.py +++ b/validphys2/src/validphys/n3fit_data_utils.py @@ -74,7 +74,7 @@ def fk_parser(fk, is_hadronic=False): return dict_out -def new_fk_parser(fkspec, is_hadronic=False): +def new_fk_parser(fkspec, cuts, is_hadronic=False): """ # Arguments: - `fkspec`: fkspec object @@ -87,15 +87,15 @@ def new_fk_parser(fkspec, is_hadronic=False): - 'basis' - 'fktable' """ - fktable_data = load_fktable(fkspec) + if cuts: + fktable_data = load_fktable(fkspec).with_cuts(cuts) + else: + fktable_data = load_fktable(fkspec) ndata = fktable_data.ndata xgrid_flat = fktable_data.xgrid nx = len(xgrid_flat) - if is_hadronic: - xgrid = xgrid_flat.reshape(1, nx) - else: - xgrid = xgrid_flat.reshape(1, nx) + xgrid = xgrid_flat.reshape(1, nx) # n of active flavours basis = fktable_data.luminosity_mapping @@ -194,7 +194,7 @@ def common_data_reader_dataset(dataset_c, dataset_spec): how_many = dataset_c.GetNSigma() dict_fktables = [] for fkspec in dataset_spec.fkspecs: - dict_fktables.append(new_fk_parser(fkspec, dataset_c.IsHadronic())) + dict_fktables.append(new_fk_parser(fkspec, cuts, dataset_c.IsHadronic())) # for i in range(how_many): # fktable = dataset_c.GetFK(i) @@ -241,7 +241,7 @@ def positivity_reader(pos_spec): # assuming that all positivity sets have only one fktable # parsed_set = [fk_parser(pos_c, pos_c.IsHadronic())] - parsed_set = [new_fk_parser(pos_spec.fkspecs[0], pos_c.IsHadronic())] + parsed_set = [new_fk_parser(pos_spec.fkspecs[0], cuts=False, is_hadronic=pos_c.IsHadronic())] pos_sets = [ { From ff8691425182264f7956d897c4d61becbcef923c Mon Sep 17 00:00:00 2001 From: Mark Nestor Costantini Date: Sun, 14 Apr 2024 16:34:32 +0100 Subject: [PATCH 08/35] works with theory 270 --- validphys2/src/validphys/n3fit_data.py | 17 +++++++++++++---- validphys2/src/validphys/n3fit_data_utils.py | 5 ++++- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/validphys2/src/validphys/n3fit_data.py b/validphys2/src/validphys/n3fit_data.py index 6ac06e872..d842d8841 100644 --- a/validphys2/src/validphys/n3fit_data.py +++ b/validphys2/src/validphys/n3fit_data.py @@ -180,11 +180,20 @@ def _mask_fk_tables(dataset_dicts, tr_masks): vl_fks = [] ex_fks = [] vl_mask = ~tr_mask + for fktable_dict in dataset_dict["fktables"]: - tr_fks.append(fktable_dict["fktable"][tr_mask]) - vl_fks.append(fktable_dict["fktable"][vl_mask]) - ex_fks.append(fktable_dict.get("fktable")) - dataset_dict['ds_tr_mask'] = tr_mask + if not dataset_dict["use_fixed_predictions"]: + tr_fks.append(fktable_dict["fktable"][tr_mask]) + vl_fks.append(fktable_dict["fktable"][vl_mask]) + ex_fks.append(fktable_dict.get("fktable")) + dataset_dict['ds_tr_mask'] = tr_mask + # note: fixed observables have a fake fktable + else: + tr_fks.append(fktable_dict["fktable"]) + vl_fks.append([]) + ex_fks.append(fktable_dict.get("fktable")) + dataset_dict['ds_tr_mask'] = tr_mask + dataset_dict["tr_fktables"] = tr_fks dataset_dict["vl_fktables"] = vl_fks dataset_dict["ex_fktables"] = ex_fks diff --git a/validphys2/src/validphys/n3fit_data_utils.py b/validphys2/src/validphys/n3fit_data_utils.py index c3ca324b9..0cf3c577b 100644 --- a/validphys2/src/validphys/n3fit_data_utils.py +++ b/validphys2/src/validphys/n3fit_data_utils.py @@ -87,7 +87,10 @@ def new_fk_parser(fkspec, cuts, is_hadronic=False): - 'basis' - 'fktable' """ - if cuts: + # for fixed predictions the same fake fktable is always loaded + if fkspec.use_fixed_predictions: + fktable_data = load_fktable(fkspec) + elif cuts: fktable_data = load_fktable(fkspec).with_cuts(cuts) else: fktable_data = load_fktable(fkspec) From 8552a1eb34cdafac85ad38e69bb1805627a2a513 Mon Sep 17 00:00:00 2001 From: Mark Nestor Costantini Date: Sat, 27 Apr 2024 19:26:13 +0100 Subject: [PATCH 09/35] tmp modifications --- validphys2/src/validphys/config.py | 8 +++++-- validphys2/src/validphys/core.py | 36 +++++++++++++++++++++--------- validphys2/src/validphys/loader.py | 34 ++++++++++++++++++++++------ 3 files changed, 58 insertions(+), 20 deletions(-) diff --git a/validphys2/src/validphys/config.py b/validphys2/src/validphys/config.py index 8375b3f2e..1560b2674 100644 --- a/validphys2/src/validphys/config.py +++ b/validphys2/src/validphys/config.py @@ -507,7 +507,7 @@ def produce_simu_parameters_linear_combinations(self, simu_parameters=None): def parse_dataset_input(self, dataset: Mapping, simu_parameters_names, simu_parameters_scales, n_simu_parameters, simu_parameters_linear_combinations, simu_parameters=None): """The mapping that corresponds to the dataset specifications in the fit files""" - known_keys = {"dataset", "sys", "cfac", "frac", "weight", "custom_group", "simu_fac", "use_fixed_predictions", "contamination"} + known_keys = {"dataset", "sys", "cfac", "frac", "weight", "custom_group", "simu_fac", "use_fixed_predictions", "contamination", "new_commondata"} try: name = dataset["dataset"] if not isinstance(name, str): @@ -517,6 +517,7 @@ def parse_dataset_input(self, dataset: Mapping, simu_parameters_names, simu_para "'dataset' must be a mapping with " "'dataset' and 'sysnum'" ) + new_commondata = dataset.get("new_commondata", False) sysnum = dataset.get("sys") cfac = dataset.get("cfac", tuple()) frac = dataset.get("frac", 1) @@ -559,7 +560,8 @@ def parse_dataset_input(self, dataset: Mapping, simu_parameters_names, simu_para custom_group=custom_group, use_fixed_predictions=use_fixed_predictions, contamination=contamination, - **bsm_data + **bsm_data, + new_commondata=new_commondata, ) def parse_use_fitcommondata(self, do_use: bool): @@ -746,6 +748,7 @@ def produce_dataset( use_fixed_predictions = dataset_input.use_fixed_predictions contamination = dataset_input.contamination contamination_data = contamination_data + new_commondata = dataset_input.new_commondata try: ds = self.loader.check_dataset( @@ -763,6 +766,7 @@ def produce_dataset( use_fixed_predictions=use_fixed_predictions, contamination=contamination, contamination_data=contamination_data, + new_commondata=new_commondata, ) except DataNotFoundError as e: raise ConfigError(str(e), name, self.loader.available_datasets) diff --git a/validphys2/src/validphys/core.py b/validphys2/src/validphys/core.py index d2d1e0ec4..9adbcbf98 100644 --- a/validphys2/src/validphys/core.py +++ b/validphys2/src/validphys/core.py @@ -324,7 +324,7 @@ def plot_kinlabels(self): class DataSetInput(TupleComp): """Represents whatever the user enters in the YAML to specify a dataset.""" - def __init__(self, *, name, sys, cfac, frac, weight, custom_group, simu_parameters_names, simu_parameters_linear_combinations, use_fixed_predictions, contamination): + def __init__(self, *, name, sys, cfac, frac, weight, custom_group, simu_parameters_names, simu_parameters_linear_combinations, use_fixed_predictions, contamination, new_commondata): self.name=name self.sys=sys self.cfac = cfac @@ -335,6 +335,7 @@ def __init__(self, *, name, sys, cfac, frac, weight, custom_group, simu_paramete self.simu_parameters_linear_combinations = simu_parameters_linear_combinations self.use_fixed_predictions = use_fixed_predictions self.contamination = contamination + self.new_commondata = new_commondata super().__init__(name, sys, cfac, frac, weight, custom_group) def __str__(self): @@ -572,26 +573,28 @@ def __str__(self): return self.name class FKTableSpec(TupleComp): - def __init__(self, fkpath, cfactors, use_fixed_predictions=False, fixed_predictions_path=None, metadata=None): + def __init__(self, fkpath, cfactors, use_fixed_predictions=False, fixed_predictions_path=None, metadata=None, legacy=True): self.fkpath = fkpath self.cfactors = cfactors if cfactors is not None else [] - self.legacy = False + self.legacy = legacy self.use_fixed_predictions = use_fixed_predictions self.fixed_predictions_path = fixed_predictions_path - if not isinstance(fkpath, (tuple, list)): - self.legacy = True - else: - fkpath = tuple(fkpath) + # if not isinstance(fkpath, (tuple, list)): + # self.legacy = True + # else: + # fkpath = tuple(fkpath) + if not self.legacy: + fkpath = tuple([fkpath]) self.metadata = metadata # For non-legacy theory, add the metadata since it defines how the theory is to be loaded # and thus, it should also define the hash of the class - if not self.legacy: - super().__init__(fkpath, cfactors, self.metadata) - else: - super().__init__(fkpath, cfactors) + # if not self.legacy: + # super().__init__(fkpath, cfactors, self.metadata) + # else: + super().__init__(fkpath, cfactors) #NOTE: We cannot do this because Fkset owns the fktable, and trying @@ -600,6 +603,17 @@ def __init__(self, fkpath, cfactors, use_fixed_predictions=False, fixed_predicti def load(self): return FKTable(str(self.fkpath), [str(factor) for factor in self.cfactors]) + + def load_cfactors(self): + """Each of the sub-fktables that form the complete FKTable can have several cfactors + applied to it. This function uses ``parse_cfactor`` to make them into CFactorData + """ + from validphys.fkparser import parse_cfactor + if self.legacy: + raise NotImplementedError("cfactor loading from spec not implemented for old theories") + + return [[parse_cfactor(c.open("rb")) for c in cfacs] for cfacs in self.cfactors] + class PositivitySetSpec(DataSetSpec): """Extends DataSetSpec to work around the particularities of the positivity datasets""" diff --git a/validphys2/src/validphys/loader.py b/validphys2/src/validphys/loader.py index 1c911b3d8..6f8b058b3 100644 --- a/validphys2/src/validphys/loader.py +++ b/validphys2/src/validphys/loader.py @@ -351,7 +351,7 @@ def get_commondata(self, setname, sysnum): return cd.load() # @functools.lru_cache() - def check_fktable(self, theoryID, setname, cfac, use_fixed_predictions=False): + def check_fktable(self, theoryID, setname, cfac, use_fixed_predictions=False, new_commondata=False): _, theopath = self.check_theoryID(theoryID) if use_fixed_predictions: @@ -362,14 +362,33 @@ def check_fktable(self, theoryID, setname, cfac, use_fixed_predictions=False): fixed_predictions_path = theopath/ 'simu_factors' / ('SIMU_%s.yaml' % setname) cfactors = self.check_cfactor(theoryID, setname, cfac) return FKTableSpec(fkpath, cfactors, use_fixed_predictions=True, fixed_predictions_path=fixed_predictions_path) + + # use different file name for the FK table if the commondata is new + if new_commondata: + fkpath = tuple([theopath/ 'fastkernel' / (f'{setname}.pineappl.lz4')]) + for path in fkpath: + if not path.exists(): + raise FKTableNotFound(("Could not find FKTable for set '%s'. " + "File '%s' not found") % (setname, path) ) + else: + fkpath = theopath/ 'fastkernel' / ('FK_%s.dat' % setname) - fkpath = theopath/ 'fastkernel' / ('FK_%s.dat' % setname) - if not fkpath.exists(): - raise FKTableNotFound(("Could not find FKTable for set '%s'. " - "File '%s' not found") % (setname, fkpath) ) + if not fkpath.exists(): + raise FKTableNotFound(("Could not find FKTable for set '%s'. " + "File '%s' not found") % (setname, fkpath) ) cfactors = self.check_cfactor(theoryID, setname, cfac) - return FKTableSpec(fkpath, cfactors) + if new_commondata: + # load metadata into dataclass and pass it to FKTableSpec + metadata_path = theopath/ 'fastkernel' / f'{setname}_metadata.yaml' + + # load yaml metadata file into dict + with open(metadata_path, 'r') as stream: + metadata = yaml.safe_load(stream) + + return FKTableSpec(fkpath, cfactors, metadata=metadata, legacy=False) + else: + return FKTableSpec(fkpath, cfactors) def check_compound(self, theoryID, setname, cfac): thid, theopath = self.check_theoryID(theoryID) @@ -549,6 +568,7 @@ def check_dataset( use_fixed_predictions=False, contamination=None, contamination_data=None, + new_commondata=False, ): if not isinstance(theoryid, TheoryIDSpec): @@ -561,7 +581,7 @@ def check_dataset( try: fkspec, op = self.check_compound(theoryno, name, cfac) except CompoundNotFound: - fkspec = self.check_fktable(theoryno, name, cfac, use_fixed_predictions=use_fixed_predictions) + fkspec = self.check_fktable(theoryno, name, cfac, use_fixed_predictions=use_fixed_predictions, new_commondata=new_commondata) op = None #Note this is simply for convenience when scripting. The config will From 32a2891c1359779955803eaa949f3afd9d3d77ee Mon Sep 17 00:00:00 2001 From: Mark Nestor Costantini Date: Sun, 28 Apr 2024 14:09:36 +0100 Subject: [PATCH 10/35] added load_commondata method to CommonDataSpec --- validphys2/src/validphys/core.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/validphys2/src/validphys/core.py b/validphys2/src/validphys/core.py index 9adbcbf98..544a36191 100644 --- a/validphys2/src/validphys/core.py +++ b/validphys2/src/validphys/core.py @@ -316,6 +316,14 @@ def load(self)->CommonData: #TODO: Use better path handling in python 3.6 return CommonData.ReadFile(str(self.datafile), str(self.sysfile)) + def load_commondata(self, cuts=None): + # import here to avoid circular imports + from validphys.commondataparser import load_commondata + cd = load_commondata(self) + if cuts is not None: + cd = cd.with_cuts(cuts) + return cd + @property def plot_kinlabels(self): return get_plot_kinlabels(self) From dfa661729752d5915f25063f8925ba14e5cc3661 Mon Sep 17 00:00:00 2001 From: Mark Nestor Costantini Date: Sun, 28 Apr 2024 14:55:19 +0100 Subject: [PATCH 11/35] added commondatawriter module --- validphys2/src/validphys/commondatawriter.py | 84 ++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 validphys2/src/validphys/commondatawriter.py diff --git a/validphys2/src/validphys/commondatawriter.py b/validphys2/src/validphys/commondatawriter.py new file mode 100644 index 000000000..650df84cb --- /dev/null +++ b/validphys2/src/validphys/commondatawriter.py @@ -0,0 +1,84 @@ +""" +This module contains functions to write commondata and systypes +tables to files +""" + + +def write_commondata_data(commondata, buffer): + """ + write commondata table to buffer, this can be a memory map, + compressed archive or strings (using for instance StringIO) + + + Parameters + ---------- + + commondata : validphys.coredata.CommonData + + buffer : memory map, compressed archive or strings + example: StringIO object + + + Example + ------- + >>> from validphys.loader import Loader + >>> from io import StringIO + + >>> l = Loader() + >>> cd = l.check_commondata("NMC").load_commondata_instance() + >>> sio = StringIO() + >>> write_commondata_data(cd,sio) + >>> print(sio.getvalue()) + + """ + header = f"{commondata.setname} {commondata.nsys} {commondata.ndata}\n" + buffer.write(header) + commondata.commondata_table.to_csv(buffer, sep="\t", header=None) + + +def write_commondata_to_file(commondata, path): + """ + write commondata table to file + """ + with open(path, "w") as file: + write_commondata_data(commondata, file) + + +def write_systype_data(commondata, buffer): + """ + write systype table to buffer, this can be a memory map, + compressed archive or strings (using for instance StringIO) + + + Parameters + ---------- + + commondata : validphys.coredata.CommonData + + buffer : memory map, compressed archive or strings + example: StringIO object + + + Example + ------- + >>> from validphys.loader import Loader + >>> from io import StringIO + + >>> l = Loader() + >>> cd = l.check_commondata("NMC").load_commondata_instance() + >>> sio = StringIO() + >>> write_systype_data(cd,sio) + >>> print(sio.getvalue()) + + """ + header = f"{commondata.nsys}\n" + buffer.write(header) + commondata.systype_table.to_csv(buffer, sep="\t", header=None) + + +def write_systype_to_file(commondata, path): + """ + write systype table to file + """ + with open(path, "w") as file: + write_systype_data(commondata, file) From 9701a59601bb7728e5aa057fe7b5ede83338e3cb Mon Sep 17 00:00:00 2001 From: Mark Nestor Costantini Date: Sun, 28 Apr 2024 14:57:20 +0100 Subject: [PATCH 12/35] added export method to CommonData -> allows to use _filter_real_data python only --- validphys2/src/validphys/core.py | 4 ++++ validphys2/src/validphys/coredata.py | 19 +++++++++++++++++-- validphys2/src/validphys/filters.py | 2 +- 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/validphys2/src/validphys/core.py b/validphys2/src/validphys/core.py index 544a36191..2c45ff9fe 100644 --- a/validphys2/src/validphys/core.py +++ b/validphys2/src/validphys/core.py @@ -317,6 +317,10 @@ def load(self)->CommonData: return CommonData.ReadFile(str(self.datafile), str(self.sysfile)) def load_commondata(self, cuts=None): + """ + Loads a coredata.CommonData object from a core.CommonDataSetSpec object + cuts are applied if provided. + """ # import here to avoid circular imports from validphys.commondataparser import load_commondata cd = load_commondata(self) diff --git a/validphys2/src/validphys/coredata.py b/validphys2/src/validphys/coredata.py index fb9038239..c3b2026d1 100644 --- a/validphys2/src/validphys/coredata.py +++ b/validphys2/src/validphys/coredata.py @@ -5,11 +5,11 @@ """ import dataclasses -from typing import Dict +import yaml import numpy as np import pandas as pd - +from validphys.commondatawriter import write_commondata_to_file, write_systype_to_file @dataclasses.dataclass(eq=False) class FKTableData: @@ -361,3 +361,18 @@ def with_central_value(self, cv): tb = self.commondata_table.copy() tb["data"] = cv return dataclasses.replace(self, commondata_table=tb) + + def export(self, path): + """Export the data, and error types + Use the same format as libNNPDF: + + - A DATA_.dat file with the dataframe of accepted points + - A systypes/STYPES_.dat file with the error types + """ + + dat_path = path / f"DATA_{self.setname}.dat" + sys_path = path / "systypes" / f"SYSTYPE_{self.setname}_DEFAULT.dat" + sys_path.parent.mkdir(exist_ok=True) + + write_systype_to_file(self, sys_path) + write_commondata_to_file(self, dat_path) diff --git a/validphys2/src/validphys/filters.py b/validphys2/src/validphys/filters.py index 9c930c8f2..700dde0bd 100644 --- a/validphys2/src/validphys/filters.py +++ b/validphys2/src/validphys/filters.py @@ -164,7 +164,7 @@ def _filter_real_data(filter_path, data): nfull, ncut = _write_ds_cut_data(path, dataset) total_data_points += nfull total_cut_data_points += ncut - dataset.load().Export(str(path)) + dataset.commondata.load_commondata(cuts=dataset.cuts).export(path) return total_data_points, total_cut_data_points From e69ac936634b1a6d59dcb21dd20fe05a2eea80eb Mon Sep 17 00:00:00 2001 From: Mark Nestor Costantini Date: Sun, 28 Apr 2024 15:00:19 +0100 Subject: [PATCH 13/35] common_data_reader_dataset and experiment in n3fit_data_utils now only depend on dataset spec and not C++ --- validphys2/src/validphys/n3fit_data.py | 10 +++----- validphys2/src/validphys/n3fit_data_utils.py | 27 ++++++++++++-------- validphys2/src/validphys/utils.py | 1 - 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/validphys2/src/validphys/n3fit_data.py b/validphys2/src/validphys/n3fit_data.py index d842d8841..c4b68c88c 100644 --- a/validphys2/src/validphys/n3fit_data.py +++ b/validphys2/src/validphys/n3fit_data.py @@ -252,13 +252,9 @@ def fitting_data_dict( # TODO: Plug in the python data loading when available. Including but not # limited to: central values, ndata, replica generation, covmat construction if data.datasets: - try: - spec_c = data.load() - except: - breakpoint() - ndata = spec_c.GetNData() - expdata_true = spec_c.get_cv().reshape(1, ndata) - datasets = common_data_reader_experiment(spec_c, data) + ndata = sum([ds.commondata.load_commondata(cuts=ds.cuts).ndata for ds in data.datasets]) + expdata_true = np.array([ds.commondata.load_commondata(cuts=ds.cuts).central_values for ds in data.datasets]).reshape(1,ndata) + datasets = common_data_reader_experiment(data) for i in range(len(data.datasets)): if data.datasets[i].use_fixed_predictions: datasets[i]['use_fixed_predictions'] = True diff --git a/validphys2/src/validphys/n3fit_data_utils.py b/validphys2/src/validphys/n3fit_data_utils.py index 0cf3c577b..801e663f7 100644 --- a/validphys2/src/validphys/n3fit_data_utils.py +++ b/validphys2/src/validphys/n3fit_data_utils.py @@ -170,7 +170,7 @@ def parse_simu_parameters_names_CF(simu_parameters_names_CF, simu_parameters_lin return name_cfac_map -def common_data_reader_dataset(dataset_c, dataset_spec): +def common_data_reader_dataset(dataset_spec): """ Import fktable, common data and experimental data for the given data_name @@ -194,22 +194,28 @@ def common_data_reader_dataset(dataset_c, dataset_spec): instead of the dictionary object that model_gen needs """ cuts = dataset_spec.cuts - how_many = dataset_c.GetNSigma() dict_fktables = [] + + hadronic = load_fktable(dataset_spec.fkspecs[0]).hadronic + # check that all fkspecs have the same hadronicity for fkspec in dataset_spec.fkspecs: - dict_fktables.append(new_fk_parser(fkspec, cuts, dataset_c.IsHadronic())) - + if hadronic != load_fktable(fkspec).hadronic: + raise ValueError("All fkspecs in a dataset must have the same hadronicity") + + for fkspec in dataset_spec.fkspecs: + dict_fktables.append(new_fk_parser(fkspec, cuts, hadronic)) + # for i in range(how_many): # fktable = dataset_c.GetFK(i) # dict_fktables.append(fk_parser(fktable, dataset_c.IsHadronic())) dataset_dict = { "fktables": dict_fktables, - "hadronic": dataset_c.IsHadronic(), + "hadronic": hadronic, "operation": dataset_spec.op, - "name": dataset_c.GetSetName(), + "name": str(dataset_spec), "frac": dataset_spec.frac, - "ndata": dataset_c.GetNData(), + "ndata": dataset_spec.commondata.load_commondata(cuts=dataset_spec.cuts).ndata, "simu_parameters_names_CF": parse_simu_parameters_names_CF(dataset_spec.simu_parameters_names_CF, dataset_spec.simu_parameters_linear_combinations, cuts), "simu_parameters_names": dataset_spec.simu_parameters_names, } @@ -217,21 +223,20 @@ def common_data_reader_dataset(dataset_c, dataset_spec): return [dataset_dict] -def common_data_reader_experiment(experiment_c, experiment_spec): +def common_data_reader_experiment(experiment_spec): """ Wrapper around the experiments. Loop over all datasets in an experiment, calls common_data_reader on them and return a list with the content. # Arguments: - - `experiment_c`: c representation of the experiment object - `experiment_spec`: python representation of the experiment object # Returns: - `[parsed_datasets]`: a list of dictionaries output from `common_data_reader_dataset` """ parsed_datasets = [] - for dataset_c, dataset_spec in zip(experiment_c.DataSets(), experiment_spec.datasets): - parsed_datasets += common_data_reader_dataset(dataset_c, dataset_spec) + for dataset_spec in experiment_spec.datasets: + parsed_datasets += common_data_reader_dataset(dataset_spec) return parsed_datasets diff --git a/validphys2/src/validphys/utils.py b/validphys2/src/validphys/utils.py index 0c2956daa..f7bdb3e55 100644 --- a/validphys2/src/validphys/utils.py +++ b/validphys2/src/validphys/utils.py @@ -56,7 +56,6 @@ def parse_yaml_inp(inp, spec, path): current_exc = current_exc.__cause__ raise ValidationError('\n'.join(error_text_lines)) from e - @contextlib.contextmanager def tempfile_cleaner(root, exit_func, exc, prefix=None, **kwargs): """A context manager to handle temporary directory creation and From 8fe5beabdfb5e3c2e1809f131f7bbf7fbebd9f27 Mon Sep 17 00:00:00 2001 From: Mark Nestor Costantini Date: Sun, 28 Apr 2024 15:01:40 +0100 Subject: [PATCH 14/35] when new_commondata: True -> legacy: False -> pass a TheoryMeta to FKSpec and load it with pineappl_reader. Still needs more testing, especially does not support shifts and normalization yet --- validphys2/src/validphys/core.py | 4 +- validphys2/src/validphys/loader.py | 20 ++++-- validphys2/src/validphys/pineparser.py | 89 ++++++++++++++++++++++++-- 3 files changed, 98 insertions(+), 15 deletions(-) diff --git a/validphys2/src/validphys/core.py b/validphys2/src/validphys/core.py index 2c45ff9fe..fa275d87f 100644 --- a/validphys2/src/validphys/core.py +++ b/validphys2/src/validphys/core.py @@ -585,7 +585,7 @@ def __str__(self): return self.name class FKTableSpec(TupleComp): - def __init__(self, fkpath, cfactors, use_fixed_predictions=False, fixed_predictions_path=None, metadata=None, legacy=True): + def __init__(self, fkpath, cfactors, use_fixed_predictions=False, fixed_predictions_path=None, theory_meta=None, legacy=True): self.fkpath = fkpath self.cfactors = cfactors if cfactors is not None else [] self.legacy = legacy @@ -599,7 +599,7 @@ def __init__(self, fkpath, cfactors, use_fixed_predictions=False, fixed_predicti if not self.legacy: fkpath = tuple([fkpath]) - self.metadata = metadata + self.theory_meta = theory_meta # For non-legacy theory, add the metadata since it defines how the theory is to be loaded # and thus, it should also define the hash of the class diff --git a/validphys2/src/validphys/loader.py b/validphys2/src/validphys/loader.py index 6f8b058b3..a48be4d50 100644 --- a/validphys2/src/validphys/loader.py +++ b/validphys2/src/validphys/loader.py @@ -32,6 +32,7 @@ InternalCutsWrapper, HyperscanSpec) from validphys.utils import tempfile_cleaner from validphys import lhaindex +from validphys.pineparser import parse_theory_meta DEFAULT_NNPDF_PROFILE_PATH = f"{sys.prefix}/share/NNPDF/nnprofile.yaml" @@ -379,14 +380,21 @@ def check_fktable(self, theoryID, setname, cfac, use_fixed_predictions=False, ne cfactors = self.check_cfactor(theoryID, setname, cfac) if new_commondata: - # load metadata into dataclass and pass it to FKTableSpec - metadata_path = theopath/ 'fastkernel' / f'{setname}_metadata.yaml' + # Need to pass a TheoryMeta object to FKTableSpec + path_metadata = theopath / 'fastkernel' / f'{setname}_metadata.yaml' + # get observable name from the setname + with open(path_metadata, 'r') as f: + metadata = yaml.safe_load(f) - # load yaml metadata file into dict - with open(metadata_path, 'r') as stream: - metadata = yaml.safe_load(stream) + common_prefix = os.path.commonprefix([metadata['setname'], setname]) - return FKTableSpec(fkpath, cfactors, metadata=metadata, legacy=False) + observable_name = setname[len(common_prefix):] + if observable_name.startswith('_'): + observable_name = observable_name[1:] + + theory_meta = parse_theory_meta(path_metadata, observable_name=observable_name) + + return FKTableSpec(fkpath, cfactors, theory_meta=theory_meta, legacy=False) else: return FKTableSpec(fkpath, cfactors) diff --git a/validphys2/src/validphys/pineparser.py b/validphys2/src/validphys/pineparser.py index fc4847a05..f1f4914c8 100644 --- a/validphys2/src/validphys/pineparser.py +++ b/validphys2/src/validphys/pineparser.py @@ -9,6 +9,8 @@ import numpy as np import pandas as pd +import dataclasses +import yaml from validphys.commondataparser import EXT from validphys.coredata import FKTableData @@ -16,6 +18,80 @@ log = logging.getLogger(__name__) +@dataclasses.dataclass(frozen=True) +class TheoryMeta: + """Contains the necessary information to load the associated fktables + + The theory metadata must always contain a key ``FK_tables`` which defines + the fktables to be loaded. + The ``FK_tables`` is organized as a double list such that: + + The inner list is concatenated + In practice these are different fktables that might refer to the same observable but + that are divided in subgrids for practical reasons. + The outer list instead are the operands for whatever operation needs to be computed + in order to match the experimental data. + + In addition there are other flags that can affect how the fktables are read or used: + - operation: defines the operation to apply to the outer list + - shifts: mapping with the single fktables and their respective shifts + useful to create "gaps" so that the fktables and the respective experimental data + are ordered in the same way (for instance, when some points are missing from a grid) + + This class is inmutable, what is read from the commondata metadata should be considered final + + Example + ------- + >>> from validphys.commondataparser import TheoryMeta + ... from validobj import parse_input + ... from reportengine.compat import yaml + ... theory_raw = ''' + ... FK_tables: + ... - - fk1 + ... - - fk2 + ... - fk3 + ... operation: ratio + ... ''' + ... theory = yaml.safe_load(theory_raw) + ... parse_input(theory, TheoryMeta) + TheoryMeta(FK_tables=[['fk1'], ['fk2', 'fk3']], operation='RATIO', shifts = None, conversion_factor=1.0, comment=None, normalization=None)) + """ + + FK_tables: list[tuple] + operation: str + conversion_factor: float + shifts: None + normalization: None + comment: None + + +def parse_theory_meta(metadata_path, observable_name): + """ + From the metadata.yaml file parses something similar to validphys.commondataparser.TheoryMeta. + This is then needed by the FKspec taken by the pineappl_reader function. + """ + + with open(metadata_path, "r") as f: + meta_card = yaml.safe_load(f) + + # Get the theory metadata + for obs in meta_card['implemented_observables']: + if obs['observable_name'] == observable_name: + obs_metadata = obs + + # TODO: still figure out how to fill shifts and normalization + th_meta = TheoryMeta( + FK_tables= obs_metadata['theory']['FK_tables'], + operation=obs_metadata['theory']['operation'], + conversion_factor=obs_metadata['theory']['conversion_factor'], + shifts=None, + normalization=None, + comment=None + ) + + return th_meta + + def _pinelumi_to_columns(pine_luminosity, hadronic): """Makes the pineappl luminosity into the column indices of a dataframe These corresponds to the indices of a flattened (14x14) matrix for hadronic observables @@ -127,8 +203,8 @@ def pineappl_reader(fkspec): # normalization instead {fktable_name: normalization to apply} # since this parser doesn't know about operations, we need to convert it to a list # then we just iterate over the fktables and apply the shift in the right order - shifts = fkspec.metadata.shifts - normalization_per_fktable = fkspec.metadata.normalization + shifts = fkspec.theory_meta.shifts + normalization_per_fktable = fkspec.theory_meta.normalization fknames = [i.name.replace(f".{EXT}", "") for i in fkspec.fkpath] if cfactors is not None: cfactors = dict(zip(fknames, cfactors)) @@ -168,7 +244,7 @@ def pineappl_reader(fkspec): if hadronic: raw_fktable = np.insert(raw_fktable, miss_index, 0.0, axis=3) # Check conversion factors and remove the x* from the fktable - raw_fktable *= fkspec.metadata.conversion_factor / xdivision + raw_fktable *= fkspec.theory_meta.conversion_factor / xdivision # Create the multi-index for the dataframe # for optimized pineappls different grids can potentially have different indices @@ -191,9 +267,9 @@ def pineappl_reader(fkspec): sigma = pd.concat(partial_fktables, sort=True, copy=False).fillna(0.0) # Check whether this is a 1-point normalization fktable and, if that's the case, protect! - if fkspec.metadata.operation == "RATIO" and len(pines) == 1: + if fkspec.theory_meta.operation == "RATIO" and len(pines) == 1: # it _might_ be, check whether it is the divisor fktable - divisor = fkspec.metadata.FK_tables[-1][0] + divisor = fkspec.theory_meta.FK_tables[-1][0] name = fkspec.fkpath[0].name.replace(f".{EXT}", "") if np.allclose(sigma.loc[1:], 0.0): @@ -211,8 +287,7 @@ def pineappl_reader(fkspec): sigma=sigma, ndata=ndata, Q0=Q0, - metadata=fkspec.metadata, + metadata=fkspec.theory_meta, hadronic=hadronic, xgrid=xgrid, - protected=protected, ) From a5878fa15b70b1a5a47505caebffb235bc94f686 Mon Sep 17 00:00:00 2001 From: Francesco Merlotti Date: Wed, 10 Jul 2024 15:49:11 +0100 Subject: [PATCH 15/35] added test for metadata existence, array append, compatibility with 2d datasets --- validphys2/src/validphys/loader.py | 17 +++++++++++------ validphys2/src/validphys/n3fit_data.py | 6 +++++- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/validphys2/src/validphys/loader.py b/validphys2/src/validphys/loader.py index a48be4d50..05601fb89 100644 --- a/validphys2/src/validphys/loader.py +++ b/validphys2/src/validphys/loader.py @@ -366,7 +366,17 @@ def check_fktable(self, theoryID, setname, cfac, use_fixed_predictions=False, ne # use different file name for the FK table if the commondata is new if new_commondata: - fkpath = tuple([theopath/ 'fastkernel' / (f'{setname}.pineappl.lz4')]) + # Need to pass a TheoryMeta object to FKTableSpec + path_metadata = theopath / 'fastkernel' / f'{setname}_metadata.yaml' + if not path_metadata.exists(): + raise InconsistentMetaDataError(f"Could not find '_metadata.yaml' file for set {setname}." + f"File '{path_metadata}' not found.") + # get observable name from the setname + with open(path_metadata, 'r') as f: + metadata = yaml.safe_load(f) + # NOTE: write a "_metadata.yaml" file for each observable (then `metadata["implemented_observables"][0]` makes sense) + fktables = metadata["implemented_observables"][0]["theory"]["FK_tables"][0] + fkpath = tuple([theopath/ 'fastkernel' / (f'{fktable}.pineappl.lz4') for fktable in fktables]) for path in fkpath: if not path.exists(): raise FKTableNotFound(("Could not find FKTable for set '%s'. " @@ -380,11 +390,6 @@ def check_fktable(self, theoryID, setname, cfac, use_fixed_predictions=False, ne cfactors = self.check_cfactor(theoryID, setname, cfac) if new_commondata: - # Need to pass a TheoryMeta object to FKTableSpec - path_metadata = theopath / 'fastkernel' / f'{setname}_metadata.yaml' - # get observable name from the setname - with open(path_metadata, 'r') as f: - metadata = yaml.safe_load(f) common_prefix = os.path.commonprefix([metadata['setname'], setname]) diff --git a/validphys2/src/validphys/n3fit_data.py b/validphys2/src/validphys/n3fit_data.py index c4b68c88c..d7ed1b43c 100644 --- a/validphys2/src/validphys/n3fit_data.py +++ b/validphys2/src/validphys/n3fit_data.py @@ -253,7 +253,11 @@ def fitting_data_dict( # limited to: central values, ndata, replica generation, covmat construction if data.datasets: ndata = sum([ds.commondata.load_commondata(cuts=ds.cuts).ndata for ds in data.datasets]) - expdata_true = np.array([ds.commondata.load_commondata(cuts=ds.cuts).central_values for ds in data.datasets]).reshape(1,ndata) + expdata_true = np.array([]) + for ds in data.datasets: + expdata_true = np.append(expdata_true, ds.commondata.load_commondata(cuts=ds.cuts).central_values) + expdata_true = expdata_true.reshape(1, ndata) + # expdata_true = np.array([ds.commondata.load_commondata(cuts=ds.cuts).central_values for ds in data.datasets]).reshape(1,ndata) datasets = common_data_reader_experiment(data) for i in range(len(data.datasets)): if data.datasets[i].use_fixed_predictions: From 82245d83116e39b2e203b1a6ebde11b2a41bf0bd Mon Sep 17 00:00:00 2001 From: Francesco Merlotti Date: Wed, 8 May 2024 16:44:56 +0100 Subject: [PATCH 16/35] fix typo --- validphys2/src/validphys/pdfplots.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/validphys2/src/validphys/pdfplots.py b/validphys2/src/validphys/pdfplots.py index dd85b51be..4f4493760 100644 --- a/validphys2/src/validphys/pdfplots.py +++ b/validphys2/src/validphys/pdfplots.py @@ -630,7 +630,7 @@ def plot_lumi1d( if isinstance(gv, MCStats) and show_mc_errors: ax.plot(mx, errstddown / norm, linestyle="--", color=color) ax.plot(mx, errstdup / norm, linestyle="--", color=color) - label_add = r"($68%$ c.l.+$1\sigma$)" if legend_stat_labels else "" + label_add = r"($68\%$ c.l.+$1\sigma$)" if legend_stat_labels else "" outer = True else: label_add = r"($68\%$ c.l.)" if legend_stat_labels else "" From 247ec4d641b0f4a6fd0e9ed4444e580fa920db30 Mon Sep 17 00:00:00 2001 From: Francesco Merlotti Date: Mon, 15 Jul 2024 16:16:28 +0100 Subject: [PATCH 17/35] added load_commondata to core, level0_commondata_wc and make_level1_data to pseudodata --- validphys2/src/validphys/core.py | 2 +- validphys2/src/validphys/pseudodata.py | 152 ++++++++++++++++++++++++- 2 files changed, 152 insertions(+), 2 deletions(-) diff --git a/validphys2/src/validphys/core.py b/validphys2/src/validphys/core.py index fa275d87f..4d5aefd10 100644 --- a/validphys2/src/validphys/core.py +++ b/validphys2/src/validphys/core.py @@ -327,7 +327,7 @@ def load_commondata(self, cuts=None): if cuts is not None: cd = cd.with_cuts(cuts) return cd - + @property def plot_kinlabels(self): return get_plot_kinlabels(self) diff --git a/validphys2/src/validphys/pseudodata.py b/validphys2/src/validphys/pseudodata.py index 9fd5863a9..34f2a6c82 100644 --- a/validphys2/src/validphys/pseudodata.py +++ b/validphys2/src/validphys/pseudodata.py @@ -10,7 +10,9 @@ import numpy as np import pandas as pd -from validphys.covmats import INTRA_DATASET_SYS_NAME +from validphys.covmats import INTRA_DATASET_SYS_NAME, dataset_t0_predictions + +from validphys.core import PDF from reportengine import collect @@ -235,6 +237,154 @@ def indexed_make_replica(groups_index, make_replica): return pd.DataFrame(make_replica, index=groups_index, columns=["data"]) +def level0_commondata_wc(data, fakepdf): + """ + Given a validphys.core.DataGroupSpec object, load commondata and + generate a new commondata instance with central values replaced + by fakepdf prediction + + Parameters + ---------- + + data : validphys.core.DataGroupSpec + + fakepdf: str + + Returns + ------- + list + list of validphys.coredata.CommonData instances corresponding to + all datasets within one experiment. The central value is replaced + by Level 0 fake data. + + Example + ------- + >>> from validphys.api import API + >>> API.level0_commondata_wc(dataset_inputs=[{"dataset":"NMC"}], use_cuts="internal", theoryid=200, fakepdf="NNPDF40_nnlo_as_01180") + + [CommonData(setname='NMC', ndata=204, commondataproc='DIS_NCE', nkin=3, nsys=16)] + """ + + level0_commondata_instances_wc = [] + + # argument fakepdf is passed as str + # generate a core.PDF object with name equal to fakepdf argument + fakepdf = PDF(name=fakepdf) + + for dataset in data.datasets: + commondata_wc = dataset.commondata.load_commondata() + if dataset.cuts is not None: + cuts = dataset.cuts.load() + commondata_wc = commondata_wc.with_cuts(cuts) + # == Generate a new CommonData instance with central value given by Level 0 data generated with fakepdf ==# + t0_prediction = dataset_t0_predictions( + dataset=dataset, t0set=fakepdf + ) # N.B. cuts already applied to th. pred. + level0_commondata_instances_wc.append(commondata_wc.with_central_value(t0_prediction)) + + return level0_commondata_instances_wc + + +def make_level1_data( + # data, + level0_commondata_wc, + filterseed, + data_index): + """ + Given a list of Level 0 commondata instances, return the + same list with central values replaced by Level 1 data. + + Level 1 data is generated using validphys.make_replica. + The covariance matrix, from which the stochastic Level 1 + noise is sampled, is built from Level 0 commondata + instances (level0_commondata_wc). This, in particular, + means that the multiplicative systematics are generated + from the Level 0 central values. + + Note that the covariance matrix used to generate Level 2 + pseudodata is consistent with the one used at Level 1 + up to corrections of the order eta * eps, where eta and + eps are defined as shown below: + + Generate L1 data: L1 = L0 + eta, eta ~ N(0,CL0) + Generate L2 data: L2_k = L1 + eps_k, eps_k ~ N(0,CL1) + + where CL0 and CL1 means that the multiplicative entries + have been constructed from Level 0 and Level 1 central + values respectively. + + + Parameters + ---------- + + REMOVE: data : validphys.core.DataGroupSpec + + level0_commondata_wc : list + list of validphys.coredata.CommonData instances corresponding to + all datasets within one experiment. The central value is replaced + by Level 0 fake data. Cuts already applied. + + filterseed : int + random seed used for the generation of Level 1 data + + data_index : pandas.MultiIndex + + Returns + ------- + list + list of validphys.coredata.CommonData instances corresponding to + all datasets within one experiment. The central value is replaced + by Level 1 fake data. + + Example + ------- + + >>> from validphys.api import API + >>> dataset='NMC' + >>> l1_cd = API.make_level1_data(dataset_inputs = [{"dataset":dataset}],use_cuts="internal", theoryid=200, + fakepdf = "NNPDF40_nnlo_as_01180", filterseed=1, data_index) + >>> l1_cd + [CommonData(setname='NMC', ndata=204, commondataproc='DIS_NCE', nkin=3, nsys=16)] + """ + + # from validphys.covmats import dataset_inputs_covmat_from_systematics + + # dataset_input_list = list(data.dsinputs) + + # covmat = dataset_inputs_covmat_from_systematics( + # level0_commondata_wc, + # dataset_input_list, + # use_weights_in_covmat=False, + # norm_threshold=None, + # _list_of_central_values=None, + # ) + + # ================== generation of Level1 data ======================# + level1_data = make_replica( + level0_commondata_wc, + filterseed, + # covmat, + genrep=True + ) + + indexed_level1_data = indexed_make_replica(data_index, level1_data) + + dataset_order = {cd.setname: i for i, cd in enumerate(level0_commondata_wc)} + + # ===== create commondata instances with central values given by pseudo_data =====# + level1_commondata_dict = {c.setname: c for c in level0_commondata_wc} + level1_commondata_instances_wc = [] + + for xx, grp in indexed_level1_data.groupby('dataset'): + level1_commondata_instances_wc.append( + level1_commondata_dict[xx].with_central_value(grp.values) + ) + # sort back so as to mantain same order as in level0_commondata_wc + level1_commondata_instances_wc.sort(key=lambda x: dataset_order[x.setname]) + + return level1_commondata_instances_wc + + _group_recreate_pseudodata = collect('indexed_make_replica', ('group_dataset_inputs_by_experiment',)) _recreate_fit_pseudodata = collect('_group_recreate_pseudodata', ('fitreplicas', 'fitenvironment')) _recreate_pdf_pseudodata = collect('_group_recreate_pseudodata', ('pdfreplicas', 'fitenvironment')) From 39659d9a65b6892fb723683be3c73e86ff6e99a7 Mon Sep 17 00:00:00 2001 From: Francesco Merlotti Date: Thu, 16 May 2024 17:02:40 +0100 Subject: [PATCH 18/35] added parse_fakepdf to config.py --- validphys2/src/validphys/config.py | 5 +++++ validphys2/src/validphys/pseudodata.py | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/validphys2/src/validphys/config.py b/validphys2/src/validphys/config.py index 1560b2674..8eb6f863e 100644 --- a/validphys2/src/validphys/config.py +++ b/validphys2/src/validphys/config.py @@ -38,6 +38,7 @@ MatchedCuts, SimilarCuts, ThCovMatSpec, + PDF, ) from validphys.fitdata import fitted_replica_indexes, num_fitted_replicas from validphys.loader import ( @@ -171,6 +172,10 @@ def parse_pdf(self, name: str): except NotImplementedError as e: raise ConfigError(str(e)) return pdf + + def parse_fakepdf(self, name: str) -> PDF: + """PDF set used to generate the fake data in a closure test.""" + return self.parse_pdf(name) def parse_load_weights_from_fit(self, name: str): """A fit in the results folder, containing at least a valid filter result.""" diff --git a/validphys2/src/validphys/pseudodata.py b/validphys2/src/validphys/pseudodata.py index 34f2a6c82..37917b108 100644 --- a/validphys2/src/validphys/pseudodata.py +++ b/validphys2/src/validphys/pseudodata.py @@ -248,7 +248,7 @@ def level0_commondata_wc(data, fakepdf): data : validphys.core.DataGroupSpec - fakepdf: str + fakepdf: validphys.core.PDF Returns ------- @@ -269,7 +269,7 @@ def level0_commondata_wc(data, fakepdf): # argument fakepdf is passed as str # generate a core.PDF object with name equal to fakepdf argument - fakepdf = PDF(name=fakepdf) + # fakepdf = PDF(name=fakepdf) for dataset in data.datasets: commondata_wc = dataset.commondata.load_commondata() From ff762f9b40a590befa9b4f89416d3f4c15e7fb0b Mon Sep 17 00:00:00 2001 From: Francesco Merlotti Date: Mon, 20 May 2024 10:14:53 +0100 Subject: [PATCH 19/35] add chi2 provider functions --- validphys2/src/validphys/coredata.py | 4 + validphys2/src/validphys/pseudodata.py | 304 ++++++++++++++++++++++--- validphys2/src/validphys/results.py | 59 ++++- 3 files changed, 328 insertions(+), 39 deletions(-) diff --git a/validphys2/src/validphys/coredata.py b/validphys2/src/validphys/coredata.py index c3b2026d1..e7017c56c 100644 --- a/validphys2/src/validphys/coredata.py +++ b/validphys2/src/validphys/coredata.py @@ -325,6 +325,10 @@ def additive_errors(self): add_table.columns = add_systype["name"].to_numpy() return add_table.loc[:, add_table.columns != "SKIP"] + @property + def cuts(self): + indices = self.commondata_table.index + return slice(indices[0]-1, indices[-1]) def systematic_errors(self, central_values=None): """Returns all systematic errors as absolute uncertainties, with a diff --git a/validphys2/src/validphys/pseudodata.py b/validphys2/src/validphys/pseudodata.py index 37917b108..1d82e4ef8 100644 --- a/validphys2/src/validphys/pseudodata.py +++ b/validphys2/src/validphys/pseudodata.py @@ -9,10 +9,13 @@ import numpy as np import pandas as pd +import os +import yaml from validphys.covmats import INTRA_DATASET_SYS_NAME, dataset_t0_predictions -from validphys.core import PDF +from validphys.convolution import central_predictions +from validphys.loader import Loader from reportengine import collect @@ -20,6 +23,8 @@ log = logging.getLogger(__name__) +l = Loader() + DataTrValSpec = namedtuple('DataTrValSpec', ['pseudodata', 'tr_idx', 'val_idx']) context_index = collect("groups_index", ("fitcontext",)) @@ -237,7 +242,10 @@ def indexed_make_replica(groups_index, make_replica): return pd.DataFrame(make_replica, index=groups_index, columns=["data"]) -def level0_commondata_wc(data, fakepdf): +def level0_commondata_wc( + data, + fakepdf + ): """ Given a validphys.core.DataGroupSpec object, load commondata and generate a new commondata instance with central values replaced @@ -260,33 +268,35 @@ def level0_commondata_wc(data, fakepdf): Example ------- >>> from validphys.api import API - >>> API.level0_commondata_wc(dataset_inputs=[{"dataset":"NMC"}], use_cuts="internal", theoryid=200, fakepdf="NNPDF40_nnlo_as_01180") + >>> API.level0_commondata_wc(dataset_inputs=[{"dataset":"NMC"}], + use_cuts="internal", + theoryid=200, + fakepdf="NNPDF40_nnlo_as_01180") [CommonData(setname='NMC', ndata=204, commondataproc='DIS_NCE', nkin=3, nsys=16)] """ level0_commondata_instances_wc = [] - # argument fakepdf is passed as str - # generate a core.PDF object with name equal to fakepdf argument - # fakepdf = PDF(name=fakepdf) + # import IPython; IPython.embed() for dataset in data.datasets: + commondata_wc = dataset.commondata.load_commondata() if dataset.cuts is not None: cuts = dataset.cuts.load() - commondata_wc = commondata_wc.with_cuts(cuts) + commondata_wc = commondata_wc.with_cuts(cuts=cuts) + # == Generate a new CommonData instance with central value given by Level 0 data generated with fakepdf ==# - t0_prediction = dataset_t0_predictions( - dataset=dataset, t0set=fakepdf - ) # N.B. cuts already applied to th. pred. + t0_prediction = dataset_t0_predictions(dataset=dataset, + t0set=fakepdf) + # N.B. cuts already applied to th. pred. level0_commondata_instances_wc.append(commondata_wc.with_central_value(t0_prediction)) return level0_commondata_instances_wc def make_level1_data( - # data, level0_commondata_wc, filterseed, data_index): @@ -317,8 +327,6 @@ def make_level1_data( Parameters ---------- - REMOVE: data : validphys.core.DataGroupSpec - level0_commondata_wc : list list of validphys.coredata.CommonData instances corresponding to all datasets within one experiment. The central value is replaced @@ -340,32 +348,20 @@ def make_level1_data( ------- >>> from validphys.api import API - >>> dataset='NMC' - >>> l1_cd = API.make_level1_data(dataset_inputs = [{"dataset":dataset}],use_cuts="internal", theoryid=200, - fakepdf = "NNPDF40_nnlo_as_01180", filterseed=1, data_index) - >>> l1_cd + >>> API.make_level1_data(dataset_inputs=[{"dataset": "NMC"}], + use_cuts="internal", + theoryid=200, + fakepdf="NNPDF40_nnlo_as_01180", + filterseed=0, + data_index) [CommonData(setname='NMC', ndata=204, commondataproc='DIS_NCE', nkin=3, nsys=16)] """ - # from validphys.covmats import dataset_inputs_covmat_from_systematics - - # dataset_input_list = list(data.dsinputs) - - # covmat = dataset_inputs_covmat_from_systematics( - # level0_commondata_wc, - # dataset_input_list, - # use_weights_in_covmat=False, - # norm_threshold=None, - # _list_of_central_values=None, - # ) - # ================== generation of Level1 data ======================# - level1_data = make_replica( - level0_commondata_wc, - filterseed, - # covmat, - genrep=True - ) + level1_data = make_replica(level0_commondata_wc, + filterseed, + genrep=True, + ) indexed_level1_data = indexed_make_replica(data_index, level1_data) @@ -385,6 +381,244 @@ def make_level1_data( return level1_commondata_instances_wc +def make_level1_list_data( + level0_commondata_wc, + filterseed, + n_samples, + data_index, +): + """ + Given a list of validphys.coredata.CommonData instances with central + values replaced with `fakepdf` predictions with cuts applied + generate a list of level 1 data from such instances + + Parameters + ---------- + + level0_commondata:_wc: list of validphys.coredata.CommonData instances + where the central value is replaced by level 0 + `fakepdf` predictions + + filterseed: int starting seed used to make different replicas + + n_samples: int number of replicas + + data_index: pandas.MultiIndex providing information on the experiment, + the dataset, and the cut index + + Returns + ------- + list + list of lists of validphys.coredata.CommonData instances corresponding + to all datasets within one experiment. The central value is replaced + by Level 1 fake data. + + Example + ------- + >>> from validphys.api import API + >>> from validphys.loader import Loader + >>> from validphys.results import data_index + >>> l = Loader() + >>> dataset = l.check_dataset(name="NMC", theoryid=200) + >>> experiment = l.check_experiment(name="data", datasets=[dataset]) + >>> lv0_cd_wc = API.level0_commondata_wc(dataset_inputs=[{"dataset":"NMC"}], + use_cuts="internal", + theoryid=200, + fakepdf="NNPDF40_nnlo_as_01180" + ) + >>> API.make_level1_list_data(level0_commondata_wc=lv0_cd_wc, + filterseed=0, + n_samples=1, + data_index=data_index(experiment) + ) + + [[CommonData(setname='NMC', ndata=204, commondataproc='DIS_NCE', nkin=3, nsys=16)]] + """ + samples = [make_level1_data(level0_commondata_wc=level0_commondata_wc, + filterseed=filterseed+i, + data_index=data_index) for i in range(n_samples)] + + return samples + + +def sm_predictions( + dataset_inputs, + pdf, + theoryid + ): + + """ + Parameters + ---------- + dataset_inputs: NSList of core.DataSetInput objects + + pdf: core.PDF object + + theoryid: TheoryIDSpec + + Returns + ------- + + dict + dictionary of standard model predictions for the + given dataset_input, pdf, and theory + + """ + + sm_dict = {} + + for dataset in dataset_inputs: + data = l.check_dataset(dataset.name, cfac=dataset.cfac, theoryid=theoryid) + + sm_dict[dataset.name] = central_predictions(data, pdf) + + return sm_dict + + +def load_contamination( + contamination_parameters, + theoryid, + dataset_inputs + ): + + """ + Parameters + ---------- + + contamination_parameters: dict with + + theoryid: TheoryIDSpec + + dataset_inputs: NSList of DataSetInput objects + + Returns + ------- + + dict + dictionary of BSM k-factors to apply on certain datasets + + """ + + cont_path = l.datapath / f"theory_{theoryid.id}" / "simu_factors" + + cont_name = contamination_parameters["name"] + cont_value = contamination_parameters["value"] + cont_lin_comb = contamination_parameters["linear_combination"] + + bsm_dict = {} + + for dataset in dataset_inputs: + + bsmfile = cont_path / f"SIMU_{dataset.name}.yaml" + + cont_order = dataset.contamination + + if cont_order == None: + log.warning( + f"{dataset.name} is not contaminated. Is it right?" + ) + bsm_dict[dataset.name] = np.array([1.]) + elif not os.path.exists(bsmfile): + log.error( + f"Could not find a BSM-factor for {dataset.name}. Are you sure they exist in the given theory?" + ) + bsm_dict[dataset.name] = np.array([1.]) + else: + log.info( + f"Loading {dataset.name}" + ) + with open(bsmfile, "r+") as stream: + simu_card = yaml.safe_load(stream) + stream.close() + + k_factors = np.zeros(len(simu_card["SM_fixed"])) + for op in cont_lin_comb: + k_factors += cont_lin_comb[op] * np.array(simu_card[cont_order][op]) + k_factors = 1. + k_factors * cont_value / np.array(simu_card[cont_order]["SM"]) + + bsm_dict[dataset.name] = k_factors + + return bsm_dict + + +def compute_chi2( + make_level1_list_data, + sm_predictions, + groups_covmat, + load_contamination + ): + + """ + Parameters + ---------- + + make_level1_list_data + + sm_predictions + + groups_covmat + + load_contamination + + Returns + ------- + + dict + dictionary of lists of chi2 per dataset + + """ + + covmat = groups_covmat + samples = make_level1_list_data + bsm_factors = load_contamination + + chi2_dict = {dataset.setname: [] for dataset in samples[0]} + + for sample in samples: + + for dataset in sample: + data_name = dataset.setname + bsm_fac = bsm_factors[data_name] + + if bsm_fac.shape[0] == 1: + data_values = dataset.central_values * bsm_fac + else: + cuts = dataset.cuts + data_values = dataset.central_values * bsm_fac[cuts] + + num_data = dataset.ndata + + covmat_dataset = ( + covmat.xs(data_name, level=1, drop_level=False) + .T.xs(data_name, level=1, drop_level=False) + .values + ) + + theory = sm_predictions[data_name].values.squeeze() + + diff = (data_values - theory).squeeze() + + if diff.size == 1: + chi2 = diff**2 / covmat_dataset[0,0] / num_data + else: + chi2 = (diff.T @ np.linalg.inv(covmat_dataset) @ diff) / num_data + + chi2_dict[data_name].append(chi2) + + return chi2_dict + + +def write_chi2(pdf, compute_chi2, level0_commondata_wc): + + ndata = {dataset.setname: [dataset.ndata] for dataset in level0_commondata_wc} + + df_ndat = pd.DataFrame(ndata) + df_chi2 = pd.DataFrame(compute_chi2) + + chi2 = pd.concat([df_ndat, df_chi2], ignore_index=True) + + chi2.to_csv(f"{pdf}_chi2_dist.csv", index=False) + _group_recreate_pseudodata = collect('indexed_make_replica', ('group_dataset_inputs_by_experiment',)) _recreate_fit_pseudodata = collect('_group_recreate_pseudodata', ('fitreplicas', 'fitenvironment')) _recreate_pdf_pseudodata = collect('_group_recreate_pseudodata', ('pdfreplicas', 'fitenvironment')) diff --git a/validphys2/src/validphys/results.py b/validphys2/src/validphys/results.py index 950a11af1..48a9f5222 100644 --- a/validphys2/src/validphys/results.py +++ b/validphys2/src/validphys/results.py @@ -37,6 +37,8 @@ predictions, PredictionsRequireCutsError, ) +from validphys.plotoptions.core import get_info + from validphys.n3fit_data_utils import parse_simu_parameters_names_CF @@ -169,6 +171,55 @@ def from_convolution(cls, pdf, posset): procs_data = collect("data", ("group_dataset_inputs_by_process",)) +def data_index(data): + + """ + Parameters + ---------- + + data: core.DataGroupSpec + + Returns + ------- + + pandas.MultiIndex + + Example + ------- + + >>> from validphys.loader import Loader + >>> from validphys.results import data_index + >>> l = Loader() + >>> dataset = l.check_dataset(name="NMC", + theoryid=200 + ) + >>> experiment = l.check_experiment(name="data", + datasets=[dataset] + ) + >>> data_index(experiment) + + MultiIndex([('NMC', 'NMC', 16), + ('NMC', 'NMC', 21), + ('NMC', 'NMC', 22), + ... + ('NMC', 'NMC', 289), + ('NMC', 'NMC', 290), + ('NMC', 'NMC', 291)], + names=['experiment', 'dataset', 'id'], length=204) + + """ + + tuples = [] + + for dataset in data.datasets: + exp = get_info(dataset).experiment + for i in dataset.cuts.load(): + tp = (exp, dataset.name, i) + tuples.append(tp) + + return pd.MultiIndex.from_tuples(tuples, names=('experiment', 'dataset', 'id')) + + def groups_index(groups_data): """Return a pandas.MultiIndex with levels for group, dataset and point respectively, the group is determined by a key in the dataset metadata, and @@ -541,7 +592,7 @@ def dataset_inputs_results( # ``results`` to support this. # TODO: The above comment doesn't make sense after adding T0. Deprecate this def pdf_results( - dataset: (DataSetSpec, DataGroupSpec), + dataset: (DataSetSpec, DataGroupSpec), # type: ignore pdfs: Sequence, covariance_matrix, sqrt_covmat, @@ -557,12 +608,12 @@ def pdf_results( @require_one("pdfs", "pdf") @remove_outer("pdfs", "pdf") def one_or_more_results( - dataset: (DataSetSpec, DataGroupSpec), + dataset: (DataSetSpec, DataGroupSpec), # type: ignore covariance_matrix, sqrt_covmat, dataset_bsm_factor, - pdfs: (type(None), Sequence) = None, - pdf: (type(None), PDF) = None, + pdfs: (type(None), Sequence) = None, # type: ignore + pdf: (type(None), PDF) = None, # type: ignore ): """Generate a list of results, where the first element is the data values, and the next is either the prediction for pdf or for each of the pdfs. From 5366d7075904aff4a777c322e71d9156805ffab0 Mon Sep 17 00:00:00 2001 From: Francesco Merlotti Date: Mon, 20 May 2024 10:43:19 +0100 Subject: [PATCH 20/35] added usage write_chi2 --- validphys2/src/validphys/pseudodata.py | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/validphys2/src/validphys/pseudodata.py b/validphys2/src/validphys/pseudodata.py index 1d82e4ef8..fd6fafc7f 100644 --- a/validphys2/src/validphys/pseudodata.py +++ b/validphys2/src/validphys/pseudodata.py @@ -608,7 +608,26 @@ def compute_chi2( return chi2_dict -def write_chi2(pdf, compute_chi2, level0_commondata_wc): +def write_chi2( + pdf, + compute_chi2, + level0_commondata_wc + ): + + """ + Parameters + ---------- + + pdf: core.PDF + + compute_chi2 + + level0_commondata_wc + + Returns + ------- + + """ ndata = {dataset.setname: [dataset.ndata] for dataset in level0_commondata_wc} @@ -617,7 +636,7 @@ def write_chi2(pdf, compute_chi2, level0_commondata_wc): chi2 = pd.concat([df_ndat, df_chi2], ignore_index=True) - chi2.to_csv(f"{pdf}_chi2_dist.csv", index=False) + chi2.to_csv(f"output/tables/{pdf}_chi2_dist.csv", index=False) _group_recreate_pseudodata = collect('indexed_make_replica', ('group_dataset_inputs_by_experiment',)) _recreate_fit_pseudodata = collect('_group_recreate_pseudodata', ('fitreplicas', 'fitenvironment')) From 9a8850013e65b7c2ec8e714cfdb5a1c75eefe890 Mon Sep 17 00:00:00 2001 From: Francesco Merlotti Date: Mon, 20 May 2024 10:46:39 +0100 Subject: [PATCH 21/35] fixed repo --- validphys2/src/validphys/pseudodata.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/validphys2/src/validphys/pseudodata.py b/validphys2/src/validphys/pseudodata.py index fd6fafc7f..67ed1431b 100644 --- a/validphys2/src/validphys/pseudodata.py +++ b/validphys2/src/validphys/pseudodata.py @@ -636,7 +636,7 @@ def write_chi2( chi2 = pd.concat([df_ndat, df_chi2], ignore_index=True) - chi2.to_csv(f"output/tables/{pdf}_chi2_dist.csv", index=False) + chi2.to_csv(f"{pdf}_chi2_dist.csv", index=False) _group_recreate_pseudodata = collect('indexed_make_replica', ('group_dataset_inputs_by_experiment',)) _recreate_fit_pseudodata = collect('_group_recreate_pseudodata', ('fitreplicas', 'fitenvironment')) From bbf01ca0faf1cda058b64dff4cc26587ccb88036 Mon Sep 17 00:00:00 2001 From: Francesco Merlotti Date: Sun, 26 May 2024 13:40:32 +0100 Subject: [PATCH 22/35] moved function in simunet_analysis & changed their name --- validphys2/src/validphys/pseudodata.py | 163 ----------------- validphys2/src/validphys/simunet_analysis.py | 174 ++++++++++++++++++- 2 files changed, 170 insertions(+), 167 deletions(-) diff --git a/validphys2/src/validphys/pseudodata.py b/validphys2/src/validphys/pseudodata.py index 67ed1431b..4c682e2f7 100644 --- a/validphys2/src/validphys/pseudodata.py +++ b/validphys2/src/validphys/pseudodata.py @@ -475,169 +475,6 @@ def sm_predictions( return sm_dict -def load_contamination( - contamination_parameters, - theoryid, - dataset_inputs - ): - - """ - Parameters - ---------- - - contamination_parameters: dict with - - theoryid: TheoryIDSpec - - dataset_inputs: NSList of DataSetInput objects - - Returns - ------- - - dict - dictionary of BSM k-factors to apply on certain datasets - - """ - - cont_path = l.datapath / f"theory_{theoryid.id}" / "simu_factors" - - cont_name = contamination_parameters["name"] - cont_value = contamination_parameters["value"] - cont_lin_comb = contamination_parameters["linear_combination"] - - bsm_dict = {} - - for dataset in dataset_inputs: - - bsmfile = cont_path / f"SIMU_{dataset.name}.yaml" - - cont_order = dataset.contamination - - if cont_order == None: - log.warning( - f"{dataset.name} is not contaminated. Is it right?" - ) - bsm_dict[dataset.name] = np.array([1.]) - elif not os.path.exists(bsmfile): - log.error( - f"Could not find a BSM-factor for {dataset.name}. Are you sure they exist in the given theory?" - ) - bsm_dict[dataset.name] = np.array([1.]) - else: - log.info( - f"Loading {dataset.name}" - ) - with open(bsmfile, "r+") as stream: - simu_card = yaml.safe_load(stream) - stream.close() - - k_factors = np.zeros(len(simu_card["SM_fixed"])) - for op in cont_lin_comb: - k_factors += cont_lin_comb[op] * np.array(simu_card[cont_order][op]) - k_factors = 1. + k_factors * cont_value / np.array(simu_card[cont_order]["SM"]) - - bsm_dict[dataset.name] = k_factors - - return bsm_dict - - -def compute_chi2( - make_level1_list_data, - sm_predictions, - groups_covmat, - load_contamination - ): - - """ - Parameters - ---------- - - make_level1_list_data - - sm_predictions - - groups_covmat - - load_contamination - - Returns - ------- - - dict - dictionary of lists of chi2 per dataset - - """ - - covmat = groups_covmat - samples = make_level1_list_data - bsm_factors = load_contamination - - chi2_dict = {dataset.setname: [] for dataset in samples[0]} - - for sample in samples: - - for dataset in sample: - data_name = dataset.setname - bsm_fac = bsm_factors[data_name] - - if bsm_fac.shape[0] == 1: - data_values = dataset.central_values * bsm_fac - else: - cuts = dataset.cuts - data_values = dataset.central_values * bsm_fac[cuts] - - num_data = dataset.ndata - - covmat_dataset = ( - covmat.xs(data_name, level=1, drop_level=False) - .T.xs(data_name, level=1, drop_level=False) - .values - ) - - theory = sm_predictions[data_name].values.squeeze() - - diff = (data_values - theory).squeeze() - - if diff.size == 1: - chi2 = diff**2 / covmat_dataset[0,0] / num_data - else: - chi2 = (diff.T @ np.linalg.inv(covmat_dataset) @ diff) / num_data - - chi2_dict[data_name].append(chi2) - - return chi2_dict - - -def write_chi2( - pdf, - compute_chi2, - level0_commondata_wc - ): - - """ - Parameters - ---------- - - pdf: core.PDF - - compute_chi2 - - level0_commondata_wc - - Returns - ------- - - """ - - ndata = {dataset.setname: [dataset.ndata] for dataset in level0_commondata_wc} - - df_ndat = pd.DataFrame(ndata) - df_chi2 = pd.DataFrame(compute_chi2) - - chi2 = pd.concat([df_ndat, df_chi2], ignore_index=True) - - chi2.to_csv(f"{pdf}_chi2_dist.csv", index=False) - _group_recreate_pseudodata = collect('indexed_make_replica', ('group_dataset_inputs_by_experiment',)) _recreate_fit_pseudodata = collect('_group_recreate_pseudodata', ('fitreplicas', 'fitenvironment')) _recreate_pdf_pseudodata = collect('_group_recreate_pseudodata', ('pdfreplicas', 'fitenvironment')) diff --git a/validphys2/src/validphys/simunet_analysis.py b/validphys2/src/validphys/simunet_analysis.py index 2e7d054e1..1aa3f7f48 100644 --- a/validphys2/src/validphys/simunet_analysis.py +++ b/validphys2/src/validphys/simunet_analysis.py @@ -20,6 +20,8 @@ import pandas as pd import seaborn as sns import itertools +import yaml +import os from reportengine.figure import figure, figuregen from reportengine.checks import make_check, CheckError, make_argcheck, check @@ -715,9 +717,9 @@ def plot_bsm_pdf_corr( Q, bsm_names_to_latex, mark_threshold: float = 0.9, - ymin: (float, type(None)) = None, - ymax: (float, type(None)) = None, - dashed_line_flavours: (list, type(None)) = None, + ymin: (float, type(None)) = None, # type: ignore + ymax: (float, type(None)) = None, # type: ignore + dashed_line_flavours: (list, type(None)) = None, # type: ignore ): """ Plot the correlation between BSM factors and a PDF. @@ -1969,4 +1971,168 @@ def principal_component_vectors(fisher_information_matrix, simu_parameters_names fisher = fisher - fisher.mean(axis=0) _, _, vectors = np.linalg.svd(fisher) vectors = pd.DataFrame(vectors, columns=simu_parameters_names) - return vectors \ No newline at end of file + return vectors + + +def load_datasets_contamination( + contamination_parameters, + theoryid, + dataset_inputs + ): + + """ + Parameters + ---------- + + contamination_parameters: dict with + + theoryid: TheoryIDSpec + + dataset_inputs: NSList of DataSetInput objects + + Returns + ------- + + dict + dictionary of BSM k-factors to apply on certain datasets + + """ + + cont_path = l.datapath / f"theory_{theoryid.id}" / "simu_factors" + + cont_name = contamination_parameters["name"] + cont_value = contamination_parameters["value"] + cont_lin_comb = contamination_parameters["linear_combination"] + + bsm_dict = {} + + for dataset in dataset_inputs: + + bsmfile = cont_path / f"SIMU_{dataset.name}.yaml" + + cont_order = dataset.contamination + + if cont_order == None: + log.warning( + f"{dataset.name} is not contaminated. Is it right?" + ) + bsm_dict[dataset.name] = np.array([1.]) + elif not os.path.exists(bsmfile): + log.error( + f"Could not find a BSM-factor for {dataset.name}. Are you sure they exist in the given theory?" + ) + bsm_dict[dataset.name] = np.array([1.]) + else: + log.info( + f"Loading {dataset.name}" + ) + with open(bsmfile, "r+") as stream: + simu_card = yaml.safe_load(stream) + stream.close() + + k_factors = np.zeros(len(simu_card["SM_fixed"])) + for op in cont_lin_comb: + k_factors += cont_lin_comb[op] * np.array(simu_card[cont_order][op]) + k_factors = 1. + k_factors * cont_value / np.array(simu_card[cont_order]["SM"]) + + bsm_dict[dataset.name] = k_factors + + return bsm_dict + + +def compute_datasets_chi2_dist( + make_level1_list_data, + sm_predictions, + groups_covmat, + load_datasets_contamination + ): + + """ + Parameters + ---------- + + make_level1_list_data + + sm_predictions + + groups_covmat + + load_contamination + + Returns + ------- + + dict + dictionary of lists of chi2 per dataset + + """ + + covmat = groups_covmat + samples = make_level1_list_data + bsm_factors = load_datasets_contamination + + chi2_dict = {dataset.setname: [] for dataset in samples[0]} + + for sample in samples: + + for dataset in sample: + data_name = dataset.setname + bsm_fac = bsm_factors[data_name] + + if bsm_fac.shape[0] == 1: + data_values = dataset.central_values * bsm_fac + else: + cuts = dataset.cuts + data_values = dataset.central_values * bsm_fac[cuts] + + num_data = dataset.ndata + + covmat_dataset = ( + covmat.xs(data_name, level=1, drop_level=False) + .T.xs(data_name, level=1, drop_level=False) + .values + ) + + theory = sm_predictions[data_name].values.squeeze() + + diff = (data_values - theory).squeeze() + + if diff.size == 1: + chi2 = diff**2 / covmat_dataset[0,0] / num_data + else: + chi2 = (diff.T @ np.linalg.inv(covmat_dataset) @ diff) / num_data + + chi2_dict[data_name].append(chi2) + + return chi2_dict + + +def write_datasets_chi2_dist_csv( + pdf, + compute_datasets_chi2_dist, + level0_commondata_wc + ): + + """ + Parameters + ---------- + + pdf: core.PDF + + compute_chi2 + + level0_commondata_wc + + Returns + ------- + + """ + + ndata = {dataset.setname: [dataset.ndata] for dataset in level0_commondata_wc} + + df_ndat = pd.DataFrame(ndata) + df_chi2 = pd.DataFrame(compute_datasets_chi2_dist) + + chi2 = pd.concat([df_ndat, df_chi2], ignore_index=True) + + chi2.to_csv(f"{pdf}_chi2_dist.csv", index=False) From 709980315cf035409cb3ab68ab335585d91dd3ec Mon Sep 17 00:00:00 2001 From: Francesco Merlotti Date: Thu, 30 May 2024 21:22:57 +0100 Subject: [PATCH 23/35] changed cuts to commondata_table_indices --- validphys2/src/validphys/coredata.py | 5 ++--- validphys2/src/validphys/simunet_analysis.py | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/validphys2/src/validphys/coredata.py b/validphys2/src/validphys/coredata.py index e7017c56c..8486269be 100644 --- a/validphys2/src/validphys/coredata.py +++ b/validphys2/src/validphys/coredata.py @@ -326,9 +326,8 @@ def additive_errors(self): return add_table.loc[:, add_table.columns != "SKIP"] @property - def cuts(self): - indices = self.commondata_table.index - return slice(indices[0]-1, indices[-1]) + def commondata_table_indices(self): + return self.commondata_table.index - 1 def systematic_errors(self, central_values=None): """Returns all systematic errors as absolute uncertainties, with a diff --git a/validphys2/src/validphys/simunet_analysis.py b/validphys2/src/validphys/simunet_analysis.py index 1aa3f7f48..419ffdee0 100644 --- a/validphys2/src/validphys/simunet_analysis.py +++ b/validphys2/src/validphys/simunet_analysis.py @@ -2082,8 +2082,8 @@ def compute_datasets_chi2_dist( if bsm_fac.shape[0] == 1: data_values = dataset.central_values * bsm_fac else: - cuts = dataset.cuts - data_values = dataset.central_values * bsm_fac[cuts] + indices = dataset.commondata_table_indices + data_values = dataset.central_values * bsm_fac[indices] num_data = dataset.ndata From a596c621667ba6840f6347a36d8272d10d60c180 Mon Sep 17 00:00:00 2001 From: Francesco Merlotti Date: Thu, 30 May 2024 21:23:42 +0100 Subject: [PATCH 24/35] changed cuts to commondata_table_indices --- validphys2/src/validphys/simunet_analysis.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/validphys2/src/validphys/simunet_analysis.py b/validphys2/src/validphys/simunet_analysis.py index 419ffdee0..cafc97ba7 100644 --- a/validphys2/src/validphys/simunet_analysis.py +++ b/validphys2/src/validphys/simunet_analysis.py @@ -1637,7 +1637,7 @@ def dataset_scaled_fit_cfactor(dataset, pdf, read_pdf_cfactors, quad_cfacs): res: np.arrays An ``ndat`` x ``nrep`` array containing the scaled fit cfactors. """ - parsed_cfacs = parse_fit_cfac(dataset.fit_cfac, dataset.cuts) + parsed_cfacs = parse_fit_cfac(dataset.fit_cfac, dataset.cuts) # type: ignore if parsed_cfacs is None or not read_pdf_cfactors.values.size: # We want an array of ones that ndata x nrep # where ndata is the number of post cut datapoints @@ -1651,7 +1651,7 @@ def dataset_scaled_fit_cfactor(dataset, pdf, read_pdf_cfactors, quad_cfacs): scaled_replicas = read_pdf_cfactors.values * fit_cfac_df.values[:, np.newaxis] if quad_cfacs: log.debug("Scaling results using quadratic cfactors") - parsed_quads = parse_quad_cfacs(dataset.fit_cfac, dataset.cuts, quad_cfacs) + parsed_quads = parse_quad_cfacs(dataset.fit_cfac, dataset.cuts, quad_cfacs) # type: ignore quad_cfac_df = pd.DataFrame( {k: v.central_value.squeeze() for k, v in parsed_quads.items()} ) From 5dbdb45c2ef967d1d20ea451b2928e1ac0a481d8 Mon Sep 17 00:00:00 2001 From: Francesco Merlotti Date: Mon, 15 Jul 2024 16:20:28 +0100 Subject: [PATCH 25/35] added rules classes, static KIN_LABEL dict, and replaced cpp Export method with new Python one --- validphys2/src/validphys/config.py | 65 +++++++++++++++++--------- validphys2/src/validphys/filters.py | 71 +++++++++++++++++++++++++++-- 2 files changed, 110 insertions(+), 26 deletions(-) diff --git a/validphys2/src/validphys/config.py b/validphys2/src/validphys/config.py index 8eb6f863e..273d6d30a 100644 --- a/validphys2/src/validphys/config.py +++ b/validphys2/src/validphys/config.py @@ -30,6 +30,14 @@ from reportengine import report from reportengine.compat import yaml +from validphys.filters import ( + Rule, + FilterRule, + AddedFilterRule, + RuleProcessingError, + default_filter_rules_input, + ) + from validphys.core import ( DataGroupSpec, DataSetInput, @@ -186,7 +194,7 @@ def parse_load_weights_from_fit(self, name: str): @element_of("theoryids") @_id_with_label - def parse_theoryid(self, theoryID: (str, int)): + def parse_theoryid(self, theoryID: (str, int)): # type: ignore """A number corresponding to the database theory ID where the corresponding theory folder is installed in te data directory.""" try: @@ -199,7 +207,7 @@ def parse_theoryid(self, theoryID: (str, int)): display_alternatives="all", ) - def parse_use_cuts(self, use_cuts: (bool, str)): + def parse_use_cuts(self, use_cuts: (bool, str)): # type: ignore # type: ignore """Whether to filter the points based on the cuts applied in the fit, or the whole data in the dataset. The possible options are: @@ -237,7 +245,7 @@ def produce_replicas(self, nreplica: int): return NSList(range(1, nreplica+1), nskey="replica") def produce_inclusive_use_scalevar_uncertainties(self, use_scalevar_uncertainties: bool = False, - point_prescription: (str, None) = None): + point_prescription: (str, None) = None): # type: ignore """Whether to use a scale variation uncertainty theory covmat. Checks whether a point prescription is included in the runcard and if so assumes scale uncertainties are to be used.""" @@ -1208,7 +1216,7 @@ def res(*args, **kwargs): return res def produce_fitthcovmat( - self, use_thcovmat_if_present: bool = False, fit: (str, type(None)) = None + self, use_thcovmat_if_present: bool = False, fit: (str, type(None)) = None # type: ignore ): """If a `fit` is specified and `use_thcovmat_if_present` is `True` then returns the corresponding covariance matrix for the given fit if it exists. If the fit doesn't have a @@ -1262,7 +1270,7 @@ def produce_fitthcovmat( fit_theory_covmat = None return fit_theory_covmat - def parse_speclabel(self, label: (str, type(None))): + def parse_speclabel(self, label: (str, type(None))): # type: ignore """A label for a dataspec. To be used in some plots""" return label @@ -1296,7 +1304,7 @@ def parse_groupby(self, grouping: str): ) return grouping - def parse_norm_threshold(self, val: (numbers.Number, type(None))): + def parse_norm_threshold(self, val: (numbers.Number, type(None))): # type: ignore """The threshold to use for covariance matrix normalisation, sets the maximum l2 norm of the inverse covariance matrix, by clipping smallest eigenvalues @@ -1319,7 +1327,7 @@ def produce_no_covmat_reg(self): return {"norm_threshold": None} @configparser.record_from_defaults - def parse_default_filter_rules(self, spec: (str, type(None))): + def parse_default_filter_rules(self, spec: (str, type(None))): # type: ignore return spec def load_default_default_filter_rules(self, spec): @@ -1343,7 +1351,7 @@ def load_default_default_filter_rules(self, spec): display_alternatives="all", ) - def parse_filter_rules(self, filter_rules: (list, type(None))): + def parse_filter_rules(self, filter_rules: (list, type(None))): # type: ignore """A list of filter rules. See https://docs.nnpdf.science/vp/filters.html for details on the syntax""" log.warning("Overwriting filter rules") @@ -1355,6 +1363,13 @@ def parse_default_filter_rules_recorded_spec_(self, spec): it reportengine detects a conflict in the `dataset` key. """ return spec + + def parse_added_filter_rules(self, rules: (list, type(None)) = None): # type: ignore + """ + Returns a tuple of AddedFilterRule objects. Rules are immutable after parsing. + AddedFilterRule objects inherit from FilterRule objects. + """ + return tuple(AddedFilterRule(**rule) for rule in rules) if rules else None def produce_rules( self, @@ -1364,15 +1379,9 @@ def produce_rules( default_filter_rules=None, filter_rules=None, default_filter_rules_recorded_spec_=None, + added_filter_rules=None, ): - """Produce filter rules based on the user defined input and defaults.""" - from validphys.filters import ( - Rule, - RuleProcessingError, - default_filter_rules_input, - ) - theory_parameters = theoryid.get_description() if filter_rules is None: @@ -1396,11 +1405,25 @@ def produce_rules( ] except RuleProcessingError as e: raise ConfigError(f"Error Processing filter rules: {e}") from e - - return rule_list + + if added_filter_rules: + for i, rule in enumerate(added_filter_rules): + try: + rule_list.append( + Rule( + initial_data=rule, + defaults=defaults, + theory_parameters=theory_parameters, + loader=self.loader, + ) + ) + except RuleProcessingError as e: + raise ConfigError(f"Error processing added rule {i+1}: {e}") from e + + return tuple(rule_list) @configparser.record_from_defaults - def parse_default_filter_settings(self, spec: (str, type(None))): + def parse_default_filter_settings(self, spec: (str, type(None))): # type: ignore return spec def load_default_default_filter_settings(self, spec): @@ -1424,7 +1447,7 @@ def load_default_default_filter_settings(self, spec): display_alternatives="all", ) - def parse_filter_defaults(self, filter_defaults: (dict, type(None))): + def parse_filter_defaults(self, filter_defaults: (dict, type(None))): # type: ignore """A mapping containing the default kinematic limits to be used when filtering data (when using internal cuts). Currently these limits are ``q2min`` and ``w2min``. @@ -1504,8 +1527,8 @@ def produce_data( def _parse_data_input_from_( self, - parse_from_value: (str, type(None)), - additional_context: (dict, type(None)) = None, + parse_from_value: (str, type(None)), # type: ignore + additional_context: (dict, type(None)) = None, # type: ignore ): """Function which parses the ``data_input`` from a namespace. Usage is similar to :py:meth:`self.parse_from_` except this function bridges diff --git a/validphys2/src/validphys/filters.py b/validphys2/src/validphys/filters.py index 700dde0bd..6cf8b61dd 100644 --- a/validphys2/src/validphys/filters.py +++ b/validphys2/src/validphys/filters.py @@ -3,9 +3,11 @@ """ import logging +import dataclasses import re from collections.abc import Mapping from importlib.resources import read_text +from typing import Union import numpy as np @@ -16,6 +18,32 @@ log = logging.getLogger(__name__) +KIN_LABEL = { + "DIS": ("x", "Q2", "y"), + "DYP": ("y", "M2", "sqrts"), + "JET": ("eta", "p_T2", "sqrts"), + "DIJET": ("eta", "m_12", "sqrts"), + "PHT": ("eta_gamma", "E_Tgamma2", "sqrts"), + "INC": ("0", "mu2", "sqrts"), + "EWK_RAP": ("etay", "M2", "sqrts"), + "EWK_PT": ("p_T", "M2", "sqrts"), + "EWK_PTRAP": ("etay", "p_T2", "sqrts"), + "EWK_MLL": ("M_ll", "M_ll2", "sqrts"), + "EWJ_RAP": ("etay", "M2", "sqrts"), + "EWJ_PT": ("p_T", "M2", "sqrt(s)"), + "EWJ_PTRAP": ("etay", "p_T2", "sqrts"), + "EWJ_JRAP": ("etay", "M2", "sqrts"), + "EWJ_JPT": ("p_T", "M2", "sqrts"), + "EWJ_MLL": ("M_ll", "M_ll2", "sqrts"), + "HQP_YQQ": ("yQQ", "mu2", "sqrts"), + "HQP_MQQ": ("MQQ", "mu2", "sqrts"), + "HQP_PTQQ": ("p_TQQ", "mu2", "sqrts"), + "HQP_YQ": ("yQ", "mu2", "sqrts"), + "HQP_PTQ": ("p_TQ", "mu2", "sqrts"), + "HIG_RAP": ("y", "M_H2", "sqrts"), + "SIA": ("z", "Q2", "y"), +} + class RuleProcessingError(Exception): """Exception raised when we couldn't process a rule.""" @@ -31,6 +59,34 @@ class MissingRuleAttribute(RuleProcessingError, AttributeError): class FatalRuleError(Exception): """Exception raised when a rule application failed at runtime.""" +@dataclasses.dataclass(frozen=True) +class FilterRule: + """ + Dataclass which carries the filter rule information. + """ + + dataset: str = None + process_type: str = None + rule: str = None + reason: str = None + local_variables: Mapping[str, Union[str, float]] = None + PTO: str = None + FNS: str = None + IC: str = None + + def to_dict(self): + rule_dict = dataclasses.asdict(self) + filtered_dict = {k: v for k, v in rule_dict.items() if v is not None} + return filtered_dict + +@dataclasses.dataclass(frozen=True) +class AddedFilterRule(FilterRule): + """ + Dataclass which carries extra filter rule that is added to the + default rule. + """ + + pass def default_filter_settings_input(): """Return a dictionary with the default hardcoded filter settings. @@ -164,7 +220,9 @@ def _filter_real_data(filter_path, data): nfull, ncut = _write_ds_cut_data(path, dataset) total_data_points += nfull total_cut_data_points += ncut - dataset.commondata.load_commondata(cuts=dataset.cuts).export(path) + # dataset.load().Export(str(path)) + cuts = dataset.cuts.load() + dataset.commondata.load_commondata(cuts=cuts).export(path) return total_data_points, total_cut_data_points @@ -320,6 +378,9 @@ def __init__( self.dataset = None self.process_type = None self._local_variables_code = {} + + if isinstance(initial_data, FilterRule): + initial_data = initial_data.to_dict() for key in initial_data: setattr(self, key, initial_data[key]) @@ -343,14 +404,14 @@ def __init__( f"Could not find dataset {self.dataset}" ) from e if cd.process_type[:3] == "DIS": - self.variables = CommonData.kinLabel["DIS"] + self.variables = KIN_LABEL["DIS"] else: - self.variables = CommonData.kinLabel[cd.process_type] + self.variables = KIN_LABEL[cd.process_type] else: if self.process_type[:3] == "DIS": - self.variables = CommonData.kinLabel["DIS"] + self.variables = KIN_LABEL["DIS"] else: - self.variables = CommonData.kinLabel[self.process_type] + self.variables = KIN_LABEL[self.process_type] if hasattr(self, "local_variables"): if not isinstance(self.local_variables, Mapping): From ea24cba768672d1f3d9e98bfe4b32887a71593ef Mon Sep 17 00:00:00 2001 From: Francesco Merlotti Date: Mon, 15 Jul 2024 16:21:39 +0100 Subject: [PATCH 26/35] added commondatawriter.py & export method for CommonData python objects --- validphys2/src/validphys/coredata.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/validphys2/src/validphys/coredata.py b/validphys2/src/validphys/coredata.py index 8486269be..3ef4b762c 100644 --- a/validphys2/src/validphys/coredata.py +++ b/validphys2/src/validphys/coredata.py @@ -7,6 +7,8 @@ import dataclasses import yaml +from validphys.commondatawriter import write_commondata_to_file, write_systype_to_file + import numpy as np import pandas as pd from validphys.commondatawriter import write_commondata_to_file, write_systype_to_file @@ -283,7 +285,6 @@ def with_cuts(self, cuts): self, ndata=newndata, commondata_table=new_commondata_table ) - @property def central_values(self): return self.commondata_table["data"] From 9e13d11e359b322637d3e935db39d1d8f48ed102 Mon Sep 17 00:00:00 2001 From: Francesco Merlotti Date: Wed, 12 Jun 2024 16:07:53 +0100 Subject: [PATCH 27/35] added xq2 map for hadronic MQQ processes ref. [2303.06159] --- validphys2/src/validphys/plotoptions/kintransforms.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/validphys2/src/validphys/plotoptions/kintransforms.py b/validphys2/src/validphys/plotoptions/kintransforms.py index 786d166f7..1a3e86392 100644 --- a/validphys2/src/validphys/plotoptions/kintransforms.py +++ b/validphys2/src/validphys/plotoptions/kintransforms.py @@ -188,6 +188,12 @@ def xq2map(self, k1, k2, k3, **extra_labels): Q = (np.sqrt(QQMASS2+k1*k1)+k1) return Q/k3, Q*Q +class HQQMQQXQ2MapMixin: + def xq2map(self, k1, k2, k3, **extra_labels): + """in inv mass Experiments k1 is the mttbar, k2 is mu, and k3 is sqrt(s)""" + Q = k1 / 4 + return Q / k3, Q*Q + class dyp_sqrt_scale(SqrtScaleMixin, DYXQ2MapMixin): qlabel = '$M (GeV)$' @@ -257,8 +263,9 @@ class ewk_rap_sqrt_scale(SqrtScaleMixin,DYXQ2MapMixin): # EWK_RAP -> DY okay class hig_rap_sqrt_scale(SqrtScaleMixin,DYXQ2MapMixin): #okay, but it does not exist qlabel = '$M_H (GeV)$' -class hqp_mqq_sqrt_scale(SqrtScaleMixin,DYMXQ2MapMixin): # HQP_MQQ -> DYM okay - qlabel = r'$\mu (GeV)$' +class hqp_mqq_sqrt_scale(SqrtScaleMixin,HQQMQQXQ2MapMixin): # HQP_MQQ -> DYM okay + # qlabel = r'$\mu (GeV)$' + qlabel = r'$M^{QQ} (GeV) / 4$' class hqp_ptq_sqrt_scale(SqrtScaleMixin,HQPTXQ2MapMixin): # HQP_PTQ -> HQPT okay qlabel = r'$\mu (GeV)$' From c8514a928092b927edbaf1164490d56d99e4df20 Mon Sep 17 00:00:00 2001 From: Francesco Merlotti Date: Mon, 13 May 2024 16:34:35 +0100 Subject: [PATCH 28/35] add .pdf PBSP logos --- PBSP_logos/PBSP_black.pdf | Bin 0 -> 61882 bytes PBSP_logos/PBSP_dark.pdf | Bin 0 -> 125802 bytes PBSP_logos/PBSP_light.pdf | Bin 0 -> 29063 bytes 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 PBSP_logos/PBSP_black.pdf create mode 100644 PBSP_logos/PBSP_dark.pdf create mode 100644 PBSP_logos/PBSP_light.pdf diff --git a/PBSP_logos/PBSP_black.pdf b/PBSP_logos/PBSP_black.pdf new file mode 100644 index 0000000000000000000000000000000000000000..2b2780dc793d07c8a61f67dc4d32bdf66b700340 GIT binary patch literal 61882 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 literal 0 HcmV?d00001 diff --git a/PBSP_logos/PBSP_dark.pdf b/PBSP_logos/PBSP_dark.pdf new file mode 100644 index 0000000000000000000000000000000000000000..40ac2baf28f2e60289387f49040b590e23f87301 GIT binary patch literal 125802 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 literal 0 HcmV?d00001 From 9b3aa027c405d679e8044c8666e1c3eab096c325 Mon Sep 17 00:00:00 2001 From: Francesco Merlotti Date: Tue, 18 Jun 2024 20:53:51 +0100 Subject: [PATCH 29/35] Revert "added xq2 map for hadronic MQQ processes ref. [2303.06159]" This reverts commit 06d9c50373e1abd9367b21c0bae7b9043e9ca350. --- validphys2/src/validphys/plotoptions/kintransforms.py | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/validphys2/src/validphys/plotoptions/kintransforms.py b/validphys2/src/validphys/plotoptions/kintransforms.py index 1a3e86392..786d166f7 100644 --- a/validphys2/src/validphys/plotoptions/kintransforms.py +++ b/validphys2/src/validphys/plotoptions/kintransforms.py @@ -188,12 +188,6 @@ def xq2map(self, k1, k2, k3, **extra_labels): Q = (np.sqrt(QQMASS2+k1*k1)+k1) return Q/k3, Q*Q -class HQQMQQXQ2MapMixin: - def xq2map(self, k1, k2, k3, **extra_labels): - """in inv mass Experiments k1 is the mttbar, k2 is mu, and k3 is sqrt(s)""" - Q = k1 / 4 - return Q / k3, Q*Q - class dyp_sqrt_scale(SqrtScaleMixin, DYXQ2MapMixin): qlabel = '$M (GeV)$' @@ -263,9 +257,8 @@ class ewk_rap_sqrt_scale(SqrtScaleMixin,DYXQ2MapMixin): # EWK_RAP -> DY okay class hig_rap_sqrt_scale(SqrtScaleMixin,DYXQ2MapMixin): #okay, but it does not exist qlabel = '$M_H (GeV)$' -class hqp_mqq_sqrt_scale(SqrtScaleMixin,HQQMQQXQ2MapMixin): # HQP_MQQ -> DYM okay - # qlabel = r'$\mu (GeV)$' - qlabel = r'$M^{QQ} (GeV) / 4$' +class hqp_mqq_sqrt_scale(SqrtScaleMixin,DYMXQ2MapMixin): # HQP_MQQ -> DYM okay + qlabel = r'$\mu (GeV)$' class hqp_ptq_sqrt_scale(SqrtScaleMixin,HQPTXQ2MapMixin): # HQP_PTQ -> HQPT okay qlabel = r'$\mu (GeV)$' From 201234c99760506270e6fd8b2db57963a554b935 Mon Sep 17 00:00:00 2001 From: Francesco Merlotti Date: Mon, 15 Jul 2024 16:22:18 +0100 Subject: [PATCH 30/35] Revert "added commondatawriter.py & export method for CommonData python objects" This reverts commit d2d2a5c7dd1effbda3fcf12ac2c342a54bc22423. --- validphys2/src/validphys/commondatawriter.py | 84 -------------------- validphys2/src/validphys/coredata.py | 3 +- 2 files changed, 1 insertion(+), 86 deletions(-) delete mode 100644 validphys2/src/validphys/commondatawriter.py diff --git a/validphys2/src/validphys/commondatawriter.py b/validphys2/src/validphys/commondatawriter.py deleted file mode 100644 index 650df84cb..000000000 --- a/validphys2/src/validphys/commondatawriter.py +++ /dev/null @@ -1,84 +0,0 @@ -""" -This module contains functions to write commondata and systypes -tables to files -""" - - -def write_commondata_data(commondata, buffer): - """ - write commondata table to buffer, this can be a memory map, - compressed archive or strings (using for instance StringIO) - - - Parameters - ---------- - - commondata : validphys.coredata.CommonData - - buffer : memory map, compressed archive or strings - example: StringIO object - - - Example - ------- - >>> from validphys.loader import Loader - >>> from io import StringIO - - >>> l = Loader() - >>> cd = l.check_commondata("NMC").load_commondata_instance() - >>> sio = StringIO() - >>> write_commondata_data(cd,sio) - >>> print(sio.getvalue()) - - """ - header = f"{commondata.setname} {commondata.nsys} {commondata.ndata}\n" - buffer.write(header) - commondata.commondata_table.to_csv(buffer, sep="\t", header=None) - - -def write_commondata_to_file(commondata, path): - """ - write commondata table to file - """ - with open(path, "w") as file: - write_commondata_data(commondata, file) - - -def write_systype_data(commondata, buffer): - """ - write systype table to buffer, this can be a memory map, - compressed archive or strings (using for instance StringIO) - - - Parameters - ---------- - - commondata : validphys.coredata.CommonData - - buffer : memory map, compressed archive or strings - example: StringIO object - - - Example - ------- - >>> from validphys.loader import Loader - >>> from io import StringIO - - >>> l = Loader() - >>> cd = l.check_commondata("NMC").load_commondata_instance() - >>> sio = StringIO() - >>> write_systype_data(cd,sio) - >>> print(sio.getvalue()) - - """ - header = f"{commondata.nsys}\n" - buffer.write(header) - commondata.systype_table.to_csv(buffer, sep="\t", header=None) - - -def write_systype_to_file(commondata, path): - """ - write systype table to file - """ - with open(path, "w") as file: - write_systype_data(commondata, file) diff --git a/validphys2/src/validphys/coredata.py b/validphys2/src/validphys/coredata.py index 3ef4b762c..8486269be 100644 --- a/validphys2/src/validphys/coredata.py +++ b/validphys2/src/validphys/coredata.py @@ -7,8 +7,6 @@ import dataclasses import yaml -from validphys.commondatawriter import write_commondata_to_file, write_systype_to_file - import numpy as np import pandas as pd from validphys.commondatawriter import write_commondata_to_file, write_systype_to_file @@ -285,6 +283,7 @@ def with_cuts(self, cuts): self, ndata=newndata, commondata_table=new_commondata_table ) + @property def central_values(self): return self.commondata_table["data"] From 2f30c4abfece87f1f0d2a99db739de68be9bcb55 Mon Sep 17 00:00:00 2001 From: Francesco Merlotti Date: Tue, 18 Jun 2024 20:54:06 +0100 Subject: [PATCH 31/35] Revert "added rules classes, static KIN_LABEL dict, and replaced cpp Export method with new Python one" This reverts commit 3f30ee0425229034c6b5e29bc4c1b34644316fd9. --- validphys2/src/validphys/config.py | 65 +++++++++----------------- validphys2/src/validphys/filters.py | 71 ++--------------------------- 2 files changed, 26 insertions(+), 110 deletions(-) diff --git a/validphys2/src/validphys/config.py b/validphys2/src/validphys/config.py index 273d6d30a..8eb6f863e 100644 --- a/validphys2/src/validphys/config.py +++ b/validphys2/src/validphys/config.py @@ -30,14 +30,6 @@ from reportengine import report from reportengine.compat import yaml -from validphys.filters import ( - Rule, - FilterRule, - AddedFilterRule, - RuleProcessingError, - default_filter_rules_input, - ) - from validphys.core import ( DataGroupSpec, DataSetInput, @@ -194,7 +186,7 @@ def parse_load_weights_from_fit(self, name: str): @element_of("theoryids") @_id_with_label - def parse_theoryid(self, theoryID: (str, int)): # type: ignore + def parse_theoryid(self, theoryID: (str, int)): """A number corresponding to the database theory ID where the corresponding theory folder is installed in te data directory.""" try: @@ -207,7 +199,7 @@ def parse_theoryid(self, theoryID: (str, int)): # type: ignore display_alternatives="all", ) - def parse_use_cuts(self, use_cuts: (bool, str)): # type: ignore # type: ignore + def parse_use_cuts(self, use_cuts: (bool, str)): """Whether to filter the points based on the cuts applied in the fit, or the whole data in the dataset. The possible options are: @@ -245,7 +237,7 @@ def produce_replicas(self, nreplica: int): return NSList(range(1, nreplica+1), nskey="replica") def produce_inclusive_use_scalevar_uncertainties(self, use_scalevar_uncertainties: bool = False, - point_prescription: (str, None) = None): # type: ignore + point_prescription: (str, None) = None): """Whether to use a scale variation uncertainty theory covmat. Checks whether a point prescription is included in the runcard and if so assumes scale uncertainties are to be used.""" @@ -1216,7 +1208,7 @@ def res(*args, **kwargs): return res def produce_fitthcovmat( - self, use_thcovmat_if_present: bool = False, fit: (str, type(None)) = None # type: ignore + self, use_thcovmat_if_present: bool = False, fit: (str, type(None)) = None ): """If a `fit` is specified and `use_thcovmat_if_present` is `True` then returns the corresponding covariance matrix for the given fit if it exists. If the fit doesn't have a @@ -1270,7 +1262,7 @@ def produce_fitthcovmat( fit_theory_covmat = None return fit_theory_covmat - def parse_speclabel(self, label: (str, type(None))): # type: ignore + def parse_speclabel(self, label: (str, type(None))): """A label for a dataspec. To be used in some plots""" return label @@ -1304,7 +1296,7 @@ def parse_groupby(self, grouping: str): ) return grouping - def parse_norm_threshold(self, val: (numbers.Number, type(None))): # type: ignore + def parse_norm_threshold(self, val: (numbers.Number, type(None))): """The threshold to use for covariance matrix normalisation, sets the maximum l2 norm of the inverse covariance matrix, by clipping smallest eigenvalues @@ -1327,7 +1319,7 @@ def produce_no_covmat_reg(self): return {"norm_threshold": None} @configparser.record_from_defaults - def parse_default_filter_rules(self, spec: (str, type(None))): # type: ignore + def parse_default_filter_rules(self, spec: (str, type(None))): return spec def load_default_default_filter_rules(self, spec): @@ -1351,7 +1343,7 @@ def load_default_default_filter_rules(self, spec): display_alternatives="all", ) - def parse_filter_rules(self, filter_rules: (list, type(None))): # type: ignore + def parse_filter_rules(self, filter_rules: (list, type(None))): """A list of filter rules. See https://docs.nnpdf.science/vp/filters.html for details on the syntax""" log.warning("Overwriting filter rules") @@ -1363,13 +1355,6 @@ def parse_default_filter_rules_recorded_spec_(self, spec): it reportengine detects a conflict in the `dataset` key. """ return spec - - def parse_added_filter_rules(self, rules: (list, type(None)) = None): # type: ignore - """ - Returns a tuple of AddedFilterRule objects. Rules are immutable after parsing. - AddedFilterRule objects inherit from FilterRule objects. - """ - return tuple(AddedFilterRule(**rule) for rule in rules) if rules else None def produce_rules( self, @@ -1379,9 +1364,15 @@ def produce_rules( default_filter_rules=None, filter_rules=None, default_filter_rules_recorded_spec_=None, - added_filter_rules=None, ): + """Produce filter rules based on the user defined input and defaults.""" + from validphys.filters import ( + Rule, + RuleProcessingError, + default_filter_rules_input, + ) + theory_parameters = theoryid.get_description() if filter_rules is None: @@ -1405,25 +1396,11 @@ def produce_rules( ] except RuleProcessingError as e: raise ConfigError(f"Error Processing filter rules: {e}") from e - - if added_filter_rules: - for i, rule in enumerate(added_filter_rules): - try: - rule_list.append( - Rule( - initial_data=rule, - defaults=defaults, - theory_parameters=theory_parameters, - loader=self.loader, - ) - ) - except RuleProcessingError as e: - raise ConfigError(f"Error processing added rule {i+1}: {e}") from e - - return tuple(rule_list) + + return rule_list @configparser.record_from_defaults - def parse_default_filter_settings(self, spec: (str, type(None))): # type: ignore + def parse_default_filter_settings(self, spec: (str, type(None))): return spec def load_default_default_filter_settings(self, spec): @@ -1447,7 +1424,7 @@ def load_default_default_filter_settings(self, spec): display_alternatives="all", ) - def parse_filter_defaults(self, filter_defaults: (dict, type(None))): # type: ignore + def parse_filter_defaults(self, filter_defaults: (dict, type(None))): """A mapping containing the default kinematic limits to be used when filtering data (when using internal cuts). Currently these limits are ``q2min`` and ``w2min``. @@ -1527,8 +1504,8 @@ def produce_data( def _parse_data_input_from_( self, - parse_from_value: (str, type(None)), # type: ignore - additional_context: (dict, type(None)) = None, # type: ignore + parse_from_value: (str, type(None)), + additional_context: (dict, type(None)) = None, ): """Function which parses the ``data_input`` from a namespace. Usage is similar to :py:meth:`self.parse_from_` except this function bridges diff --git a/validphys2/src/validphys/filters.py b/validphys2/src/validphys/filters.py index 6cf8b61dd..9c930c8f2 100644 --- a/validphys2/src/validphys/filters.py +++ b/validphys2/src/validphys/filters.py @@ -3,11 +3,9 @@ """ import logging -import dataclasses import re from collections.abc import Mapping from importlib.resources import read_text -from typing import Union import numpy as np @@ -18,32 +16,6 @@ log = logging.getLogger(__name__) -KIN_LABEL = { - "DIS": ("x", "Q2", "y"), - "DYP": ("y", "M2", "sqrts"), - "JET": ("eta", "p_T2", "sqrts"), - "DIJET": ("eta", "m_12", "sqrts"), - "PHT": ("eta_gamma", "E_Tgamma2", "sqrts"), - "INC": ("0", "mu2", "sqrts"), - "EWK_RAP": ("etay", "M2", "sqrts"), - "EWK_PT": ("p_T", "M2", "sqrts"), - "EWK_PTRAP": ("etay", "p_T2", "sqrts"), - "EWK_MLL": ("M_ll", "M_ll2", "sqrts"), - "EWJ_RAP": ("etay", "M2", "sqrts"), - "EWJ_PT": ("p_T", "M2", "sqrt(s)"), - "EWJ_PTRAP": ("etay", "p_T2", "sqrts"), - "EWJ_JRAP": ("etay", "M2", "sqrts"), - "EWJ_JPT": ("p_T", "M2", "sqrts"), - "EWJ_MLL": ("M_ll", "M_ll2", "sqrts"), - "HQP_YQQ": ("yQQ", "mu2", "sqrts"), - "HQP_MQQ": ("MQQ", "mu2", "sqrts"), - "HQP_PTQQ": ("p_TQQ", "mu2", "sqrts"), - "HQP_YQ": ("yQ", "mu2", "sqrts"), - "HQP_PTQ": ("p_TQ", "mu2", "sqrts"), - "HIG_RAP": ("y", "M_H2", "sqrts"), - "SIA": ("z", "Q2", "y"), -} - class RuleProcessingError(Exception): """Exception raised when we couldn't process a rule.""" @@ -59,34 +31,6 @@ class MissingRuleAttribute(RuleProcessingError, AttributeError): class FatalRuleError(Exception): """Exception raised when a rule application failed at runtime.""" -@dataclasses.dataclass(frozen=True) -class FilterRule: - """ - Dataclass which carries the filter rule information. - """ - - dataset: str = None - process_type: str = None - rule: str = None - reason: str = None - local_variables: Mapping[str, Union[str, float]] = None - PTO: str = None - FNS: str = None - IC: str = None - - def to_dict(self): - rule_dict = dataclasses.asdict(self) - filtered_dict = {k: v for k, v in rule_dict.items() if v is not None} - return filtered_dict - -@dataclasses.dataclass(frozen=True) -class AddedFilterRule(FilterRule): - """ - Dataclass which carries extra filter rule that is added to the - default rule. - """ - - pass def default_filter_settings_input(): """Return a dictionary with the default hardcoded filter settings. @@ -220,9 +164,7 @@ def _filter_real_data(filter_path, data): nfull, ncut = _write_ds_cut_data(path, dataset) total_data_points += nfull total_cut_data_points += ncut - # dataset.load().Export(str(path)) - cuts = dataset.cuts.load() - dataset.commondata.load_commondata(cuts=cuts).export(path) + dataset.load().Export(str(path)) return total_data_points, total_cut_data_points @@ -378,9 +320,6 @@ def __init__( self.dataset = None self.process_type = None self._local_variables_code = {} - - if isinstance(initial_data, FilterRule): - initial_data = initial_data.to_dict() for key in initial_data: setattr(self, key, initial_data[key]) @@ -404,14 +343,14 @@ def __init__( f"Could not find dataset {self.dataset}" ) from e if cd.process_type[:3] == "DIS": - self.variables = KIN_LABEL["DIS"] + self.variables = CommonData.kinLabel["DIS"] else: - self.variables = KIN_LABEL[cd.process_type] + self.variables = CommonData.kinLabel[cd.process_type] else: if self.process_type[:3] == "DIS": - self.variables = KIN_LABEL["DIS"] + self.variables = CommonData.kinLabel["DIS"] else: - self.variables = KIN_LABEL[self.process_type] + self.variables = CommonData.kinLabel[self.process_type] if hasattr(self, "local_variables"): if not isinstance(self.local_variables, Mapping): From c416434f9e1e4ad1dcf7aeba296c2eea18a932ab Mon Sep 17 00:00:00 2001 From: Francesco Merlotti Date: Tue, 18 Jun 2024 21:07:09 +0100 Subject: [PATCH 32/35] Revert "Revert "added rules classes, static KIN_LABEL dict, and replaced cpp Export method with new Python one"" This reverts commit bf4f6688336b9b72aa0a6f1bfaf7502107c46360. --- validphys2/src/validphys/config.py | 65 +++++++++++++++++--------- validphys2/src/validphys/filters.py | 71 +++++++++++++++++++++++++++-- 2 files changed, 110 insertions(+), 26 deletions(-) diff --git a/validphys2/src/validphys/config.py b/validphys2/src/validphys/config.py index 8eb6f863e..273d6d30a 100644 --- a/validphys2/src/validphys/config.py +++ b/validphys2/src/validphys/config.py @@ -30,6 +30,14 @@ from reportengine import report from reportengine.compat import yaml +from validphys.filters import ( + Rule, + FilterRule, + AddedFilterRule, + RuleProcessingError, + default_filter_rules_input, + ) + from validphys.core import ( DataGroupSpec, DataSetInput, @@ -186,7 +194,7 @@ def parse_load_weights_from_fit(self, name: str): @element_of("theoryids") @_id_with_label - def parse_theoryid(self, theoryID: (str, int)): + def parse_theoryid(self, theoryID: (str, int)): # type: ignore """A number corresponding to the database theory ID where the corresponding theory folder is installed in te data directory.""" try: @@ -199,7 +207,7 @@ def parse_theoryid(self, theoryID: (str, int)): display_alternatives="all", ) - def parse_use_cuts(self, use_cuts: (bool, str)): + def parse_use_cuts(self, use_cuts: (bool, str)): # type: ignore # type: ignore """Whether to filter the points based on the cuts applied in the fit, or the whole data in the dataset. The possible options are: @@ -237,7 +245,7 @@ def produce_replicas(self, nreplica: int): return NSList(range(1, nreplica+1), nskey="replica") def produce_inclusive_use_scalevar_uncertainties(self, use_scalevar_uncertainties: bool = False, - point_prescription: (str, None) = None): + point_prescription: (str, None) = None): # type: ignore """Whether to use a scale variation uncertainty theory covmat. Checks whether a point prescription is included in the runcard and if so assumes scale uncertainties are to be used.""" @@ -1208,7 +1216,7 @@ def res(*args, **kwargs): return res def produce_fitthcovmat( - self, use_thcovmat_if_present: bool = False, fit: (str, type(None)) = None + self, use_thcovmat_if_present: bool = False, fit: (str, type(None)) = None # type: ignore ): """If a `fit` is specified and `use_thcovmat_if_present` is `True` then returns the corresponding covariance matrix for the given fit if it exists. If the fit doesn't have a @@ -1262,7 +1270,7 @@ def produce_fitthcovmat( fit_theory_covmat = None return fit_theory_covmat - def parse_speclabel(self, label: (str, type(None))): + def parse_speclabel(self, label: (str, type(None))): # type: ignore """A label for a dataspec. To be used in some plots""" return label @@ -1296,7 +1304,7 @@ def parse_groupby(self, grouping: str): ) return grouping - def parse_norm_threshold(self, val: (numbers.Number, type(None))): + def parse_norm_threshold(self, val: (numbers.Number, type(None))): # type: ignore """The threshold to use for covariance matrix normalisation, sets the maximum l2 norm of the inverse covariance matrix, by clipping smallest eigenvalues @@ -1319,7 +1327,7 @@ def produce_no_covmat_reg(self): return {"norm_threshold": None} @configparser.record_from_defaults - def parse_default_filter_rules(self, spec: (str, type(None))): + def parse_default_filter_rules(self, spec: (str, type(None))): # type: ignore return spec def load_default_default_filter_rules(self, spec): @@ -1343,7 +1351,7 @@ def load_default_default_filter_rules(self, spec): display_alternatives="all", ) - def parse_filter_rules(self, filter_rules: (list, type(None))): + def parse_filter_rules(self, filter_rules: (list, type(None))): # type: ignore """A list of filter rules. See https://docs.nnpdf.science/vp/filters.html for details on the syntax""" log.warning("Overwriting filter rules") @@ -1355,6 +1363,13 @@ def parse_default_filter_rules_recorded_spec_(self, spec): it reportengine detects a conflict in the `dataset` key. """ return spec + + def parse_added_filter_rules(self, rules: (list, type(None)) = None): # type: ignore + """ + Returns a tuple of AddedFilterRule objects. Rules are immutable after parsing. + AddedFilterRule objects inherit from FilterRule objects. + """ + return tuple(AddedFilterRule(**rule) for rule in rules) if rules else None def produce_rules( self, @@ -1364,15 +1379,9 @@ def produce_rules( default_filter_rules=None, filter_rules=None, default_filter_rules_recorded_spec_=None, + added_filter_rules=None, ): - """Produce filter rules based on the user defined input and defaults.""" - from validphys.filters import ( - Rule, - RuleProcessingError, - default_filter_rules_input, - ) - theory_parameters = theoryid.get_description() if filter_rules is None: @@ -1396,11 +1405,25 @@ def produce_rules( ] except RuleProcessingError as e: raise ConfigError(f"Error Processing filter rules: {e}") from e - - return rule_list + + if added_filter_rules: + for i, rule in enumerate(added_filter_rules): + try: + rule_list.append( + Rule( + initial_data=rule, + defaults=defaults, + theory_parameters=theory_parameters, + loader=self.loader, + ) + ) + except RuleProcessingError as e: + raise ConfigError(f"Error processing added rule {i+1}: {e}") from e + + return tuple(rule_list) @configparser.record_from_defaults - def parse_default_filter_settings(self, spec: (str, type(None))): + def parse_default_filter_settings(self, spec: (str, type(None))): # type: ignore return spec def load_default_default_filter_settings(self, spec): @@ -1424,7 +1447,7 @@ def load_default_default_filter_settings(self, spec): display_alternatives="all", ) - def parse_filter_defaults(self, filter_defaults: (dict, type(None))): + def parse_filter_defaults(self, filter_defaults: (dict, type(None))): # type: ignore """A mapping containing the default kinematic limits to be used when filtering data (when using internal cuts). Currently these limits are ``q2min`` and ``w2min``. @@ -1504,8 +1527,8 @@ def produce_data( def _parse_data_input_from_( self, - parse_from_value: (str, type(None)), - additional_context: (dict, type(None)) = None, + parse_from_value: (str, type(None)), # type: ignore + additional_context: (dict, type(None)) = None, # type: ignore ): """Function which parses the ``data_input`` from a namespace. Usage is similar to :py:meth:`self.parse_from_` except this function bridges diff --git a/validphys2/src/validphys/filters.py b/validphys2/src/validphys/filters.py index 9c930c8f2..6cf8b61dd 100644 --- a/validphys2/src/validphys/filters.py +++ b/validphys2/src/validphys/filters.py @@ -3,9 +3,11 @@ """ import logging +import dataclasses import re from collections.abc import Mapping from importlib.resources import read_text +from typing import Union import numpy as np @@ -16,6 +18,32 @@ log = logging.getLogger(__name__) +KIN_LABEL = { + "DIS": ("x", "Q2", "y"), + "DYP": ("y", "M2", "sqrts"), + "JET": ("eta", "p_T2", "sqrts"), + "DIJET": ("eta", "m_12", "sqrts"), + "PHT": ("eta_gamma", "E_Tgamma2", "sqrts"), + "INC": ("0", "mu2", "sqrts"), + "EWK_RAP": ("etay", "M2", "sqrts"), + "EWK_PT": ("p_T", "M2", "sqrts"), + "EWK_PTRAP": ("etay", "p_T2", "sqrts"), + "EWK_MLL": ("M_ll", "M_ll2", "sqrts"), + "EWJ_RAP": ("etay", "M2", "sqrts"), + "EWJ_PT": ("p_T", "M2", "sqrt(s)"), + "EWJ_PTRAP": ("etay", "p_T2", "sqrts"), + "EWJ_JRAP": ("etay", "M2", "sqrts"), + "EWJ_JPT": ("p_T", "M2", "sqrts"), + "EWJ_MLL": ("M_ll", "M_ll2", "sqrts"), + "HQP_YQQ": ("yQQ", "mu2", "sqrts"), + "HQP_MQQ": ("MQQ", "mu2", "sqrts"), + "HQP_PTQQ": ("p_TQQ", "mu2", "sqrts"), + "HQP_YQ": ("yQ", "mu2", "sqrts"), + "HQP_PTQ": ("p_TQ", "mu2", "sqrts"), + "HIG_RAP": ("y", "M_H2", "sqrts"), + "SIA": ("z", "Q2", "y"), +} + class RuleProcessingError(Exception): """Exception raised when we couldn't process a rule.""" @@ -31,6 +59,34 @@ class MissingRuleAttribute(RuleProcessingError, AttributeError): class FatalRuleError(Exception): """Exception raised when a rule application failed at runtime.""" +@dataclasses.dataclass(frozen=True) +class FilterRule: + """ + Dataclass which carries the filter rule information. + """ + + dataset: str = None + process_type: str = None + rule: str = None + reason: str = None + local_variables: Mapping[str, Union[str, float]] = None + PTO: str = None + FNS: str = None + IC: str = None + + def to_dict(self): + rule_dict = dataclasses.asdict(self) + filtered_dict = {k: v for k, v in rule_dict.items() if v is not None} + return filtered_dict + +@dataclasses.dataclass(frozen=True) +class AddedFilterRule(FilterRule): + """ + Dataclass which carries extra filter rule that is added to the + default rule. + """ + + pass def default_filter_settings_input(): """Return a dictionary with the default hardcoded filter settings. @@ -164,7 +220,9 @@ def _filter_real_data(filter_path, data): nfull, ncut = _write_ds_cut_data(path, dataset) total_data_points += nfull total_cut_data_points += ncut - dataset.load().Export(str(path)) + # dataset.load().Export(str(path)) + cuts = dataset.cuts.load() + dataset.commondata.load_commondata(cuts=cuts).export(path) return total_data_points, total_cut_data_points @@ -320,6 +378,9 @@ def __init__( self.dataset = None self.process_type = None self._local_variables_code = {} + + if isinstance(initial_data, FilterRule): + initial_data = initial_data.to_dict() for key in initial_data: setattr(self, key, initial_data[key]) @@ -343,14 +404,14 @@ def __init__( f"Could not find dataset {self.dataset}" ) from e if cd.process_type[:3] == "DIS": - self.variables = CommonData.kinLabel["DIS"] + self.variables = KIN_LABEL["DIS"] else: - self.variables = CommonData.kinLabel[cd.process_type] + self.variables = KIN_LABEL[cd.process_type] else: if self.process_type[:3] == "DIS": - self.variables = CommonData.kinLabel["DIS"] + self.variables = KIN_LABEL["DIS"] else: - self.variables = CommonData.kinLabel[self.process_type] + self.variables = KIN_LABEL[self.process_type] if hasattr(self, "local_variables"): if not isinstance(self.local_variables, Mapping): From fa1766e107eb9ba08c1f2cb26e2f58df02d4c2d8 Mon Sep 17 00:00:00 2001 From: Francesco Merlotti Date: Mon, 15 Jul 2024 16:22:37 +0100 Subject: [PATCH 33/35] Revert "Revert "added commondatawriter.py & export method for CommonData python objects"" This reverts commit 17d3296eb4a567b00a7e032913a3b5e48739c52e. --- validphys2/src/validphys/commondatawriter.py | 84 ++++++++++++++++++++ validphys2/src/validphys/coredata.py | 5 +- 2 files changed, 87 insertions(+), 2 deletions(-) create mode 100644 validphys2/src/validphys/commondatawriter.py diff --git a/validphys2/src/validphys/commondatawriter.py b/validphys2/src/validphys/commondatawriter.py new file mode 100644 index 000000000..650df84cb --- /dev/null +++ b/validphys2/src/validphys/commondatawriter.py @@ -0,0 +1,84 @@ +""" +This module contains functions to write commondata and systypes +tables to files +""" + + +def write_commondata_data(commondata, buffer): + """ + write commondata table to buffer, this can be a memory map, + compressed archive or strings (using for instance StringIO) + + + Parameters + ---------- + + commondata : validphys.coredata.CommonData + + buffer : memory map, compressed archive or strings + example: StringIO object + + + Example + ------- + >>> from validphys.loader import Loader + >>> from io import StringIO + + >>> l = Loader() + >>> cd = l.check_commondata("NMC").load_commondata_instance() + >>> sio = StringIO() + >>> write_commondata_data(cd,sio) + >>> print(sio.getvalue()) + + """ + header = f"{commondata.setname} {commondata.nsys} {commondata.ndata}\n" + buffer.write(header) + commondata.commondata_table.to_csv(buffer, sep="\t", header=None) + + +def write_commondata_to_file(commondata, path): + """ + write commondata table to file + """ + with open(path, "w") as file: + write_commondata_data(commondata, file) + + +def write_systype_data(commondata, buffer): + """ + write systype table to buffer, this can be a memory map, + compressed archive or strings (using for instance StringIO) + + + Parameters + ---------- + + commondata : validphys.coredata.CommonData + + buffer : memory map, compressed archive or strings + example: StringIO object + + + Example + ------- + >>> from validphys.loader import Loader + >>> from io import StringIO + + >>> l = Loader() + >>> cd = l.check_commondata("NMC").load_commondata_instance() + >>> sio = StringIO() + >>> write_systype_data(cd,sio) + >>> print(sio.getvalue()) + + """ + header = f"{commondata.nsys}\n" + buffer.write(header) + commondata.systype_table.to_csv(buffer, sep="\t", header=None) + + +def write_systype_to_file(commondata, path): + """ + write systype table to file + """ + with open(path, "w") as file: + write_systype_data(commondata, file) diff --git a/validphys2/src/validphys/coredata.py b/validphys2/src/validphys/coredata.py index 8486269be..333d4e6bd 100644 --- a/validphys2/src/validphys/coredata.py +++ b/validphys2/src/validphys/coredata.py @@ -7,6 +7,8 @@ import dataclasses import yaml +from validphys.commondatawriter import write_commondata_to_file, write_systype_to_file + import numpy as np import pandas as pd from validphys.commondatawriter import write_commondata_to_file, write_systype_to_file @@ -283,7 +285,6 @@ def with_cuts(self, cuts): self, ndata=newndata, commondata_table=new_commondata_table ) - @property def central_values(self): return self.commondata_table["data"] @@ -378,4 +379,4 @@ def export(self, path): sys_path.parent.mkdir(exist_ok=True) write_systype_to_file(self, sys_path) - write_commondata_to_file(self, dat_path) + write_commondata_to_file(self, dat_path) \ No newline at end of file From 44b97d9d39c09dceaaeabd44b2e003da994ef6a9 Mon Sep 17 00:00:00 2001 From: Francesco Merlotti Date: Tue, 18 Jun 2024 21:08:18 +0100 Subject: [PATCH 34/35] Revert "Revert "added xq2 map for hadronic MQQ processes ref. [2303.06159]"" This reverts commit 85dd428bd0583f9d595aaa7f34dd5d097c2b3622. --- validphys2/src/validphys/plotoptions/kintransforms.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/validphys2/src/validphys/plotoptions/kintransforms.py b/validphys2/src/validphys/plotoptions/kintransforms.py index 786d166f7..1a3e86392 100644 --- a/validphys2/src/validphys/plotoptions/kintransforms.py +++ b/validphys2/src/validphys/plotoptions/kintransforms.py @@ -188,6 +188,12 @@ def xq2map(self, k1, k2, k3, **extra_labels): Q = (np.sqrt(QQMASS2+k1*k1)+k1) return Q/k3, Q*Q +class HQQMQQXQ2MapMixin: + def xq2map(self, k1, k2, k3, **extra_labels): + """in inv mass Experiments k1 is the mttbar, k2 is mu, and k3 is sqrt(s)""" + Q = k1 / 4 + return Q / k3, Q*Q + class dyp_sqrt_scale(SqrtScaleMixin, DYXQ2MapMixin): qlabel = '$M (GeV)$' @@ -257,8 +263,9 @@ class ewk_rap_sqrt_scale(SqrtScaleMixin,DYXQ2MapMixin): # EWK_RAP -> DY okay class hig_rap_sqrt_scale(SqrtScaleMixin,DYXQ2MapMixin): #okay, but it does not exist qlabel = '$M_H (GeV)$' -class hqp_mqq_sqrt_scale(SqrtScaleMixin,DYMXQ2MapMixin): # HQP_MQQ -> DYM okay - qlabel = r'$\mu (GeV)$' +class hqp_mqq_sqrt_scale(SqrtScaleMixin,HQQMQQXQ2MapMixin): # HQP_MQQ -> DYM okay + # qlabel = r'$\mu (GeV)$' + qlabel = r'$M^{QQ} (GeV) / 4$' class hqp_ptq_sqrt_scale(SqrtScaleMixin,HQPTXQ2MapMixin): # HQP_PTQ -> HQPT okay qlabel = r'$\mu (GeV)$' From 957a9923b73f01a76c79c1126d9024ba805bf536 Mon Sep 17 00:00:00 2001 From: Francesco Merlotti Date: Wed, 19 Jun 2024 17:33:29 +0100 Subject: [PATCH 35/35] added Q2min, Q2max to plot kincov with fixed y_lim --- validphys2/src/validphys/dataplots.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/validphys2/src/validphys/dataplots.py b/validphys2/src/validphys/dataplots.py index af2e7b657..f3c8eba12 100644 --- a/validphys2/src/validphys/dataplots.py +++ b/validphys2/src/validphys/dataplots.py @@ -1066,6 +1066,8 @@ def plot_xq2( dataset_inputs_by_groups_xq2map, use_cuts, data_input, + Q2min=None, + Q2max=None, display_cuts:bool=True, marker_by:str='process type', highlight_label:str='highlight', @@ -1300,4 +1302,13 @@ def next_options(): ax.set_ylabel(r'$Q^2$ (GeV$^2$)') ax.set_xscale('log') ax.set_yscale('log') + + _, curr_Q2max = ax.get_ylim() + if Q2min: + ax.set_ylim(Q2min, curr_Q2max) + + curr_Q2min, _ = ax.get_ylim() + if Q2max: + ax.set_ylim(curr_Q2min, Q2max) + return fig