def test_hist_input_type_validation(logging_mixin: Any, bin_edges: np.ndarray, y: np.ndarray, errors_squared: np.ndarray, expect_corrected_types: str) -> None: """ Check histogram input type validation. """ if expect_corrected_types: h = histogram.Histogram1D(bin_edges = bin_edges, y = y, errors_squared = errors_squared) for arr in [h.bin_edges, h.y, h.errors_squared]: assert isinstance(arr, np.ndarray) else: with pytest.raises(ValueError) as exception_info: h = histogram.Histogram1D(bin_edges = bin_edges, y = y, errors_squared = errors_squared) assert "Arrays must be numpy arrays" in exception_info.value.args[0]
def setup_basic_hist(logging_mixin: Any) -> Tuple[histogram.Histogram1D, np.ndarray, np.ndarray, np.ndarray]: """ Setup a basic `Histogram1D` for basic tests. This histogram contains 4 bins, with edges of [0, 1, 2, 3, 5], values of [2, 2, 3, 0], with errors of [4, 2, 3, 0], simulating the first bin being filled once with a weight of 2, and the rest being filled normally. It could be reproduced in ROOT with: >>> bins = np.array([0, 1, 2, 3, 5], dtype = np.float64) >>> hist = ROOT.TH1F("test", "test", 4, binning) >>> hist.Fill(0, 2) >>> hist.Fill(1) >>> hist.Fill(1) >>> hist.Fill(2) >>> hist.Fill(2) >>> hist.Fill(2) Args: None. Returns: hist, bin_edges, y, errors_squared """ bin_edges = np.array([0, 1, 2, 3, 5]) y = np.array([2, 2, 3, 0]) # As if the first bin was filled with weight of 2. errors_squared = np.array([4, 2, 3, 0]) h = histogram.Histogram1D(bin_edges = bin_edges, y = y, errors_squared = errors_squared) return h, bin_edges, y, errors_squared
def convert_to_histogram_1D(self, bin_edges: np.array) -> histogram.Histogram1D: """ Convert these stored values into a ``Histogram1D``. """ return histogram.Histogram1D( bin_edges = bin_edges, y = self.y, errors_squared = self.errors_squared, )
def _calculate_resolution(self) -> histogram.Histogram1D: """ Calculate the event plane resolution. Args: None. Returns: Resolutions and errors calculated for each centrality bin. """ # R = sqrt(multiply all other detectors/main detector) # NOTE: main_detector actually contains the cosine of the difference of the other detectors. The terminology # is a bit confusing. As an example, for VZERO resolution, sqrt((TPC_Pos * TPC_Neg)/VZERO) # NOTE: The output of reduce is an element-wise multiplication of all other_detector arrays. For # the example of two other detectors, this is the same as just other[0].data * other[1].data resolution_squared = reduce( lambda x, y: x * y, [other.data for other in self.other_detectors]) / self.main_detector.data # We sometimes end up with a few very small negative values. This is a problem for sqrt, so we # explicitly set them to 0. resolution_squared.y[resolution_squared.y < 0] = 0 resolutions = np.sqrt(resolution_squared.y) # Sometimes the resolution is 0, so we carefully divide to avoid NaNs errors = np.divide( 1. / 2 * resolution_squared.errors, resolutions, out=np.zeros_like(resolution_squared.errors), where=resolutions != 0, ) # Return the values in a histogram for convenience return histogram.Histogram1D( bin_edges=self.main_detector.data.bin_edges, y=resolutions, errors_squared=errors**2)
def test_hist_identical_arrays(logging_mixin: Any) -> None: """ Test handling receiving identical numpy arrays. """ bin_edges = np.array([1, 2, 3]) y = np.array([1, 2]) h = histogram.Histogram1D(bin_edges = bin_edges, y = y, errors_squared = y) # Even through we passed the same array, they should be copied by the validation. assert np.may_share_memory(h.y, h.errors_squared) is False
def test_histogram1D_equality(logging_mixin, setup_basic_hist, test_equality, access_attributes_which_are_stored): """ Test for Histogram1D equality. """ h, bin_edges, y, errors_squared = setup_basic_hist h1 = histogram.Histogram1D(bin_edges = bin_edges, y = y, errors_squared = errors_squared) h2 = histogram.Histogram1D(bin_edges = bin_edges, y = y, errors_squared = errors_squared) if access_attributes_which_are_stored: # This attribute will be stored (but under "_x"), so we want to make sure that it # doesn't disrupt the equality comparison. h1.x if not test_equality: h1.bin_edges = np.array([5, 6, 7, 8, 9]) if test_equality: assert h1 == h2 else: assert h1 != h2
def test_chi_squared(logging_mixin: Any) -> None: """ Test the chi squared calculation. """ # Setup h = histogram.Histogram1D( bin_edges=np.array(np.arange(-0.5, 5.5)), y=np.array(np.ones(5)), errors_squared=np.ones(5), ) chi_squared = cost_function.ChiSquared(f=func_1, data=h) # Check that it's set up properly assert chi_squared.func_code.co_varnames == ["a", "b"] # Calculate the chi_squared for the given parameters. result = chi_squared(np.array(range(-1, -6, -1)), np.zeros(5)) # Each term is (1 - -1)^2 / 1^2 = 4 assert result == 4 * 5
def signal_dominated_with_background_function( analysis: "correlations.Correlations") -> None: """ Plot the signal dominated hist with the background function. """ # Setup fig, ax = plt.subplots(figsize=(8, 6)) # Plot signal and background dominated hists plot_correlations.plot_and_label_1d_signal_and_background_with_matplotlib_on_axis( ax=ax, jet_hadron=analysis) # Plot background function # First we retrieve the signal dominated histogram to get reference x values and bin edges. h = histogram.Histogram1D.from_existing_hist( analysis.correlation_hists_delta_phi.signal_dominated.hist) background = histogram.Histogram1D( bin_edges=h.bin_edges, y=analysis.fit_object.evaluate_background(h.x), errors_squared=analysis.fit_object. calculate_background_function_errors(h.x)**2, ) background_plot = ax.plot(background.x, background.y, label="Background function") ax.fill_between( background.x, background.y - background.errors, background.y + background.errors, facecolor=background_plot[0].get_color(), alpha=0.9, ) # Labeling 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}_signal_background_function_comparison" ) plt.close(fig)
def test_Histogram1D_with_yaml(logging_mixin: Any) -> None: """ Test writing and then reading a Histogram1D via YAML. This ensures that writing a histogram1D can be done successfully. """ # Setup # YAML object y = yaml.yaml(classes_to_register=[histogram.Histogram1D]) # Test hist input_hist = histogram.Histogram1D(bin_edges=np.linspace(0, 10, 11), y=np.linspace(2, 20, 10), errors_squared=np.linspace(2, 20, 10)) # Access "x" since it is generated but then stored in the class. This could disrupt YAML, so # we should explicitly test it. input_hist.x # Dump and load (ie round trip) output_hist = dump_to_string_and_retrieve(input_hist, y=y) # Check the result assert input_hist == output_hist
def to_histogram1D(self) -> Any: """Convert to a Histogram 1D. This is entirely a convenience function. Generally, it's best to stay with BinnedData, but a Histogram1D is required in some cases, such as for fitting. Returns: Histogram1D containing the data. """ # Validation if len(self.axes) > 1: raise ValueError( f"Can only convert to 1D histogram. Given {len(self.axes)} axes" ) from pachyderm import histogram return histogram.Histogram1D( bin_edges=self.axes[0].bin_edges, y=self.values, errors_squared=self.variances, )
def restrict_hist_range(hist: histogram.Histogram1D, min_x: float, max_x: float) -> histogram.Histogram1D: """ Restrict the histogram to only be within a provided x range. Args: hist: Histogram to be restricted. min_x: Minimum x value. max_x: Maximum x value. Returns: Restricted histogram. """ selected_range = slice(hist.find_bin(min_x + epsilon), hist.find_bin(max_x - epsilon) + 1) # Need the +/- epsilons here to be extra safe, because apparently some of the <= and >= can fail # (as might be guessed with floats, but I hadn't observed until now). We don't do this above # because we don't want to be inclusive on the edges. bin_edges_selected_range = ((hist.bin_edges >= min_x - epsilon) & (hist.bin_edges <= max_x + epsilon)) return histogram.Histogram1D( bin_edges=hist.bin_edges[bin_edges_selected_range], y=hist.y[selected_range], errors_squared=hist.errors_squared[selected_range])
def _setup( self, h: histogram.Histogram1D ) -> Tuple[histogram.Histogram1D, FitArguments]: """ Setup the histogram and arguments for the fit. Args: h: Background subtracted histogram to be fit. Returns: Histogram to use for the fit, default arguments for the fit. Note that the histogram may be range restricted or otherwise modified here. """ # Restrict the range so that we only fit within the desired input. restricted_range = (h.x > self.fit_range.min) & (h.x < self.fit_range.max) restricted_hist = histogram.Histogram1D( # We need the bin edges to be inclusive. bin_edges=h.bin_edges[(h.bin_edges >= self.fit_range.min) & (h.bin_edges <= self.fit_range.max)], y=h.y[restricted_range], errors_squared=h.errors_squared[restricted_range]) # Default arguments required for the fit arguments: FitArguments = { "pedestal": 0, "limit_pedestal": (-1000, 1000), "error_pedestal": 0.1, "amplitude": 1, "limit_amplitude": (0.05, 100), "error_amplitude": 0.1 * 1, "mean": 0, "limit_mean": (-0.5, 0.5), "error_mean": 0.05, "width": 0.15, "limit_width": (0.05, 0.8), "error_width": 0.1 * 0.15, } return restricted_hist, arguments
def _load_JEWEL_predictions_from_file(path: Path) -> histogram.Histogram1D: """ Load JEWEL predictions from file. The file format is: ``` pt_bin_center pt_bin_center_uncertainty value value_uncertainty ``` Args: path: Path to the file containing the JEWEL predictions. Returns: The data loaded into a histogram. """ bin_edges = np.array([0.5, 1, 1.5, 2, 3, 4, 5, 6, 10]) bin_centers_of_interest = bin_edges[:-1] + (bin_edges[1:] - bin_edges[:-1]) / 2 values = [] errors = [] with open(path, "r") as f: for line in f: # Remove remaining newline. line = line.rstrip("\n") # Convert to floats converted_values = [float(v) for v in line.split("\t")] bin_center, _, value, value_uncertainty = converted_values # Only store if the bin center is of interest. for c in bin_centers_of_interest: if np.isclose(bin_center, c): break else: continue values.append(value) errors.append(value_uncertainty) return histogram.Histogram1D(bin_edges=bin_edges, y=np.array(values), errors_squared=np.array(errors)**2)
def _setup( self, h: histogram.Histogram1D ) -> Tuple[histogram.Histogram1D, FitArguments]: """ Setup the histogram and arguments for the fit. Args: h: Background subtracted histogram to be fit. Returns: Histogram to use for the fit, default arguments for the fit. Note that the histogram may be range restricted or otherwise modified here. """ restricted_range = ( # For example, -1.2 < h.x < -0.8 (h.x < -1 * self.fit_range.min) & (h.x > -1 * self.fit_range.max) # For example, 0.8 < h.x < 1.2 | (h.x > self.fit_range.min) & (h.x < self.fit_range.max)) # Same conditions as above, but we need the bin edges to be inclusive. bin_edges_restricted_range = ( (h.bin_edges <= -1 * self.fit_range.min) & (h.bin_edges >= -1 * self.fit_range.max) | (h.bin_edges >= self.fit_range.min) & (h.bin_edges <= self.fit_range.max)) restricted_hist = histogram.Histogram1D( bin_edges=h.bin_edges[bin_edges_restricted_range], y=h.y[restricted_range], errors_squared=h.errors_squared[restricted_range]) # Default arguments # Use the minimum of the histogram as the starting value. min_hist = np.min(restricted_hist.y) arguments: FitArguments = { "pedestal": min_hist, "error_pedestal": min_hist * 0.1, "limit_pedestal": (-1000, 1000), } return restricted_hist, arguments
def test_integration(logging_mixin: Any) -> None: """ Test our implementation of the Simpson 3/8 rule, along with some other integration methods. """ # Setup f = func_1 h = histogram.Histogram1D(bin_edges=np.array([0, 1, 2]), y=np.array([0, 1]), errors_squared=np.array([1, 2])) args = [0, 0] integral = cost_function._simpson_38(f, h.bin_edges, *args) # Evaluate at the bin center expected = np.array([f(i, *args) for i in h.x]) np.testing.assert_allclose(integral, expected) # Compare against our quad implementation integral_quad = cost_function._quad(f, h.bin_edges, *args) np.testing.assert_allclose(integral, integral_quad) # Also compare against probfit and scipy for good measure probfit = pytest.importorskip("probfit") expected_probfit = [] expected_scipy = [] expected_quad = [] for i in h.bin_edges[1:]: # Assumes uniform bin width expected_probfit.append( probfit.integrate1d(f, (i - 1, i), 1, tuple(args))) scipy_x = np.linspace(i - 1, i, 5) expected_scipy.append( scipy.integrate.simps(y=f(scipy_x, *args), x=scipy_x)) res, _ = scipy.integrate.quad(f, i - 1, i, args=tuple(args)) expected_quad.append(res) np.testing.assert_allclose(integral, expected_probfit) np.testing.assert_allclose(integral, expected_scipy) np.testing.assert_allclose(integral, expected_quad)
def test_get_array_from_hist(self, logging_mixin: Any, test_root_hists: Any) -> None: """ Test getting numpy arrays from a 1D hist. Note: This test is from the legacy get_array_from_hist(...) function. This functionality is superseded by Histogram1D.from_existing_hist(...), but we leave this test for good measure. """ hist = test_root_hists.hist1D hist_array = histogram.Histogram1D.from_existing_hist(hist) # Determine expected values x_bins = range(1, hist.GetXaxis().GetNbins() + 1) expected_bin_edges = np.empty(len(x_bins) + 1) expected_bin_edges[:-1] = [hist.GetXaxis().GetBinLowEdge(i) for i in x_bins] expected_bin_edges[-1] = hist.GetXaxis().GetBinUpEdge(hist.GetXaxis().GetNbins()) expected_hist_array = histogram.Histogram1D( bin_edges = expected_bin_edges, y = np.array([hist.GetBinContent(i) for i in x_bins]), errors_squared = np.array([hist.GetBinError(i) for i in x_bins])**2, ) logger.debug(f"sumw2: {len(hist.GetSumw2())}") logger.debug(f"sumw2: {hist.GetSumw2N()}") assert check_hist(hist_array, expected_hist_array) is True
def setup_histogram_conversion() -> Tuple[str, str, histogram.Histogram1D]: """ Setup expected values for histogram conversion tests. This set of expected values corresponds to: >>> hist = ROOT.TH1F("test", "test", 10, 0, 10) >>> hist.Fill(3, 2) >>> hist.Fill(8) >>> hist.Fill(8) >>> hist.Fill(8) Note: The error on bin 9 (one-indexed) is just sqrt(counts), while the error on bin 4 is sqrt(4) because we filled it with weight 2 (sumw2 squares this values). """ expected = histogram.Histogram1D(bin_edges = np.linspace(0, 10, 11), y = np.array([0, 0, 0, 2, 0, 0, 0, 0, 3, 0]), errors_squared = np.array([0, 0, 0, 4, 0, 0, 0, 0, 3, 0])) hist_name = "rootHist" filename = os.path.join(os.path.dirname(os.path.realpath(__file__)), "testFiles", "convertHist.root") if not os.path.exists(filename): # Need to create the initial histogram. # This shouldn't happen very often, as the file is stored in the repository. import ROOT root_hist = ROOT.TH1F(hist_name, hist_name, 10, 0, 10) root_hist.Fill(3, 2) for _ in range(3): root_hist.Fill(8) # Write out with normal ROOT so we can avoid further dependencies fOut = ROOT.TFile(filename, "RECREATE") root_hist.Write() fOut.Close() return filename, hist_name, expected
def _plot_rp_fit_residuals(rp_fit: reaction_plane_fit.fit.ReactionPlaneFit, ep_analyses: List[Tuple[ Any, "correlations.Correlations"]], axes: matplotlib.axes.Axes) -> None: """ Plot fit residuals on a given set of axes. Args: rp_fit: Reaction plane fit object. ep_analyses: Event plane dependent correlation analysis objects. axes: Axes on which the residual should be plotted. It must have an axis per component. Returns: None. The axes are modified in place. """ # Validation if len(rp_fit.components) != len(axes): raise TypeError( f"Number of axes is not equal to the number of fit components." f" # of components: {len(rp_fit.components)}, # of axes: {len(axes)}" ) if len(ep_analyses) != len(axes): raise TypeError( f"Number of axes is not equal to the number of EP analysis objects." f" # of analysis objects: {len(ep_analyses)}, # of axes: {len(axes)}" ) x = rp_fit.fit_result.x for (key_index, analysis), ax in zip(ep_analyses, axes): # Setup # Get the relevant data if analysis.reaction_plane_orientation == params.ReactionPlaneOrientation.inclusive: h: Union["correlations.DeltaPhiSignalDominated", "correlations.DeltaPhiBackgroundDominated"] = \ analysis.correlation_hists_delta_phi.signal_dominated else: h = analysis.correlation_hists_delta_phi.background_dominated hist = histogram.Histogram1D.from_existing_hist(h) # We create a histogram to represent the fit so that we can take advantage # of the error propagation in the Histogram1D object. fit_hist = histogram.Histogram1D( # Bin edges must be the same bin_edges=hist.bin_edges, y=analysis.fit_object.evaluate_fit(x=x), errors_squared=analysis.fit_object.fit_result.errors**2, ) # NOTE: Residual = data - fit / fit, not just data-fit residual = (hist - fit_hist) / fit_hist # Plot the main values plot = ax.plot(x, residual.y, label="Residual", color=plot_base.AnalysisColors.fit) # Plot the fit errors ax.fill_between( x, residual.y - residual.errors, residual.y + residual.errors, facecolor=plot[0].get_color(), alpha=0.9, ) # Set the y-axis limit to be symmetric # Selected the value by looking at the data. ax.set_ylim(bottom=-0.1, top=0.1)
def test_hist_input_length_validation(logging_mixin: Any, bin_edges: np.ndarray, y: np.ndarray, errors_squared: np.ndarray) -> None: """ Check histogram input length validation. """ with pytest.raises(ValueError) as exception_info: histogram.Histogram1D(bin_edges = bin_edges, y = y, errors_squared = errors_squared) assert "Length of input arrays doesn't match!" in exception_info.value.args[0]
def new_combine_gaussians(label: str, widths: Sequence[float]) -> None: """ Use a new approach devised in July 2019. """ # Validation if len(widths) != 3: raise ValueError(f"Must pass only three widths. Passed: {widths}") # Imagine with 3 EP angles + inclusive # First define the 3 EP angles gaussians = [] #for width in range(1, 4): for width in widths: gaussians.append(np.random.normal(0, width, size=1000000)) n_trigs = [375, 300, 325] # Add the inclusive to the start of the number of trigs n_trigs.insert(0, np.sum(n_trigs)) # Create the inclusive, and then histogram it inclusive = np.array([(i, val) for i, gaussian in enumerate(gaussians, 1) for val in gaussian]) # Recall that [:, 0] contains 1-3, while [:, 1] contains the x values. h_inclusive, x_bin_edges, y_bin_edges = np.histogram2d( inclusive[:, 1], inclusive[:, 0], bins=[200, np.linspace(0.5, 3.5, 3 + 1)], range=[[-10, 10], [0.5, 3.5]]) logger.debug(f"h_inclusive.shape: {h_inclusive.shape}") #logger.debug(f"x_bin_edges: {x_bin_edges}") #logger.debug(f"y_bin_edges: {y_bin_edges}") logger.debug(f"inclusive[:, 0]: {inclusive[:, 0]}") fig, ax = plt.subplots(figsize=(8, 6)) X, Y = np.meshgrid(x_bin_edges, y_bin_edges) ax.pcolormesh(X, Y, h_inclusive.T) ax.set_xlabel("x") ax.set_ylabel("EP orientation proxy") fig.tight_layout() fig.savefig("gaussian_2d_histogram.pdf") # Basically, the first two arguments are h.x and h.y binned_mean, _, _ = scipy.stats.binned_statistic(inclusive[:, 0], inclusive[:, 1], "std", bins=np.linspace( 0.5, 3.5, 3 + 1)) logger.debug(f"Binned mean: {binned_mean}") inclusive_binned_mean, _, _ = scipy.stats.binned_statistic(inclusive[:, 0], inclusive[:, 1], "std", bins=1) logger.debug(f"Inclusive binned mean: {inclusive_binned_mean}") hists = [] # Inclusive hists.append( histogram.Histogram1D( bin_edges=x_bin_edges, y=np.sum(h_inclusive, axis=1), errors_squared=np.sum(h_inclusive, axis=1), )) # Width = 1 hists.append( histogram.Histogram1D( bin_edges=x_bin_edges, y=h_inclusive[:, 0], errors_squared=h_inclusive[:, 0], )) # Width = 2 hists.append( histogram.Histogram1D( bin_edges=x_bin_edges, y=h_inclusive[:, 1], errors_squared=h_inclusive[:, 1], )) # Width = 3 hists.append( histogram.Histogram1D( bin_edges=x_bin_edges, y=h_inclusive[:, 2], errors_squared=h_inclusive[:, 2], )) # Scale by number of triggers. #for h, n_trig in zip(hists, n_trigs): #for i, n_trig in enumerate(n_trigs): # logger.debug(f" pre scale {i}: {np.max(hists[i].y)}") # logger.debug(f"scale by {1 / n_trig}") # hists[i] *= 1.0 / n_trig # #h = h * 1.0 / n_trig # logger.debug(f"post scale {i}: {np.max(hists[i].y)}") # Quickly plot hists fig, ax = plt.subplots(figsize=(8, 6)) for i, h in enumerate(hists): ax.errorbar(h.x, h.y, yerr=h.errors, marker="o", linestyle="", label=f"Data {i}") # Final adjustments ax.legend() fig.tight_layout() fig.savefig(f"gaussian_data.pdf") # Fit to gaussians def scaled_gaussian(x: float, mean: float, sigma: float, amplitude: float) -> float: return amplitude * fit.gaussian(x=x, mean=mean, sigma=sigma) fig, ax = plt.subplots(figsize=(8, 6)) gaussian_fit_results = [] for i, h in enumerate(hists): # First try just -3 to 3 #cost_func = fit.BinnedLogLikelihood(f = scaled_gaussian, data = restrict_hist_range(h, -3, 3)) cost_func = fit.BinnedLogLikelihood(f=unnormalized_gaussian, data=restrict_hist_range(h, -3, 3)) minuit_args: Dict[str, Union[float, Tuple[float, float]]] = { "mean": 0, "fix_mean": True, "sigma": 1.0, "error_sigma": 0.1, "limit_sigma": (0, 10), "amplitude": 100.0, "error_amplitude": 0.1, } fit_result, _ = fit.fit_with_minuit(cost_func=cost_func, minuit_args=minuit_args, x=h.x) gaussian_fit_results.append(fit_result) # Plot for a sanity check plot_label: str if i > 0: plot_label = fr"$\sigma = {widths[i-1]:0.2f}$" else: plot_label = "inclusive" ax.errorbar(h.x, h.y, yerr=h.errors, marker="o", linestyle="", label=f"Data {plot_label}") #ax.plot(h.x, scaled_gaussian(h.x, *list(fit_result.values_at_minimum.values())), label = f"Fit {plot_label}", zorder = 5) ax.plot(h.x, unnormalized_gaussian( h.x, *list(fit_result.values_at_minimum.values())), label=f"Fit {plot_label}", zorder=5) values_at_zero_from_hist = [] for h in hists: values_at_zero_from_hist.append(h.y[h.find_bin(0.0)]) values_at_zero_from_fits = [] for fit_result in gaussian_fit_results: #values_at_zero_from_fits.append(scaled_gaussian(0, *list(fit_result.values_at_minimum.values()))) values_at_zero_from_fits.append( unnormalized_gaussian(0, *list( fit_result.values_at_minimum.values()))) sum_of_last_3_from_hist = np.sum(values_at_zero_from_hist[1:]) sum_of_last_3_from_fit = np.sum(values_at_zero_from_fits[1:]) logger.debug( f"Values at 0 from hist: {values_at_zero_from_hist}, Sum of last 3: {sum_of_last_3_from_hist}, Diff: {_percent_diff(values_at_zero_from_hist[0], sum_of_last_3_from_hist):.3f}%" ) logger.debug( f"Values at 0 from fit: {values_at_zero_from_fits}, Sum of last 3: {sum_of_last_3_from_fit}, Diff: {_percent_diff(values_at_zero_from_fits[0], sum_of_last_3_from_fit):.3f}%" ) # Predict gaussian fit and plot based on previous fit calculate_variances(hists, gaussian_fit_results) import IPython IPython.embed() # Final adjustments ax.legend() ax.set_title(f"{label} widths") fig.tight_layout() fig.savefig(f"gaussian_fit_{label}.pdf")
def process_result( self, selected_analysis_options: params.SelectedAnalysisOptions, glauber_version: str, output_info: analysis_objects.PlottingOutputWrapper) -> None: """ Perform final processing of the event-by-event results. """ logger.info("Performing final processing.") # Setup main_font_size = 18 general_labels = fr"TGlauberMC v{glauber_version}, ${selected_analysis_options.event_activity.display_str()}\:{selected_analysis_options.collision_system.display_str()}$" general_labels += "\n" + fr"$\sigma = {self.cross_section.value} \pm {self.cross_section.width}$ mb" general_labels += "\n" + fr"$b = {self.impact_parameter_range.min} - {self.impact_parameter_range.max}$ fm" # Create histograms of the two axes h_x, x_edges = np.histogram( self.max_x, bins=100, range=(0, 10), ) hist_max_x = histogram.Histogram1D( bin_edges=x_edges, y=h_x, errors_squared=h_x, ) h_y, y_edges = np.histogram( self.max_y, bins=100, range=(0, 10), ) hist_max_y = histogram.Histogram1D( bin_edges=y_edges, y=h_y, errors_squared=h_y, ) # Plot the distributions import matplotlib.pyplot as plt fig, ax = plt.subplots(figsize=(8, 6)) ax.errorbar( hist_max_x.x, hist_max_x.y, yerr=hist_max_x.errors, marker="o", linestyle="", # Equivalent to semi-minor label=fr"{params.ReactionPlaneOrientation.in_plane.display_str()}", ) ax.errorbar( hist_max_y.x, hist_max_y.y, yerr=hist_max_y.errors, marker="o", linestyle="", # Equivalent to semi-major label= fr"{params.ReactionPlaneOrientation.out_of_plane.display_str()}", ) # Label and final adjustments ax.legend( loc="upper left", bbox_to_anchor=(0.01, 0.99), borderaxespad=0, frameon=False, fontsize=main_font_size, ) ax.set_xlabel("Max length (fm)") ax.set_ylabel("Counts / 0.1") ax.tick_params(axis='both', which='major') ax.text(0.97, 0.97, s=general_labels, horizontalalignment="right", verticalalignment="top", multialignment="right", fontsize=main_font_size, transform=ax.transAxes) fig.tight_layout() # Save plot and cleanup plot_base.save_plot(output_info, fig, "glauber_lengths") plt.close(fig) # Plot ratio fig, ax = plt.subplots(figsize=(8, 6)) h, edges = np.histogram(self.ratio, bins=60, range=(-1, 2)) h = histogram.Histogram1D( bin_edges=edges, y=h, errors_squared=h, ) ax.errorbar( h.x, h.y, yerr=h.errors, marker="o", linestyle="", label="out-of-plane/in-plane", ) # Label and final adjustments ax.legend(loc="upper right", bbox_to_anchor=(0.99, 0.99), borderaxespad=0, frameon=False, fontsize=main_font_size) ax.set_xlabel("Length ratio") ax.set_ylabel("Counts / 0.05") ax.tick_params(axis='both', which='major') ratio_label = general_labels ratio_label += "\n" + fr"Mean: ${np.mean(self.ratio):.2f} \pm {_calculate_array_RMS(self.ratio):.2f}$ (RMS)" ax.text(0.03, 0.97, s=ratio_label, horizontalalignment="left", verticalalignment="top", multialignment="left", fontsize=main_font_size, transform=ax.transAxes) fig.tight_layout() # Plot and cleanup plot_base.save_plot(output_info, fig, "glauber_ratio") plt.close(fig)