Files
musrfit/src/musredit_qt6/mupp/PmuppScript.cpp

1270 lines
43 KiB
C++

/***************************************************************************
PmuppScript.cpp
Author: Andreas Suter
e-mail: andreas.suter@psi.ch
***************************************************************************/
/***************************************************************************
* Copyright (C) 2007-2025 by Andreas Suter *
* andreas.suter@psi.ch *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
/**
* @file PmuppScript.cpp
*
* @brief Implementation of the mupp scripting interface for batch processing.
*
* This file implements the non-interactive scripting system for mupp, enabling
* automated batch processing of parameter data analysis and plotting tasks.
*
* Key functionality implemented:
* - Script command parsing and execution
* - Path management with environment variable expansion
* - Data collection loading and selection
* - X-Y axis parameter configuration
* - Variable expression evaluation and storage
* - ROOT macro generation with full plot specification
* - Batch mode plot creation via ROOT subprocess
* - Label formatting for publication-quality plots
*
* Script command processing:
* The executeScript() method processes commands sequentially, maintaining
* state throughout execution. Each command type (loadPath, load, select,
* x, y, var, norm, plot, macro) has a dedicated handler method.
*
* ROOT macro generation:
* The macro() method creates complete ROOT C++ macros including:
* - Data array definitions
* - TGraphAsymmErrors objects for error bars
* - Canvas and styling setup
* - Axis labels and ranges
* - Marker and color configuration
* - Optional normalization
*
* Variable system:
* Variables are mathematical expressions combining collection parameters.
* The var_cmd() method integrates with PVarHandler for expression parsing
* and evaluation, enabling derived quantity calculations.
*/
#include <cmath>
#include <iostream>
#include <QProcessEnvironment>
#include <QString>
#include <QFile>
#include <QTextStream>
#include <QDateTime>
#include <QProcess>
#include <QtGlobal> // Q_ASSERT
#include "PmuppScript.h"
//--------------------------------------------------------------------------
/**
* @brief Constructor for the script interpreter.
*
* Initializes the script execution environment with default settings:
* - Load path: current directory
* - Save path: current directory
* - Selection state: nothing selected
* - Normalization: disabled
* - Administration object for configuration access
*
* @param script list of script commands to be executed
*/
PmuppScript::PmuppScript(QStringList script) :
fScript(script)
{
fLoadPath = QString("./");
fSavePath = QString("./");
fSelected = -2; // nothing selected
fNorm = false;
fAdmin = std::make_unique<PmuppAdmin>();
}
//--------------------------------------------------------------------------
/**
* @brief Executes all script commands sequentially.
*
* Processes each command in the script list, dispatching to appropriate
* handler methods based on command type. Maintains state throughout
* execution including:
* - Loaded data collections
* - Current selection
* - Plot configurations
* - Variable definitions
*
* Command dispatch table:
* - "loadPath" → setLoadPath()
* - "savePath" → setSavePath()
* - "load " → loadCollection()
* - "selectAll" → selectAll()
* - "select " → select()
* - "x" → addX()
* - "y" → addY()
* - "norm" → sets fNorm flag
* - "plot" → plot()
* - "macro" → macro()
* - "var" → var_cmd()
* - "col" → (handled internally by var)
*
* Emits finished() signal upon completion or error.
*
* @return 0 on success, non-zero error code on failure
*/
int PmuppScript::executeScript()
{
fParamDataHandler = std::make_unique<PParamDataHandler>();
if (fParamDataHandler == nullptr) {
std::cerr << std::endl << "**ERROR** couldn't invoke data handler ..." << std::endl << std::endl;
return -1;
}
QString cmd;
int status;
for (int i=0; i<fScript.size(); i++) {
status = 0;
cmd = fScript.at(i);
if (cmd.startsWith("loadPath")) {
setLoadPath(cmd);
} else if (cmd.startsWith("savePath")) {
setSavePath(cmd);
} else if (cmd.startsWith("load ")) {
status = loadCollection(cmd);
} else if (cmd.startsWith("selectAll")) {
status = selectAll();
} else if (cmd.startsWith("select ")) {
status = select(cmd);
} else if (cmd.startsWith("x")) {
status = addX(cmd);
} else if (cmd.startsWith("y")) {
status = addY(cmd);
} else if (cmd.startsWith("norm")) {
fNorm = true;
} else if (cmd.startsWith("plot")) {
status = plot(cmd);
} else if (cmd.startsWith("macro")) {
status = macro(cmd);
} else if (cmd.startsWith("var")) {
status = var_cmd(cmd);
} else if (cmd.startsWith("col")) {
// nothing to be done here, since var handles it internally
} else {
std::cerr << "**ERROR** found unkown script command '" << cmd.toLatin1().constData() << "'." << std::endl << std::endl;
status = -2;
}
// check for errors
if (status != 0) {
emit finished();
return status;
}
}
emit finished();
return 0;
}
//--------------------------------------------------------------------------
/**
* @brief Sets the load path for input data files.
*
* Parses the loadPath command string and extracts the directory path.
* Supports environment variable expansion: variables prefixed with '$'
* are replaced with their values from the system environment.
*
* Path processing:
* 1. Removes "loadPath " prefix from command
* 2. Tokenizes path by '/' separator
* 3. Expands environment variables ($VAR_NAME)
* 4. Reconstructs full path with '/' separators
*
* Example: "loadPath $HOME/data/musrfit/"
* → "/home/user/data/musrfit/"
*
* @param cmd loadPath command string including path specification
*/
void PmuppScript::setLoadPath(const QString cmd)
{
QString str = cmd;
QStringList tok;
// remove command from string
str = str.remove("loadPath ");
// tokenize path string
tok = str.split("/", Qt::SkipEmptyParts);
// check if there is a bash variable which needs to be resolved
QProcessEnvironment procEnv = QProcessEnvironment::systemEnvironment();
QString path = QString("");
for (int i=0; i<tok.size(); i++) {
str = tok.at(i);
if (str.startsWith("$")) {
str = str.remove("$");
QString var = procEnv.value(str, "");
path += var + "/";
} else {
path += str + "/";
}
}
fLoadPath = path;
}
//--------------------------------------------------------------------------
/**
* @brief Sets the save path for output files.
*
* Parses the savePath command string and extracts the directory path.
* Uses the same environment variable expansion mechanism as setLoadPath().
*
* Path processing:
* 1. Removes "savePath " prefix from command
* 2. Tokenizes path by '/' separator
* 3. Expands environment variables ($VAR_NAME)
* 4. Reconstructs full path with '/' separators
*
* Example: "savePath $HOME/plots/"
* → "/home/user/plots/"
*
* @param cmd savePath command string including path specification
*/
void PmuppScript::setSavePath(const QString cmd)
{
QString str = cmd;
QStringList tok;
// remove command from string
str = str.remove("savePath ");
// tokenize path string
tok = str.split("/", Qt::SkipEmptyParts);
// check if there is a bash variable which needs to be resolved
QProcessEnvironment procEnv = QProcessEnvironment::systemEnvironment();
QString path = QString("");
for (int i=0; i<tok.size(); i++) {
str = tok.at(i);
if (str.startsWith("$")) {
str = str.remove("$");
QString var = procEnv.value(str, "");
path += var + "/";
} else {
path += str + "/";
}
}
fSavePath = path;
}
//--------------------------------------------------------------------------
/**
* @brief Loads a data collection from file.
*
* Parses the load command to extract the filename, prepends the load path,
* and reads the parameter data file via the PParamDataHandler.
*
* Processing steps:
* 1. Removes "load " prefix from command string
* 2. Trims whitespace from filename
* 3. Prepends fLoadPath to create full file path
* 4. Calls ReadParamFile() to load the data
*
* @param str load command string (format: "load filename.ext")
*
* @return 0 on success, 1 if file reading fails
*/
int PmuppScript::loadCollection(const QString str)
{
QString fln = str;
fln = fln.remove("load ");
fln = fln.trimmed();
fln = fln.prepend(fLoadPath);
QStringList flnList;
flnList << fln;
QString errorMsg("");
if (!fParamDataHandler->ReadParamFile(flnList, errorMsg))
return 1;
return 0;
}
//--------------------------------------------------------------------------
/**
* @brief Selects a specific collection for plotting.
*
* Parses the select command to identify a collection by either:
* - Numeric index (0, 1, 2, ...)
* - Collection name string
*
* The selected collection becomes the target for subsequent x and y
* commands. Sets fSelected to the collection index.
*
* @param str select command string (format: "select <index|name>")
*
* @return 0 on success
* @return -1 wrong command syntax
* @return -2 collection index out of range
* @return -3 collection name not found
*/
int PmuppScript::select(const QString str)
{
QString cmd = str;
QStringList tok = cmd.split(' ', Qt::SkipEmptyParts);
if (tok.size() != 2) {
std::cerr << std::endl << "**ERROR** wrong 'select' command syntax." << std::endl << std::endl;
return -1;
}
bool ok;
int ival = tok[1].toInt(&ok);
if (ok) { // collection index given
if (ival >= fParamDataHandler->GetNoOfCollections()) {
std::cerr << std::endl << "**ERROR** try to select a collection with index " << ival << ", which is >= # collections (" << fParamDataHandler->GetNoOfCollections() << ")." << std::endl << std::endl;
return -2;
}
fSelected = ival;
} else { // assume that a collection name is given
ival = fParamDataHandler->GetCollectionIndex(tok[1]);
if (ival == -1) {
std::cerr << std::endl << "**ERROR** couldn't find collection '" << tok[1].toLatin1().constData() << "'." << std::endl << std::endl;
return -3;
}
if (ival >= fParamDataHandler->GetNoOfCollections()) {
std::cerr << std::endl << "**ERROR** try to select a collection with index " << ival << ", which is >= # collections (" << fParamDataHandler->GetNoOfCollections() << ")." << std::endl << std::endl;
return -2;
}
fSelected = ival;
}
return 0;
}
//--------------------------------------------------------------------------
/**
* @brief Selects all loaded collections for plotting.
*
* Enables batch plotting mode where subsequent x and y commands apply
* to all collections. Sets fSelected to -1 (all-selected state).
*
* This mode is useful for creating overlay plots showing multiple
* data sets with the same parameter axes.
*
* @return 0 on success, -1 if no collections are loaded
*/
int PmuppScript::selectAll()
{
int noColl = fParamDataHandler->GetNoOfCollections();
if ( noColl > 0) {
fSelected = -1; // all collections are selected
} else {
std::cerr << std::endl << "**ERROR** no collections present, hence it is not possible to select them." << std::endl << std::endl;
return -1;
}
return 0;
}
//--------------------------------------------------------------------------
/**
* @brief Adds a parameter label to the X-axis.
*
* Configures the X-axis (independent variable) for the plot. The label
* can be either:
* - A parameter name from the collection (e.g., "dataT", "dataB")
* - A defined variable expression
*
* Behavior depends on selection state:
* - fSelected == -2: Error (no selection made)
* - fSelected == -1: Applies to all collections (creates fPlotInfo entries)
* - fSelected >= 0: Applies to specific collection (sets fPlotEntry)
*
* Validation:
* - Checks if label exists in collection(s) or as a variable
* - Reports error if label not found
*
* @param str x command string (format: "x <label>")
*
* @return 0 on success
* @return -1 wrong command syntax
* @return -2 no collection selected
* @return -3 selected collection not found
* @return -4 label not found in collection or variables
*/
int PmuppScript::addX(const QString str)
{
QString cmd = str, label;
QStringList tok = cmd.split(' ', Qt::SkipEmptyParts);
if (tok.size() != 2) {
std::cerr << std::endl << "**ERROR** in addX: number of tokens missmatch." << std::endl << std::endl;
return -1;
}
label = tok[1].trimmed();
PmuppCollection *coll=0;
if (fSelected == -2) { // no selection -> error
std::cerr << std::endl << "**ERROR** in addX. addX called without previous 'select' command." << std::endl << std::endl;
return -2;
} else if (fSelected == -1) { // i.e. select ALL
// clean up plot info first
fPlotInfo.clear();
// make sure that label is found in ALL collections, or in variables
// first check collections
bool foundInColl(true), foundInVar(true);
QString collName("");
for (int i=0; i<fParamDataHandler->GetNoOfCollections(); i++) {
coll = fParamDataHandler->GetCollection(i);
if (!foundLabel(coll, label)) { // label not found
foundInColl = false;
collName = coll->GetName();
break;
}
}
// second check variables
if (!foundVariable(label)) { // label not found
foundInVar = false;
}
// make sure label(s) have been found
if (!foundInColl && !foundInVar) { // not all labels found, neither in collection nor variables.
std::cerr << std::endl << "**ERROR** couldn't find '" << label.toLatin1().constData() << "' in collection '" << collName.toLatin1().constData() << "'," << std::endl;
std::cerr << " nor is it a defined variable" << std::endl << std::endl;
return -4;
}
// resize fPlotInfo to the number of selections
fPlotInfo.resize(fParamDataHandler->GetNoOfCollections());
// feed plot info
for (int i=0; i<fPlotInfo.size(); i++) {
fPlotInfo[i].collIdx = i;
fPlotInfo[i].xLabel = label;
}
} else { // a specific selection
// check that label is found in the selected collection, or in variables
coll = fParamDataHandler->GetCollection(fSelected);
if (coll == 0) {
std::cerr << std::endl << "**ERROR** in addX: selected collection couldn't be found ..." << std::endl << std::endl;
return -3;
}
// first check collection
bool foundInColl(true), foundInVar(true);
QString collName("");
if (!foundLabel(coll, label)) { // label not found
foundInColl = false;
collName = coll->GetName();
}
// second check variables
if (!foundVariable(label)) { // label not found
foundInVar = false;
}
// make sure label(s) have been found
if (!foundInColl && !foundInVar) { // not all labels found, neither in collection nor variables.
std::cerr << std::endl << "**ERROR** couldn't find '" << label.toLatin1().constData() << "' in collection '" << collName.toLatin1().constData() << "'," << std::endl;
std::cerr << " nor is it a defined variable" << std::endl << std::endl;
return -4;
}
// feed plot entry
fPlotEntry.collIdx = fSelected;
fPlotEntry.xLabel = label;
}
return 0;
}
//--------------------------------------------------------------------------
/**
* @brief Adds one or more parameter labels to the Y-axis.
*
* Configures the Y-axis (dependent variable(s)) for the plot. Multiple
* Y-axis parameters can be specified in a single command, enabling
* multi-curve plots.
*
* Each label can be either:
* - A parameter name from the collection
* - A defined variable expression
*
* Behavior depends on selection state:
* - fSelected == -2: Error (no selection made)
* - fSelected == -1: Applies to all collections (updates fPlotInfo)
* - fSelected >= 0: Applies to specific collection (updates fPlotEntry,
* adds to fPlotInfo)
*
* Validation:
* - Checks if each label exists in collection(s) or as a variable
* - Reports error on first missing label
*
* @param str y command string (format: "y <label1> [label2] [label3] ...")
*
* @return 0 on success
* @return -1 wrong command syntax (< 2 tokens)
* @return -2 no collection selected
* @return -3 selected collection not found
* @return -4 label not found in collection or variables
*/
int PmuppScript::addY(const QString str)
{
QString cmd = str;
QVector<QString> label;
QStringList tok = cmd.split(' ', Qt::SkipEmptyParts);
if (tok.size() < 2) {
std::cerr << std::endl << "**ERROR** in addY: number of tokens < 2." << std::endl << std::endl;
return -1;
}
// collect all potential labels
for (int i=1; i<tok.size(); i++)
label.push_back(tok[i].trimmed());
PmuppCollection *coll=0;
if (fSelected == -2) { // no selection -> error
std::cerr << std::endl << "**ERROR** in addY. addY called without previous 'select' command." << std::endl << std::endl;
return -2;
} else if (fSelected == -1) { // i.e. select ALL
// make sure that label(s) is/are found in ALL collections, or in variables
// first check collections
bool foundInColl(true), foundInVar(true);
int idx = -1;
QString collName("");
for (int i=0; i<fParamDataHandler->GetNoOfCollections(); i++) {
coll = fParamDataHandler->GetCollection(i);
for (int j=0; j<label.size(); j++) {
if (!foundLabel(coll, label[j])) { // label not found
foundInColl = false;
collName = coll->GetName();
idx = j;
break;
}
}
}
// second check variables
for (int i=0; i<label.size(); i++) {
if (!foundVariable(label[i])) { // label not found
foundInVar = false;
idx = i;
break;
}
}
// make sure label(s) have been found
if (!foundInColl && !foundInVar) { // not all labels found, neither in collection nor variables.
std::cerr << std::endl << "**ERROR** couldn't find '" << label[idx].toLatin1().constData() << "' in collection '" << collName.toLatin1().constData() << "'," << std::endl;
std::cerr << " nor is it a defined variable" << std::endl << std::endl;
return -4;
}
// feed plot info with y-label(s)
for (int i=0; i<fPlotInfo.size(); i++) {
fPlotInfo[i].yLabel = label;
}
} else { // a specific selection
// clear yLabel
fPlotEntry.yLabel.clear();
// check that label is found in the selected collection, or in the variables
coll = fParamDataHandler->GetCollection(fSelected);
if (coll == 0) {
std::cerr << std::endl << "**ERROR** in addY: selected collection couldn't be found ..." << std::endl << std::endl;
return -3;
}
// first check specific collection
bool foundInColl(true), foundInVar(true);
int idx = -1;
QString collName("");
for (int i=0; i<label.size(); i++) {
if (!foundLabel(coll, label[i])) { // label not found
foundInColl = false;
collName = coll->GetName();
idx = i;
break;
}
}
// second check variables
for (int i=0; i<label.size(); i++) {
if (!foundVariable(label[i])) { // label not found
foundInVar = false;
idx = i;
break;
}
}
// make sure label(s) have been found
if (!foundInColl && !foundInVar) { // not all labels found, neither in collection nor variables.
std::cerr << std::endl << "**ERROR** couldn't find '" << label[idx].toLatin1().constData() << "' in collection '" << collName.toLatin1().constData() << "'," << std::endl;
std::cerr << " nor is it a defined variable" << std::endl << std::endl;
return -4;
}
fPlotEntry.yLabel = label;
// add plot entry
fPlotInfo.push_back(fPlotEntry);
}
return 0;
}
//--------------------------------------------------------------------------
/**
* @brief Creates a plot file by generating and executing a ROOT macro.
*
* This method performs the complete workflow for batch plot creation:
* 1. Generates a temporary ROOT C++ macro (__out.C)
* 2. Launches root.exe in batch mode to execute the macro
* 3. Creates the output plot file (format determined by extension)
* 4. Cleans up the temporary macro file
*
* Supported output formats (via ROOT):
* - .pdf - PDF document
* - .png - PNG image
* - .eps - Encapsulated PostScript
* - .svg - Scalable Vector Graphics
* - .root - ROOT file
*
* The method configures environment variables (LD_LIBRARY_PATH/
* DYLD_LIBRARY_PATH) to ensure ROOT finds its libraries.
*
* @param str plot command string (format: "plot output_filename.ext")
*
* @return 0 on success
* @return -1 wrong command syntax or macro generation failed
* @return -2 failed to create QProcess
* @return -3 failed to start root.exe
*/
int PmuppScript::plot(const QString str)
{
QString cmd = str;
QStringList tok = cmd.split(' ', Qt::SkipEmptyParts);
if (tok.size() != 2) {
std::cerr << std::endl << "**ERROR** in plot: number of tokens != 2." << std::endl << std::endl;
return -1;
}
QString flnOut = fSavePath + tok[1];
QString macroOut = fSavePath + "__out.C";
if (macro(QString("macro " + macroOut), flnOut) != 0) {
std::cerr << std::endl << "**ERROR** temporary macro generation failed." << std::endl << std::endl;
return -1;
}
// call root via batch
QProcess *proc = new QProcess(this);
if (proc == nullptr) {
std::cerr << std::endl << "**ERROR** couldn't invoke root.exe in batch mode." << std::endl << std::endl;
return -2;
}
// make sure that the system environment variables are properly set
QString exec_cmd = "root.exe";
QStringList arg;
arg << "-b";
arg << macroOut;
arg << "-q";
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
#if defined(Q_OS_DARWIN)
env.insert("DYLD_LIBRARY_PATH", env.value("ROOTSYS") + "/lib:/usr/local/lib:" + env.value("DYLD_LIBRARY_PATH"));
#else
env.insert("LD_LIBRARY_PATH", env.value("ROOTSYS") + "/lib:" + env.value("LD_LIBRARY_PATH"));
#endif
proc->setProcessEnvironment(env);
proc->setWorkingDirectory(fSavePath);
proc->start(exec_cmd, arg);
if (!proc->waitForStarted()) {
std::cerr << std::endl << "**ERROR** Could not execute the output command: " << exec_cmd.toLatin1().constData() << std::endl << std::endl;
QFile::remove(macroOut);
return -3;
}
proc->waitForFinished();
QFile::remove(macroOut);
return 0;
}
//--------------------------------------------------------------------------
/**
* @brief Generates a ROOT C++ macro for plotting.
*
* Creates a complete, standalone ROOT macro that can be executed
* independently. The macro includes:
*
* Generated content:
* - Header with timestamp and filename
* - ROOT initialization and style settings
* - Data array declarations (xx, yy, error arrays)
* - Data population from collections/variables
* - TGraphAsymmErrors object creation
* - Canvas setup and configuration
* - Marker and color styling from configuration
* - Axis labels and ranges
* - Optional normalization
* - Optional SaveAs() call for plot output
*
* Data handling:
* - Retrieves X/Y values from collections or variables
* - Handles asymmetric error bars (positive/negative)
* - Calculates axis ranges with 5% padding
* - Supports normalization to maximum Y value
*
* Styling:
* - Uses marker and color definitions from PmuppAdmin
* - Falls back to default black circles if config exhausted
* - Applies ROOT formatting for Greek letters and subscripts
*
* @param str macro command string (format: "macro filename.C")
* @param plotFln optional output plot filename (adds SaveAs to macro)
*
* @return 0 on success
* @return -1 wrong command syntax
* @return -2 failed to open macro file for writing
* @return -3 X or Y label data not found (internal error)
*/
int PmuppScript::macro(const QString str, const QString plotFln)
{
QVector<PmuppMarker> marker = fAdmin->getMarkers();
QVector<PmuppColor> color = fAdmin->getColors();
QString cmd = str;
QStringList tok = cmd.split(' ', Qt::SkipEmptyParts);
if (tok.size() != 2) {
std::cerr << std::endl << "**ERROR** macro command with wrong number of arguments (" << tok.size() << ")." << std::endl << std::endl;
return -1;
}
QString macroName = tok[1].trimmed();
QString fln = fSavePath + macroName;
QFile file(fln);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
std::cerr << std::endl << "**ERROR** Couldn't open macro file for writting." << std::endl << std::endl;
return -2;
}
QTextStream fout(&file);
// write header
fout << "// --------------------------" << Qt::endl;
fout << "// " << fln.toLatin1().constData() << Qt::endl;
fout << "// " << QDateTime::currentDateTime().toString("yy/MM/dd - HH:mm:ss") << Qt::endl;
fout << "// --------------------------" << Qt::endl;
fout << "{" << Qt::endl;
fout << " gROOT->Reset();" << Qt::endl;
fout << Qt::endl;
fout << " gStyle->SetOptTitle(0);" << Qt::endl;
fout << " gStyle->SetOptDate(0);" << Qt::endl;
fout << " gStyle->SetPadColor(TColor::GetColor(255,255,255)); // pad bkg to white" << Qt::endl;
fout << " gStyle->SetCanvasColor(TColor::GetColor(255,255,255)); // canvas bkg to white" << Qt::endl;
fout << Qt::endl;
fout << " Int_t nn=0, i=0;" << Qt::endl;
fout << " Double_t null[512];" << Qt::endl;
fout << " Double_t xx[512];" << Qt::endl;
fout << " Double_t yy[512];" << Qt::endl;
fout << " Double_t yyPosErr[512];" << Qt::endl;
fout << " Double_t yyNegErr[512];" << Qt::endl;
fout << Qt::endl;
// write data
QVector<double> xx, yy, yyPosErr, yyNegErr;
QString collName;
int count=0;
double x_min=0.0, x_max=0.0, x_min_new=0.0, x_max_new=0.0, y_min=0.0, y_max=0.0, y_min_new=0.0, y_max_new=0.0;
double dval, y_min_norm=1.0e10;
for (int i=0; i<fPlotInfo.size(); i++) {
// get collection name
collName = fParamDataHandler->GetCollectionName(i);
xx = fParamDataHandler->GetValues(collName, fPlotInfo[i].xLabel);
if (xx.size() == 0) { // it is a variable
int idx = getVarIndex(fPlotInfo[i].xLabel);
if (idx == -1) {
std::cerr << std::endl;
std::cerr << "**ERROR** Couldn't get x-label '" << fPlotInfo[i].xLabel.toLatin1().data() << "'." << std::endl;
std::cerr << " This should never happens." << std::endl;
return -3;
}
std::vector<double> xVal = fVarHandler[idx].getValues();
QVector<double> qvec(xVal.begin(), xVal.end());
xx = qvec;
}
// get x-axis min/max
minMax(xx, x_min, x_max);
if (count==0) {
x_min_new = x_min;
x_max_new = x_max;
} else {
if (x_min < x_min_new)
x_min_new = x_min;
if (y_max > x_max_new)
x_max_new = x_max;
}
for (int j=0; j<fPlotInfo[i].yLabel.size(); j++) {
yy = fParamDataHandler->GetValues(collName, fPlotInfo[i].yLabel[j]);
yyPosErr = fParamDataHandler->GetPosErr(collName, fPlotInfo[i].yLabel[j]);
yyNegErr = fParamDataHandler->GetNegErr(collName, fPlotInfo[i].yLabel[j]);
if (yy.size() == 0) { // it's a variable
int idx = getVarIndex(fPlotInfo[i].yLabel[j]);
if (idx == -1) {
std::cerr << std::endl;
std::cerr << "**ERROR** Couldn't get y-label '" << fPlotInfo[i].yLabel[j].toLatin1().data() << "'." << std::endl;
std::cerr << " This should never happens." << std::endl;
return -3;
}
std::vector<double> yVal = fVarHandler[idx].getValues();
QVector<double> qvecV(yVal.begin(), yVal.end());
yy = qvecV;
std::vector<double> yErr = fVarHandler[idx].getErrors();
QVector<double> qvecE(yErr.begin(), yErr.end());
yyPosErr = qvecE;
yyNegErr = qvecE;
}
// get y-axis min/max
minMax(yy, y_min, y_max);
if (count==0) {
y_min_new = y_min;
y_max_new = y_max;
} else {
if (y_min < y_min_new)
y_min_new = y_min;
if (y_max > y_max_new)
y_max_new = y_max;
}
fout << " // " << ++count << ". data set" << Qt::endl;
fout << " nn = " << xx.size() << ";" << Qt::endl;
fout << " // null-values" << Qt::endl;
for (int k=0; k<xx.size(); k++) {
fout << " null[" << k << "]=0.0;" << Qt::endl;
}
fout << " // x-values" << Qt::endl;
for (int k=0; k<xx.size(); k++) {
fout << " xx[" << k << "]=" << xx[k] << ";" << Qt::endl;
}
fout << " // y-values" << Qt::endl;
for (int k=0; k<yy.size(); k++) {
dval = yy[k];
if (fNorm) {
dval /= y_max;
if (dval < y_min_norm)
y_min_norm = dval;
}
fout << " yy[" << k << "]=" << dval << ";" << Qt::endl;
}
fout << " // yyNegErr-values" << Qt::endl;
for (int k=0; k<yyNegErr.size(); k++) {
dval = fabs(yyNegErr[k]);
if (fNorm)
dval /= fabs(y_max);
fout << " yyNegErr[" << k << "]=" << dval << ";" << Qt::endl;
}
fout << " // yyPosErr-values" << Qt::endl;
for (int k=0; k<yyPosErr.size(); k++) {
dval = fabs(yyPosErr[k]);
if (fNorm)
dval /= fabs(y_max);
fout << " yyPosErr[" << k << "]=" << dval << ";" << Qt::endl;
}
fout << Qt::endl;
fout << " TGraphAsymmErrors *g_" << i << "_" << j << " = new TGraphAsymmErrors(nn, xx, yy, null, null, yyNegErr, yyPosErr);" << Qt::endl;
fout << Qt::endl;
}
}
// x,y-min/max range
double diff;
if (x_min_new > 0.0)
x_min = 0.0;
else
x_min = x_min_new;
diff = x_max-x_min;
x_max = x_max_new + 0.05*diff;
diff = y_max_new - y_min_new;
y_min = y_min_new - 0.05 * diff;
y_max = y_max_new + 0.05 * diff;
if (fNorm) {
diff = 1.0 - y_min_norm;
y_min = y_min_norm - 0.05 * diff;
y_max = 1.0 + 0.05 * diff;
}
// plotting
fout << " //**********" << Qt::endl;
fout << " // plotting " << Qt::endl;
fout << " //**********" << Qt::endl;
fout << " TCanvas *c1 = new TCanvas(\"c1\", \"" << macroName.toLatin1().constData() << "\", 10, 10, 600, 700);" << Qt::endl;
fout << Qt::endl;
count = 0;
int rr, gg, bb;
for (int i=0; i<fPlotInfo.size(); i++) {
for (int j=0; j<fPlotInfo[i].yLabel.size(); j++) {
if (count == 0) {
if (count < marker.size()) {
fout << " g_" << i << "_" << j << "->SetMarkerStyle(" << marker[count].getMarker() << ");" << Qt::endl;
fout << " g_" << i << "_" << j << "->SetMarkerSize(" << marker[count].getMarkerSize() << ");" << Qt::endl;
color[count].getRGB(rr, gg, bb);
fout << " g_" << i << "_" << j << "->SetMarkerColor(TColor::GetColor(" << rr << "," << gg << "," << bb << "));" << Qt::endl;
fout << " g_" << i << "_" << j << "->SetLineColor(TColor::GetColor(" << rr << "," << gg << "," << bb << "));" << Qt::endl;
} else {
fout << " g_" << i << "_" << j << "->SetMarkerStyle(20);" << Qt::endl;
fout << " g_" << i << "_" << j << "->SetMarkerSize(1.3);" << Qt::endl;
fout << " g_" << i << "_" << j << "->SetMarkerColor(TColor::GetColor(0,0,0));" << Qt::endl;
fout << " g_" << i << "_" << j << "->SetLineColor(TColor::GetColor(0,0,0));" << Qt::endl;
}
fout << " g_" << i << "_" << j << "->SetFillColor(kWhite);" << Qt::endl;
fout << " g_" << i << "_" << j << "->GetXaxis()->SetTitle(\"" << getNicerLabel(fPlotInfo[0].xLabel).toLatin1().data() << "\");" << Qt::endl;
fout << " g_" << i << "_" << j << "->GetXaxis()->SetTitleSize(0.05);" << Qt::endl;
fout << " g_" << i << "_" << j << "->GetXaxis()->SetDecimals(kTRUE);" << Qt::endl;
fout << " g_" << i << "_" << j << "->GetXaxis()->SetLimits(" << x_min << "," << x_max << ");" << Qt::endl;
fout << " g_" << i << "_" << j << "->GetYaxis()->SetTitle(\"" << getNicerLabel(fPlotInfo[0].yLabel[0]).toLatin1().data() << "\");" << Qt::endl;
fout << " g_" << i << "_" << j << "->GetYaxis()->SetTitleSize(0.05);" << Qt::endl;
fout << " g_" << i << "_" << j << "->GetYaxis()->SetTitleOffset(1.30);" << Qt::endl;
fout << " g_" << i << "_" << j << "->GetYaxis()->SetRangeUser(" << y_min << "," << y_max << ");" << Qt::endl;
fout << " g_" << i << "_" << j << "->Draw(\"AP\");" << Qt::endl;
} else {
if (count < marker.size()) {
fout << " g_" << i << "_" << j << "->SetMarkerStyle(" << marker[count].getMarker() << ");" << Qt::endl;
fout << " g_" << i << "_" << j << "->SetMarkerSize(" << marker[count].getMarkerSize() << ");" << Qt::endl;
color[count].getRGB(rr, gg, bb);
fout << " g_" << i << "_" << j << "->SetMarkerColor(TColor::GetColor(" << rr << "," << gg << "," << bb << "));" << Qt::endl;
fout << " g_" << i << "_" << j << "->SetLineColor(TColor::GetColor(" << rr << "," << gg << "," << bb << "));" << Qt::endl;
} else {
fout << " g_" << i << "_" << j << "->SetMarkerStyle(20);" << Qt::endl;
fout << " g_" << i << "_" << j << "->SetMarkerSize(1.3);" << Qt::endl;
fout << " g_" << i << "_" << j << "->SetMarkerColor(TColor::GetColor(0,0,0));" << Qt::endl;
fout << " g_" << i << "_" << j << "->SetLineColor(TColor::GetColor(0,0,0));" << Qt::endl;
}
fout << " g_" << i << "_" << j << "->SetFillColor(kWhite);" << Qt::endl;
fout << " g_" << i << "_" << j << "->Draw(\"Psame\");" << Qt::endl;
}
count++;
}
}
fout << " c1->SetMargin(0.15, 0.05, 0.12, 0.05);" << Qt::endl;
fout << " c1->Update();" << Qt::endl;
if (!plotFln.isEmpty()) {
fout << Qt::endl;
fout << " c1->SaveAs(\"" << plotFln.toLatin1().constData() << "\");" << Qt::endl;
}
fout << "}" << Qt::endl;
return 0;
}
//--------------------------------------------------------------------------
/**
* @brief Processes a variable definition command.
*
* Variables enable defining derived quantities as mathematical expressions
* combining collection parameters. For example:
* - "var amplitude sigma+lambda"
* - "var asymmetry (forward-backward)/(forward+backward)"
*
* Processing steps:
* 1. Extracts variable name from command
* 2. Checks if variable is linked to a collection (via col command)
* 3. Locates optional error variable definition (varNameErr)
* 4. Creates PVarHandler for expression parsing and evaluation
* 5. Validates variable and stores handler if valid
*
* Error handling:
* - Error variables (ending in "Err") are processed silently
* - Invalid expressions write errors to ~/.musrfit/mupp/mupp_err.log
* - Error log is displayed and deleted on failure
*
* Integration with col command:
* The "col" command links variables to specific collections, enabling
* variable resolution during plot generation.
*
* @param str var command string (format: "var <name> <expression>")
*
* @return 0 on success
* @return 0 if variable not linked to collection (ignored)
* @return 0 if error variable (processing deferred)
* @return 1 if variable expression is invalid
*/
int PmuppScript::var_cmd(const QString str)
{
QStringList tok;
int idx=0;
// get linked collection index for further use
tok = str.split(' ', Qt::SkipEmptyParts);
if (tok[1].endsWith("Err")) // error variable no need to do something
return 0;
idx = getCollectionIndex(tok[1]);
if (idx == -1) // var not linked to collection, ignore it
return 0;
// check if the related error variable is present
QString varErr = QString("%1%2").arg(tok[1]).arg("Err");
QString varErrCmd("");
for (int i=0; i<fScript.size(); i++) {
if (fScript.at(i).contains(varErr, Qt::CaseSensitive)) {
varErrCmd = fScript.at(i);
break;
}
}
std::string parse_str = str.toLatin1().data();
if (!varErrCmd.isEmpty()) {
parse_str += "\n";
parse_str += varErrCmd.toLatin1().data();
}
PVarHandler varHandler(fParamDataHandler->GetCollection(idx), parse_str, tok[1].toLatin1().data());
if (!varHandler.isValid()) {
// deal with errors
QString mupp_err = QString("%1/.musrfit/mupp/mupp_err.log").arg(QString(qgetenv("HOME")));
// dump error messages if present
QFile fout(mupp_err);
if (fout.open(QIODevice::ReadOnly | QIODevice::Text)) {
QString msg;
while (!fout.atEnd()) {
msg = fout.readLine();
std::cerr << msg.toLatin1().data();
}
// delete potentially present mupp_err.log file
QFile::remove(mupp_err);
}
return 1;
}
fVarHandler.push_back(varHandler);
return 0;
}
//--------------------------------------------------------------------------
/**
* @brief Checks if a parameter label exists in a collection.
*
* Searches the first run of the collection for a parameter matching
* the given label. Assumes all runs in a collection have the same
* parameter structure.
*
* @param coll pointer to collection to search
* @param label parameter label to find
*
* @return true if label found in collection, false otherwise
*/
bool PmuppScript::foundLabel(PmuppCollection *coll, const QString label)
{
bool result = false;
for (int i=0; i<coll->GetRun(0).GetNoOfParam(); i++) {
if (!coll->GetRun(0).GetParam(i).GetName().compare(label)) {
result = true;
break;
}
}
return result;
}
//--------------------------------------------------------------------------
/**
* @brief Checks if a variable is defined in the variable handler.
*
* Searches the variable handler vector for a variable with the
* specified name. Used to validate variable references in x and y
* commands.
*
* @param var variable name to search for
*
* @return true if variable found, false otherwise
*/
bool PmuppScript::foundVariable(const QString var)
{
bool result = false;
for (int i=0; i<fVarHandler.size(); i++) {
if (!fVarHandler[i].getVarName().compare(var)) {
result = true;
break;
}
}
return result;
}
//--------------------------------------------------------------------------
/**
* @brief Gets the index of a variable in the handler vector.
*
* Searches for a variable by name and returns its position in the
* fVarHandler vector. Used during macro generation to retrieve
* variable values.
*
* @param var variable name to search for
*
* @return variable index (0-based), or -1 if not found
*/
int PmuppScript::getVarIndex(const QString var)
{
int idx = -1;
for (int i=0; i<fVarHandler.size(); i++) {
if (!fVarHandler[i].getVarName().compare(var)) {
idx = i;
break;
}
}
return idx;
}
//--------------------------------------------------------------------------
/**
* @brief Finds minimum and maximum values in a data vector.
*
* Scans through the data vector to determine the range. Used for
* automatic axis range calculation in plot generation.
*
* @param dvec data vector to analyze
* @param min output parameter: minimum value in vector
* @param max output parameter: maximum value in vector
*/
void PmuppScript::minMax(QVector<double> dvec, double &min, double &max)
{
min = 99.0e6;
max = -99.0e6;
for (int i=0; i<dvec.size(); i++) {
if (dvec[i] < min)
min = dvec[i];
if (dvec[i] > max)
max = dvec[i];
}
}
//--------------------------------------------------------------------------
/**
* @brief Converts parameter labels to publication-quality ROOT format.
*
* Transforms standard parameter names into formatted labels suitable
* for publication plots, including:
* - Greek letters using ROOT markup (#sigma, #lambda, #alpha)
* - Subscripts and superscripts
* - Physical units
* - Normalization indicators (when fNorm is true)
*
* Special case handling:
* - dataE → "E (keV)"
* - dataT → "T (K)"
* - dataB → "B (G)"
* - sigma → "#sigma (1/#mus)" or normalized version
* - lambda → "#lambda (1/#mus)" or normalized version
* - rate → "Rate (1/#mus)" or normalized version
* - alpha_LR → "#alpha_{LR}" or normalized version
* - alpha_TB → "#alpha_{TB}" or normalized version
* - others → "label/ max(label)" if normalized
*
* @param label original parameter label string
*
* @return formatted label with ROOT markup
*/
QString PmuppScript::getNicerLabel(const QString label)
{
QString nice = label;
if (label == "dataE") {
nice = "E (keV)";
} else if (label == "dataT") {
nice = "T (K)";
} else if (label == "dataB") {
nice = "B (G)";
} else if (!label.compare("sigma", Qt::CaseInsensitive)) {
if (fNorm)
nice = "#sigma/max(#sigma)";
else
nice = "#sigma (1/#mus)";
} else if (!label.compare("lambda", Qt::CaseInsensitive)) {
if (fNorm)
nice = "#lambda/max(#lambda)";
else
nice = "#lambda (1/#mus)";
} else if (!label.compare("rate", Qt::CaseInsensitive)) {
if (fNorm)
nice = "Rate/max(Rate)";
else
nice = "Rate (1/#mus)";
} else if (!label.compare("alpha_LR", Qt::CaseInsensitive)) {
if (fNorm)
nice = "#alpha_{LR}/max(#alpha_{LR})";
else
nice = "#alpha_{LR}";
} else if (!label.compare("alpha_TB", Qt::CaseInsensitive)) {
if (fNorm)
nice = "#alpha_{TB}/max(#alpha_{TB})";
else
nice = "#alpha_{TB}";
} else {
if (fNorm) {
nice = label + "/ max(" + label + ")";
}
}
return nice;
}
//--------------------------------------------------------------------------
/**
* @brief Gets the collection index associated with a variable.
*
* Searches the script for a "col" command linking the variable to a
* collection. The col command format is:
* "col <collection_idx> -> <variable_name>"
*
* This association is necessary for variable evaluation, as the
* variable needs to know which collection's parameters to use.
*
* @param var_name variable name to search for
*
* @return collection index (0-based), or -1 if not found
*/
int PmuppScript::getCollectionIndex(const QString var_name)
{
int idx = -1;
QString cmd;
QStringList tok;
bool ok;
for (int i=0; i<fScript.size(); i++) {
cmd = fScript.at(i);
if (cmd.startsWith("col")) {
tok.clear();
tok = cmd.split(' ', Qt::SkipEmptyParts);
if (tok[3] == var_name) {
idx = tok[1].toInt(&ok);
if (!ok) {
Q_ASSERT(0);
}
break;
}
}
}
return idx;
}