Source code for ekobox.utils

"""Generic utilities to work with EKOs."""

from collections import defaultdict
from pathlib import Path
from typing import Optional

import numpy as np

from eko.io.struct import EKO, Operator

CONTRACTION = "ajbk,bkcl -> ajcl"


[docs] def ekos_product( eko_ini: EKO, eko_fin: EKO, rtol: float = 1e-6, atol: float = 1e-10, path: Optional[Path] = None, ): """Compute the product of two ekos. Parameters ---------- eko_ini : initial eko operator eko_fin : final eko operator rtol : relative tolerance on Q2, used to check compatibility atol : absolute tolerance on Q2, used to check compatibility path : if not provided, the operation is done in-place, otherwie a new operator is written at the given path """ # TODO: add a control on the theory (but before we need to implement # another kind of output which includes the theory and operator runcards) ep_match = eko_ini.approx( (eko_fin.operator_card.init[0] ** 2, eko_fin.operator_card.init[1]), rtol=rtol, atol=atol, ) if ep_match is None: raise ValueError( "Initial Q2 of final eko operator does not match any final Q2 in" " the initial eko operator" ) ope1 = eko_ini[ep_match] assert ope1 is not None ope1_value = ope1.operator.copy() ope1_error = ope1.error if ope1_error is not None: ope1_error = ope1_error.copy() if path is None: final_eko = eko_ini else: eko_ini.deepcopy(path) final_eko = EKO.edit(path) for q2, op2 in eko_fin.items(): if q2 in eko_ini: continue op = np.einsum(CONTRACTION, ope1_value, op2.operator) if ope1_error is not None and op2.error is not None: error = np.einsum(CONTRACTION, ope1_value, op2.error) + np.einsum( CONTRACTION, ope1_error, op2.operator ) else: error = None final_eko[q2] = Operator(operator=op, error=error) if path is not None: final_eko.close()
[docs] def regroup_evolgrid(evolgrid: list): """Split evolution points by nf and sort by scale.""" by_nf = defaultdict(list) for q, nf in sorted(evolgrid, key=lambda ep: ep[1]): by_nf[nf].append(q) return {nf: sorted(qs) for nf, qs in by_nf.items()}