def test_jet_pt_string(self, logging_mixin): """ Test the jet pt string generation functions. Each bin (except for the last) is tested. The last pt bin is left for a separate test because it is printed differently (see ``test_jet_pt_string_for_last_pt_bin()`` for more). """ pt_bins = [] for i, (min, max) in enumerate( zip(self.jet_pt_bins[:-2], self.jet_pt_bins[1:-1])): pt_bins.append( analysis_objects.JetPtBin(bin=i, range=params.SelectedRange(min, max))) for pt_bin, expected_min, expected_max in zip(pt_bins, self.jet_pt_bins[:-2], self.jet_pt_bins[1:-1]): logger.debug( f"Checking bin {pt_bin}, {pt_bin.range}, {type(pt_bin)}") assert labels.jet_pt_range_string( pt_bin ) == r"$%(lower)s < p_{\text{T,jet}}^{\text{ch+ne}} < %(upper)s\:\mathrm{GeV/\mathit{c}}$" % { "lower": expected_min, "upper": expected_max }
def post_projection_processing_for_2d_correlation( hist: Hist, normalization_factor: float, title_label: str, jet_pt: analysis_objects.JetPtBin, track_pt: analysis_objects.TrackPtBin, rebin_factors: Optional[Tuple[int, int]] = None) -> None: """ Basic post processing tasks for a new 2D correlation observable. Args: hist: Histogram to be post processed. normalization_factor: Factor by which the hist should be scaled. title_label: Histogram title label. jet_pt: Jet pt bin. track_pt: Track pt bin. rebin_factors: (x rebin factor, y rebin factor). Both values must be specified (can set to 1 if you don't want to rebin a particular axis). Default: None. Returns: None. The histogram is modified in place. """ # If we specify a rebin factor, then rebin. if rebin_factors is not None: hist.Rebin2D(*rebin_factors) # Scale hist.Scale(1.0 / normalization_factor) # Set title, axis labels jet_pt_bins_title = labels.jet_pt_range_string(jet_pt) track_pt_bins_title = labels.track_pt_range_string(track_pt) hist.SetTitle( rf"{title_label}\:\mathrm{{with}}\:{jet_pt_bins_title} \mathrm{{,}} {track_pt_bins_title}" ) hist.GetXaxis().SetTitle(r"$\Delta\varphi$") hist.GetYaxis().SetTitle(r"$\Delta\eta$")
def trigger_jets_EP( ep_analyses: List[Tuple[Any, "correlations.Correlations"]], output_info: analysis_objects.PlottingOutputWrapper) -> None: """ Plot jets triggers as a function of event plane orientation. Args: ep_analyses: Event plane dependent analyses. output_info: Output information. Returns: None. The triggers are plotted. """ # Setup fig, ax = plt.subplots(figsize=(8, 6)) # Plot for key_index, analysis in ep_analyses: h = histogram.Histogram1D.from_existing_hist( analysis.number_of_triggers_observable.hist) # Scale by the bin width h *= 1.0 / h.bin_widths[0] ax.errorbar( h.x, h.y, xerr=h.bin_widths / 2, yerr=h.errors, marker="o", linestyle="None", label= fr"{analysis.reaction_plane_orientation.display_str()}: $N_{{\text{{trig}}}} = {analysis.number_of_triggers:g}$", ) ax.set_xlim(0, 100) ax.text( 0.025, 0.025, r"$N_{\text{trig}}$ restricted to " + labels.jet_pt_range_string(analysis.jet_pt), transform=ax.transAxes, horizontalalignment="left", verticalalignment="bottom", multialignment="left", ) # Final presentation settings ax.set_xlabel( labels.make_valid_latex_string( fr"{labels.jet_pt_display_label()}\:({labels.momentum_units_label_gev()})" )) ax.set_ylabel( labels.make_valid_latex_string( fr"d\text{{n}}/d{labels.jet_pt_display_label()}\:({labels.momentum_units_label_gev()}^{{-1}})" )) ax.set_yscale("log") ax.legend(frameon=False, loc="upper right") fig.tight_layout() # Finally, save and cleanup output_name = f"trigger_jet_spectra_EP" plot_base.save_plot(output_info, fig, output_name) plt.close(fig)
def delta_eta_fit_subtracted(analysis: "correlations.Correlations") -> None: """ Plot the subtracted delta eta near-side and away-side. """ # Setup fig, ax = plt.subplots(figsize=(8, 6)) # Plot both the near side and the away side. attribute_names = ["near_side", "away_side"] for attribute_name in attribute_names: # Setup an individual hist correlation = getattr(analysis.correlation_hists_delta_eta_subtracted, attribute_name) h = correlation.hist # Plot the data ax.errorbar( h.x, h.y, yerr=h.errors, marker="o", linestyle="", label=f"Subtracted {correlation.type.display_str()}", ) # Add horizontal line at 0 for comparison. ax.axhline(y=0, color="black", linestyle="dashed", zorder=1) # Labels. ax.set_xlabel( labels.make_valid_latex_string(correlation.axis.display_str())) ax.set_ylabel( labels.make_valid_latex_string(labels.delta_eta_axis_label())) jet_pt_label = labels.jet_pt_range_string(analysis.jet_pt) track_pt_label = labels.track_pt_range_string(analysis.track_pt) ax.set_title( fr"Subtracted 1D ${correlation.axis.display_str()}$," f" {analysis.reaction_plane_orientation.display_str()} event plane orient.," f" {jet_pt_label}, {track_pt_label}") ax.legend(loc="upper right") # Final adjustments fig.tight_layout() # Save plot and cleanup plot_base.save_plot( analysis.output_info, fig, f"jetH_delta_eta_{analysis.identifier}_{attribute_name}_subtracted" ) # Reset for the next iteration of the loop ax.clear() # Final cleanup plt.close(fig)
def fit_subtracted_signal_dominated( analysis: "correlations.Correlations") -> None: """ Plot the subtracted signal dominated hist. """ # Setup fig, ax = plt.subplots(figsize=(8, 6)) hists = analysis.correlation_hists_delta_phi_subtracted h = hists.signal_dominated.hist # Plot the subtracted hist ax.errorbar( h.x, h.y, yerr=h.errors, label=f"Subtracted {hists.signal_dominated.type.display_str()}", marker="o", linestyle="", ) # Plot the background uncertainty separately. background_error = analysis.fit_object.calculate_background_function_errors( h.x) ax.fill_between( h.x, h.y - background_error, h.y + background_error, label="RP background uncertainty", color=plot_base.AnalysisColors.fit, ) # Line for comparison ax.axhline(y=0, color="black", linestyle="dashed", zorder=1) # Labels. ax.set_xlabel( labels.make_valid_latex_string( hists.signal_dominated.axis.display_str())) ax.set_ylabel(labels.make_valid_latex_string( labels.delta_phi_axis_label())) jet_pt_label = labels.jet_pt_range_string(analysis.jet_pt) track_pt_label = labels.track_pt_range_string(analysis.track_pt) ax.set_title( fr"Subtracted 1D ${hists.signal_dominated.axis.display_str()}$," f" {analysis.reaction_plane_orientation.display_str()} event plane orient.," f" {jet_pt_label}, {track_pt_label}") ax.legend(loc="upper right", frameon=False) # Final adjustments fig.tight_layout() # Save plot and cleanup plot_base.save_plot(analysis.output_info, fig, f"jetH_delta_phi_{analysis.identifier}_subtracted") plt.close(fig)
def plot_and_label_1d_signal_and_background_with_matplotlib_on_axis(ax: matplotlib.axes.Axes, jet_hadron: "correlations.Correlations", apply_correlation_scale_factor: bool = True) -> None: """ Plot and label the signal and background dominated hists on the given axis. This is a helper function so that we don't have to repeat code when we need to plot these hists. It can also be used in other modules. Args: ax: Axis on which the histograms should be plotted. jet_hadron: Correlations object from which the delta_phi hists should be retrieved. apply_correlation_scale_factor: Whether to scale the histogram by the correlation scale factor. Returns: None. The given axis is modified. """ # Setup hists = jet_hadron.correlation_hists_delta_phi h_signal = histogram.Histogram1D.from_existing_hist(hists.signal_dominated.hist) if apply_correlation_scale_factor: h_signal *= jet_hadron.correlation_scale_factor ax.errorbar( h_signal.x, h_signal.y, yerr = h_signal.errors, label = hists.signal_dominated.type.display_str(), marker = "o", linestyle = "", ) h_background = histogram.Histogram1D.from_existing_hist(hists.background_dominated.hist) if apply_correlation_scale_factor: h_background *= jet_hadron.correlation_scale_factor # Plot with opacity first background_plot = ax.errorbar( h_background.x, h_background.y, yerr = h_background.errors, marker = "o", linestyle = "", alpha = 0.5, ) # Then restrict range and plot without opacity near_side = len(h_background.x) // 2 ax.errorbar( h_background.x[:near_side], h_background.y[:near_side], yerr = h_background.errors[:near_side], label = hists.background_dominated.type.display_str(), marker = "o", linestyle = "", color = background_plot[0].get_color() ) # Set labels. ax.set_xlabel(labels.make_valid_latex_string(hists.signal_dominated.hist.GetXaxis().GetTitle())) ax.set_ylabel(labels.make_valid_latex_string(hists.signal_dominated.hist.GetYaxis().GetTitle())) jet_pt_label = labels.jet_pt_range_string(jet_hadron.jet_pt) track_pt_label = labels.track_pt_range_string(jet_hadron.track_pt) ax.set_title(fr"Unsubtracted 1D ${hists.signal_dominated.axis.display_str()}$," f" {jet_hadron.reaction_plane_orientation.display_str()} event plane orient.," f" {jet_pt_label}, {track_pt_label}")
def test_jet_pt_string_for_last_pt_bin(self, logging_mixin): """ Test the jet pt string generation function for the last jet pt bin. In the case of the last pt bin, we only want to show the lower range. """ pt_bin = len(self.jet_pt_bins) - 2 jet_pt_bin = analysis_objects.JetPtBin( bin=pt_bin, range=params.SelectedRange(self.jet_pt_bins[pt_bin], self.jet_pt_bins[pt_bin + 1])) assert labels.jet_pt_range_string( jet_pt_bin ) == r"$%(lower)s < p_{\text{T,jet}}^{\text{ch+ne}}\:\mathrm{GeV/\mathit{c}}$" % { "lower": self.jet_pt_bins[-2] }
def post_creation_processing_for_1d_correlations( hist: Hist, normalization_factor: float, rebin_factor: int, title_label: str, axis_label: str, jet_pt: analysis_objects.JetPtBin, track_pt: analysis_objects.TrackPtBin) -> None: """ Basic post processing tasks for a new 1D correlation observable. """ # Rebin to decrease the fluctuations in the correlations # We don't scale by the rebin factor here because we will scale by bin width later. # Since we will handle it later, it doesn't make sense to try to preserve normalization here. hist.Rebin(rebin_factor) # Scale hist.Scale(1.0 / normalization_factor) # Set title, labels jet_pt_bins_title = labels.jet_pt_range_string(jet_pt) track_pt_bins_title = labels.track_pt_range_string(track_pt) # This won't look so good in ROOT, but that's just because their latex rendering is absolutely atrocious... hist.SetTitle( rf"{title_label} with {jet_pt_bins_title}, {track_pt_bins_title}") hist.GetXaxis().SetTitle(axis_label) hist.GetYaxis().SetTitle(fr"$\mathrm{{dN}}/\mathrm{{d}}{axis_label}$")
def delta_eta_fit(analysis: "correlations.Correlations") -> None: """ Plot the delta eta correlations with the fit. """ # Setup fig, ax = plt.subplots(figsize=(8, 6)) # Plot both the near side and the away side. for (attribute_name, correlation), (fit_attribute_name, fit_object) in \ zip(analysis.correlation_hists_delta_eta, analysis.fit_objects_delta_eta): if attribute_name != fit_attribute_name: raise ValueError( "Issue extracting hist and pedestal fit object together." f"Correlation obj name: {attribute_name}, pedestal fit obj name: {fit_attribute_name}" ) # Setup an individual hist h = histogram.Histogram1D.from_existing_hist(correlation.hist) label = correlation.type.display_str() # Determine the fit range so we can show it in the plot. # For example, -1.2 < h.x < -0.8 negative_restricted_range = ( (h.x < -1 * analysis.background_dominated_eta_region.min) & (h.x > -1 * analysis.background_dominated_eta_region.max)) # For example, 0.8 < h.x < 1.2 positive_restricted_range = ( (h.x > analysis.background_dominated_eta_region.min) & (h.x < analysis.background_dominated_eta_region.max)) restricted_range = negative_restricted_range | positive_restricted_range # First plot all of the data with opacity data_plot = ax.errorbar( h.x, h.y, yerr=h.errors, marker="o", linestyle="", alpha=0.5, ) # Then plot again without opacity highlighting the fit range. ax.errorbar(h.x[restricted_range], h.y[restricted_range], yerr=h.errors[restricted_range], label=label, marker="o", linestyle="", color=data_plot[0].get_color()) # Next, plot the pedestal following the same format # First plot the restricted values # We have to plot the fit data in two separate halves to prevent the lines # from being connected across the region where were don't fit. # Plot the left half pedestal_plot = ax.plot( h.x[negative_restricted_range], fit_object(h.x[negative_restricted_range], **fit_object.fit_result.values_at_minimum), label="Pedestal", ) # And then the right half ax.plot( h.x[positive_restricted_range], fit_object(h.x[positive_restricted_range], **fit_object.fit_result.values_at_minimum), color=pedestal_plot[0].get_color(), ) # Then plot the errors over the entire range. fit_values = fit_object(h.x, **fit_object.fit_result.values_at_minimum) fit_errors = fit_object.calculate_errors(h.x) ax.fill_between( h.x, fit_values - fit_errors, fit_values + fit_errors, facecolor=pedestal_plot[0].get_color(), alpha=0.7, ) # Then plot over the entire range using a dashed line. ax.plot( h.x, fit_values, linestyle="--", color=pedestal_plot[0].get_color(), ) # Labels. ax.set_xlabel( labels.make_valid_latex_string(correlation.axis.display_str())) ax.set_ylabel( labels.make_valid_latex_string(labels.delta_eta_axis_label())) jet_pt_label = labels.jet_pt_range_string(analysis.jet_pt) track_pt_label = labels.track_pt_range_string(analysis.track_pt) ax.set_title( fr"Unsubtracted 1D ${correlation.axis.display_str()}$," f" {analysis.reaction_plane_orientation.display_str()} event plane orient.," f" {jet_pt_label}, {track_pt_label}") ax.legend(loc="upper right") # Final adjustments fig.tight_layout() # Save plot and cleanup plot_base.save_plot( analysis.output_info, fig, f"jetH_delta_eta_{analysis.identifier}_{attribute_name}_fit") # Reset for the next iteration of the loop ax.clear() # Final cleanup plt.close(fig)
def plot_RP_fit(rp_fit: reaction_plane_fit.fit.ReactionPlaneFit, inclusive_analysis: "correlations.Correlations", ep_analyses: List[Tuple[Any, "correlations.Correlations"]], output_info: analysis_objects.PlottingOutputWrapper, output_name: str) -> None: """ Basic plot of the reaction plane fit. Args: rp_fit: Reaction plane fit object. inclusive_analysis: Inclusive analysis object. Mainly used for labeling. ep_analyses: Event plane dependent correlation analysis objects. output_info: Output information. output_name: Name of the output plot. Returns: None. The plot will be saved. """ # Setup n_components = len(rp_fit.components) fig, axes = plt.subplots(2, n_components, sharey="row", sharex=True, gridspec_kw={"height_ratios": [3, 1]}, figsize=(3 * n_components, 6)) flat_axes = axes.flatten() # Plot the fits on the upper panels. _plot_rp_fit_components(rp_fit=rp_fit, ep_analyses=ep_analyses, axes=flat_axes[:n_components]) # Plot the residuals on the lower panels. _plot_rp_fit_residuals(rp_fit=rp_fit, ep_analyses=ep_analyses, axes=flat_axes[n_components:]) # Define upper panel labels. # In-plane text = labels.track_pt_range_string(inclusive_analysis.track_pt) text += "\n" + labels.constituent_cuts() text += "\n" + labels.make_valid_latex_string( inclusive_analysis.leading_hadron_bias.display_str()) _add_label_to_rpf_plot_axis(ax=flat_axes[0], label=text) # Mid-plane text = labels.make_valid_latex_string( inclusive_analysis.alice_label.display_str()) text += "\n" + labels.system_label( energy=inclusive_analysis.collision_energy, system=inclusive_analysis.collision_system, activity=inclusive_analysis.event_activity) text += "\n" + labels.jet_pt_range_string(inclusive_analysis.jet_pt) text += "\n" + labels.jet_finding() _add_label_to_rpf_plot_axis(ax=flat_axes[1], label=text) # Out-of-plane #text = "Background: $0.8<|\Delta\eta|<1.2$" #text += "\nSignal + Background: $|\Delta\eta|<0.6$" #_add_label_to_rpf_plot_axis(ax = flat_axes[2], label = text) # Inclusive text = (r"\chi^{2}/\mathrm{NDF} = " f"{rp_fit.fit_result.minimum_val:.1f}/{rp_fit.fit_result.nDOF} = " f"{rp_fit.fit_result.minimum_val / rp_fit.fit_result.nDOF:.3f}") _add_label_to_rpf_plot_axis(ax=flat_axes[2], label=labels.make_valid_latex_string(text)) # Define lower panel labels. for ax in flat_axes[n_components:]: # Increase the frequency of major ticks to once every integer. ax.xaxis.set_major_locator(matplotlib.ticker.MultipleLocator(base=1.0)) # Add axis labels ax.set_xlabel( labels.make_valid_latex_string( inclusive_analysis.correlation_hists_delta_phi. signal_dominated.axis.display_str())) # Improve the viewable range for the lower panels. # This value is somewhat arbitrarily selected, but seems to work well enough. flat_axes[n_components].set_ylim(-0.2, 0.2) # Specify shared y axis label # Delta phi correlations first flat_axes[0].set_ylabel(labels.delta_phi_axis_label()) # Then label the residual flat_axes[n_components].set_ylabel("data - fit / fit") # Final adjustments fig.tight_layout() # Reduce spacing between subplots fig.subplots_adjust(hspace=0, wspace=0) # Save plot and cleanup plot_base.save_plot(output_info, fig, output_name) plt.close(fig)
def rp_fit_subtracted(ep_analyses: List[Tuple[Any, "correlations.Correlations"]], inclusive_analysis: "correlations.Correlations", output_info: analysis_objects.PlottingOutputWrapper, output_name: str) -> None: """ Basic plot of the reaction plane fit subtracted hists. Args: ep_analyses: Event plane dependent correlation analysis objects. inclusive_analysis: Inclusive analysis object. Mainly used for labeling. output_info: Output information. output_name: Name of the output plot. Returns: None. The plot will be saved. """ # Setup n_components = len(ep_analyses) fig, axes = plt.subplots( 1, n_components, sharey="row", sharex=True, #gridspec_kw = {"height_ratios": [3, 1]}, figsize=(3 * n_components, 6)) flat_axes = axes.flatten() # Plot the fits on the upper panels. _plot_rp_fit_subtracted(ep_analyses=ep_analyses, axes=flat_axes[:n_components]) # Define upper panel labels. # In-plane text = labels.track_pt_range_string(inclusive_analysis.track_pt) text += "\n" + labels.constituent_cuts() text += "\n" + labels.make_valid_latex_string( inclusive_analysis.leading_hadron_bias.display_str()) _add_label_to_rpf_plot_axis(ax=flat_axes[0], label=text) # Mid-plane text = labels.make_valid_latex_string( inclusive_analysis.alice_label.display_str()) text += "\n" + labels.system_label( energy=inclusive_analysis.collision_energy, system=inclusive_analysis.collision_system, activity=inclusive_analysis.event_activity) text += "\n" + labels.jet_pt_range_string(inclusive_analysis.jet_pt) text += "\n" + labels.jet_finding() _add_label_to_rpf_plot_axis(ax=flat_axes[1], label=text) # Out-of-plane #text = "Background: $0.8<|\Delta\eta|<1.2$" #text += "\nSignal + Background: $|\Delta\eta|<0.6$" #_add_label_to_rpf_plot_axis(ax = flat_axes[2], label = text) _add_label_to_rpf_plot_axis(ax=flat_axes[2], label=labels.make_valid_latex_string(text)) for ax in flat_axes: # Increase the frequency of major ticks to once every integer. ax.xaxis.set_major_locator(matplotlib.ticker.MultipleLocator(base=1.0)) # Set label ax.set_xlabel(labels.make_valid_latex_string(r"\Delta\varphi")) flat_axes[0].set_ylabel( labels.make_valid_latex_string(labels.delta_phi_axis_label())) #jet_pt_label = labels.jet_pt_range_string(inclusive_analysis.jet_pt) #track_pt_label = labels.track_pt_range_string(inclusive_analysis.track_pt) #ax.set_title(fr"Subtracted 1D ${inclusive_analysis.correlation_hists_delta_phi_subtracted.signal_dominated.axis.display_str()}$," # f" {inclusive_analysis.reaction_plane_orientation.display_str()} event plane orient.," # f" {jet_pt_label}, {track_pt_label}") ax.legend(loc="upper right") # Final adjustments fig.tight_layout() # Reduce spacing between subplots fig.subplots_adjust(hspace=0, wspace=0) # Save plot and cleanup plot_base.save_plot( output_info, fig, f"jetH_delta_phi_{inclusive_analysis.identifier}_rp_subtracted") plt.close(fig)
def plot_RPF_fit_regions(jet_hadron: "correlations.Correlations", filename: str) -> None: """ Plot showing highlighted RPF fit regions. Args: jet_hadron: Main analysis object. filename: Filename under which the hist should be saved. Returns: None """ # Retrieve the hist to be plotted # Here we selected the corrected 2D correlation hist = jet_hadron.correlation_hists_2d.signal.hist.Clone(f"{jet_hadron.correlation_hists_2d.signal.name}_RPF_scaled") hist.Scale(jet_hadron.correlation_scale_factor) with sns.plotting_context(context = "notebook", font_scale = 1.5): # Perform the plotting # The best option for clarify of viewing seems to be to just replace the colors with the overlay colors. # mathematical_blending and screen_colors look similar and seem to be the next most promising option. (fig, ax) = highlight_RPF.plot_RPF_fit_regions( histogram.get_array_from_hist2D(hist), highlight_regions = define_highlight_regions( signal_dominated_eta_region = jet_hadron.signal_dominated_eta_region, background_dominated_eta_region = jet_hadron.background_dominated_eta_region, near_side_phi_region = jet_hadron.near_side_phi_region, ), use_color_overlay = False, use_color_screen = False, use_mathematical_blending = False, ) # Add additional labeling # Axis # Needed to fix z axis rotation. See: https://stackoverflow.com/a/21921168 ax.zaxis.set_rotate_label(False) ax.set_zlabel(r"$1/N_{\mathrm{trig}}\mathrm{d^{2}}N/\mathrm{d}\Delta\varphi\mathrm{d}\Delta\eta$", rotation=90) # Set the distance from axis to label in pixels. # This is not ideal, but clearly tight_layout doesn't work as well for 3D plots ax.xaxis.labelpad = 12 # Visually, dEta looks closer ax.yaxis.labelpad = 15 ax.zaxis.labelpad = 12 # Overall alice_label = labels.make_valid_latex_string(jet_hadron.alice_label.display_str()) system_label = labels.system_label( energy = jet_hadron.collision_energy, system = jet_hadron.collision_system, activity = jet_hadron.event_activity ) jet_finding = labels.jet_finding() constituent_cuts = labels.constituent_cuts() leading_hadron = "$" + jet_hadron.leading_hadron_bias.display_str() + "$" jet_pt = labels.jet_pt_range_string(jet_hadron.jet_pt) assoc_pt = labels.track_pt_range_string(jet_hadron.track_pt) # Upper left side upper_left_text = "" upper_left_text += alice_label upper_left_text += "\n" + system_label upper_left_text += "\n" + jet_pt upper_left_text += "\n" + jet_finding # Upper right side upper_right_text = "" upper_right_text += leading_hadron upper_right_text += "\n" + constituent_cuts upper_right_text += "\n" + assoc_pt # Need a different text function since we have a 3D axis ax.text2D(0.01, 0.99, upper_left_text, horizontalalignment = "left", verticalalignment = "top", multialignment = "left", transform = ax.transAxes) ax.text2D(0.00, 0.00, upper_right_text, horizontalalignment = "left", verticalalignment = "bottom", multialignment = "left", transform = ax.transAxes) # Finish up plot_base.save_plot(jet_hadron.output_info, fig, filename) plt.close(fig)
def plot_2d_correlations(jet_hadron: "correlations.Correlations") -> None: """ Plot the 2D correlations. """ canvas = ROOT.TCanvas("canvas2D", "canvas2D") # Iterate over 2D hists for name, observable in jet_hadron.correlation_hists_2d: hist = observable.hist.Clone(f"{observable.name}_scaled") logger.debug(f"name: {name}, hist: {hist}") # We don't want to scale the mixed event hist because we already determined the normalization if "mixed" not in observable.type: hist.Scale(jet_hadron.correlation_scale_factor) # We don't need the title with all of the labeling hist.SetTitle("") # Draw plot hist.Draw("surf2") # Label axes hist.GetXaxis().CenterTitle(True) hist.GetXaxis().SetTitleSize(0.08) hist.GetXaxis().SetLabelSize(0.06) hist.GetYaxis().CenterTitle(True) hist.GetYaxis().SetTitleSize(0.08) # If I remove this, it looks worse, even though this is not supposed to do anything hist.GetYaxis().SetTitleOffset(1.2) hist.GetYaxis().SetLabelSize(0.06) hist.GetZaxis().CenterTitle(True) hist.GetZaxis().SetTitleSize(0.06) hist.GetZaxis().SetLabelSize(0.05) hist.GetZaxis().SetTitleOffset(0.8) canvas.SetLeftMargin(0.13) if "mixed" in observable.type: hist.GetZaxis().SetTitle(r"$a(\Delta\varphi,\Delta\eta)$") hist.GetZaxis().SetTitleOffset(0.9) else: z_title = r"$1/N_{\mathrm{trig}}\mathrm{d^{2}}N%(label)s/\mathrm{d}\Delta\varphi\mathrm{d}\Delta\eta$" if "signal" in observable.type: z_title = z_title % {"label": ""} else: z_title = z_title % {"label": r"_{\mathrm{raw}}"} # Decrease size so it doesn't overlap with the other labels hist.GetZaxis().SetTitleSize(0.05) hist.GetZaxis().SetTitle(z_title) # Add labels # PDF DOES NOT WORK HERE: https://root-forum.cern.ch/t/latex-sqrt-problem/17442/15 # Instead, print to EPS and then convert to PDF alice_label = labels.make_valid_latex_string(jet_hadron.alice_label.display_str()) system_label = labels.system_label( energy = jet_hadron.collision_energy, system = jet_hadron.collision_system, activity = jet_hadron.event_activity ) jet_finding = labels.jet_finding() constituent_cuts = labels.constituent_cuts() leading_hadron = "$" + jet_hadron.leading_hadron_bias.display_str() + "$" jet_pt = labels.jet_pt_range_string(jet_hadron.jet_pt) assoc_pt = labels.track_pt_range_string(jet_hadron.track_pt) logger.debug(f"label: {alice_label}, system_label: {system_label}, constituent_cuts: {constituent_cuts}, leading_hadron: {leading_hadron}, jet_pt: {jet_pt}, assoc_pt: {assoc_pt}") tex = ROOT.TLatex() tex.SetTextSize(0.04) # Upper left side tex.DrawLatexNDC(.005, .96, labels.use_label_with_root(alice_label)) tex.DrawLatexNDC(.005, .91, labels.use_label_with_root(system_label)) tex.DrawLatexNDC(.005, .86, labels.use_label_with_root(jet_pt)) tex.DrawLatexNDC(.005, .81, labels.use_label_with_root(jet_finding)) # Upper right side tex.DrawLatexNDC(.67, .96, labels.use_label_with_root(assoc_pt)) tex.DrawLatexNDC(.7275, .91, labels.use_label_with_root(leading_hadron)) tex.DrawLatexNDC(.73, .86, labels.use_label_with_root(constituent_cuts)) # Save plot plot_base.save_plot(jet_hadron.output_info, canvas, observable.name) # Draw as colz to view more precisely hist.Draw("colz") plot_base.save_plot(jet_hadron.output_info, canvas, observable.name + "_colz") canvas.Clear()
def delta_eta_with_gaussian(analysis: "correlations.Correlations") -> None: """ Plot the subtracted delta eta near-side. """ # Setup fig, ax = plt.subplots(figsize=(8, 6)) for (attribute_name, width_obj), (correlation_attribute_name, correlation) in \ zip(analysis.widths_delta_eta, analysis.correlation_hists_delta_eta_subtracted): # Setup # Sanity check if attribute_name != correlation_attribute_name: raise ValueError( "Issue extracting width and hist together." f"Width obj name: {attribute_name}, hist obj name: {correlation_attribute_name}" ) # Plot only the near side for now because the away-side doesn't have a gaussian shape if attribute_name == "away_side": continue # Plot the data. h = correlation.hist ax.errorbar( h.x, h.y, yerr=h.errors, marker="o", linestyle="", label=f"{correlation.type.display_str()}", ) # Plot the fit gauss = width_obj.fit_object(h.x, **width_obj.fit_result.values_at_minimum) fit_plot = ax.plot( h.x, gauss, label= fr"Gaussian fit: $\mu = $ {width_obj.mean:.2f}, $\sigma = $ {width_obj.width:.2f}", ) # Fill in the error band. error = width_obj.fit_object.calculate_errors(x=h.x) ax.fill_between( h.x, gauss - error, gauss + error, facecolor=fit_plot[0].get_color(), alpha=0.5, ) # Labels. ax.set_xlabel( labels.make_valid_latex_string(correlation.axis.display_str())) ax.set_ylabel( labels.make_valid_latex_string(labels.delta_eta_axis_label())) jet_pt_label = labels.jet_pt_range_string(analysis.jet_pt) track_pt_label = labels.track_pt_range_string(analysis.track_pt) ax.set_title( fr"Subtracted 1D ${correlation.axis.display_str()}$," f" {analysis.reaction_plane_orientation.display_str()} event plane orient.," f" {jet_pt_label}, {track_pt_label}") ax.legend(loc="upper right") # Final adjustments fig.tight_layout() # Save plot and cleanup plot_base.save_plot( analysis.output_info, fig, f"jetH_delta_eta_{analysis.identifier}_width_{attribute_name}_fit") # Reset for the next iteration of the loop ax.clear() # Final cleanup. plt.close(fig)
def _extracted_values( analyses: Mapping[Any, "correlations.Correlations"], selected_iterables: Mapping[str, Sequence[Any]], extract_value_func: Callable[["correlations.Correlations"], analysis_objects.ExtractedObservable], plot_labels: plot_base.PlotLabels, output_name: str, output_info: analysis_objects.PlottingOutputWrapper, projection_range_func: Optional[Callable[["correlations.Correlations"], str]] = None, extraction_range_func: Optional[Callable[["correlations.Correlations"], str]] = None ) -> None: """ Plot extracted values. Note: It's best to fully define the ``extract_value_func`` function even though it can often be easily accomplished with a lambda because only a full function definition can use explicit type checking. Since this function uses a variety of different sources for the data, this type checking is particularly helpful. So writing a full function with full typing is strongly preferred to ensure that we get it right. Args: analyses: Correlation analyses. selected_iterables: Iterables that were used in constructing the analysis objects. We use them to iterate over some iterators in a particular order (particularly the reaction plane orientation). extract_value_func: Function to retrieve the extracted value and error. plot_labels: Titles and axis labels for the plot. output_name: Base of name under which the plot will be stored. output_info: Information needed to determine where to store the plot. projection_range_func: Function which will provide the projection range of the extracted value given the inclusive object. Default: None. extraction_range_func: Function which will provide the extraction range of the extracted value given the inclusive object. Default: None. """ # Setup fig, ax = plt.subplots(figsize=(8, 6)) # Specify plotting properties # color, marker, fill marker or not # NOTE: Fill marker is specified when plotting because of a matplotlib bug # NOTE: This depends on iterating over the EP orientation in the exact manner specified below. ep_plot_properties = { # black, diamond, no fill params.ReactionPlaneOrientation.inclusive: ("black", "D", "none"), # blue = "C0", square, fill params.ReactionPlaneOrientation.in_plane: ("tab:blue", "s", "full"), # green = "C2", triangle up, fill params.ReactionPlaneOrientation.mid_plane: ("tab:green", "^", "full"), # red = "C3", circle, fill params.ReactionPlaneOrientation.out_of_plane: ("tab:red", "o", "full"), } cyclers = [] plot_property_values = list(ep_plot_properties.values()) for i, prop in enumerate(["color", "marker", "fillstyle"]): cyclers.append(cycler(prop, [p[i] for p in plot_property_values])) # We skip the fillstyle because apparently it doesn't work with the cycler at the moment due to a bug... # They didn't implement their add operation to handle 0, so we have to give it the explicit start value. combined_cyclers = sum(cyclers[1:-1], cyclers[0]) ax.set_prop_cycle(combined_cyclers) # Used for labeling purposes. The values that are used are identical for all analyses. inclusive_analysis: Optional["correlations.Correlations"] = None for displace_index, ep_orientation in enumerate( selected_iterables["reaction_plane_orientation"]): # Store the values to be plotted values: Dict[analysis_objects.PtBin, analysis_objects.ExtractedObservable] = {} for key_index, analysis in \ analysis_config.iterate_with_selected_objects( analyses, reaction_plane_orientation = ep_orientation ): # Store each extracted value. values[analysis.track_pt] = extract_value_func(analysis) # These are both used for labeling purposes and are identical for all analyses that are iterated over. if ep_orientation == params.ReactionPlaneOrientation.inclusive and inclusive_analysis is None: inclusive_analysis = analysis # Plot the values bin_centers = np.array([k.bin_center for k in values]) bin_centers = bin_centers + displace_index * 0.05 ax.errorbar( bin_centers, [v.value for v in values.values()], yerr=[v.error for v in values.values()], label=ep_orientation.display_str(), linestyle="", fillstyle=ep_plot_properties[ep_orientation][2], ) # Help out mypy... assert inclusive_analysis is not None # Labels. # General text = labels.make_valid_latex_string( inclusive_analysis.alice_label.display_str()) text += "\n" + labels.system_label( energy=inclusive_analysis.collision_energy, system=inclusive_analysis.collision_system, activity=inclusive_analysis.event_activity) text += "\n" + labels.jet_pt_range_string(inclusive_analysis.jet_pt) text += "\n" + labels.jet_finding() text += "\n" + labels.constituent_cuts() text += "\n" + labels.make_valid_latex_string( inclusive_analysis.leading_hadron_bias.display_str()) # Deal with projection range, extraction range string. additional_label = _proj_and_extract_range_label( inclusive_analysis=inclusive_analysis, projection_range_func=projection_range_func, extraction_range_func=extraction_range_func, ) if additional_label: text += "\n" + additional_label # Finally, add the text to the axis. ax.text(0.97, 0.97, text, horizontalalignment="right", verticalalignment="top", multialignment="right", transform=ax.transAxes) # Axes and titles ax.set_xlabel( labels.make_valid_latex_string(labels.track_pt_display_label())) # Apply any specified labels if plot_labels.title is not None: plot_labels.title = plot_labels.title + f" for {labels.jet_pt_range_string(inclusive_analysis.jet_pt)}" plot_labels.apply_labels(ax) ax.legend(loc="center right", frameon=False) # Final adjustments fig.tight_layout() # Save plot and cleanup plot_base.save_plot( output_info, fig, f"{output_name}_{inclusive_analysis.jet_pt_identifier}") plt.close(fig)
def delta_phi_with_gaussians(analysis: "correlations.Correlations") -> None: """ Plot the subtracted delta phi correlation with gaussian fits to the near and away side. """ # Setup fig, ax = plt.subplots(figsize=(8, 6)) correlation = analysis.correlation_hists_delta_phi_subtracted.signal_dominated h = correlation.hist # First we plot the data ax.errorbar( h.x, h.y, yerr=h.errors, marker="o", linestyle="", label=f"{correlation.type.display_str()}", ) # Plot the fit. for attribute_name, width_obj in analysis.widths_delta_phi: # Setup # Convert the attribute name to display better. Ex: "near_side" -> "Near side" attribute_display_name = attribute_name.replace("_", " ").capitalize() # We only want to plot the fit over the range that it was fit. restricted_range = (h.x > width_obj.fit_object.fit_range.min) & ( h.x < width_obj.fit_object.fit_range.max) x = h.x[restricted_range] # Plot the fit gauss = width_obj.fit_object(x, **width_obj.fit_result.values_at_minimum) fit_plot = ax.plot( x, gauss, label= fr"{attribute_display_name} gaussian fit: $\mu = $ {width_obj.mean:.2f}" fr", $\sigma = $ {width_obj.width:.2f}", ) # Fill in the error band. error = width_obj.fit_object.calculate_errors(x=x) ax.fill_between( x, gauss - error, gauss + error, facecolor=fit_plot[0].get_color(), alpha=0.5, ) # This means that we extracted values from the RP fit. Let's also plot them for comparison if width_obj.fit_args != {}: args = dict(width_obj.fit_result.values_at_minimum) args.update({ # Help out mypy... k: cast(float, v) for k, v in width_obj.fit_args.items() if "error_" not in k }) rpf_gauss = width_obj.fit_object(x, **args) rp_fit_plot = ax.plot( x, rpf_gauss, label= fr"RPF {attribute_display_name} gaussian fit: $\mu = $ {width_obj.mean:.2f}" fr", $\sigma = $ {width_obj.fit_args['width']:.2f}", ) # Fill in the error band. # NOTE: Strictly speaking, this error band isn't quite right (since it is dependent on the fit result # of the actual width fit), but I think it's fine for these purposes. error = width_obj.fit_object.calculate_errors(x=x) ax.fill_between( x, rpf_gauss - error, rpf_gauss + error, facecolor=rp_fit_plot[0].get_color(), alpha=0.5, ) # Labels. ax.set_xlabel( labels.make_valid_latex_string(correlation.axis.display_str())) ax.set_ylabel(labels.make_valid_latex_string( labels.delta_phi_axis_label())) jet_pt_label = labels.jet_pt_range_string(analysis.jet_pt) track_pt_label = labels.track_pt_range_string(analysis.track_pt) ax.set_title( fr"Subtracted 1D ${correlation.axis.display_str()}$," f" {analysis.reaction_plane_orientation.display_str()} event plane orient.," f" {jet_pt_label}, {track_pt_label}") ax.legend(loc="upper right") # Final adjustments fig.tight_layout() # Save plot and cleanup plot_base.save_plot( analysis.output_info, fig, f"jetH_delta_phi_{analysis.identifier}_width_signal_dominated_fit") plt.close(fig)