Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Group FONLL configuration by theory #131

Merged
merged 6 commits into from
Oct 20, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 9 additions & 7 deletions benchmarks/bench_evolve.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,12 @@
import pineko.theory_card


@pytest.mark.parametrize("theoryid", [400, 208])
@pytest.mark.parametrize("theoryid,is_mixed", [(400, False), (208, True)])
def benchmark_write_operator_card_from_file_num_fonll(
tmp_path, test_files, test_configs, theoryid
tmp_path, test_files, test_configs, theoryid, is_mixed
):
tcard = pineko.theory_card.load(theoryid)
fonll_config = pineko.fonll.FonllSchemeConfig(tcard["FNS"], tcard["DAMP"])
tcards_path_list = pineko.fonll.produce_fonll_tcards(tcard, tmp_path, theoryid)
tcards_path_list = pineko.fonll.dump_tcards(tcard, tmp_path, theoryid)
pine_path = (
test_files
/ "data"
Expand All @@ -27,7 +26,7 @@ def benchmark_write_operator_card_from_file_num_fonll(
/ "HERA_NC_225GEV_EP_SIGMARED.pineappl.lz4"
)
default_path = test_files / "data" / "operator_cards" / "400" / "_template.yaml"
num_opcard = 7 if fonll_config.is_mixed() else 5
num_opcard = 7 if is_mixed else 5
targets_path_list = [
tmp_path / f"test_opcard_{num}.yaml" for num in range(num_opcard)
]
Expand All @@ -38,11 +37,14 @@ def benchmark_write_operator_card_from_file_num_fonll(
pine_path, default_path, target_path, tcard
)
# Check if the opcards are ok
for opcard_path, nfff in zip(targets_path_list, fonll_config.subfks_nfff()):
for opcard_path, cfg in zip(
targets_path_list,
pineko.fonll.MIXED_FNS_CONFIG if is_mixed else pineko.fonll.FNS_CONFIG,
):
with open(opcard_path, encoding="utf-8") as f:
ocard = yaml.safe_load(f)
for entry in ocard["mugrid"]:
assert entry[1] == int(nfff)
assert entry[1] == int(cfg.nf)


def benchmark_write_operator_card_from_file(tmp_path, test_files, test_configs):
Expand Down
54 changes: 30 additions & 24 deletions benchmarks/bench_fonll.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,33 @@

import pineko


@pytest.mark.parametrize("theoryid", [400, 208])
def benchmark_produce_fonll_tcards(tmp_path, test_files, test_configs, theoryid):
tcard = pineko.theory_card.load(theoryid)
fonll_config = pineko.fonll.FonllSchemeConfig(tcard["FNS"], tcard["DAMP"])
tcard_paths_list = pineko.fonll.produce_fonll_tcards(tcard, tmp_path, theoryid)
# Check they are correct
theorycards = []
for path in tcard_paths_list:
with open(path, encoding="UTF-8") as f:
theorycards.append(yaml.safe_load(f))
for num_fonll_tcard, fns, nfff, po, part in zip(
theorycards,
fonll_config.subfks_fns(),
fonll_config.subfks_nfff(),
fonll_config.subfks_ptos(),
fonll_config.subfks_parts(),
):
assert num_fonll_tcard["FNS"] == fns
assert num_fonll_tcard["NfFF"] == int(nfff)
assert num_fonll_tcard["PTO"] == po
if fonll_config.is_mixed() and fns == "FONLL-FFN0":
assert num_fonll_tcard["PTODIS"] == po + 1
assert num_fonll_tcard["FONLLParts"] == part
# @pytest.mark.parametrize("theoryid,is_mixed", [(400, False), (208, True)])
# def benchmark_produce_fonll_tcards(
# tmp_path, test_files, test_configs, theoryid, is_mixed
# ):
# tcard = pineko.theory_card.load(theoryid)
# tcard_paths_list = pineko.fonll.produce_fonll_tcards(tcard, tmp_path, theoryid)
# # Check they are correct
# theorycards = []
# for path in tcard_paths_list:
# with open(path, encoding="UTF-8") as f:
# theorycards.append(yaml.safe_load(f))
# for num_fonll_tcard, fns, nfff, po, part in zip(
# theorycards,
# np.array(pineko.fonll.MIXED_FNS_CONFIG).transpose().tolist()[0]
# if is_mixed
# else np.array(pineko.fonll.FNS_CONFIG).transpose().tolist()[0],
# np.array(pineko.fonll.MIXED_FNS_CONFIG).transpose().tolist()[1]
# if is_mixed
# else np.array(pineko.fonll.FNS_CONFIG).transpose().tolist()[1],
# pineko.fonll.produce_ptos(tcard["FNS"], is_mixed),
# np.array(pineko.fonll.MIXED_FNS_CONFIG).transpose().tolist()[2]
# if is_mixed
# else np.array(pineko.fonll.FNS_CONFIG).transpose().tolist()[2],
# ):
# assert num_fonll_tcard["FNS"] == fns
# assert num_fonll_tcard["NfFF"] == int(nfff)
# assert (
# num_fonll_tcard["PTO"] == po - 1 if is_mixed and fns == "FONLL-FFN0" else po
# )
# assert num_fonll_tcard["FONLLParts"] == part
3 changes: 2 additions & 1 deletion src/pineko/cli/fonll.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,4 +171,5 @@ def fonll_tcards(theoryid, cfg):
tcard_parent_path = theory_card.path(theoryid).parent
if "FONLL" not in tcard["FNS"]:
raise TheoryCardError("The theorycard does not correspond to an FONLL scheme.")
_ = fonll.produce_fonll_tcards(tcard, tcard_parent_path, theoryid)
fonll.dump_tcards(tcard, tcard_parent_path, theoryid)
return
felixhekhorn marked this conversation as resolved.
Show resolved Hide resolved
234 changes: 84 additions & 150 deletions src/pineko/fonll.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
"""Module to manage FONLL predictions."""

import copy
import dataclasses
import json
import logging
import tempfile
from dataclasses import dataclass
from pathlib import Path

import numpy as np
import pineappl
import rich
import yaml
Expand All @@ -16,116 +15,6 @@

logger = logging.getLogger(__name__)

FNS_BASE_PTO = {"FONLL-A": 1, "FONLL-B": 1, "FONLL-C": 2, "FONLL-D": 2, "FONLL-E": 3}
MIXED_ORDER_FNS = ["FONLL-B", "FONLL-D"]
# Notice we rely on the order defined by the FONLLInfo class
FK_TO_DAMP = {
"mc": ["ffn03", "ffns4til", "ffn04", "ffns5til"],
"mb": ["ffn04", "ffns5til"],
}
FK_WITH_MINUS = ["ffn03", "ffn04"] # asy terms should be subtracted, therefore the sign


@dataclass
class FonllSchemeConfig:
"""Class to group FONLL scheme configs."""

fonll_fns: str
is_damp: bool

def is_mixed(self) -> bool:
"""Whether the fonll fns is of mixed type."""
return self.fonll_fns in MIXED_ORDER_FNS

def base_pto(self) -> int:
"""Return the base perturbative order of the fonll fns."""
return FNS_BASE_PTO[self.fonll_fns]

def subfks_fns(self) -> list:
"""Return the fns of the sub FK table components of the fonll fns."""
if self.is_mixed() or self.is_damp:
return [
"FONLL-FFNS",
"FONLL-FFN0",
"FONLL-FFNS",
"FONLL-FFNS",
"FONLL-FFN0",
"FONLL-FFNS",
"FONLL-FFNS",
]
else:
return [
"FONLL-FFNS",
"FONLL-FFN0",
"FONLL-FFNS",
"FONLL-FFN0",
"FONLL-FFNS",
]

def subfks_nfff(self) -> list:
"""Return the nfff of the sub FK table components of the fonll fns."""
if self.is_mixed() or self.is_damp:
return [3, 3, 4, 4, 4, 5, 5]
else:
return [3, 3, 4, 4, 5]

def subfks_parts(self) -> list:
"""Return the parts of the sub FK table components of the fonll fns that need to be computed."""
if self.is_mixed() or self.is_damp:
return [
"full",
"full",
"massless",
"massive",
"full",
"massless",
"massive",
]
else:
return ["full" for _ in range(5)]

def subfks_ptos(self) -> list:
"""Return the ptos of the sub FK table components of the fonll fns."""
base_pto = self.base_pto()
if self.is_mixed():
return [
base_pto + 1,
base_pto,
base_pto,
base_pto + 1,
base_pto,
base_pto,
base_pto + 1,
]
elif self.is_damp:
return [base_pto for _ in range(7)]
else:
return [base_pto for _ in range(5)]

def produce_fonll_recipe(self) -> dict:
"""Produce the different fonll recipes according to which FONLL is asked for."""
fonll_recipe = []
for fns, nfff, po, part in zip(
self.subfks_fns(),
self.subfks_nfff(),
self.subfks_ptos(),
self.subfks_parts(),
):
fonll_recipe.append(
{
"FNS": fns,
"NfFF": nfff,
"PTO": po,
"FONLLParts": part,
}
)
# In a mixed FONLL scheme we only subract the resummed terms that are
# present in the FFNS scheme at nf+1. E.g. for FONLL-B in FFN03 we
# only subract up to NLL since there is no NNLL in FFNS4
if self.is_mixed() and fns == "FONLL-FFN0":
fonll_recipe[-1]["PTODIS"] = po + 1
return fonll_recipe


class FONLLInfo:
"""Class containing all the information for FONLL predictions."""
Expand Down Expand Up @@ -196,6 +85,14 @@ def Q2grid(self):
return self.fks[list(self.fks)[0]].bin_left(0)


# Notice we rely on the order defined by the FONLLInfo class
FK_TO_DAMP = {
"mc": ["ffn03", "ffns4til", "ffn04", "ffns5til"],
"mb": ["ffn04", "ffns5til"],
}
FK_WITH_MINUS = ["ffn03", "ffn04"] # asy terms should be subtracted, therefore the sign


def update_fk_theorycard(combined_fk, input_theorycard_path):
"""Update theorycard entries for the combined fktable by reading the yamldb of the original theory."""
with open(input_theorycard_path) as f:
Expand Down Expand Up @@ -262,18 +159,18 @@ def produce_combined_fk(
)
theorycard_constituent_fks = fonll_info.theorycard_no_fns_pto
fk_dict = fonll_info.fks
if damp[0] == 0:
# then there is no damping, not even Heaviside only
combined_fk = combine(fk_dict)
else:
dampings = produce_dampings(theorycard_constituent_fks, fonll_info, damp)
combined_fk = combine(fk_dict, dampings=dampings)
dampings = (
None
if damp[0] == 0
else produce_dampings(theorycard_constituent_fks, fonll_info, damp)
)
combined_fk = combine(fk_dict, dampings=dampings)
input_theorycard_path = (
Path(configs.load(configs.detect(cfg))["paths"]["theory_cards"])
/ f"{theoryid}.yaml"
)
update_fk_theorycard(combined_fk, input_theorycard_path)
# save final "fonll" fktable
# save final FONLL fktable
fk_folder = Path(configs.load(configs.detect(cfg))["paths"]["fktables"]) / str(
theoryid
)
Expand All @@ -282,40 +179,77 @@ def produce_combined_fk(
combined_fk.write_lz4(output_path_fk)


def produce_ptos(fns, is_mixed_or_damp):
"""Produce the list of PTOs needed for the requested fns."""
base_pto = FNS_BASE_PTO[fns]
# mixed FONLL
if fns in MIXED_ORDER_FNS:
return [
base_pto + 1,
base_pto + 1,
base_pto,
base_pto + 1,
base_pto + 1,
base_pto,
base_pto + 1,
]
# plain FONLL with damping
elif is_mixed_or_damp:
return [base_pto for _ in range(7)]
# plain FONLL without damping
# In the case of not mixed and not damped FK tables, 5 sub FK tables are enough
else:
return [base_pto for _ in range(5)]


def produce_fonll_tcards(tcard, tcard_parent_path, theoryid):
"""Produce the seven fonll tcards from an original tcard.

The produced tcards are dumped in tcard_parent_path with names from '{theoryid}00.yaml' to '{theoryid}06.yaml'.
FNS_BASE_PTO = {"FONLL-A": 1, "FONLL-B": 1, "FONLL-C": 2, "FONLL-D": 2, "FONLL-E": 3}
MIXED_ORDER_FNS = ["FONLL-B", "FONLL-D"]


@dataclasses.dataclass
class SubTheoryConfig:
"""Single (sub-)theory configuration."""

scheme: str
nf: int
parts: str
delta_pto: int = 0


MIXED_FNS_CONFIG = [
SubTheoryConfig("FONLL-FFNS", 3, "full", 1),
SubTheoryConfig("FONLL-FFN0", 3, "full", 1),
SubTheoryConfig("FONLL-FFNS", 4, "massless"),
SubTheoryConfig("FONLL-FFNS", 4, "massive", 1),
SubTheoryConfig("FONLL-FFN0", 4, "full", 1),
SubTheoryConfig("FONLL-FFNS", 5, "massless"),
SubTheoryConfig("FONLL-FFNS", 5, "massive", 1),
]
"""Mixed FONLL schemes."""

FNS_CONFIG = [
SubTheoryConfig("FONLL-FFNS", 3, "full"),
SubTheoryConfig("FONLL-FFN0", 3, "full"),
SubTheoryConfig("FONLL-FFNS", 4, "full"),
SubTheoryConfig("FONLL-FFN0", 4, "full"),
SubTheoryConfig("FONLL-FFNS", 5, "full"),
]
"""Plain FONLL schemes."""
felixhekhorn marked this conversation as resolved.
Show resolved Hide resolved


def collect_updates(fonll_fns, damp):
"""Produce the different theory cards according to which FONLL is asked for."""
updates = []
is_mixed = fonll_fns in MIXED_ORDER_FNS
is_damped = damp != 0
base_pto = FNS_BASE_PTO[fonll_fns]
cfgs = MIXED_FNS_CONFIG if is_mixed or is_damped else FNS_CONFIG
for cfg in cfgs:
po = int(base_pto) + (cfg.delta_pto if is_mixed else 0)
updates.append(
{
"FNS": cfg.scheme,
"NfFF": cfg.nf,
"PTO": po,
"FONLLParts": cfg.parts,
}
)
# In a mixed FONLL scheme we only subract the resummed terms that are
# present in the FFNS scheme at nf+1. E.g. for FONLL-B in FFN03 we
# only subract up to NLL since there is no NNLL in FFNS4
if fonll_fns in MIXED_ORDER_FNS and cfg.scheme == "FONLL-FFN0":
updates[-1]["PTODIS"] = po
updates[-1]["PTO"] = po - 1
return updates


def dump_tcards(tcard, tcard_parent_path, theoryid):
"""Produce the seven FONLL theory cards from the original one.

The produced theory cards are dumped in `tcard_parent_path` with names from '{theoryid}00.yaml' to '{theoryid}06.yaml'.
"""
fonll_config = FonllSchemeConfig(tcard["FNS"], tcard["DAMP"])
fonll_recipe = fonll_config.produce_fonll_recipe()
n_theory = len(fonll_recipe)
updates = collect_updates(tcard["FNS"], tcard["DAMP"])
n_theory = len(updates)
theorycards = [copy.deepcopy(tcard) for _ in range(n_theory)]
paths_list = []
for num, (theorycard, recipe) in enumerate(zip(theorycards, fonll_recipe)):
for num, (theorycard, recipe) in enumerate(zip(theorycards, updates)):
# update cards entries
theorycard.update(recipe)
theorycard["ID"] = int(f"{theoryid}0{num}")
Expand Down