"""High level interface to the observable names.
Wrap the name of the observables in order to perform operations on
them. For instance, it checks what kind of observable is being dealt
with and whether or not the given flavor is heavy.
"""
fake_kind = "??"
sfs = ["F2", "FL", "F3", "g1", "gL", "g4"]
# xs = ["XSreduced", "XSyreduced"]
xs = [
"XSHERANC",
"XSHERANCAVG",
"XSHERACC",
"XSCHORUSCC",
"XSNUTEVCC",
"XSNUTEVNU",
"FW",
"F1",
"g5",
"XSFPFCC",
]
kinds = sfs + xs + [fake_kind]
# external flavors:
heavys = ["charm", "bottom", "top"]
heavylights = [h + "light" for h in heavys]
external_flavors = heavys + ["light", "total"] + heavylights
# internally we allow in addition for the flavor families
flavors = external_flavors + ["heavy"]
[docs]
class ObservableName:
r"""Wrapper to observable names to easy split them into two parts.
Parameters
----------
name : str
full observable name
"""
def __init__(self, name):
p = name.split("_")
if len(p) == 1:
self.kind, self.flavor = p[0], "total"
elif len(p) == 2:
self.kind, self.flavor = p[0], p[1]
else:
raise ValueError(f"Unknown obsname {name}")
if self.kind not in kinds:
raise ValueError(f"Unknown kind {self.kind}")
if self.flavor not in flavors:
raise ValueError(f"Unknown flavor {self.flavor}")
@property
def name(self):
"""Return joint name."""
return self.kind + "_" + self.flavor
@property
def is_parity_violating(self):
"""Check if it is a parity violating observable."""
if self.kind in ["F3", "gL", "g4"]:
return True
return False
[docs]
def __eq__(self, other):
"""Test equality of kind and flavor."""
return self.kind == other.kind and self.flavor == other.flavor
[docs]
def apply_kind(self, kind):
r"""Create new object with given kind and our flavor.
Parameters
----------
kind : str
new kind
Returns
-------
apply_kind : type(self)
new kind and our flavor
"""
return type(self)(kind + "_" + self.flavor)
[docs]
def __repr__(self):
"""Return the full representation name."""
return self.name
[docs]
def apply_flavor(self, flavor):
r"""Create new object with given flavor and our kind.
Parameters
----------
flavor : str
new flavor
Returns
-------
apply_flavor : type(self)
our kind and new flavor
"""
return type(self)(self.kind + "_" + flavor)
@property
def is_heavy(self):
r"""Check if the given flavor is heavy.
Returns
-------
is_heavy : bool
is a heavy flavor?
"""
return self.flavor != "light"
@property
def is_raw_heavy(self):
"""Check if it is a raw heavy flavor.
i.e. charm, bottom, or, top.
"""
return self.flavor in heavys
@property
def is_heavylight(self):
"""Check if it is a heavylight flavor.
i.e. charmlight, bottomlight, or, toplight.
"""
return self.flavor in heavylights
@property
def is_composed(self):
"""Check if it is a composed flavor.
i.e. total.
"""
return self.flavor == "total"
@property
def flavor_family(self):
"""Return abstract flavor family name."""
if self.is_raw_heavy:
return "heavy"
if self.is_heavylight:
return "light"
return self.flavor
[docs]
def apply_flavor_family(self):
r"""Return name with abstract flavor family name.
Returns
-------
apply_flavor_family : type(self)
new ObservableName
"""
return self.apply_flavor(self.flavor_family)
@property
def hqnumber(self):
"""Return Heavy quark flavor number"""
if self.is_heavylight:
idx = heavylights.index(self.flavor)
elif self.flavor_family in ["light", "total"]:
idx = -4
else:
idx = heavys.index(self.flavor)
return 4 + idx
@property
def raw_flavor(self):
"""Return underlying raw flavor."""
if self.flavor == "light":
return self.flavor
return heavys[self.hqnumber - 4]
[docs]
@classmethod
def has_heavies(cls, names):
r"""Check if there are any heavy objects in names.
Parameters
----------
names : list(str)
names to check
Returns
-------
has_heavies : bool
are there heavy obs in names?
"""
for n in names:
if not cls.is_valid(n):
continue
o = cls(n)
if o.is_heavy:
return True
return False
[docs]
@classmethod
def has_lights(cls, names):
r"""Check if there are any light objects in names.
Parameters
----------
names : list(str)
names to check
Returns
-------
has_lights : bool
are there light obs in names?
"""
for n in names:
if not cls.is_valid(n):
continue
o = cls(n)
if o.flavor == "light":
return True
return False
@property
def mass_label(self):
"""Add mass label in the theory runcard."""
if self.flavor == "light":
return None
else:
return f"m{self.flavor[0]}"
[docs]
@classmethod
def is_valid(cls, name):
r"""Test whether the name is a valid observable name.
Returns
-------
is_valid : bool
is valid name?
"""
try:
cls(name)
return True
except ValueError:
return False