mupp 1.1.0
Loading...
Searching...
No Matches
PVarHandler.cpp
Go to the documentation of this file.
1/***************************************************************************
2
3 PVarHandler.cpp
4
5 Author: Andreas Suter
6 e-mail: andreas.suter@psi.ch
7
8***************************************************************************/
9
10/***************************************************************************
11 * Copyright (C) 2007-2026 by Andreas Suter *
12 * andreas.suter@psi.ch *
13 * *
14 * This program is free software; you can redistribute it and/or modify *
15 * it under the terms of the GNU General Public License as published by *
16 * the Free Software Foundation; either version 2 of the License, or *
17 * (at your option) any later version. *
18 * *
19 * This program is distributed in the hope that it will be useful, *
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
22 * GNU General Public License for more details. *
23 * *
24 * You should have received a copy of the GNU General Public License *
25 * along with this program; if not, write to the *
26 * Free Software Foundation, Inc., *
27 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
28 ***************************************************************************/
29
30#include <iostream>
31#include <fstream>
32#include <cstdlib>
33#include <string>
34
35#include "PVarHandler.h"
36
37#include "PSkipper.hpp"
38#include "PErrorHandler.hpp"
39#include "PStatement.hpp"
40#include "PStatementDef.hpp"
41#include "PProgram.hpp"
42
43#include <boost/spirit/home/x3.hpp>
44
45#ifdef MUPP_PYTHON
46#include <cmath>
47#include <cstring>
48#include <cctype>
49#include <set>
50#include <sstream>
51#include <iomanip>
52
53#include <TROOT.h>
54#include <TSystem.h>
55#include <TPython.h>
56#endif // MUPP_PYTHON
57
58//--------------------------------------------------------------------------
67static void mupp_writeErrLog(const std::string &msg)
68{
69 const char *home = std::getenv("HOME");
70 std::string path = std::string(home ? home : ".") + "/.musrfit/mupp/mupp_err.log";
71 std::ofstream fout(path.c_str(), std::ios::app);
72 if (fout.is_open()) {
73 fout << msg << std::endl;
74 fout.close();
75 }
76 std::cerr << msg << std::endl;
77}
78
79#ifdef MUPP_PYTHON
80//--------------------------------------------------------------------------
84static std::string mupp_pyNum(double d)
85{
86 if (std::isnan(d))
87 return "float('nan')";
88 if (std::isinf(d))
89 return (d < 0) ? "float('-inf')" : "float('inf')";
90 std::ostringstream os;
91 os << std::setprecision(17) << d;
92 return os.str();
93}
94
95//--------------------------------------------------------------------------
99static std::string mupp_pyList(const std::vector<double> &v)
100{
101 std::ostringstream os;
102 os << "[";
103 for (size_t i=0; i<v.size(); i++) {
104 if (i)
105 os << ", ";
106 os << mupp_pyNum(v[i]);
107 }
108 os << "]";
109 return os.str();
110}
111
112//--------------------------------------------------------------------------
119static std::string mupp_pyStr(const std::string &s)
120{
121 std::ostringstream os;
122 os << "'";
123 for (size_t i=0; i<s.size(); i++) {
124 if (s[i] == '\\' || s[i] == '\'')
125 os << '\\';
126 os << s[i];
127 }
128 os << "'";
129 return os.str();
130}
131
132//--------------------------------------------------------------------------
139static std::vector<double> mupp_collValues(PmuppCollection *coll, int pidx)
140{
141 std::vector<double> v;
142 for (int i=0; i<coll->GetNoOfRuns(); i++)
143 v.push_back(coll->GetRun(i).GetParam(pidx).GetValue());
144 return v;
145}
146
147//--------------------------------------------------------------------------
154static std::vector<double> mupp_collErrors(PmuppCollection *coll, int pidx)
155{
156 std::vector<double> v;
157 for (int i=0; i<coll->GetNoOfRuns(); i++) {
158 double p = coll->GetRun(i).GetParam(pidx).GetPosErr();
159 double n = coll->GetRun(i).GetParam(pidx).GetNegErr();
160 v.push_back(std::sqrt(std::fabs(p*n)));
161 }
162 return v;
163}
164
165//--------------------------------------------------------------------------
172static bool mupp_isSafePyName(const std::string &s)
173{
174 if (s.empty())
175 return false;
176 if (!(std::isalpha((unsigned char)s[0]) || s[0] == '_'))
177 return false;
178 for (size_t i=0; i<s.size(); i++) {
179 if (!(std::isalnum((unsigned char)s[i]) || s[i] == '_'))
180 return false;
181 }
182 static const std::set<std::string> kw = {
183 "False","None","True","and","as","assert","async","await","break","class",
184 "continue","def","del","elif","else","except","finally","for","from","global",
185 "if","import","in","is","lambda","nonlocal","not","or","pass","raise","return",
186 "try","while","with","yield"
187 };
188 return kw.find(s) == kw.end();
189}
190
191//--------------------------------------------------------------------------
197static std::string mupp_indentLines(const std::string &code, const std::string &prefix)
198{
199 std::ostringstream os;
200 std::istringstream is(code);
201 std::string line;
202 while (std::getline(is, line))
203 os << prefix << line << "\n";
204 return os.str();
205}
206
207//--------------------------------------------------------------------------
219static bool mupp_getPyString(const std::string &pyExpr, std::string &out)
220{
221 std::any res;
222 std::string cmd = "_anyresult = ROOT.std.make_any['std::string'](" + pyExpr + ")";
223 if (!TPython::Exec(cmd.c_str(), &res))
224 return false;
225 if (!res.has_value())
226 return false;
227 try {
228 out = std::any_cast<std::string>(res);
229 } catch (const std::bad_any_cast &) {
230 return false;
231 }
232 return true;
233}
234
235//--------------------------------------------------------------------------
247static std::vector<double> mupp_readPyList(const std::string &name, bool &ok)
248{
249 ok = false;
250 std::vector<double> out;
251
252 // serialize on the python side; the try/except guards undefined/non-iterable
253 std::string prep =
254 "try:\n"
255 " _mupp_s = '1 ' + ' '.join(repr(float(_mupp_x)) for _mupp_x in " + name + ")\n"
256 "except Exception:\n"
257 " _mupp_s = '0'\n";
258 if (!TPython::Exec(prep.c_str()))
259 return out;
260
261 std::string s;
262 if (!mupp_getPyString("_mupp_s", s))
263 return out;
264
265 std::istringstream is(s);
266 int status = 0;
267 if (!(is >> status) || status != 1)
268 return out;
269
270 double d;
271 while (is >> d)
272 out.push_back(d);
273
274 ok = true;
275 return out;
276}
277#endif // MUPP_PYTHON
278
279//--------------------------------------------------------------------------
287 fColl(nullptr), fParseStr(""), fVarName(""), fIsValid(false)
288{
289 // nothing to be done here
290}
291
292//--------------------------------------------------------------------------
312PVarHandler::PVarHandler(PmuppCollection *coll, std::string parse_str, std::string var_name,
313 const std::vector<PmuppCollection*> &allColl) :
314 fColl(coll), fAllColl(allColl), fParseStr(parse_str), fVarName(var_name), fIsValid(false)
315{
316 // route <python> ... </python> definitions to the embedded Python interpreter
317 if (fParseStr.find("<python>") != std::string::npos) {
318 evaluatePython();
319 return;
320 }
321
323
324 typedef std::string::const_iterator iterator_type;
325 iterator_type iter = fParseStr.begin();
326 iterator_type end = fParseStr.end();
327
328 mupp::PErrorHandler<iterator_type> error_handler(iter, end); // the error handler
329 mupp::prog::PProgram prog(error_handler); // our compiler, and exec
330
331 // perform the parsing
332 namespace x3 = boost::spirit::x3;
333 bool success = x3::phrase_parse(iter, end, mupp::parser::statement_list, mupp::parser::skipper, fAst);
334 if (success && iter == end) {
335 if (prog(fAst)) { // semantic analysis
336 std::vector<double> data, dataErr;
337 for (unsigned int i=0; i<fColl->GetRun(0).GetNoOfParam(); i++) {
338 data = getData(i);
339 dataErr = getDataErr(i);
340 prog.add_predef_var_values(getVarName(i), data, dataErr);
341 }
342 if (!fVarName.empty()) {
343 mupp::prog::PProgEval eval(prog.getVars()); // setup evaluation stage
344 eval(fAst); // evaluate stuff
345
346 // keep data
347 bool ok;
348 fVar = eval.getVar(fVarName, ok);
349 if (!ok) {
350 std::cerr << "**ERROR** evalution failed..." << std::endl;
351 fIsValid = false;
352 }
353 }
354 }
355 fIsValid = true;
356 } else {
357 std::cerr << "**ERROR** parsing failed..." << std::endl;
358 }
359}
360
361//--------------------------------------------------------------------------
369{
370#ifdef MUPP_PYTHON
371 // The embedded Python interpreter reads PYTHONPATH at initialization, and
372 // TPython's first call imports cppyy/ROOT. Make sure ROOT's library directory
373 // (which holds those modules) is on PYTHONPATH before the first TPython call,
374 // so the read-back works even when thisroot.sh was not sourced.
375 static bool s_pythonPathSet = false;
376 if (!s_pythonPathSet) {
377 TString pp = TROOT::GetLibDir();
378 const char *cur = gSystem->Getenv("PYTHONPATH");
379 if (cur != nullptr && cur[0] != '\0') {
380 pp += ":";
381 pp += cur;
382 }
383 gSystem->Setenv("PYTHONPATH", pp.Data());
384 s_pythonPathSet = true;
385 }
386
387 // extract the code between the <python> and </python> tags
388 const std::string openTag("<python>");
389 const std::string closeTag("</python>");
390 std::size_t p0 = fParseStr.find(openTag);
391 std::size_t p1 = fParseStr.find(closeTag, p0);
392 if (p0 == std::string::npos || p1 == std::string::npos) {
393 mupp_writeErrLog("**ERROR** python block: missing <python> ... </python> tags.");
394 fIsValid = false;
395 return;
396 }
397 std::size_t codeStart = p0 + openTag.size();
398 std::string code = fParseStr.substr(codeStart, p1 - codeStart);
399
400 // assemble the script: inject all collection parameters, then run the user code
401 std::ostringstream py;
402 py << "import ROOT\n"; // needed for the make_any based read-back below
403 py << "par = {}\n";
404 py << "parErr = {}\n";
405
406 int nParam = fColl->GetRun(0).GetNoOfParam();
407 for (int i=0; i<nParam; i++) {
408 std::string name = fColl->GetRun(0).GetParam(i).GetName().toLatin1().data();
409 std::vector<double> val = getData(i);
410 std::vector<double> err = getDataErr(i);
411 py << "par['" << name << "'] = " << mupp_pyList(val) << "\n";
412 py << "parErr['" << name << "'] = " << mupp_pyList(err) << "\n";
413 if (mupp_isSafePyName(name)) {
414 py << name << " = par['" << name << "']\n";
415 py << name << "Err = parErr['" << name << "']\n";
416 }
417 }
418
419 // expose every loaded collection explicitly so that a single <python> block
420 // can compute (and combine) variables for several collections. coll[<idx>]
421 // mirrors the script 'col <idx>' and coll['<name>'] uses the collection name;
422 // both keys reference the same per-parameter dict (likewise for collErr).
423 py << "coll = {}\n";
424 py << "collErr = {}\n";
425 for (size_t c=0; c<fAllColl.size(); c++) {
426 PmuppCollection *pc = fAllColl[c];
427 if (pc == nullptr || pc->GetNoOfRuns() == 0)
428 continue;
429 py << "_c = {}\n";
430 py << "_ce = {}\n";
431 int np = pc->GetRun(0).GetNoOfParam();
432 for (int j=0; j<np; j++) {
433 std::string pname = pc->GetRun(0).GetParam(j).GetName().toLatin1().data();
434 py << "_c[" << mupp_pyStr(pname) << "] = " << mupp_pyList(mupp_collValues(pc, j)) << "\n";
435 py << "_ce[" << mupp_pyStr(pname) << "] = " << mupp_pyList(mupp_collErrors(pc, j)) << "\n";
436 }
437 std::string cname = pc->GetName().toLatin1().data();
438 py << "coll[" << c << "] = _c\n";
439 py << "collErr[" << c << "] = _ce\n";
440 py << "coll[" << mupp_pyStr(cname) << "] = _c\n";
441 py << "collErr[" << mupp_pyStr(cname) << "] = _ce\n";
442 }
443
444 // run the user code inside a try/except so we can capture the traceback
445 py << "import traceback as _mupp_tb\n";
446 py << "_mupp_err = ''\n";
447 py << "try:\n";
448 py << mupp_indentLines(code, " ");
449 py << " pass\n";
450 py << "except Exception:\n";
451 py << " _mupp_err = _mupp_tb.format_exc()\n";
452
453 if (!TPython::Exec(py.str().c_str())) {
454 mupp_writeErrLog("**ERROR** python block failed to execute "
455 "(syntax error or interpreter problem). See stderr for details.");
456 fIsValid = false;
457 return;
458 }
459
460 // did the user code raise at run time?
461 std::string errStr;
462 if (mupp_getPyString("_mupp_err", errStr) && !errStr.empty()) {
463 mupp_writeErrLog(std::string("**ERROR** python evaluation failed:\n") + errStr);
464 fIsValid = false;
465 return;
466 }
467
468 // check-only mode (no variable requested): success if the code ran
469 if (fVarName.empty()) {
470 fIsValid = true;
471 return;
472 }
473
474 // read back value and (user-supplied) error
475 bool okVal = false, okErr = false;
476 std::vector<double> val = mupp_readPyList(fVarName, okVal);
477 std::vector<double> err = mupp_readPyList(fVarName + "Err", okErr);
478
479 if (!okVal) {
480 mupp_writeErrLog(std::string("**ERROR** python block did not define an iterable variable '") + fVarName + "'.");
481 fIsValid = false;
482 return;
483 }
484
485 int nRuns = fColl->GetNoOfRuns();
486 if ((int)val.size() != nRuns) {
487 std::ostringstream msg;
488 msg << "**ERROR** python variable '" << fVarName << "' has " << val.size()
489 << " values, but the collection has " << nRuns << " runs.";
490 mupp_writeErrLog(msg.str());
491 fIsValid = false;
492 return;
493 }
494
495 // the error is user supplied; default to zeros if not provided / size mismatch
496 if (!okErr || err.size() != val.size())
497 err.assign(val.size(), 0.0);
498
499 fVar.SetValue(val);
500 fVar.SetError(err);
501 fIsValid = true;
502#else
503 mupp_writeErrLog("**ERROR** this mupp build has no Python support; "
504 "rebuild ROOT with -Dtpython=ON to use <python> variable blocks.");
505 fIsValid = false;
506#endif // MUPP_PYTHON
507}
508
509//--------------------------------------------------------------------------
518std::vector<double> PVarHandler::getValues()
519{
520 std::vector<double> data;
521 if (fIsValid)
522 data = fVar.GetValue();
523
524 return data;
525}
526
527//--------------------------------------------------------------------------
536std::vector<double> PVarHandler::getErrors()
537{
538 std::vector<double> data;
539 if (fIsValid)
540 data = fVar.GetError();
541
542 return data;
543}
544
545//--------------------------------------------------------------------------
556{
557 mupp::ast::statement var_stat;
559
560 std::string varName, errVarName;
561 PmuppRun run = fColl->GetRun(0);
562 for (int i=0; i<run.GetNoOfParam(); i++) {
563 varName = run.GetParam(i).GetName().toLatin1().data();
564 errVarName = varName + "Err";
565 // inject err_name
566 var.lhs.name = errVarName;
567 var_stat = var;
568 fAst.push_front(var_stat);
569 // inject var_name
570 var.lhs.name = varName;
571 var_stat = var;
572 fAst.push_front(var_stat);
573 }
574}
575
576//--------------------------------------------------------------------------
586std::string PVarHandler::getVarName(int idx)
587{
588 std::string name("??");
589
590 // make sure idx is in range
591 if (idx >= fColl->GetRun(0).GetNoOfParam())
592 return name;
593
594 return fColl->GetRun(0).GetParam(idx).GetName().toLatin1().data();
595}
596
597//--------------------------------------------------------------------------
607std::vector<double> PVarHandler::getData(int idx)
608{
609 std::vector<double> data;
610
611 // make sure idx is in range
612 if (idx >= fColl->GetRun(0).GetNoOfParam())
613 return data;
614
615 double dval;
616 for (int i=0; i<fColl->GetNoOfRuns(); i++) {
617 dval = fColl->GetRun(i).GetParam(idx).GetValue();
618 data.push_back(dval);
619 }
620
621 return data;
622}
623
624//--------------------------------------------------------------------------
635std::vector<double> PVarHandler::getDataErr(int idx)
636{
637 std::vector<double> err;
638
639 // make sure idx is in range
640 if (idx >= fColl->GetRun(0).GetNoOfParam())
641 return err;
642
643 double dvalPos, dvalNeg;
644 for (int i=0; i<fColl->GetNoOfRuns(); i++) {
645 dvalPos = fColl->GetRun(i).GetParam(idx).GetPosErr();
646 dvalNeg = fColl->GetRun(i).GetParam(idx).GetNegErr();
647 dvalPos = sqrt(fabs(dvalPos*dvalNeg)); // geometric mean of pos/neg error
648 err.push_back(dvalPos);
649 }
650
651 return err;
652}
static void mupp_writeErrLog(const std::string &msg)
Appends an error message to ~/.musrfit/mupp/mupp_err.log and stderr.
PmuppCollection * fColl
pointer to collection containing run data needed for parsing and evaluation
std::vector< double > getErrors()
Gets the computed errors for the variable.
QString getVarName()
Gets the variable name.
std::vector< PmuppCollection * > fAllColl
all loaded collections (handler-index order); Python path only, exposed as coll[]/collErr[]
PVarHandler()
Default constructor.
void evaluatePython()
Evaluates the variable using an embedded Python3 interpreter (TPython).
std::vector< double > getData(int idx)
Gets the data values for a specific parameter across all runs.
std::string fParseStr
the variable input string to be parsed
void injectPredefVariables()
Injects predefined variables from the collection into the AST.
bool fIsValid
flag indicating whether parsing and evaluation succeeded
mupp::prog::PVarHandler fVar
variable handler storing the computed values and errors
std::vector< double > getValues()
Gets the computed values for the variable.
mupp::ast::statement_list fAst
Abstract Syntax Tree generated from parsing.
std::string fVarName
name of the variable to extract from evaluation results
std::vector< double > getDataErr(int idx)
Gets the error values for a specific parameter across all runs.
Represents a collection of related experimental runs.
Definition Pmupp.h:234
PmuppRun GetRun(unsigned int idx)
Retrieves a run from a collection by index.
Definition Pmupp.cpp:204
int GetNoOfRuns()
Gets the number of runs in this collection.
Definition Pmupp.h:275
QString GetName()
Gets the collection name.
Definition Pmupp.h:269
double GetNegErr()
Gets the negative error of the parameter.
Definition Pmupp.h:136
double GetPosErr()
Gets the positive error of the parameter.
Definition Pmupp.h:130
double GetValue()
Gets the parameter value.
Definition Pmupp.h:124
QString GetName()
Gets the parameter name.
Definition Pmupp.h:118
Represents all fit parameters from a single experimental run.
Definition Pmupp.h:156
PmuppParam GetParam(unsigned int idx)
Retrieves a parameter from a run by index.
Definition Pmupp.cpp:184
int GetNoOfParam()
Gets the number of parameters in this run.
Definition Pmupp.h:207
boost::variant< variable_declaration, assignment > statement
Variant type representing a single statement.
Definition PAst.hpp:260
statement_list_type const statement_list
auto const skipper
Definition PSkipper.hpp:75
The PErrorHandler struct handles parsing and semantic errors.
Represents a variable declaration with optional initialization.
Definition PAst.hpp:247
variable lhs
The variable being declared.
Definition PAst.hpp:248
std::string name
Variable name without the '$' prefix.
Definition PAst.hpp:145
The PProgEval struct evaluates expressions using the AST.
Definition PProgram.hpp:341
PVarHandler getVar(const std::string name, bool &ok)
Retrieves a variable by name after evaluation.
Definition PProgram.cpp:685
The PProgram struct performs semantic analysis on the AST.
Definition PProgram.hpp:165
std::vector< PVarHandler > getVars()
Gets all variables from the symbol table.
Definition PProgram.hpp:311
void add_predef_var_values(const std::string &name, std::vector< double > &val, std::vector< double > &err)
Injects predefined variable values from collection data.
Definition PProgram.cpp:261