diff --git a/src/musredit_qt6/mupp/MUPP_PY.README b/src/musredit_qt6/mupp/MUPP_PY.README
index 56514d0d5..db91509f1 100644
--- a/src/musredit_qt6/mupp/MUPP_PY.README
+++ b/src/musredit_qt6/mupp/MUPP_PY.README
@@ -107,7 +107,7 @@ run.
INPUT - injected into the Python namespace before your code runs:
- * For every collection parameter
:
+ * For every parameter
of the LINKED collection (the one given by 'col'):
: 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,
@@ -115,10 +115,24 @@ INPUT - injected into the Python namespace before your code runs:
* 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:
+ the dictionaries that always contain every parameter of the linked collection:
par['lambda'] # value list
parErr['lambda'] # error list
+ * EVERY loaded collection (not just the linked one) is additionally available
+ through two dictionaries, so a single block can compute - and
+ combine - variables for several collections without leaving the block:
+ coll[][''] # value list of parameter
+ collErr[][''] # error list of parameter
+ The collection can be addressed either by its integer INDEX (the same index
+ used by the 'col ' command and by 'select ') or by its NAME (the
+ collection name as loaded, e.g. 'YBCO-...-Tscan.db'); both keys reference the
+ same per-parameter dictionary:
+ coll[0]['Sigma'] # by index
+ coll['YBCO-...-B150mT-Tscan.db']['Sigma'] # by name (equivalent)
+ The bare names / par[] / parErr[] above are just a convenience alias for the
+ linked collection; coll[]/collErr[] expose all of them explicitly.
+
OUTPUT - your code must assign, for a variable named :
: the value list (length = number of runs)
@@ -177,14 +191,44 @@ and add the ... block anywhere in the script:
y sigSC
plot sigSC-vs-temp.pdf
+Several collections in ONE block: use coll[]/collErr[] to address each collection
+explicitly (by index or by name), so a single block can serve them all:
+
+ loadPath ./
+ load YBCO-40nm-FC-E3p8keV-B10mT-Tscan.db # collection 0
+ load YBCO-40nm-FC-E3p8keV-B150mT-Tscan.db # collection 1
+
+ var SigmaSC_10 = python
+ var SigmaSC_10Err = python
+ var SigmaSC_150 = python
+ var SigmaSC_150Err = python
+
+
+ import numpy as np
+ def sigSC(idx, B0):
+ s = np.array(coll[idx]['Sigma'])
+ se = np.array(collErr[idx]['Sigma'])
+ sc = np.sqrt(np.abs(s**2 - B0**2))
+ return sc, np.sqrt((s*se)**2 + (B0*0.0025)**2)/sc
+ SigmaSC_10, SigmaSC_10Err = sigSC(0, 0.11)
+ SigmaSC_150, SigmaSC_150Err = sigSC(1, 0.075)
+
+
+ col 0 : SigmaSC_10
+ col 1 : SigmaSC_150
+
Notes for scripts:
* The ... block is read VERBATIM: indentation and the
characters '#', '%', '//' inside the block are preserved (the normal mupp
comment handling is bypassed for the block).
* The closing tag must be on its own line.
- * Only ONE block per script is supported; it may define several
- variables.
+ * Several blocks are allowed: each "var = python" declaration is
+ paired with the next block in script order. Alternatively, a single block
+ placed AFTER all the declarations may define every variable; address the
+ individual collections via coll[]/collErr[] as shown above.
+ * Each variable's output length must match the number of runs of the collection
+ it is linked to with 'col' (this is what coll[] provides).
* As with X3 variables, only the value variable is linked with 'col'; its error
variable (Err) is associated implicitly.
* Tip: loadPath/savePath strip a leading '/'; use $HOME/... or a relative path.
@@ -196,5 +240,6 @@ Notes for scripts:
* Requires ROOT built with -Dtpython=ON (see section 1).
* No automatic error propagation in Python mode - errors are user-supplied.
- * One block per variable definition / per script.
+ * coll[]/collErr[] expose collections by index and by name; the name is the
+ collection name as loaded (it includes the .db/.dat extension).
================================================================================
diff --git a/src/musredit_qt6/mupp/PmuppGui.cpp b/src/musredit_qt6/mupp/PmuppGui.cpp
index c911d6898..3a34c1f57 100644
--- a/src/musredit_qt6/mupp/PmuppGui.cpp
+++ b/src/musredit_qt6/mupp/PmuppGui.cpp
@@ -1432,9 +1432,17 @@ void PmuppGui::addVar()
*/
void PmuppGui::check(QString varStr, QVector idx)
{
+ // expose every loaded collection inside a block (coll[]/collErr[]),
+ // addressable by index and by name, so a check exercises the same namespace
+ // the actual evaluation will see.
+ std::vector allColl;
+ for (int i=0; iGetNoOfCollections(); i++)
+ allColl.push_back(fParamDataHandler->GetCollection(i));
+
int count = 0;
for (int i=0; iGetCollection(i), varStr.toLatin1().data());
+ PVarHandler var_check(fParamDataHandler->GetCollection(i), varStr.toLatin1().data(),
+ std::string(), allColl);
if (var_check.isValid()) {
count++;
} else {
@@ -1461,13 +1469,20 @@ void PmuppGui::add(QString varStr, QVector idx)
// the PVarHandler class handles only ONE variable of ONE collection.
QStringList varNames = getVarNames(varStr);
+ // expose every loaded collection inside a block (coll[]/collErr[]),
+ // addressable by index and by name.
+ std::vector allColl;
+ for (int i=0; iGetNoOfCollections(); i++)
+ allColl.push_back(fParamDataHandler->GetCollection(i));
+
// go through all collections
for (int i=0; iGetCollection(idx[i]),
varStr.toLatin1().data(),
- varNames[j].toLatin1().data());
+ varNames[j].toLatin1().data(),
+ allColl);
if (!var.isValid()) {
parseErrMsgDlg();
return;
diff --git a/src/musredit_qt6/mupp/PmuppScript.cpp b/src/musredit_qt6/mupp/PmuppScript.cpp
index e74883760..493c8a150 100644
--- a/src/musredit_qt6/mupp/PmuppScript.cpp
+++ b/src/musredit_qt6/mupp/PmuppScript.cpp
@@ -1051,8 +1051,13 @@ int PmuppScript::var_cmd(const QString str, int script_idx)
<< "' declared as python, but no ... block was found." << std::endl;
return 1;
}
+ // expose every loaded collection inside the block (coll[]/collErr[]),
+ // addressable by index and by name, so one block can serve several collections.
+ std::vector allColl;
+ for (int i=0; iGetNoOfCollections(); i++)
+ allColl.push_back(fParamDataHandler->GetCollection(i));
PVarHandler varHandler(fParamDataHandler->GetCollection(idx),
- pyBlock.toLatin1().data(), tok[1].toLatin1().data());
+ pyBlock.toLatin1().data(), tok[1].toLatin1().data(), allColl);
if (!varHandler.isValid()) {
// dump error messages if present
QString mupp_err = QString("%1/.musrfit/mupp/mupp_err.log").arg(QString(qgetenv("HOME")));
diff --git a/src/musredit_qt6/mupp/var/include/PVarHandler.h b/src/musredit_qt6/mupp/var/include/PVarHandler.h
index c215cd609..acb7f1da8 100644
--- a/src/musredit_qt6/mupp/var/include/PVarHandler.h
+++ b/src/musredit_qt6/mupp/var/include/PVarHandler.h
@@ -85,8 +85,14 @@ class PVarHandler {
* @param coll pointer to the PmuppCollection containing run data and parameters
* @param parse_str the variable definition string to parse (e.g., "var x = $T1 + 1.0")
* @param var_name optional variable name to extract from the evaluation results; if empty, only parsing/checking is performed
+ * @param allColl optional list of all loaded collections (in handler-index
+ * order). Only used by the Python path, where it is exposed inside
+ * the <python> block as coll[]/collErr[] (see evaluatePython()).
+ * If empty, only the bound collection 'coll' is injected (bare names
+ * and par[]/parErr[]).
*/
- PVarHandler(PmuppCollection *coll, std::string parse_str, std::string var_name="");
+ PVarHandler(PmuppCollection *coll, std::string parse_str, std::string var_name="",
+ const std::vector &allColl = std::vector());
/**
* @brief Checks if the parsing and evaluation were successful.
@@ -120,6 +126,7 @@ class PVarHandler {
private:
PmuppCollection *fColl; ///< pointer to collection containing run data needed for parsing and evaluation
+ std::vector fAllColl; ///< all loaded collections (handler-index order); Python path only, exposed as coll[]/collErr[]
std::string fParseStr; ///< the variable input string to be parsed
std::string fVarName; ///< name of the variable to extract from evaluation results
mupp::prog::PVarHandler fVar; ///< variable handler storing the computed values and errors
@@ -141,11 +148,17 @@ class PVarHandler {
* @brief Evaluates the variable using an embedded Python3 interpreter (TPython).
*
* Used when the input string contains a ... block.
- * All collection parameters are injected into the interpreter as bare-name lists
- * (one value per run) plus par[]/parErr[] dictionaries as a fallback for names
- * that are not valid Python identifiers (e.g. the keyword 'lambda'). The user
- * script must assign the requested variable and its error counterpart (Err).
- * Both value and error arrays are read back and stored in fVar.
+ * The bound collection's parameters are injected into the interpreter as
+ * bare-name lists (one value per run) plus par[]/parErr[] dictionaries as a
+ * fallback for names that are not valid Python identifiers (e.g. the keyword
+ * 'lambda'). In addition, every loaded collection passed via fAllColl is
+ * exposed through the coll[]/collErr[] dictionaries, addressable both by
+ * integer index (matching the script 'col ') and by collection name;
+ * index and name keys reference the same per-parameter dict. This lets a
+ * single block compute (and combine) variables for several
+ * collections. The user script must assign the requested variable and its
+ * error counterpart (Err). Both value and error arrays are read back
+ * and stored in fVar.
*
* If mupp was built without TPython support, the handler is marked invalid.
*/
diff --git a/src/musredit_qt6/mupp/var/src/PVarHandler.cpp b/src/musredit_qt6/mupp/var/src/PVarHandler.cpp
index 3d5a3097b..fc3a97555 100644
--- a/src/musredit_qt6/mupp/var/src/PVarHandler.cpp
+++ b/src/musredit_qt6/mupp/var/src/PVarHandler.cpp
@@ -109,6 +109,59 @@ static std::string mupp_pyList(const std::vector &v)
return os.str();
}
+//--------------------------------------------------------------------------
+/**
+ * @brief Builds a single-quoted Python string literal, escaping \ and '.
+ *
+ * Used for dictionary keys (parameter and collection names) so that names with
+ * special characters cannot break the generated script.
+ */
+static std::string mupp_pyStr(const std::string &s)
+{
+ std::ostringstream os;
+ os << "'";
+ for (size_t i=0; i mupp_collValues(PmuppCollection *coll, int pidx)
+{
+ std::vector v;
+ for (int i=0; iGetNoOfRuns(); i++)
+ v.push_back(coll->GetRun(i).GetParam(pidx).GetValue());
+ return v;
+}
+
+//--------------------------------------------------------------------------
+/**
+ * @brief Collects a parameter's error across all runs of an arbitrary collection.
+ *
+ * Uses the same convention as PVarHandler::getDataErr(): the geometric mean of
+ * the positive and negative fit errors, sqrt(abs(posErr * negErr)).
+ */
+static std::vector mupp_collErrors(PmuppCollection *coll, int pidx)
+{
+ std::vector v;
+ for (int i=0; iGetNoOfRuns(); i++) {
+ double p = coll->GetRun(i).GetParam(pidx).GetPosErr();
+ double n = coll->GetRun(i).GetParam(pidx).GetNegErr();
+ v.push_back(std::sqrt(std::fabs(p*n)));
+ }
+ return v;
+}
+
//--------------------------------------------------------------------------
/**
* @brief Checks whether name is a valid Python identifier and not a keyword.
@@ -254,9 +307,11 @@ PVarHandler::PVarHandler() :
* @param coll pointer to the collection containing run data
* @param parse_str the variable definition string to parse
* @param var_name optional variable name to extract; if empty, only validation is performed
+ * @param allColl optional list of all loaded collections (Python path only)
*/
-PVarHandler::PVarHandler(PmuppCollection *coll, std::string parse_str, std::string var_name) :
- fColl(coll), fParseStr(parse_str), fVarName(var_name), fIsValid(false)
+PVarHandler::PVarHandler(PmuppCollection *coll, std::string parse_str, std::string var_name,
+ const std::vector &allColl) :
+ fColl(coll), fAllColl(allColl), fParseStr(parse_str), fVarName(var_name), fIsValid(false)
{
// route ... definitions to the embedded Python interpreter
if (fParseStr.find("") != std::string::npos) {
@@ -361,6 +416,31 @@ void PVarHandler::evaluatePython()
}
}
+ // expose every loaded collection explicitly so that a single block
+ // can compute (and combine) variables for several collections. coll[]
+ // mirrors the script 'col ' and coll[''] uses the collection name;
+ // both keys reference the same per-parameter dict (likewise for collErr).
+ py << "coll = {}\n";
+ py << "collErr = {}\n";
+ for (size_t c=0; cGetNoOfRuns() == 0)
+ continue;
+ py << "_c = {}\n";
+ py << "_ce = {}\n";
+ int np = pc->GetRun(0).GetNoOfParam();
+ for (int j=0; jGetRun(0).GetParam(j).GetName().toLatin1().data();
+ py << "_c[" << mupp_pyStr(pname) << "] = " << mupp_pyList(mupp_collValues(pc, j)) << "\n";
+ py << "_ce[" << mupp_pyStr(pname) << "] = " << mupp_pyList(mupp_collErrors(pc, j)) << "\n";
+ }
+ std::string cname = pc->GetName().toLatin1().data();
+ py << "coll[" << c << "] = _c\n";
+ py << "collErr[" << c << "] = _ce\n";
+ py << "coll[" << mupp_pyStr(cname) << "] = _c\n";
+ py << "collErr[" << mupp_pyStr(cname) << "] = _ce\n";
+ }
+
// run the user code inside a try/except so we can capture the traceback
py << "import traceback as _mupp_tb\n";
py << "_mupp_err = ''\n";