# BSD 3-Clause License
#
# Copyright (c) 2018, Tai Sakuma
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# * Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
##__________________________________________________________________||
import numpy as np
__all__ = ['AltDphi']
##__________________________________________________________________||
class cache_once_property(object):
"""A property decorator that replaces the property with the value that
the property returns.
inspired by
https://stackoverflow.com/questions/4037481/caching-attributes-of-classes-in-python#answer-4037979
"""
def __init__(self, f):
self.f = f
def __get__(self, instance, owner):
val = self.f(instance)
setattr(instance, self.f.__name__, val)
return val
##__________________________________________________________________||
[docs]class AltDphi(object):
"""The class to calculate the alternative variables.
"""
varnames_main = (
'pt', 'phi',
'mht', 'mht_phi',
'max_f',
'min_omega_tilde',
'min_omega_hat',
'min_chi',
'min_dphi_star',
'xi',
'min_minimized_mht', 'min_X',
'f', 'dphi',
'omega','omega_tilde', 'omega_hat', 'chi',
'dphi_star',
'sin_dphi_tilde', 'g',
'minimized_mht',
'X',
)
varnames_intermediate = (
'px', 'py',
'mht_x', 'mht_y',
'min_omega',
'min_dphi_tilde', 'min_sin_dphi_tilde',
'min_tan_chi',
'max_h',
'cos_dphi', 'sin_dphi',
'arccot_f',
'dphi_tilde',
'sin_dphi_hat', 'dphi_hat',
'k',
'tan_chi',
'h',
)
varnames = varnames_main + varnames_intermediate
[docs] def __init__(self, pt, phi, mht=None, mht_phi=None):
"""initialize an ``AltDphi`` object
Args:
pt (numpy.array): numpy array of jet pT
phi (numpy.array): numpy array of jet phi
mht (float, optional): MHT. if not given, calculated from ``pt`` and ``phi``
mht_phi (float, optional): phi of MHT. if not given, calculated from ``pt`` and ``phi``
"""
self._repr = self._compose_repr(pt, phi, mht, mht_phi)
self.pt = pt
self.phi = phi
self.monojet_is_minus_mht = mht is None and pt.size == 1
if mht is not None:
self.mht = mht
self.mht_x = mht*np.cos(mht_phi)
self.mht_y = mht*np.sin(mht_phi)
##______________________________________________________________||
@cache_once_property
def mht_x(self):
return -np.sum(self.px)
@cache_once_property
def mht_y(self):
return -np.sum(self.py)
@cache_once_property
def mht(self):
if self.monojet_is_minus_mht:
## make mht and pt precisely the same for monojet
return self.pt[0]
return np.sqrt(self.mht_x**2 + self.mht_y**2)
@cache_once_property
def mht_phi(self):
return np.arctan2(self.mht_y, self.mht_x)
##______________________________________________________________||
@cache_once_property
def min_omega_tilde(self):
if self.omega_tilde.size == 0:
return np.nan
return self.omega_tilde.min()
@cache_once_property
def min_omega_hat(self):
if self.omega_hat.size == 0:
return np.nan
return self.omega_hat.min()
@cache_once_property
def min_chi(self):
if self.chi.size == 0:
return np.nan
return self.chi.min()
@cache_once_property
def xi(self):
if np.isnan(self.min_sin_dphi_tilde):
return np.nan
if self.monojet_is_minus_mht:
return np.pi/2
return np.arctan2(self.min_sin_dphi_tilde, self.max_h)
##______________________________________________________________||
@cache_once_property
def minimized_mht(self):
return self.mht*self.sin_dphi_tilde
@cache_once_property
def min_minimized_mht(self):
return self.mht*self.min_sin_dphi_tilde
##______________________________________________________________||
@cache_once_property
def X(self):
return self.mht*self.tan_chi
##______________________________________________________________||
@cache_once_property
def min_X(self):
return self.mht*self.min_tan_chi
##______________________________________________________________||
@cache_once_property
def min_dphi_star(self):
if self.dphi_star.size == 0:
return np.nan
return self.dphi_star.min()
@cache_once_property
def min_omega(self):
if self.omega.size == 0:
return np.nan
return self.omega.min()
@cache_once_property
def min_dphi_tilde(self):
if self.dphi_tilde.size == 0:
return np.nan
return self.dphi_tilde.min()
@cache_once_property
def min_sin_dphi_tilde(self):
if self.sin_dphi_tilde.size == 0:
return np.nan
return self.sin_dphi_tilde.min()
@cache_once_property
def max_f(self):
if self.f.size == 0:
return np.nan
return self.f.max()
@cache_once_property
def min_tan_chi(self):
if self.tan_chi.size == 0:
return np.nan
return self.tan_chi.min()
@cache_once_property
def max_h(self):
if self.h.size == 0:
return np.nan
return self.h.max()
##______________________________________________________________||
@cache_once_property
def px(self):
return self.pt*np.cos(self.phi)
@cache_once_property
def py(self):
return self.pt*np.sin(self.phi)
##______________________________________________________________||
@cache_once_property
def cos_dphi(self):
if self.monojet_is_minus_mht:
return np.array([-1.0])
ret = (self.mht_x*self.px + self.mht_y*self.py)/(self.mht*self.pt)
ret = np.minimum(ret, 1.0)
ret = np.maximum(ret, -1.0)
return ret
@cache_once_property
def sin_dphi(self):
return np.sqrt(1 - self.cos_dphi**2)
@cache_once_property
def dphi(self):
return np.arccos(self.cos_dphi)
@cache_once_property
def f(self):
return self.pt/self.mht
@cache_once_property
def arccot_f(self):
return np.arctan2(1, self.f)
##______________________________________________________________||
@cache_once_property
def dphi_star(self):
ret = np.where(
(self.f == 1) & (self.cos_dphi == -1),
np.pi/2,
np.arctan2(self.sin_dphi, self.f + self.cos_dphi)
)
return ret
##______________________________________________________________||
@cache_once_property
def sin_dphi_tilde(self):
return np.sqrt(1 + (self.g - self.f)**2 - 2*(self.g - self.f)*self.cos_dphi)
@cache_once_property
def dphi_tilde(self):
return np.where(
self.f + self.cos_dphi >= 0,
self.dphi,
np.pi - np.arcsin(self.sin_dphi_tilde)
)
@cache_once_property
def g(self):
return np.maximum(self.f + self.cos_dphi, 0)
##______________________________________________________________||
@cache_once_property
def omega(self):
return np.arctan2(self.sin_dphi, self.f)
##______________________________________________________________||
@cache_once_property
def omega_tilde(self):
return np.arctan2(self.sin_dphi_tilde, self.f)
##______________________________________________________________||
@cache_once_property
def dphi_hat(self):
return np.minimum(self.dphi, np.pi/2.0)
@cache_once_property
def sin_dphi_hat(self):
return np.sin(self.dphi_hat)
@cache_once_property
def omega_hat(self):
return np.arctan2(self.sin_dphi_hat, self.f)
##______________________________________________________________||
@cache_once_property
def k(self):
return np.minimum(self.f, self.g)
@cache_once_property
def chi(self):
ret = np.where(
(self.f == 1) & (self.cos_dphi == -1),
np.pi/2,
np.arctan2(self.sin_dphi_tilde, self.k)
)
return ret
@cache_once_property
def tan_chi(self):
return np.tan(self.chi)
##______________________________________________________________||
@cache_once_property
def h(self):
if self.sin_dphi_tilde.size == 0:
return np.array([ ])
return np.where(
self.sin_dphi_tilde == self.sin_dphi_tilde.min(),
self.g, self.f
)
##______________________________________________________________||
def __repr__(self):
return self._repr
def __str__(self):
return self.to_string()
[docs] def to_string(self, all=False):
"""create a string containing the contents of the object
Args:
all(bool): include only the main variables if ``False``.
include all variables, e.g., intermediate variables, if ``True``.
"""
ret = '{!r}:'.format(self) + '\n'
varnames = self.varnames if all else self.varnames_main
len_varname = max(len(n) for n in varnames)
ret = ret + '\n'.join(
[' {:>{}}: {}'.format(n, len_varname, str(getattr(self, n))) for n in varnames]
)
return ret
def _compose_repr(self, pt, phi, mht, mht_phi):
name_value_pairs = [('pt', pt), ('phi', phi)]
if mht is not None:
name_value_pairs.append(('mht', mht))
if mht_phi is not None:
name_value_pairs.append(('mht_phi', mht_phi))
return '{}({})'.format(
self.__class__.__name__,
', '.join(['{}={!r}'.format(n, v) for n, v in name_value_pairs]),
)
##__________________________________________________________________||