From 9d859950d2ae3183f0f4af36696406d8ff5ca520 Mon Sep 17 00:00:00 2001 From: Andreas Suter Date: Tue, 26 May 2026 19:06:07 +0200 Subject: [PATCH] mupp: optional Python3 interface for variables via ROOT TPython. Variables can now be computed by an embedded Python3 interpreter in addition to the Boost.Spirit X3 expression engine. A ... block receives all collection parameters as per-run lists (bare names, plus par[]/parErr[] dictionaries as a fallback for names that are not valid Python identifiers such as 'lambda') and must assign and Err; errors are user supplied. Works both in the GUI variable dialog and in scripts. The feature is optional and only enabled when ROOT is built with TPython (CMake target ROOT::ROOTTPython); otherwise it compiles out. See MUPP_PY.README for ROOT configure requirements and usage. Co-Authored-By: Claude Opus 4.7 --- src/musredit_qt6/mupp/CMakeLists.txt | 11 + src/musredit_qt6/mupp/MUPP_PY.README | 200 +++++++++++ src/musredit_qt6/mupp/PVarDialog.cpp | 33 +- src/musredit_qt6/mupp/PmuppGui.cpp | 6 + src/musredit_qt6/mupp/PmuppScript.cpp | 61 +++- src/musredit_qt6/mupp/PmuppScript.h | 6 + src/musredit_qt6/mupp/mupp.cpp | 37 +++ .../mupp/var/include/PVarHandler.h | 14 + src/musredit_qt6/mupp/var/src/PVarHandler.cpp | 313 ++++++++++++++++++ 9 files changed, 671 insertions(+), 10 deletions(-) create mode 100644 src/musredit_qt6/mupp/MUPP_PY.README diff --git a/src/musredit_qt6/mupp/CMakeLists.txt b/src/musredit_qt6/mupp/CMakeLists.txt index f1439e59c..883f14ef8 100644 --- a/src/musredit_qt6/mupp/CMakeLists.txt +++ b/src/musredit_qt6/mupp/CMakeLists.txt @@ -87,6 +87,17 @@ target_include_directories(mupp $ ) +#--- optional Python support for variable evaluation via ROOT's TPython ------- +#--- only available if ROOT was built with -Dtpython=ON ----------------------- +if (TARGET ROOT::ROOTTPython) + target_compile_definitions(mupp PRIVATE MUPP_PYTHON) + target_link_libraries(mupp PRIVATE ROOT::ROOTTPython) + target_include_directories(mupp PRIVATE $) + message(STATUS "mupp: optional Python variable support enabled (ROOT TPython found)") +else (TARGET ROOT::ROOTTPython) + message(STATUS "mupp: optional Python variable support disabled (ROOT built without tpython)") +endif (TARGET ROOT::ROOTTPython) + #--- use the Widgets and XML modules from Qt5 --------------------------------- target_link_libraries(mupp PRIVATE Qt6::Widgets Qt6::Xml) diff --git a/src/musredit_qt6/mupp/MUPP_PY.README b/src/musredit_qt6/mupp/MUPP_PY.README new file mode 100644 index 000000000..56514d0d5 --- /dev/null +++ b/src/musredit_qt6/mupp/MUPP_PY.README @@ -0,0 +1,200 @@ +================================================================================ + mupp - optional Python3 interface for variables +================================================================================ + +mupp lets you define "variables": derived quantities computed from the fit +parameters of a collection (one value per run). Besides the built-in expression +engine (Boost.Spirit X3, addressed with '$param'), a variable can optionally be +computed by an embedded Python3 interpreter. This is useful when the calculation +is more naturally done with numpy/scipy or any other Python code. + +The Python evaluation runs in-process through ROOT's TPython, so it is only +available if ROOT was built with TPython support (see "ROOT requirements"). + +This document applies to the Qt6 build of mupp (src/musredit_qt6/mupp). + + +-------------------------------------------------------------------------------- + 1. ROOT requirements (configure / build of ROOT) +-------------------------------------------------------------------------------- + +The Python interface uses ROOT's TPython class (C++ -> Python). Your ROOT +installation must therefore be built with both PyROOT and TPython enabled. + + a) Install the Python3 development headers (CPython dev package). They are + required for ROOT to detect Python at configure time. On Fedora: + + sudo dnf install python3-devel + + If they are missing, ROOT's configure step fails with: + Could NOT find Python3 (missing: Python3_INCLUDE_DIRS Development.Module) + + b) Configure ROOT with PyROOT *and* TPython enabled, pointing at the desired + interpreter: + + cmake -S -B \ + -Dpyroot=ON \ + -Dtpython=ON \ + -DPython3_EXECUTABLE=$(which python3) \ + + + NOTE: a minimal ROOT build (-Dgminimal=ON) turns optional components OFF by + default, including tpython. In that case you MUST add -Dtpython=ON + explicitly, otherwise only PyROOT (Python -> ROOT) is built and the C++ -> + Python direction that mupp needs is missing. + + If you reconfigure an existing ROOT build directory and the flags do not + seem to take effect, delete /CMakeCache.txt and configure again. + + c) Build / install ROOT, then verify that TPython is present: + + ls $(root-config --incdir)/TPython.h # header present + ls $(root-config --libdir)/libROOTTPython.* # library present + + (Note: "root-config --features" does not necessarily list "python"; the two + checks above are the reliable test.) + + d) The Python interpreter that ROOT was built against must contain whatever + modules your variable code uses, e.g. numpy: + + python3 -c "import numpy; print(numpy.__version__)" + + +-------------------------------------------------------------------------------- + 2. Building mupp +-------------------------------------------------------------------------------- + +mupp's CMake detects TPython automatically via the imported target +ROOT::ROOTTPython: + + * If available, the macro MUPP_PYTHON is defined, mupp links ROOT::ROOTTPython, + and you will see during configure: + + -- mupp: optional Python variable support enabled (ROOT TPython found) + + * If ROOT was built without tpython, the feature is compiled out and you see: + + -- mupp: optional Python variable support disabled (ROOT built without tpython) + + In that case ... definitions are rejected at runtime with + a clear error message; the X3 expression engine is unaffected. + +After rebuilding ROOT with TPython, reconfigure the mupp/musrfit build so the +new ROOT::ROOTTPython target is picked up (delete CMakeCache.txt if necessary). + + +-------------------------------------------------------------------------------- + 3. Runtime requirements +-------------------------------------------------------------------------------- + + * No environment setup is required for the Python modules: mupp prepends ROOT's + library directory (TROOT::GetLibDir()) to PYTHONPATH before the interpreter is + initialized, so PyROOT/cppyy import correctly even if thisroot.sh has not been + sourced. + + * Errors from the Python evaluation (syntax errors, undefined names, wrong + result length, ...) are written to: + + ~/.musrfit/mupp/mupp_err.log + + +-------------------------------------------------------------------------------- + 4. The data contract (what Python receives and must return) +-------------------------------------------------------------------------------- + +A variable maps the parameters of a collection to one value (and one error) per +run. + +INPUT - injected into the Python namespace before your code runs: + + * For every collection parameter

: +

: a list with one value per run +

Err : a list with the corresponding errors (one per run) + The error is the geometric mean of the asymmetric fit errors, + sqrt(|posErr * negErr|), i.e. the same convention as the X3 engine. + + * Parameter names that are NOT valid Python identifiers - most importantly the + Python keyword 'lambda' - are NOT available as bare names. Reach them through + the dictionaries that always contain every parameter: + par['lambda'] # value list + parErr['lambda'] # error list + +OUTPUT - your code must assign, for a variable named : + + : the value list (length = number of runs) + Err : the error list (length = number of runs) + + * Errors are USER-SUPPLIED: there is no automatic error propagation in Python + mode (unlike the X3 engine). You compute the error yourself. + * The value list length must equal the number of runs in the collection. + * Lists or numpy arrays are both accepted. + + +-------------------------------------------------------------------------------- + 5. Usage in the GUI +-------------------------------------------------------------------------------- + +Open "Add Variable", declare each variable (and its error) with "= python", and +provide the calculation inside a ... block: + + var sigSC = python + var sigSCErr = python + + import numpy as np + s = np.array(Sigma) + se = np.array(SigmaErr) + sigSC = np.sqrt(np.abs(s**2 - 0.11**2)) + sigSCErr = np.where(sigSC > 0.0, s*se/np.where(sigSC > 0.0, sigSC, 1.0), 0.0) + + +Then select the variable as x- or y-axis and plot as usual. + + +-------------------------------------------------------------------------------- + 6. Usage in a script (mupp -s