mupp: expose all collections inside a <python> block (coll[]/collErr[]).
A <python> variable block previously saw only the parameters of the single collection the variable is linked to via 'col', injected as bare names plus par[]/parErr[]. To compute variables for several collections one therefore needed a separate block per collection. Now every loaded collection is additionally injected as coll[]/collErr[] dictionaries, addressable both by integer index (matching 'col <idx>' / 'select <idx>') and by collection name; index and name keys reference the same per-parameter dict. A single block placed after all 'var = python' declarations can thus compute - and combine - variables for any collection, e.g. coll[0]['Sigma'] or coll['NAME.db']['Sigma']. The bound-collection bare names / par[] / parErr[] are unchanged, so existing scripts and GUI variables keep working. The collection list is threaded through PmuppScript::var_cmd and the GUI add()/check() paths into PVarHandler. Verified: a one-block script using both an index key and a name key reproduces the numeric output of the equivalent two-block script bit-for-bit. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -107,7 +107,7 @@ run.
|
||||
|
||||
INPUT - injected into the Python namespace before your code runs:
|
||||
|
||||
* For every collection parameter <p>:
|
||||
* For every parameter <p> of the LINKED collection (the one given by 'col'):
|
||||
<p> : a list with one value per run
|
||||
<p>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 <python> block can compute - and
|
||||
combine - variables for several collections without leaving the block:
|
||||
coll[<idx>]['<param>'] # value list of parameter <param>
|
||||
collErr[<idx>]['<param>'] # error list of parameter <param>
|
||||
The collection can be addressed either by its integer INDEX (the same index
|
||||
used by the 'col <idx>' command and by 'select <idx>') 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 <name>:
|
||||
|
||||
<name> : the value list (length = number of runs)
|
||||
@@ -177,14 +191,44 @@ and add the <python> ... </python> 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
|
||||
|
||||
<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)
|
||||
</python>
|
||||
|
||||
col 0 : SigmaSC_10
|
||||
col 1 : SigmaSC_150
|
||||
|
||||
Notes for scripts:
|
||||
|
||||
* The <python> ... </python> 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 </python> must be on its own line.
|
||||
* Only ONE <python> block per script is supported; it may define several
|
||||
variables.
|
||||
* Several <python> blocks are allowed: each "var <name> = 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[<that idx>] provides).
|
||||
* As with X3 variables, only the value variable is linked with 'col'; its error
|
||||
variable (<name>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 <python> 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).
|
||||
================================================================================
|
||||
|
||||
@@ -1432,9 +1432,17 @@ void PmuppGui::addVar()
|
||||
*/
|
||||
void PmuppGui::check(QString varStr, QVector<int> idx)
|
||||
{
|
||||
// expose every loaded collection inside a <python> block (coll[]/collErr[]),
|
||||
// addressable by index and by name, so a check exercises the same namespace
|
||||
// the actual evaluation will see.
|
||||
std::vector<PmuppCollection*> allColl;
|
||||
for (int i=0; i<fParamDataHandler->GetNoOfCollections(); i++)
|
||||
allColl.push_back(fParamDataHandler->GetCollection(i));
|
||||
|
||||
int count = 0;
|
||||
for (int i=0; i<idx.size(); i++) {
|
||||
PVarHandler var_check(fParamDataHandler->GetCollection(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<int> idx)
|
||||
// the PVarHandler class handles only ONE variable of ONE collection.
|
||||
QStringList varNames = getVarNames(varStr);
|
||||
|
||||
// expose every loaded collection inside a <python> block (coll[]/collErr[]),
|
||||
// addressable by index and by name.
|
||||
std::vector<PmuppCollection*> allColl;
|
||||
for (int i=0; i<fParamDataHandler->GetNoOfCollections(); i++)
|
||||
allColl.push_back(fParamDataHandler->GetCollection(i));
|
||||
|
||||
// go through all collections
|
||||
for (int i=0; i<idx.size(); i++) {
|
||||
// go through all the defined variables
|
||||
for (int j=0; j<varNames.count(); j++) {
|
||||
PVarHandler var(fParamDataHandler->GetCollection(idx[i]),
|
||||
varStr.toLatin1().data(),
|
||||
varNames[j].toLatin1().data());
|
||||
varNames[j].toLatin1().data(),
|
||||
allColl);
|
||||
if (!var.isValid()) {
|
||||
parseErrMsgDlg();
|
||||
return;
|
||||
|
||||
@@ -1051,8 +1051,13 @@ int PmuppScript::var_cmd(const QString str, int script_idx)
|
||||
<< "' declared as python, but no <python> ... </python> block was found." << std::endl;
|
||||
return 1;
|
||||
}
|
||||
// expose every loaded collection inside the <python> block (coll[]/collErr[]),
|
||||
// addressable by index and by name, so one block can serve several collections.
|
||||
std::vector<PmuppCollection*> allColl;
|
||||
for (int i=0; i<fParamDataHandler->GetNoOfCollections(); 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")));
|
||||
|
||||
@@ -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<PmuppCollection*> &allColl = std::vector<PmuppCollection*>());
|
||||
|
||||
/**
|
||||
* @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<PmuppCollection*> 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 <python> ... </python> 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 (<name>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 <idx>') and by collection name;
|
||||
* index and name keys reference the same per-parameter dict. This lets a
|
||||
* single <python> block compute (and combine) variables for several
|
||||
* collections. The user script must assign the requested variable and its
|
||||
* error counterpart (<name>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.
|
||||
*/
|
||||
|
||||
@@ -109,6 +109,59 @@ static std::string mupp_pyList(const std::vector<double> &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<s.size(); i++) {
|
||||
if (s[i] == '\\' || s[i] == '\'')
|
||||
os << '\\';
|
||||
os << s[i];
|
||||
}
|
||||
os << "'";
|
||||
return os.str();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
/**
|
||||
* @brief Collects a parameter's value across all runs of an arbitrary collection.
|
||||
*
|
||||
* Collection-pointer counterpart of PVarHandler::getData(), used to inject every
|
||||
* loaded collection into the Python namespace (not just the bound one).
|
||||
*/
|
||||
static std::vector<double> mupp_collValues(PmuppCollection *coll, int pidx)
|
||||
{
|
||||
std::vector<double> v;
|
||||
for (int i=0; i<coll->GetNoOfRuns(); 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<double> mupp_collErrors(PmuppCollection *coll, int pidx)
|
||||
{
|
||||
std::vector<double> v;
|
||||
for (int i=0; i<coll->GetNoOfRuns(); 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<PmuppCollection*> &allColl) :
|
||||
fColl(coll), fAllColl(allColl), fParseStr(parse_str), fVarName(var_name), fIsValid(false)
|
||||
{
|
||||
// route <python> ... </python> definitions to the embedded Python interpreter
|
||||
if (fParseStr.find("<python>") != std::string::npos) {
|
||||
@@ -361,6 +416,31 @@ void PVarHandler::evaluatePython()
|
||||
}
|
||||
}
|
||||
|
||||
// expose every loaded collection explicitly so that a single <python> block
|
||||
// can compute (and combine) variables for several collections. coll[<idx>]
|
||||
// mirrors the script 'col <idx>' and coll['<name>'] 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; c<fAllColl.size(); c++) {
|
||||
PmuppCollection *pc = fAllColl[c];
|
||||
if (pc == nullptr || pc->GetNoOfRuns() == 0)
|
||||
continue;
|
||||
py << "_c = {}\n";
|
||||
py << "_ce = {}\n";
|
||||
int np = pc->GetRun(0).GetNoOfParam();
|
||||
for (int j=0; j<np; j++) {
|
||||
std::string pname = pc->GetRun(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";
|
||||
|
||||
Reference in New Issue
Block a user