def test_invalid_PDCA_axis(logging_mixin, test_root_hists, PDCA_axis): """ Test catching a PDCA on the same axis as the projection axis. """ import ROOT # noqa: F401 # Setup projector output_observable = {} observable_to_project_from = {"hist3D": test_root_hists.hist3D} projection_name_format = "hist" obj = projectors.HistProjector( output_observable=output_observable, observable_to_project_from=observable_to_project_from, projection_name_format=projection_name_format, projection_information={}) # Set the projection axes. # It is invalid even if the ranges are different obj.projection_dependent_cut_axes.append([PDCA_axis]) obj.projection_axes.append(hist_axis_ranges.x_axis) # Perform the projection. with pytest.raises(ValueError) as exception_info: obj.project() assert "This configuration is not allowed" in exception_info.value.args[ 0]
def test_projectors(self, logging_mixin, single_observable, test_root_hists): """ Test creation and basic methods of the projection class. """ import ROOT # noqa: F401 # Args projection_name_format = "{test} world" kwdargs, observable, output_observable = determine_projector_input_args( single_observable=single_observable, hist=None, hist_label="histogram", ) kwdargs["projection_name_format"] = projection_name_format kwdargs["projection_information"] = {"test": "Hello"} # Create object obj = projectors.HistProjector(**kwdargs) assert obj.output_attribute_name is ("hist" if single_observable else None) # These objects should be overridden so they aren't super meaningful, but we can still # test to ensure that they provide the basic functionality that is expected. assert obj.projection_name( test="Hello") == projection_name_format.format(test="Hello") assert obj.get_hist( observable=test_root_hists.hist2D) == test_root_hists.hist2D assert obj.output_key_name( input_key="input_key", output_hist=test_root_hists.hist2D, projection_name=projection_name_format.format( test="Hello")) == projection_name_format.format(test="Hello") assert obj.output_hist( output_hist=test_root_hists.hist1D, input_observable=test_root_hists.hist2D) == test_root_hists.hist1D # Checking printing of the projector settings. # Add one additional per selection entry so we have something to print. obj.additional_axis_cuts.append("my_axis") obj.projection_dependent_cut_axes.append( [hist_axis_ranges_without_entries.x_axis]) obj.projection_axes.append("projection_axis") # It is rather minimal, but that is fine since it is only printed information. expected_str = "HistProjector: Projection Information:\n" expected_str += f"\tprojection_name_format: \"{projection_name_format}\"\n" expected_str += "\tprojection_information:\n" expected_str += "\n".join([ "\t\t- " + str("Arg: ") + str(val) for arg, val in { "test": "Hello" }.items() ]) expected_str += "\n\tadditional_axis_cuts:\n" expected_str += "\t\t- my_axis" expected_str += "\n\tprojection_dependent_cut_axes:\n" expected_str += "\t\t- ['an_x_axis_no_entries']" expected_str += "\n\tprojection_axes:\n" expected_str += "\t\t- projection_axis" assert str(obj) == expected_str
def _project_to_part_level(hist: Hist, outliers_removal_axis: OutliersRemovalAxis) -> Hist: """Project the input histogram to the particle level axis. Args: hist: Histogram to check for outliers. outliers_removal_axis: Axis along which outliers removal will be performed. Usually the particle level aixs. Returns: The histogram to check for outliers. """ # Setup the projector import ROOT if isinstance(hist, (ROOT.TH2, ROOT.TH3)): projection_information: Dict[str, Any] = {} output_object = _OutputObject(None) projector = projectors.HistProjector( observable_to_project_from=hist, output_observable=output_object, output_attribute_name="output", projection_name_format="outliers_removal_hist", projection_information=projection_information, ) # No additional_axis_cuts or projection_dependent_cut_axes # Projection axis projector.projection_axes.append( projectors.HistAxisRange( axis_type=outliers_removal_axis, axis_range_name="outliers_removal_axis", min_val=projectors.HistAxisRange.apply_func_to_find_bin( None, 1), max_val=projectors.HistAxisRange.apply_func_to_find_bin( ROOT.TAxis.GetNbins), )) # Perform the actual projection and return the output. projector.project() return output_object.output # If we already have a 1D hist, just return that existing hist. return hist
def test_THn_projection(logging_mixin, test_sparse, single_observable, additional_axis_cuts, expected_additional_axis_cuts_counts, projection_dependent_cut_axes, expected_projection_dependent_cut_axes_counts, projection_axes, expected_projection_axes_counts): """ Test projection of a THnSparse into a TH1. """ import ROOT # noqa: F401 # Setup hist ranges if additional_axis_cuts: additional_axis_cuts = setup_hist_axis_range(additional_axis_cuts) if projection_dependent_cut_axes: projection_dependent_cut_axes = [ setup_hist_axis_range(cut) for cut in projection_dependent_cut_axes ] projection_axes = setup_hist_axis_range(projection_axes) # Setup objects sparse, _ = test_sparse # Setup projector kwdargs, observable, output_observable = determine_projector_input_args( single_observable=single_observable, hist=sparse, hist_label="hist_sparse", ) kwdargs["projection_name_format"] = "hist" kwdargs["projection_information"] = {} obj = projectors.HistProjector(**kwdargs) # Set the projection axes. if additional_axis_cuts is not None: obj.additional_axis_cuts.append(additional_axis_cuts) if projection_dependent_cut_axes is not None: # We need to iterate here separately so that we can separate out the cuts # for the disconnected PDCAs. for axis_set in projection_dependent_cut_axes: obj.projection_dependent_cut_axes.append([axis_set]) obj.projection_axes.append(projection_axes) # Perform the projection. obj.project() # Check the output and get the projection. proj = check_and_get_projection( single_observable=single_observable, observable=observable, output_observable=output_observable, ) assert proj.GetName() == "hist" logger.debug( f"output_observable: {output_observable}, proj.GetEntries(): {proj.GetEntries()}" ) # Find the non-zero bin content so that it can be checked below. non_zero_bins = [] for x in range(1, proj.GetNcells()): if proj.GetBinContent(x) != 0 and not proj.IsBinUnderflow( x) and not proj.IsBinOverflow(x): logger.debug(f"non-zero bin at {x}") non_zero_bins.append(x) # The expected value can be more than one. We find it by multiply the expected values. We can get away with # this because the largest value will be a single 2. expected_count = expected_additional_axis_cuts_counts * expected_projection_dependent_cut_axes_counts * expected_projection_axes_counts # However, we will only find one non-zero bin regardless of the value, so we just check for the one bin if # we have a non-zero value. assert len(non_zero_bins) == (1 if expected_count else 0) # Check the precise bin which was found and the bin value. if expected_count != 0: # Only check if we actually expected a count non_zero_bin_location = next(iter(non_zero_bins)) # I determined the expected value empirically by looking at the projection. assert non_zero_bin_location == 9 assert proj.GetBinContent(non_zero_bin_location) == expected_count
def test_TH3_to_TH2_projection(self, logging_mixin, test_root_hists, single_observable, use_PDCA, additional_cuts, expected_additional_cuts, projection_axes, expected_projection_axes): """ Test projection of a TH3 into a TH2. """ import ROOT # noqa: F401 # Setup hist ranges if additional_cuts: if use_PDCA: additional_cuts = [ setup_hist_axis_range(cut) for cut in additional_cuts ] else: additional_cuts = setup_hist_axis_range(additional_cuts) projection_axes = [ setup_hist_axis_range(cut) for cut in projection_axes ] # Setup projector kwdargs, observable, output_observable = determine_projector_input_args( single_observable=single_observable, hist=test_root_hists.hist3D, hist_label="hist3D", ) kwdargs["projection_name_format"] = "hist" kwdargs["projection_information"] = {} obj = projectors.HistProjector(**kwdargs) # Set the projection axes. # Using additional cut axes or PDCA is mutually exclusive because we only have one # non-projection axis to work with. if use_PDCA: if additional_cuts is not None: # We need to iterate here separately so that we can separate out the cuts # for the disconnected PDCAs. for axis_set in additional_cuts: obj.projection_dependent_cut_axes.append([axis_set]) else: if additional_cuts is not None: obj.additional_axis_cuts.append(additional_cuts) for ax in projection_axes: obj.projection_axes.append(ax) # Perform the projection. obj.project() # Check the output and get the projection. proj = check_and_get_projection( single_observable=single_observable, observable=observable, output_observable=output_observable, ) assert proj.GetName() == "hist" logger.debug( f"output_observable: {output_observable}, proj.GetEntries(): {proj.GetEntries()}" ) # Check the axes (they should be in the same order that they are defined above). # Use the axis max as a proxy (this function name sux). assert proj.GetXaxis().GetXmax() == 60.0 assert proj.GetYaxis().GetXmax() == 0.8 logger.debug( f"x axis min: {proj.GetXaxis().GetXmin()}, y axis min: {proj.GetYaxis().GetXmin()}" ) # Find the non-zero bin content so that it can be checked below. non_zero_bins = find_non_zero_bins(hist=proj) expected_count = 0 # It will only be non-zero if all of the expected values are true. expected_non_zero_counts = all( [expected_additional_cuts, expected_projection_axes]) if expected_non_zero_counts: expected_count = 1 assert len(non_zero_bins) == expected_count # Check the precise bin which was found and the bin value. if expected_count != 0: # Only check if we actually expected a count non_zero_bin_location = next(iter(non_zero_bins)) # I determined the expected value empirically by looking at the projection. assert non_zero_bin_location == 8 assert proj.GetBinContent(non_zero_bin_location) == 1
def test_TH3_to_TH1_projection(self, logging_mixin, test_root_hists, single_observable, additional_axis_cuts, expected_additional_axis_cuts, projection_dependent_cut_axes, expected_projection_dependent_cut_axes, projection_axes, expected_projection_axes): """ Test projection from a TH3 to a TH1 derived class. """ import ROOT # noqa: F401 # Setup hist ranges if additional_axis_cuts: additional_axis_cuts = setup_hist_axis_range(additional_axis_cuts) if projection_dependent_cut_axes: projection_dependent_cut_axes = [ setup_hist_axis_range(cut) for cut in projection_dependent_cut_axes ] projection_axes = setup_hist_axis_range(projection_axes) # Setup projector kwdargs, observable, output_observable = determine_projector_input_args( single_observable=single_observable, hist=test_root_hists.hist3D, hist_label="hist3D", ) kwdargs["projection_name_format"] = "hist" kwdargs["projection_information"] = {} obj = projectors.HistProjector(**kwdargs) # Set the projection axes. if additional_axis_cuts is not None: obj.additional_axis_cuts.append(additional_axis_cuts) if projection_dependent_cut_axes is not None: # We need to iterate here separately so that we can separate out the cuts # for the disconnected PDCAs. for axis_set in projection_dependent_cut_axes: obj.projection_dependent_cut_axes.append([axis_set]) obj.projection_axes.append(projection_axes) # Perform the projection. obj.project() # Check the output and get the projection. proj = check_and_get_projection( single_observable=single_observable, observable=observable, output_observable=output_observable, ) assert proj.GetName() == "hist" logger.debug( f"output_observable: {output_observable}, proj.GetEntries(): {proj.GetEntries()}" ) expected_bins = 5 # If we don't expect a count, we've restricted the range further, so we need to reflect this in our check. if expected_projection_axes is False: expected_bins = 4 assert proj.GetXaxis().GetNbins() == expected_bins # Find the non-zero bin content so that it can be checked below. non_zero_bins = find_non_zero_bins(hist=proj) expected_count = 0 # It will only be non-zero if all of the expected values are true. expected_non_zero_counts = all([ expected_additional_axis_cuts, expected_projection_dependent_cut_axes, expected_projection_axes ]) if expected_non_zero_counts: expected_count = 1 assert len(non_zero_bins) == expected_count # Check the precise bin which was found and the bin value. if expected_count != 0: # Only check if we actually expected a count non_zero_bin_location = next(iter(non_zero_bins)) # I determined the expected value empirically by looking at the projection. assert non_zero_bin_location == 1 assert proj.GetBinContent(non_zero_bin_location) == 1