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_unsubtracted(hists: "correlations.CorrelationHistogramsDeltaEta", correlation_scale_factor: float, jet_pt: analysis_objects.JetPtBin, track_pt: analysis_objects.TrackPtBin, reaction_plane_orientation: params.ReactionPlaneOrientation, identifier: str, output_info: analysis_objects.PlottingOutputWrapper) -> None: """ Plot 1D delta eta correlations on a single plot. Args: hists: Unsubtracted delta eta histograms. correlation_scale_factor: Overall correlation scale factor. jet_pt: Jet pt bin. track_pt: Track pt bin. reaction_plane_orientation: Reaction plane orientation. identifier: Analysis identifier string. Usually contains jet pt, track pt, and other information. output_info: Standard information needed to store the output. Returns: None. """ # Setup fig, ax = plt.subplots(figsize = (8, 6)) # Plot NS, AS h_near_side = histogram.Histogram1D.from_existing_hist(hists.near_side.hist) h_near_side *= correlation_scale_factor ax.errorbar( h_near_side.x, h_near_side.y, yerr = h_near_side.errors, label = hists.near_side.type.display_str(), marker = "o", linestyle = "", ) h_away_side = histogram.Histogram1D.from_existing_hist(hists.away_side.hist) h_away_side *= correlation_scale_factor ax.errorbar( h_away_side.x, h_away_side.y, yerr = h_away_side.errors, label = hists.away_side.type.display_str(), marker = "o", linestyle = "", ) # Set labels. ax.set_xlabel(labels.make_valid_latex_string(hists.near_side.hist.GetXaxis().GetTitle())) ax.set_ylabel(labels.make_valid_latex_string(hists.near_side.hist.GetYaxis().GetTitle())) ax.set_title(fr"Unsubtracted 1D ${hists.near_side.axis.display_str()}$," f" {reaction_plane_orientation.display_str()} event plane orient.," f" {labels.jet_pt_range_string(jet_pt)}, {labels.track_pt_range_string(track_pt)}") # Labeling ax.legend(loc = "upper right") # Final adjustments fig.tight_layout() # Save and cleanup output_name = f"jetH_delta_eta_{identifier}_near_away_side_comparison" plot_base.save_plot(output_info, fig, output_name) plt.close(fig)
def finalize(self, n_events_accepted: int) -> None: """ Finalize the analysis. """ # Sanity check assert len(self.events) == n_events_accepted logger.info( f"number of accepted events: {len(self.events)}, jets in tree: {len(self.jets)}" ) # Finally, convert to a proper numpy array. It's only converted here because it's not efficient to expand # existing numpy arrays. self.jets = np.array(self.jets, dtype=DTYPE_JETS) self.events = np.array(self.events, dtype=DTYPE_EVENT_PROPERTIES) # And save out the tree so we don't have to calculate it again later. self.save_tree(arr=self.jets, output_name="jets") self.save_tree(arr=self.events, output_name="event_properties") # Create the response matrix and plot it as a cross check. # Create histogram h, x_edges, y_edges = np.histogram2d(self.jets["det_pT"], self.jets["part_pT"], bins=(60, 60), range=((0, 60), (0, 60))) # Plot import matplotlib import matplotlib.pyplot as plt # Fix normalization h[h == 0] = np.nan fig, ax = plt.subplots(figsize=(8, 6)) resp = ax.imshow( h, extent=(x_edges[0], x_edges[-1], y_edges[0], y_edges[-1]), interpolation="nearest", aspect="auto", origin="lower", norm=matplotlib.colors.Normalize(vmin=np.nanmin(h), vmax=np.nanmax(h)), ) fig.colorbar(resp) # Final labeling and presentation ax.set_xlabel( labels.make_valid_latex_string(labels.jet_pt_display_label("det"))) ax.set_ylabel( labels.make_valid_latex_string( labels.jet_pt_display_label("part"))) fig.tight_layout() fig.subplots_adjust(hspace=0, wspace=0, right=0.99) plot_base.save_plot(self.output_info, fig, f"response") 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 _plot_all_1d_correlations_with_matplotlib( jet_hadron: "correlations.Correlations") -> None: """ Plot all 1D correlations in a very basic way with matplotlib. Note: We don't want to scale the histogram any further here because it's already been fully scaled! """ # Not ideal, but it's much more convenient to import it here. Importing it at the top # of the file would cause an import loop. from jet_hadron.analysis import correlations fig, ax = plt.subplots() for correlations_groups in [ jet_hadron.correlation_hists_delta_phi, jet_hadron.correlation_hists_delta_eta ]: # Help out mypy... assert isinstance(correlations_groups, (correlations.CorrelationHistogramsDeltaPhi, correlations.CorrelationHistogramsDeltaEta)) for _, observable in correlations_groups: # Draw the 1D histogram. h = histogram.Histogram1D.from_existing_hist(observable.hist) ax.errorbar( h.x, h.y, yerr=h.errors, label=observable.hist.GetName(), marker="o", linestyle="", ) # Set labels. ax.set_xlabel( labels.make_valid_latex_string( observable.hist.GetXaxis().GetTitle())) ax.set_ylabel( labels.make_valid_latex_string( observable.hist.GetYaxis().GetTitle())) ax.set_title( labels.make_valid_latex_string(observable.hist.GetTitle())) # Final adjustments fig.tight_layout() # Save and cleanup output_name = observable.hist.GetName() + "_mpl" plot_base.save_plot(jet_hadron.output_info, fig, output_name) ax.clear() # 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 delta_eta_plot_projection_range_string( inclusive_analysis: "correlations.Correlations") -> str: """ Provides a string that describes the delta phi projection range for delta eta plots. """ # The limit is almost certainly a multiple of pi, so we try to express it more naturally # as a value like pi/2 or 3*pi/2 value = _find_pi_coefficient( value=inclusive_analysis.near_side_phi_region.max) return labels.make_valid_latex_string(fr"$|\Delta\varphi|<{value}$")
def near_side_extraction_range( analysis: "correlations.Correlations") -> str: """ Helper function to provide the yield extraction range. We want to extract the value from the hist itself in case the binning is off (it shouldn't be). """ # Setup min_value, max_value = _find_extraction_range_min_and_max_in_hist( h=analysis.correlation_hists_delta_eta_subtracted.near_side.hist, extraction_range=analysis.yields_delta_eta.near_side. extraction_range, ) # Due to floating point rounding issues, we need to apply our rounding trick here. return labels.make_valid_latex_string( r"\text{Yield extraction range:}\:" fr" |\Delta\eta| < {int(round(max_value * 10)) / 10}")
def near_side_extraction_range( analysis: "correlations.Correlations") -> str: """ Helper function to provide the yield extraction range. We want to extract the value from the hist itself in case the binning is off (it shouldn't be). """ # Setup min_value, max_value = _find_extraction_range_min_and_max_in_hist( h=analysis.correlation_hists_delta_phi_subtracted.signal_dominated. hist, extraction_range=analysis.yields_delta_phi.near_side. extraction_range, ) return labels.make_valid_latex_string( r"\text{Yield extraction range:}\:|\Delta\varphi| < " + f"{_find_pi_coefficient(max_value)}")
def delta_phi_away_side_yields( analyses: Mapping[Any, "correlations.Correlations"], selected_iterables: Mapping[str, Sequence[Any]], output_info: analysis_objects.PlottingOutputWrapper) -> None: """ Plot the delta phi away-side yields. """ def away_side_yields( analysis: "correlations.Correlations" ) -> analysis_objects.ExtractedObservable: """ Helper function to provide the ExtractedObservable. """ return analysis.yields_delta_phi.away_side.value def away_side_extraction_range( analysis: "correlations.Correlations") -> str: """ Helper function to provide the yield extraction range. We want to extract the value from the hist itself in case the binning is off (it shouldn't be). """ # Setup min_value, max_value = _find_extraction_range_min_and_max_in_hist( h=analysis.correlation_hists_delta_phi_subtracted.signal_dominated. hist, extraction_range=analysis.yields_delta_phi.away_side. extraction_range, ) return labels.make_valid_latex_string( r"\text{Yield extraction range:}\:" fr" {_find_pi_coefficient(min_value)}" r" < |\Delta\varphi| <" fr" {_find_pi_coefficient(max_value)}") _extracted_values( analyses=analyses, selected_iterables=selected_iterables, extract_value_func=away_side_yields, plot_labels=plot_base.PlotLabels( y_label=labels.make_valid_latex_string( fr"\mathrm{{d}}N/\mathrm{{d}}{labels.pt_display_label()} ({labels.momentum_units_label_gev()})^{{-1}}", ), title="Away-side yield", ), output_name="yields_delta_phi_away_side", output_info=output_info, projection_range_func=delta_phi_plot_projection_range_string, extraction_range_func=away_side_extraction_range, )
def delta_eta_near_side_yields( analyses: Mapping[Any, "correlations.Correlations"], selected_iterables: Mapping[str, Sequence[Any]], output_info: analysis_objects.PlottingOutputWrapper) -> None: """ Plot the delta eta near-side yields. """ def near_side_widths( analysis: "correlations.Correlations" ) -> analysis_objects.ExtractedObservable: """ Helper function to provide the ExtractedObservable. """ return analysis.yields_delta_eta.near_side.value def near_side_extraction_range( analysis: "correlations.Correlations") -> str: """ Helper function to provide the yield extraction range. We want to extract the value from the hist itself in case the binning is off (it shouldn't be). """ # Setup min_value, max_value = _find_extraction_range_min_and_max_in_hist( h=analysis.correlation_hists_delta_eta_subtracted.near_side.hist, extraction_range=analysis.yields_delta_eta.near_side. extraction_range, ) # Due to floating point rounding issues, we need to apply our rounding trick here. return labels.make_valid_latex_string( r"\text{Yield extraction range:}\:" fr" |\Delta\eta| < {int(round(max_value * 10)) / 10}") _extracted_values( analyses=analyses, selected_iterables=selected_iterables, extract_value_func=near_side_widths, plot_labels=plot_base.PlotLabels( y_label=labels.make_valid_latex_string( fr"\mathrm{{d}}N/\mathrm{{d}}{labels.pt_display_label()} ({labels.momentum_units_label_gev()})^{{-1}}", ), title="Near-side yield", ), output_name="yields_delta_eta_near_side", output_info=output_info, projection_range_func=delta_eta_plot_projection_range_string, extraction_range_func=near_side_extraction_range, )
def _reference_v2_a_data( reference_data: ReferenceData, ax: matplotlib.axes.Axes, selected_analysis_options: params.SelectedAnalysisOptions) -> None: # Determine the centrality key (because they don't map cleanly onto our centrality ranges) reference_data_centrality_map = { params.EventActivity.central: "0-5", params.EventActivity.semi_central: "30-40", } centrality_label = reference_data_centrality_map[ selected_analysis_options.event_activity] # Retrieve the data hist = reference_data["ptDependent"][centrality_label] # Plot and label it on the existing axis. ax.errorbar( hist.x, hist.y, yerr=hist.errors, marker="o", linestyle="", label=fr"ALICE {centrality_label}\% " + labels.make_valid_latex_string("v_{2}") + r"\{2, $| \Delta\eta |>1$\}", )
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 _matrix_values(free_parameters: Sequence[str], matrix: Dict[Tuple[str, str], float], output_info: analysis_objects.PlottingOutputWrapper, output_name: str) -> None: """ Plot the RP fit covariance matrix. Code substantially improved by using the information at https://matplotlib.org/gallery/images_contours_and_fields/image_annotated_heatmap.html Args: free_parameters: Names of the free parameters used in the fit (which will be included in the plot). matrix: Matrix values to plot. Should be either the correlation matrix or the covariance matrix. output_info: Output information. output_name: Name of the output plot. Returns: None. The covariance matrix is saved. """ # Setup fig, ax = plt.subplots(figsize=(8, 6)) # Move x labels to top to follow convention ax.tick_params(top=True, bottom=False, labeltop=True, labelbottom=False) # Create a map from the labels to valid LaTeX for improved presentation improved_labeling_map = { "ns_amplitude": "A_{NS}", "as_amplitude": "A_{AS}", "ns_sigma": r"\sigma_{NS}", "as_sigma": r"\sigma_{AS}", "BG": r"\text{Signal background}", "v1": "v_{1}", "v2_t": "v_{2}^{t}", "v2_a": "v_{2}^{a}", "v3": "v_{3}^{2}", "v4_t": "v_{4}^{t}", "v4_a": "v_{4}^{a}", "B": r"\text{RPF Background}", } # Ensure that the strings are valid LaTeX improved_labeling_map = { k: labels.make_valid_latex_string(v) for k, v in improved_labeling_map.items() } # Fixed parameters aren't in the covariance matrix. number_of_parameters = len(free_parameters) # Put the values into a form that can actually be plotted. Note that this assumes a square matrix. matrix_values = np.array(list(matrix.values())) matrix_values = matrix_values.reshape(number_of_parameters, number_of_parameters) # Plot the matrix im = ax.imshow(matrix_values, cmap="viridis") # Add the colorbar fig.colorbar(im, ax=ax) # Axis labeling parameter_labels = [b for a, b in list(matrix)[:number_of_parameters]] parameter_labels = [improved_labeling_map[l] for l in parameter_labels] # Show all axis ticks and then label them # The first step of settings the yticks is required according to the matplotlib docs ax.set_xticks(range(number_of_parameters)) ax.set_yticks(range(number_of_parameters)) # Also rotate the x-axis labels so they are all readable. ax.set_xticklabels(parameter_labels, rotation=-30, rotation_mode="anchor", horizontalalignment="right") ax.set_yticklabels(parameter_labels) # Turn spines off and create white grid for edge, spine in ax.spines.items(): spine.set_visible(False) # Use minor ticks to put a white border between each value ax.set_xticks(np.arange(number_of_parameters + 1) - .5, minor=True) ax.set_yticks(np.arange(number_of_parameters + 1) - .5, minor=True) ax.grid(which="minor", color="w", linestyle='-', linewidth=3) ax.tick_params(which="minor", bottom=False, left=False) # Have outward ticks just for this plot. They seem to look better here ax.tick_params(direction="out") # Label values in each element threshold_for_changing_label_colors = im.norm(np.max(matrix_values)) / 2 text_colors = ["white", "black"] for i in range(number_of_parameters): for j in range(number_of_parameters): color = text_colors[im.norm(matrix_values[i, j]) > threshold_for_changing_label_colors] im.axes.text(j, i, f"{matrix_values[i, j]:.1f}", horizontalalignment="center", verticalalignment="center", color=color) # Final adjustments fig.tight_layout() # Save plot and cleanup plot_base.save_plot(output_info, fig, output_name) plt.close(fig)
def fit_parameters_vs_assoc_pt( fit_objects: FitObjects, selected_analysis_options: params.SelectedAnalysisOptions, reference_data_path: str, output_info: analysis_objects.PlottingOutputWrapper) -> None: """ Plot the extracted fit parameters. Args: fit_objects: Fit objects whose parameters will be plotted. selected_analysis_options: Selected analysis options to be used for labeling. reference_data_path: Path to the reference data. output_info: Output information. """ # Extract data from the reference dataset. We extract it here to save file IO. reference_data_path = reference_data_path.format( **dict(selected_analysis_options), #collision_energy = selected_analysis_options.collision_energy ) try: reference_data: Mapping[str, Mapping[str, histogram.Histogram1D]] = {} with open(reference_data_path, "r") as f: y = yaml.yaml(modules_to_register=[histogram]) reference_data = y.load(f) except IOError: # We will just work with the empty reference data. pass pt_assoc_label = labels.make_valid_latex_string( f"{labels.track_pt_display_label()} ({labels.momentum_units_label_gev()})" ) prefix = "fit_parameter" parameters = [ ParameterInfo( name="v2_t", output_name=f"{prefix}_v2t", labels=plot_base.PlotLabels(title=r"Jet $v_{2}$", x_label=pt_assoc_label), ), ParameterInfo( name="v2_a", output_name=f"{prefix}_v2a", labels=plot_base.PlotLabels(title=r"Associated hadron $v_{2}$", x_label=pt_assoc_label), plot_reference_data_func=_reference_v2_a_data, ), ParameterInfo( name="v3", output_name=f"{prefix}_v3", labels=plot_base.PlotLabels(title=r"$v_{3}$", x_label=pt_assoc_label), ), ParameterInfo( name="v4_t", output_name=f"{prefix}_v4t", labels=plot_base.PlotLabels(title=r"Jet $v_{4}$", x_label=pt_assoc_label), ), ParameterInfo( name="v4_a", output_name=f"{prefix}_v4a", labels=plot_base.PlotLabels(title=r"Associated hadron $v_{4}$", x_label=pt_assoc_label), ), ParameterInfo( name="BG", output_name=f"{prefix}_background", labels=plot_base.PlotLabels(title=r"Effective RPF background", x_label=pt_assoc_label), ), ] # Plot the signal parameters (but only if they exist). fit_obj = next(iter(fit_objects.values())) if "ns_amplitude" in fit_obj.fit_result.parameters: parameters.append( ParameterInfo( name="ns_amplitude", output_name=f"{prefix}_ns_amplitude", labels=plot_base.PlotLabels( title=r"Near side gaussian amplitude", x_label=pt_assoc_label), )) if "ns_sigma" in fit_obj.fit_result.parameters: parameters.append( ParameterInfo( name="ns_sigma", output_name=f"{prefix}_ns_sigma", labels=plot_base.PlotLabels( title=r"Near side $\sigma$", x_label=pt_assoc_label, y_label=r"$\sigma_{\text{ns}}$", ), )) if "as_amplitude" in fit_obj.fit_result.parameters: parameters.append( ParameterInfo( name="as_amplitude", output_name=f"{prefix}_as_amplitude", labels=plot_base.PlotLabels( title=r"Away side gaussian amplitude", x_label=pt_assoc_label), )) if "as_sigma" in fit_obj.fit_result.parameters: parameters.append( ParameterInfo( name="as_sigma", output_name=f"{prefix}_as_sigma", labels=plot_base.PlotLabels(title=r"Away side $\sigma$", x_label=pt_assoc_label, y_label=r"$\sigma_{\text{as}}$"), )) for parameter in parameters: _plot_fit_parameter_vs_assoc_pt( fit_objects=fit_objects, parameter=parameter, reference_data=reference_data, selected_analysis_options=selected_analysis_options, output_info=output_info)
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 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)
def delta_phi_plot_projection_range_string( inclusive_analysis: "correlations.Correlations") -> str: """ Provides a string that describes the delta eta projection range for delta phi plots. """ return labels.make_valid_latex_string( fr"$|\Delta\eta|<{inclusive_analysis.signal_dominated_eta_region.max}$" )
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 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 plot_particle_level_spectra( ep_analyses_iter: Iterator[Tuple[Any, "response_matrix.ResponseMatrix"]], output_info: analysis_objects.PlottingOutputWrapper, plot_with_ROOT: bool = False) -> None: """ Plot the particle level spectra associated with the response. Args: ep_analyses: The event plane dependent final response matrices. output_info: Output information. plot_with_ROOT: True if the plot should be done via ROOT. Default: False Returns: None. The spectra are plotted and saved. """ # Pull out the dict because we need to grab individual analyses for some labeling information, which doesn't # play well with generators (the generator will be exhausted). ep_analyses = dict(ep_analyses_iter) # Determine the general and plot labels # First, we need some variables to define the general labels, so we retrieve the inclusive analysis. # All of the parameters retrieved here are shared by all analyses. inclusive = next(iter(ep_analyses.values())) # Then we define some additional helper variables particle_level_spectra_bin = inclusive.task_config[ "particle_level_spectra"]["particle_level_spectra_bin"] embedded_additional_label = inclusive.event_activity.display_str() # General labels general_labels = { "alice_and_collision_energy": rf"{inclusive.alice_label.display_str()}\:{inclusive.collision_energy.display_str()}", "collision_system_and_event_activity": rf"{inclusive.collision_system.display_str(embedded_additional_label = embedded_additional_label)}", "detector_pt_range": labels.pt_range_string( particle_level_spectra_bin, lower_label="T,jet", upper_label="det", ), "constituent_cuts": labels.constituent_cuts(additional_label="det"), "leading_hadron_bias": inclusive.leading_hadron_bias.display_str(additional_label="det"), "jet_finding": labels.jet_finding(), } # Ensure that each line is a valid latex line. # The heuristic is roughly that full statements (such as jet_finding) are already wrapped in "$", # while partial statements, such as the leading hadron bias, event activity, etc are not wrapped in "$". # This is due to the potential for such "$" to interfere with including those partial statements in other # statements. As an example, it would be impossible to use the ``embedded_additional_label`` above if the # ``event_activity`` included "$". for k, v in general_labels.items(): general_labels[k] = labels.make_valid_latex_string(v) # Plot labels y_label = r"\mathrm{d}N/\mathrm{d}p_{\mathrm{T}}" if inclusive.task_config["particle_level_spectra"]["normalize_by_n_jets"]: y_label = r"(1/N_{\mathrm{jets}})" + y_label y_label = y_label if inclusive.task_config["particle_level_spectra"][ "normalize_at_selected_jet_pt_bin"]: y_label = r"\mathrm{Arb. Units}" plot_labels = plot_base.PlotLabels( title="", x_label= fr"${labels.jet_pt_display_label(upper_label = 'part')}\:({labels.momentum_units_label_gev()})$", y_label=labels.make_valid_latex_string(y_label), ) # Finally, we collect our arguments for the plotting functions. kwargs: Dict[str, Any] = { "ep_analyses": ep_analyses, "output_name": "particle_level_spectra", "output_info": output_info, "general_labels": general_labels, "plot_labels": plot_labels, } if plot_with_ROOT: _plot_particle_level_spectra_with_ROOT(**kwargs) else: _plot_particle_level_spectra_with_matplotlib(**kwargs)
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 test_make_valid_latex_string(logging_mixin, value: str, expected: str): """ Test for making a string into a valid latex string. """ assert labels.make_valid_latex_string(value) == expected
def track_eta_phi(hist: Hist, event_activity: params.EventActivity, output_info: analysis_objects.PlottingOutputWrapper) -> None: """ Plot track eta phi. Also include an annotation of the EMCal eta, phi location. Args: rho_hist: Rho centrality dependent hist. output_info: Output information. includes_constituent_cut: True if the plot was produced using the constituent cut. Returns: None. The figure is plotted. """ # Setup fig, ax = plt.subplots(figsize=(8, 6)) x, y, hist_array = histogram.get_array_from_hist2D( hist, set_zero_to_NaN=True, return_bin_edges=True, ) plot = ax.imshow( hist_array.T, extent=[np.amin(x), np.amax(x), np.amin(y), np.amax(y)], interpolation="nearest", aspect="auto", origin="lower", # Intentionally stretch the scale near the top of the range to emphasise the inefficiency. norm=matplotlib.colors.Normalize(vmin=np.nanmax(hist_array) * 0.8, vmax=np.nanmax(hist_array) * 0.9), cmap="viridis", ) # Draw the colorbar based on the drawn axis above. # NOTE: This can cause the warning: # ''' # matplotlib/colors.py:1031: RuntimeWarning: invalid value encountered in less_equal # mask |= resdat <= 0" # ''' # The warning is due to the nan we introduced above. It can safely be ignored # See: https://stackoverflow.com/a/34955622 # (Could suppress, but I don't feel it's necessary at the moment) fig.colorbar(plot) # Draw EMCal boundaries r = matplotlib.patches.Rectangle( xy=(-0.7, 80 * np.pi / 180), width=0.7 + 0.7, height=(187 - 80) * np.pi / 180, facecolor="none", edgecolor="tab:red", linewidth=1.5, label="EMCal", ) ax.add_patch(r) # Final presentation settings ax.set_xlabel(labels.make_valid_latex_string(r"\eta")) ax.set_ylabel(labels.make_valid_latex_string(r"\varphi")) ax.legend(loc="upper right") fig.tight_layout() # Finally, save and cleanup output_name = f"track_eta_phi_{str(event_activity)}" plot_base.save_plot(output_info, fig, output_name) plt.close(fig) # Check the phi 1D projection track_phi = hist.ProjectionY() # Make it easier to view track_phi.Rebin(8) import ROOT canvas = ROOT.TCanvas("c", "c") # Labeling track_phi.SetTitle("") track_phi.GetXaxis().SetTitle( labels.use_label_with_root(labels.make_valid_latex_string(r"\varphi"))) track_phi.GetYaxis().SetTitle("Counts") track_phi.Draw() # Draw lines corresponding to the EMCal line_min = ROOT.TLine(80 * np.pi / 180, track_phi.GetMinimum(), 80 * np.pi / 180, track_phi.GetMaximum()) line_max = ROOT.TLine(187 * np.pi / 180, track_phi.GetMinimum(), 187 * np.pi / 180, track_phi.GetMaximum()) for l in [line_min, line_max]: l.SetLineColor(ROOT.kRed) l.Draw("same") # Save the plot plot_base.save_plot(output_info, canvas, f"track_phi_{str(event_activity)}")
def compare_STAR_and_ALICE( star_final_response_task: "response_matrix.ResponseMatrixBase", alice_particle_level_spectra: Dict[params.CollisionEnergy, Hist], output_info: analysis_objects.PlottingOutputWrapper) -> None: # Setup fig, ax = plt.subplots(figsize=(8, 6)) # First, plot the STAR points star_centrality_map = { params.EventActivity.semi_central: r"20 \textendash 50 \%", } star_hist = histogram.Histogram1D.from_existing_hist( star_final_response_task.particle_level_spectra) star_label = f"STAR ${star_final_response_task.collision_energy.display_str()}$ hard-core jets" star_label += "\n" + f"PYTHIA with ${star_centrality_map[star_final_response_task.event_activity]}$ ${star_final_response_task.collision_system.display_str()}$ det. conditions" ax.errorbar( star_hist.x, star_hist.y, xerr=(star_hist.bin_edges[1:] - star_hist.bin_edges[:-1]) / 2, yerr=star_hist.errors, label=star_label, color="blue", marker="s", linestyle="", ) # Convert and plot hist # Markers are for 2.76, 5.02 TeV markers = ["s", "o"] for (collision_energy, part_level_hist), marker in zip(alice_particle_level_spectra.items(), markers): alice_label = f"ALICE ${collision_energy.display_str()}$ biased jets" alice_label += "\n" + f"${params.CollisionSystem.embedPythia.display_str(embedded_additional_label = star_final_response_task.event_activity.display_str())}$" h = histogram.Histogram1D.from_existing_hist(part_level_hist) ax.errorbar( h.x, h.y, xerr=(h.bin_edges[1:] - h.bin_edges[:-1]) / 2, yerr=h.errors, label=alice_label, color="red", marker=marker, linestyle="", ) # Label axes y_label = r"\text{d}N/\text{d}p_{\text{T}}" if star_final_response_task.task_config["particle_level_spectra"][ "normalize_by_n_jets"]: y_label = r"(1/N_{\text{jets}})" + y_label y_label = y_label if star_final_response_task.task_config["particle_level_spectra"][ "normalize_at_selected_jet_pt_bin"]: y_label = r"\text{Arb. Units}" plot_labels = plot_base.PlotLabels( title="", x_label= fr"${labels.jet_pt_display_label(upper_label = 'part')}\:({labels.momentum_units_label_gev()})$", y_label=labels.make_valid_latex_string(y_label), ) # Apply labels individually so we can increase the font size... ax.set_xlabel(plot_labels.x_label, fontsize=16) ax.set_ylabel(plot_labels.y_label, fontsize=16) ax.set_title("") # Final presentation settings # Axis ticks ax.xaxis.set_major_locator(matplotlib.ticker.MultipleLocator(base=10)) ax.xaxis.set_minor_locator(matplotlib.ticker.MultipleLocator(base=2)) tick_shared_args = { "axis": "both", "bottom": True, "left": True, } ax.tick_params( which="major", # Size of the axis mark labels labelsize=15, length=8, **tick_shared_args, ) ax.tick_params( which="minor", length=4, **tick_shared_args, ) # Limits ax.set_xlim( 0, star_final_response_task.task_config["particle_level_spectra"] ["particle_level_max_pt"]) # Unfortunately, MPL doesn't calculate restricted log limits very nicely, so we # we have to set the values by hand. # We grab the value from the last analysis object - the value will be the same for all of them. y_limits = star_final_response_task.task_config["particle_level_spectra"][ "y_limits"] ax.set_ylim(y_limits[0], y_limits[1]) ax.set_yscale("log") # Legend ax.legend( # Here, we specify the location of the upper right corner of the legend box. loc="upper right", bbox_to_anchor=(0.99, 0.99), borderaxespad=0, frameon=True, fontsize=15, ) ax.text(0.99, 0.75, s="Inclusive event plane orientation", horizontalalignment="right", verticalalignment="top", multialignment="right", fontsize=15, transform=ax.transAxes) fig.tight_layout() # Finally, save and cleanup output_name = "particle_level_comparison_STAR_ALICE" output_name += "_mpl" plot_base.save_plot(output_info, fig, output_name) 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()