From 860bd49b2d8280f54c23461c8714b23b944af32c Mon Sep 17 00:00:00 2001 From: Anton Riedel Date: Sun, 16 Nov 2025 10:00:26 +0100 Subject: [PATCH 1/4] Feat: update cutculator --- PWGCF/Femto/Macros/cutculator.py | 69 +++++++++++++++++++++----------- 1 file changed, 46 insertions(+), 23 deletions(-) diff --git a/PWGCF/Femto/Macros/cutculator.py b/PWGCF/Femto/Macros/cutculator.py index 4b522e4e220..8f5c97ff2db 100755 --- a/PWGCF/Femto/Macros/cutculator.py +++ b/PWGCF/Femto/Macros/cutculator.py @@ -1,7 +1,5 @@ #!/usr/bin/env python3 -#!/usr/bin/env python3 - # Copyright 2019-2025 CERN and copyright holders of ALICE O2. # See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. # All rights not expressly granted are reserved. @@ -38,34 +36,47 @@ def parse_bin_label(label): def ask_user_selection(group): - """Prompt user to select bin(s) for this selection group.""" + """ + Prompt user to select bin(s) for this selection group. + - If minimal selections contain exactly 1 entry → auto-select it. + - Optional selections remain user-selectable. + """ + selection_name = group[0].get("SelectionName", "unknown") + # Separate minimal and optional bins minimal_bins = [b for b in group if b.get("MinimalCut", "0") == "1" and b.get("OptionalCut", "0") == "0"] optional_bins = [b for b in group if b.get("OptionalCut", "0") == "1"] selected_bins = [] - # Minimal selection (cannot skip, 0 = loosest minimal) + # Minimal selection if minimal_bins: - print(f"\nSelection: {group[0].get('SelectionName', 'unknown')}") - for idx, b in enumerate(minimal_bins): - print(f" [{idx}] {b.get('Value', '')}") - while True: - sel_input = input("Enter index for minimal cut (0 = loosest minimal): ") - if sel_input.strip() == "": - sel_input = "0" - try: - sel_idx = int(sel_input) - if 0 <= sel_idx < len(minimal_bins): - selected_bins.append(minimal_bins[sel_idx]) - break - except ValueError: - pass - print("Invalid input. Please enter a valid index.") + if len(minimal_bins) == 1: + # autoselect minimal OPTION + only = minimal_bins[0] + print(f"\nSelection: {selection_name} — only one minimal option → auto-selecting: {only.get('Value','')}") + selected_bins.append(only) + else: + # Multiple → ask user + print(f"\nSelection: {selection_name}") + for idx, b in enumerate(minimal_bins): + print(f" [{idx}] {b.get('Value', '')}") + while True: + sel_input = input("Enter index for minimal cut (0 = loosest minimal): ") + if sel_input.strip() == "": + sel_input = "0" + try: + sel_idx = int(sel_input) + if 0 <= sel_idx < len(minimal_bins): + selected_bins.append(minimal_bins[sel_idx]) + break + except ValueError: + pass + print("Invalid input. Please enter a valid index.") # Optional selection (can skip with 0) if optional_bins: - print(f"Selection: {group[0].get('SelectionName', 'unknown')} (optional selection, 0 to skip)") + print(f"Selection: {selection_name} (optional selection, 0 to skip)") for idx, b in enumerate(optional_bins, start=1): print(f" [{idx}] {b.get('Value', '')}") while True: @@ -145,10 +156,22 @@ def main(rootfile_path, tdir_path="femto-producer"): continue bitmask |= 1 << int(pos) + print("\n=======================================") + print("Summary of your selections:") + print("=======================================\n") + + summary = {} + for b in selected_bins: + sel = b.get("SelectionName", "unknown") + summary.setdefault(sel, []).append(b.get("Value", "")) + + for sel, values in summary.items(): + print(f" {sel}: {', '.join(values)}") + print("\nFinal selected bitmask:") - print(f"Decimal: {bitmask}") - print(f"Binary: {bin(bitmask)}") - print(f"Hex: {hex(bitmask)}") + print(f" Decimal: {bitmask}") + print(f" Binary: {bin(bitmask)}") + print(f" Hex: {hex(bitmask)}") if __name__ == "__main__": From c30beef8e561400d912b78e5c0a08498f59b54fa Mon Sep 17 00:00:00 2001 From: Anton Riedel Date: Sun, 16 Nov 2025 10:10:36 +0100 Subject: [PATCH 2/4] Feat: update CPR --- PWGCF/Femto/Core/closePairRejection.h | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/PWGCF/Femto/Core/closePairRejection.h b/PWGCF/Femto/Core/closePairRejection.h index c7044ceef88..c4558e5b567 100644 --- a/PWGCF/Femto/Core/closePairRejection.h +++ b/PWGCF/Femto/Core/closePairRejection.h @@ -63,6 +63,8 @@ struct ConfCpr : o2::framework::ConfigurableGroup { o2::framework::Configurable plotAverage{"plotAverage", true, "Plot average deta dphi distribution"}; o2::framework::Configurable detaMax{"detaMax", 0.01f, "Maximium deta"}; o2::framework::Configurable dphistarMax{"dphistarMax", 0.01f, "Maximum dphistar"}; + o2::framework::Configurable detaCenter{"detaCenter", 0.f, "Center of deta cut"}; + o2::framework::Configurable dphistarCenter{"dphistarCenter", 0.f, "Center of dphistar cut"}; o2::framework::Configurable kstarMin{"kstarMin", -1.f, "Minimum kstar of pair for plotting (Set to negative value to turn off the cut)"}; o2::framework::Configurable kstarMax{"kstarMax", -1.f, "Maximum kstar of pair for plotting (Set to negative value to turn off the cut)"}; o2::framework::ConfigurableAxis binningDeta{"binningDeta", {{250, -0.5, 0.5}}, "deta"}; @@ -157,6 +159,9 @@ class CloseTrackRejection LOG(fatal) << "Limits for Close Pair Rejection are invalid (0 or negative). Breaking..."; } + mDetaCenter = confCpr.detaCenter.value; + mDphistarCenter = confCpr.dphistarCenter.value; + mChargeAbsTrack1 = std::abs(chargeAbsTrack1); mChargeAbsTrack2 = std::abs(chargeAbsTrack2); @@ -280,7 +285,7 @@ class CloseTrackRejection bool isCloseAnyRadius = false; if (mCutAverage) { - isCloseAverage = std::hypot(mAverageDphistar / mDphistarMax, mDeta / mDetaMax) < 1.f; + isCloseAverage = std::hypot((mAverageDphistar - mDphistarCenter) / mDphistarMax, (mDeta - mDetaCenter) / mDetaMax) < 1.f; } if (mCutAnyRadius) { @@ -289,7 +294,7 @@ class CloseTrackRejection break; } if (mDphistarMask.at(i)) { - isCloseAnyRadius = std::hypot(mDphistar.at(i) / mDphistarMax, mDeta / mDetaMax) < 1.f; + isCloseAnyRadius = std::hypot((mDphistar.at(i) - mDphistarCenter) / mDphistarMax, (mDeta - mDetaCenter) / mDetaMax) < 1.f; } } } @@ -316,6 +321,8 @@ class CloseTrackRejection float mMagField = 0.f; float mDetaMax = 0.f; float mDphistarMax = 0.f; + float mDetaCenter = 0.f; + float mDphistarCenter = 0.f; float mAverageDphistar = 0.f; float mDeta = 0.f; From bed77cddd90917304645e943f07d1d41f1374b81 Mon Sep 17 00:00:00 2001 From: Anton Riedel Date: Mon, 17 Nov 2025 09:11:21 +0100 Subject: [PATCH 3/4] Feat: update cutculator --- PWGCF/Femto/Core/baseSelection.h | 1 + PWGCF/Femto/Core/selectionContainer.h | 4 ++- PWGCF/Femto/Macros/cutculator.py | 43 ++++++++++++++++++++------- 3 files changed, 36 insertions(+), 12 deletions(-) diff --git a/PWGCF/Femto/Core/baseSelection.h b/PWGCF/Femto/Core/baseSelection.h index 6e347e850d2..1210671886a 100644 --- a/PWGCF/Femto/Core/baseSelection.h +++ b/PWGCF/Femto/Core/baseSelection.h @@ -386,6 +386,7 @@ class BaseSelection } LOG(info) << ""; } + LOG(info) << "Number of occupied bits: " << mNSelectionBits << " / " << sizeof(BitmaskType) * CHAR_BIT; LOG(info) << "Printing done"; } diff --git a/PWGCF/Femto/Core/selectionContainer.h b/PWGCF/Femto/Core/selectionContainer.h index ca08b657d96..e9d67e6354a 100644 --- a/PWGCF/Femto/Core/selectionContainer.h +++ b/PWGCF/Femto/Core/selectionContainer.h @@ -360,6 +360,7 @@ class SelectionContainer std::ostringstream oss; std::string sectionDelimiter = ":::"; std::string valueDelimiter = "___"; + std::string noValue = "X"; oss << "SelectionName" << valueDelimiter << mSelectionName << sectionDelimiter << "LimitType" << valueDelimiter << getLimitTypeAsString() << sectionDelimiter << "MinimalCut" << valueDelimiter << (mIsMinimalCut ? "1" : "0") << sectionDelimiter @@ -368,7 +369,8 @@ class SelectionContainer << "Shift" << valueDelimiter << getShift() << sectionDelimiter << "Offset" << valueDelimiter << mOffset << sectionDelimiter << "Value" << valueDelimiter << (mSelectionFunctions.empty() ? std::to_string(mSelectionValues.at(selectionIndex)) : mSelectionFunctions.at(selectionIndex).GetExpFormula().Data()) << sectionDelimiter - << "BitPosition" << valueDelimiter << (mSkipMostPermissiveBit ? (selectionIndex == 0 ? "X" : std::to_string(mOffset + selectionIndex - 1)) : std::to_string(mOffset + selectionIndex)); + << "BitPosition" << valueDelimiter << (mSkipMostPermissiveBit ? (selectionIndex == 0 ? noValue : std::to_string(mOffset + selectionIndex - 1)) : std::to_string(mOffset + selectionIndex)) << sectionDelimiter + << "Comment" << valueDelimiter << (mComments.empty() ? noValue : mComments.at(selectionIndex)); return oss.str(); } diff --git a/PWGCF/Femto/Macros/cutculator.py b/PWGCF/Femto/Macros/cutculator.py index 8f5c97ff2db..c1c6a4bf9e3 100755 --- a/PWGCF/Femto/Macros/cutculator.py +++ b/PWGCF/Femto/Macros/cutculator.py @@ -35,6 +35,15 @@ def parse_bin_label(label): return result +def format_value_with_comment(b): + """Return Value plus optional (comment=...) suffix.""" + val = b.get("Value", "") + comment = b.get("Comment", "") + if comment and comment.upper() != "X": + return f"{val} (comment={comment})" + return val + + def ask_user_selection(group): """ Prompt user to select bin(s) for this selection group. @@ -49,18 +58,19 @@ def ask_user_selection(group): selected_bins = [] - # Minimal selection + # ----- Minimal selection ----- if minimal_bins: if len(minimal_bins) == 1: - # autoselect minimal OPTION only = minimal_bins[0] - print(f"\nSelection: {selection_name} — only one minimal option → auto-selecting: {only.get('Value','')}") + print( + f"\nSelection: {selection_name} — only one minimal option → auto-selecting: " + f"{format_value_with_comment(only)}" + ) selected_bins.append(only) else: - # Multiple → ask user print(f"\nSelection: {selection_name}") for idx, b in enumerate(minimal_bins): - print(f" [{idx}] {b.get('Value', '')}") + print(f" [{idx}] {format_value_with_comment(b)}") while True: sel_input = input("Enter index for minimal cut (0 = loosest minimal): ") if sel_input.strip() == "": @@ -68,30 +78,41 @@ def ask_user_selection(group): try: sel_idx = int(sel_input) if 0 <= sel_idx < len(minimal_bins): - selected_bins.append(minimal_bins[sel_idx]) + choice = minimal_bins[sel_idx] + selected_bins.append(choice) + print(f"Selected: {format_value_with_comment(choice)}") break except ValueError: pass print("Invalid input. Please enter a valid index.") - # Optional selection (can skip with 0) + # ----- Optional selection ----- if optional_bins: - print(f"Selection: {selection_name} (optional selection, 0 to skip)") + print(f"\nSelection: {selection_name} (optional selection, 0 to skip)") for idx, b in enumerate(optional_bins, start=1): - print(f" [{idx}] {b.get('Value', '')}") + print(f" [{idx}] {format_value_with_comment(b)}") + while True: sel_input = input("Enter indices separated by space (0 to skip): ") if not sel_input.strip() or sel_input.strip() == "0": + print("Selected: (skipped)") break + try: indices = [int(x) for x in sel_input.split()] if all(0 <= i <= len(optional_bins) for i in indices): + chosen = [] for i in indices: if i != 0: - selected_bins.append(optional_bins[i - 1]) + b = optional_bins[i - 1] + selected_bins.append(b) + chosen.append(format_value_with_comment(b)) + + print("Selected: " + ", ".join(chosen)) break except ValueError: pass + print("Invalid input. Please enter valid indices separated by space.") return selected_bins @@ -163,7 +184,7 @@ def main(rootfile_path, tdir_path="femto-producer"): summary = {} for b in selected_bins: sel = b.get("SelectionName", "unknown") - summary.setdefault(sel, []).append(b.get("Value", "")) + summary.setdefault(sel, []).append(format_value_with_comment(b)) for sel, values in summary.items(): print(f" {sel}: {', '.join(values)}") From c7e5e37ec2c7c0942f0edd854152e64e636ccad1 Mon Sep 17 00:00:00 2001 From: Anton Riedel Date: Mon, 17 Nov 2025 10:09:04 +0100 Subject: [PATCH 4/4] Fix: fix linter warning --- PWGCF/Femto/Core/baseSelection.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/PWGCF/Femto/Core/baseSelection.h b/PWGCF/Femto/Core/baseSelection.h index 1210671886a..ceb2184a96a 100644 --- a/PWGCF/Femto/Core/baseSelection.h +++ b/PWGCF/Femto/Core/baseSelection.h @@ -184,8 +184,8 @@ class BaseSelection void reset() { mFinalBitmask.reset(); - for (auto& container : mSelectionContainers) { // o2-linter: disable=const-ref-in-for-loop (object modified in loop) - container.reset(); + for (std::size_t i = 0; i < mSelectionContainers.size(); i++) { + mSelectionContainers.at(i).reset(); } if (mHasMinimalSelection) { mPassesMinimalSelections = true;