#!/usr/bin/env python
""" Example of how to use the Reaction Plane Fit package.
Provides example for fitting only the background, or fitting the inclusive signal.
.. code-author: Raymond Ehlers <raymond.ehlers@ycern.ch>, Yale University
"""
import argparse
import logging
import pkg_resources
from typing import Any, Dict, Tuple, Type, TYPE_CHECKING
import uproot
from reaction_plane_fit import fit
from reaction_plane_fit.fit import Data, InputData
from reaction_plane_fit.fit import FitArguments
from reaction_plane_fit import three_orientations
from reaction_plane_fit import plot
if TYPE_CHECKING:
import iminuit # noqa: F401
logger = logging.getLogger(__name__)
# Type helpers
FitReturnValues = Tuple[fit.ReactionPlaneFit, Dict[str, fit.FitComponent], Data, "iminuit.Minuit"]
[docs]def setup_data(input_filename: str) -> InputData:
""" Setup the example input data.
Read the data using uproot so we can avoid an explicit dependency on ROOT. Histograms
are assumed to be named ``{region}_{orientation}`` (for example, "signalDominated_inclusive").
Args:
input_filename: Path to the input data to use.
Returns:
dict: Containing the input data.
"""
data: Dict[str, Any] = {"signal": {}, "background": {}}
with uproot.open(input_filename) as f:
for region in ["signal", "background"]:
for rp in ["inclusive", "in_plane", "mid_plane", "out_of_plane"]:
data[region][rp] = f[f"{region}Dominated_{rp}"]
return data
[docs]def run_fit(fit_object: Type[fit.ReactionPlaneFit],
input_data: InputData,
user_arguments: FitArguments) -> FitReturnValues:
""" Driver function for performing the fit.
Note:
Here we just set the resolution parameters to 1 for convenience of the example, but they are an extremely
important part of the fit!
Args:
fit_object: Fit object to be used.
data: Input data for the fit, labeled as defined in ``setup_data()``.
user_arguments: User arguments to override the arguments to the fit.
Returns:
tuple: (rp_fit, full_components, data, minuit), where rp_fit (fit.ReactionPlaneFit) is the reaction plane
fit object from the fit, full_components is the full set of fit components (one for each RP orientation),
data (dict) is the formated data dict used for the fit, and minuit is the minuit fit object.
"""
# Define the fit.
rp_fit = fit_object(
resolution_parameters = {"R22": 1, "R42": 1, "R62": 1, "R82": 1},
use_log_likelihood = False,
signal_region = (0, 0.6),
background_region = (0.8, 1.2),
)
# Perform the actual fit.
success, data, minuit = rp_fit.fit(data = input_data, user_arguments = user_arguments)
# Create the full set of fit components
full_components = rp_fit.create_full_set_of_components(input_data = data)
if success:
logger.info(f"Fit was successful! Fit result: {rp_fit.fit_result}")
return rp_fit, full_components, data, minuit
[docs]def run_background_fit(input_filename: str, user_arguments: FitArguments) -> FitReturnValues:
""" Run the background example fit.
Args:
input_filename: Path to the input data to use.
user_arguments: User arguments to override the arguments to the fit.
Returns:
tuple: (rp_fit, full_components, data, minuit), where rp_fit (fit.ReactionPlaneFit) is the reaction plane
fit object from the fit, full_components is the full set of fit components (one for each RP orientation),
data (dict) is the formated data dict used for the fit, and minuit is the minuit fit object.
"""
# Grab the input data.
input_data = setup_data(input_filename)
return_values = run_fit(
fit_object = three_orientations.BackgroundFit,
input_data = input_data, user_arguments = user_arguments
)
return return_values
[docs]def run_inclusive_signal_fit(input_filename: str, user_arguments: FitArguments) -> FitReturnValues:
""" Run the inclusive signal example fit.
Args:
input_filename: Path to the input data to use.
user_arguments: User arguments to override the arguments to the fit.
Returns:
tuple: (rp_fit, full_components, data, minuit), where rp_fit (fit.ReactionPlaneFit) is the reaction plane
fit object from the fit, full_components is the full set of fit components (one for each RP orientation),
data (dict) is the formated data dict used for the fit, and minuit is the minuit fit object.
"""
input_data = setup_data(input_filename)
return_values = run_fit(
fit_object = three_orientations.InclusiveSignalFit,
input_data = input_data, user_arguments = user_arguments
)
return return_values
[docs]def run_differential_signal_fit(input_filename: str, user_arguments: FitArguments) -> FitReturnValues:
""" Run the differential signal example fit.
Args:
input_filename: Path to the input data to use.
user_arguments: User arguments to override the arguments to the fit.
Returns:
tuple: (rp_fit, full_components, data, minuit), where rp_fit (fit.ReactionPlaneFit) is the reaction plane
fit object from the fit, full_components is the full set of fit components (one for each RP orientation),
data (dict) is the formated data dict used for the fit, and minuit is the minuit fit object.
"""
input_data = setup_data(input_filename)
return_values = run_fit(
fit_object = three_orientations.SignalFit,
input_data = input_data, user_arguments = user_arguments
)
return return_values
if __name__ == "__main__": # pragma: nocover
""" Allow direction execution of this module.
The user can specify the input filename and which type of fit to perform. However, the names of the input
histograms must be as specified in ``setup_data(...)``.
"""
# Setup logging
logging.basicConfig(level = logging.INFO)
# Quiet down the matplotlib logging regardless of the base logging level.
logging.getLogger("matplotlib").setLevel(logging.INFO)
# Setup parser
parser = argparse.ArgumentParser(
description = "Example Reaction Plane Fit using signal and background dominated sample data."
)
sample_data_filename = pkg_resources.resource_filename("reaction_plane_fit.sample_data", "three_orientations.root")
# Set the input filename
parser.add_argument("-i", "--inputData", metavar = "filename",
type = str, default = sample_data_filename,
help="Path to input data")
# Set the fit type (defaults to differential signal fit)
parser.add_argument("-d", "--differentialSignal",
action = "store_true",
help = "Fit the differential signal regions.")
parser.add_argument("-b", "--backgroundOnly",
action = "store_true",
help = "Only fit the background.")
# Parse arguments
args = parser.parse_args()
# Execute the selected function
func = run_inclusive_signal_fit
if args.differentialSignal:
func = run_differential_signal_fit
if args.backgroundOnly:
func = run_background_fit
rp_fit, _, data, _ = func(
input_filename = args.inputData,
user_arguments = {},
)
# Save to YAML.
rp_fit.write_fit_results("example.yaml")
# Determine fit label
fit_label_map = {
three_orientations.BackgroundFit: "Standard RP fit",
three_orientations.InclusiveSignalFit: "Inclusive signal RP fit",
three_orientations.SignalFit: "Differential signal RP fit",
}
fit_label = fit_label_map[type(rp_fit)]
# Draw the plots so that they will be saved out.
plot.draw_fit(rp_fit = rp_fit, data = data, fit_label = fit_label, filename = "example_fit.png")
plot.draw_residual(rp_fit = rp_fit, data = data, fit_label = fit_label, filename = "example_residual.png")