diff --git a/src/classes/PUserFcn.cpp b/src/classes/PUserFcn.cpp index 6a7458270..8c52130a7 100644 --- a/src/classes/PUserFcn.cpp +++ b/src/classes/PUserFcn.cpp @@ -35,7 +35,15 @@ ClassImp(PUserFcn) //------------------------------------------------------ /** - *

Constructor + * \brief Default constructor for PUserFcn. + * + * Initializes a third-order polynomial user function. This implementation + * requires no special initialization as it has no internal state - all + * computation is done directly in the operator() method. + * + * \note This simple constructor serves as a template for more complex user + * functions. Functions requiring initialization (e.g., loading lookup tables, + * precomputing constants) should perform that work here or in SetGlobalPart(). */ PUserFcn::PUserFcn() { @@ -43,7 +51,16 @@ PUserFcn::PUserFcn() //------------------------------------------------------ /** - *

Destructor + * \brief Destructor for PUserFcn. + * + * Cleans up any resources allocated by the polynomial function. Since this + * implementation has no dynamically allocated resources, the destructor is + * empty. + * + * \note User functions with allocated resources (lookup tables, buffers, + * external library handles) must clean them up here to prevent memory leaks. + * If using the global part interface, ensure proper coordination with + * gGlobalUserFcn cleanup. */ PUserFcn::~PUserFcn() { @@ -51,16 +68,48 @@ PUserFcn::~PUserFcn() //------------------------------------------------------ /** - *

user function example: polynome of 3rd order + * \brief Evaluates the third-order polynomial at the given time. * - * \f[ = \sum_{k=0}^3 c_k t^k \f] + * Computes a cubic polynomial of the form: * - * meaning of paramValues: \f$c_0\f$, \f$c_1\f$, \f$c_2\f$, \f$c_3\f$ + * \f[ P(t) = c_0 + c_1 t + c_2 t^2 + c_3 t^3 = \sum_{k=0}^{3} c_k t^k \f] * - * return: function value + * where the coefficients \f$c_k\f$ are provided in the parameter vector. * - * \param t time in \f$(\mu\mathrm{s})\f$, or x-axis value for non-muSR fit - * \param param parameter vector + * \section puserfcn_op_example Example MSR Configuration + * + * \code + * FITPARAMETER + * # No Name Value Step Pos_Error Boundaries + * 1 c0 0.95 0.01 none + * 2 c1 -0.001 0.0001 none + * 3 c2 0.00001 0.000001 none + * 4 c3 0.0 0.0000001 none 0 none (fixed to zero for quadratic) + * + * THEORY + * userFcn libPUserFcn.so PUserFcn 1 2 3 4 + * \endcode + * + * \section puserfcn_op_notes Implementation Notes + * + * - Uses direct polynomial evaluation (Horner's method could improve + * numerical stability for high-precision applications) + * - Asserts exactly 4 parameters to catch MSR file configuration errors + * - No special handling for negative time values + * + * \param t Independent variable (time in μs for μSR, or general x-axis + * value for non-μSR fits) + * \param param Vector containing exactly 4 polynomial coefficients: + * - param[0]: \f$c_0\f$ - constant term (dimensionless) + * - param[1]: \f$c_1\f$ - linear coefficient (μs⁻¹) + * - param[2]: \f$c_2\f$ - quadratic coefficient (μs⁻²) + * - param[3]: \f$c_3\f$ - cubic coefficient (μs⁻³) + * + * \return The polynomial value \f$P(t)\f$ at the specified time + * + * \pre param.size() == 4 (enforced by assertion) + * + * \see PUserFcnBase::operator() for the virtual interface specification */ Double_t PUserFcn::operator()(Double_t t, const std::vector ¶m) const { diff --git a/src/classes/PUserFcnBase.cpp b/src/classes/PUserFcnBase.cpp index 152f1c3c7..4f0fafc38 100644 --- a/src/classes/PUserFcnBase.cpp +++ b/src/classes/PUserFcnBase.cpp @@ -34,21 +34,58 @@ ClassImp(PUserFcnBase) -//-------------------------------------------------------------------------- -// This function is a replacement for the ParseFile method of TSAXParser. -// It is needed because in certain environments ParseFile does not work but ParseBuffer does. //-------------------------------------------------------------------------- /** - *

Replacement for the ParseFile method of TSAXParser - * that can be used in user functions. + * \brief Parses an XML file using buffer-based parsing for better compatibility. * - *

return: - * - 1 if file cannot be read - * - 0 if the file has been parsed successfully - * - parse error code otherwise + * This function provides a replacement for TSAXParser::ParseFile() that works + * reliably across different environments. Some systems have issues with direct + * file parsing, but buffer-based parsing (ParseBuffer) works consistently. * - * \param saxParser pointer to a TSAXParser object - * \param startup_path_name full path to the XML file to be read + * \section parsexml_usage Usage in User Functions + * + * User functions that need to read XML configuration files should use this + * function instead of TSAXParser::ParseFile(): + * + * \code{.cpp} + * class TMyConfigurableFcn : public PUserFcnBase { + * private: + * MyConfigHandler fHandler; // Derived from TSAXParser callbacks + * + * public: + * Bool_t LoadConfig(const char* configFile) { + * TSAXParser parser; + * parser.ConnectToHandler("MyConfigHandler", &fHandler); + * + * Int_t status = parseXmlFile(&parser, configFile); + * if (status != 0) { + * std::cerr << "Failed to parse config: " << configFile << std::endl; + * return false; + * } + * return true; + * } + * }; + * \endcode + * + * \section parsexml_algorithm Algorithm + * + * 1. Opens the file in binary mode, seeking to end + * 2. Determines file size from stream position + * 3. Allocates buffer and reads entire file + * 4. Passes buffer to TSAXParser::ParseBuffer() + * 5. Cleans up buffer memory + * + * \param saxParser Pointer to a configured TSAXParser object. The parser + * should have its handler connected before calling this function. + * \param startup_path_name Full filesystem path to the XML file to parse. + * + * \return Status code: + * - 0: Success - file parsed without errors + * - 1: File error - could not open or read the file + * - >1: XML parse error from TSAXParser::ParseBuffer() + * + * \see PStartupHandler for an example of XML parsing in musrfit + * \see TSAXParser for ROOT's SAX parser documentation */ Int_t parseXmlFile(TSAXParser *saxParser, const char *startup_path_name) { @@ -76,5 +113,43 @@ Int_t parseXmlFile(TSAXParser *saxParser, const char *startup_path_name) return status; } -// place a void pointer vector for global user function objects which might be needed +//-------------------------------------------------------------------------- +/** + * \brief Global storage for user function objects requiring persistent state. + * + * This vector provides a global container for user functions that need to + * maintain state across multiple evaluations or share data between runs. + * It is primarily used by user functions implementing the "global part" + * interface (NeedGlobalPart(), SetGlobalPart(), GlobalPartIsValid()). + * + * \section gGlobalUserFcn_usage Usage Pattern + * + * User functions with expensive initialization (lookup tables, precomputed + * grids, loaded data files) store their global objects here: + * + * \code{.cpp} + * // In user function's SetGlobalPart implementation: + * void TMyFcn::SetGlobalPart(std::vector &globalPart, UInt_t idx) { + * if (idx < globalPart.size() && globalPart[idx] != nullptr) { + * fGlobal = static_cast(globalPart[idx]); + * } else { + * fGlobal = new MyGlobalData(); + * fGlobal->Initialize(); // Expensive one-time computation + * if (idx < globalPart.size()) + * globalPart[idx] = fGlobal; + * else + * globalPart.push_back(fGlobal); + * } + * } + * \endcode + * + * \note The vector stores void pointers, so user functions must cast + * appropriately and manage memory for their specific data types. + * + * \warning User functions are responsible for proper cleanup of their + * global objects to avoid memory leaks. + * + * \see PUserFcnBase::SetGlobalPart() for the interface to populate this vector + * \see PTheory for how global parts are initialized during theory setup + */ std::vector gGlobalUserFcn; diff --git a/src/include/PUserFcn.h b/src/include/PUserFcn.h index 577c51ea2..2640f1c19 100644 --- a/src/include/PUserFcn.h +++ b/src/include/PUserFcn.h @@ -34,15 +34,79 @@ #include "PUserFcnBase.h" +//-------------------------------------------------------------------------------------------- /** - *

User function example class. Polynome of 3rd order. + * \brief Example user function implementing a third-order polynomial. + * + * PUserFcn demonstrates how to create custom theory functions by deriving + * from PUserFcnBase. This example implements a cubic polynomial: + * + * \f[ P(t) = a_0 + a_1 t + a_2 t^2 + a_3 t^3 \f] + * + * where \f$a_0, a_1, a_2, a_3\f$ are the polynomial coefficients passed + * as fit parameters. + * + * \section puserfcn_usage Usage in MSR File + * + * To use this function in your analysis: + * + * \code + * FITPARAMETER + * # No Name Value Step Pos_Error Boundaries + * 1 a0 1.0 0.1 none + * 2 a1 0.01 0.001 none + * 3 a2 0.001 0.0001 none + * 4 a3 0.0001 0.00001 none + * + * THEORY + * userFcn libPUserFcn.so PUserFcn 1 2 3 4 (a0, a1, a2, a3) + * \endcode + * + * \section puserfcn_applications Applications + * + * Polynomial backgrounds are useful for: + * - Modeling baseline drifts in long-time measurements + * - Phenomenological fits to slowly varying relaxation + * - Testing the user function infrastructure + * + * \section puserfcn_template As a Template + * + * This class serves as a minimal working example for creating custom + * user functions. To create your own: + * + * 1. Copy PUserFcn.h and PUserFcn.cpp + * 2. Rename the class and update the ClassDef/ClassImp macros + * 3. Implement your physics in the operator() method + * 4. Create a LinkDef.h and build as a shared library + * + * \see PUserFcnBase for the abstract interface and detailed implementation guide + * \see PTheory for how user functions are loaded and evaluated */ class PUserFcn : public PUserFcnBase { public: + /// \brief Default constructor. PUserFcn(); + + /// \brief Destructor. ~PUserFcn(); + /** + * \brief Evaluates the third-order polynomial at time t. + * + * Computes: + * \f[ P(t) = \texttt{param[0]} + \texttt{param[1]} \cdot t + * + \texttt{param[2]} \cdot t^2 + \texttt{param[3]} \cdot t^3 \f] + * + * \param t Time value (typically in microseconds) + * \param param Vector of polynomial coefficients: + * - param[0]: constant term \f$a_0\f$ + * - param[1]: linear coefficient \f$a_1\f$ (per μs) + * - param[2]: quadratic coefficient \f$a_2\f$ (per μs²) + * - param[3]: cubic coefficient \f$a_3\f$ (per μs³) + * + * \return The polynomial value at time t + */ Double_t operator()(Double_t t, const std::vector ¶m) const; ClassDef(PUserFcn, 1) diff --git a/src/include/PUserFcnBase.h b/src/include/PUserFcnBase.h index f865374ac..d1a30c52f 100644 --- a/src/include/PUserFcnBase.h +++ b/src/include/PUserFcnBase.h @@ -37,52 +37,134 @@ //-------------------------------------------------------------------------------------------- /** - *

Abstract base class for user-defined theory functions. + * \brief Abstract base class for user-defined theory functions in musrfit. * - *

PUserFcnBase enables extending musrfit with custom theory functions - * beyond the 33 built-in functions. Users create derived classes implementing + * PUserFcnBase enables extending musrfit with custom theory functions + * beyond the 34 built-in functions. Users create derived classes implementing * specific physics models, compile them into shared libraries, and load them - * dynamically at runtime. + * dynamically at runtime via ROOT's plugin mechanism. * - *

Use cases: + * \section userfcn_use_cases Use Cases + * + * User functions are valuable for: * - Novel relaxation mechanisms not in standard library - * - Material-specific models (e.g., Skyrmion lattices) - * - Complex multi-component functions - * - Proprietary or experimental theory functions - * - Functions requiring external libraries (GSL, CUDA, etc.) + * - Material-specific models (e.g., Skyrmion lattices, spin ice) + * - Complex multi-component functions requiring custom logic + * - Proprietary or experimental theory functions under development + * - Functions requiring external libraries (GSL, CUDA, MKL, etc.) + * - Performance-critical implementations with custom optimization * - *

Implementation steps: - * 1. Create a class deriving from PUserFcnBase - * 2. Implement operator()(t, param) with your theory - * 3. Optionally implement global part for heavy initialization - * 4. Compile to shared library (.so/.dylib/.dll) - * 5. Reference in MSR file THEORY block: "userFcn libMyFunc TMyFuncClass" + * \section userfcn_implementation Implementation Guide + * + * Step 1: Create header file (MyUserFcn.h) + * \code{.cpp} + * #ifndef MY_USER_FCN_H + * #define MY_USER_FCN_H + * + * #include "PUserFcnBase.h" * - *

Example minimal implementation: - * @code * class TMyRelaxation : public PUserFcnBase { * public: - * Double_t operator()(Double_t t, const std::vector &par) const { - * // par[0] = rate, par[1] = exponent, par[2] = time shift - * Double_t tt = t - par[2]; - * if (tt < 0) return 0.0; - * return exp(-pow(par[0]*tt, par[1])); - * } + * TMyRelaxation() {} + * virtual ~TMyRelaxation() {} + * + * virtual Double_t operator()(Double_t t, const std::vector &par) const; + * * ClassDef(TMyRelaxation, 1) * }; - * @endcode * - *

Global part: For expensive one-time computations (lookup tables, - * matrix inversions), override NeedGlobalPart(), SetGlobalPart(), and - * GlobalPartIsValid(). The global part is initialized once and shared across - * all fit iterations. + * #endif + * \endcode * - *

MSR file usage: - * @code + * Step 2: Implement source file (MyUserFcn.cpp) + * \code{.cpp} + * #include "MyUserFcn.h" + * #include + * + * ClassImp(TMyRelaxation) + * + * Double_t TMyRelaxation::operator()(Double_t t, const std::vector &par) const { + * // par[0] = rate (lambda), par[1] = exponent (beta) + * if (t < 0) return 1.0; + * return exp(-pow(par[0] * t, par[1])); + * } + * \endcode + * + * Step 3: Create LinkDef file (MyUserFcnLinkDef.h) + * \code{.cpp} + * #ifdef __CINT__ + * #pragma link off all globals; + * #pragma link off all classes; + * #pragma link off all functions; + * + * #pragma link C++ class TMyRelaxation+; + * #endif + * \endcode + * + * Step 4: Build shared library + * \code{.sh} + * rootcint -f MyUserFcnDict.cxx -c MyUserFcn.h MyUserFcnLinkDef.h + * g++ -shared -fPIC -o libMyUserFcn.so MyUserFcn.cpp MyUserFcnDict.cxx \ + * $(root-config --cflags --libs) -I$MUSRFIT/include + * \endcode + * + * Step 5: Use in MSR file + * \code * THEORY * asymmetry 1 - * userFcn libMyRelax.so TMyRelaxation map1 2 0.5 (rate, expo, tshift) - * @endcode + * userFcn libMyUserFcn.so TMyRelaxation 2 3 (rate, exponent) + * \endcode + * + * \section userfcn_global Global Part for Expensive Computations + * + * For functions requiring expensive one-time setup (lookup tables, matrix + * decompositions, file loading), implement the global part interface: + * + * \code{.cpp} + * class TMyComplexFcn : public PUserFcnBase { + * private: + * mutable void *fGlobal; // Pointer to global data + * + * public: + * virtual Bool_t NeedGlobalPart() const { return true; } + * + * virtual void SetGlobalPart(std::vector &globalPart, UInt_t idx) { + * if (idx < globalPart.size() && globalPart[idx] != nullptr) { + * fGlobal = globalPart[idx]; // Reuse existing + * } else { + * fGlobal = new MyGlobalData(); // Create new + * static_cast(fGlobal)->Initialize(); + * if (idx < globalPart.size()) + * globalPart[idx] = fGlobal; + * else + * globalPart.push_back(fGlobal); + * } + * } + * + * virtual Bool_t GlobalPartIsValid() const { + * return fGlobal != nullptr; + * } + * + * // ... operator() uses fGlobal for fast lookup + * }; + * \endcode + * + * \section userfcn_parameters Parameter Conventions + * + * In the MSR file THEORY block: + * \code + * userFcn libName.so ClassName param1 param2 ... paramN + * \endcode + * + * Parameters can be: + * - Direct numbers: \c 1, \c 2 → parameter indices from FITPARAMETER block + * - Map references: \c map1, \c map2 → via RUN block map + * - Function references: \c fun1, \c fun2 → evaluated FUNCTIONS + * + * Convention: The last parameter is typically a time shift. + * + * \see PTheory for how user functions are loaded and called + * \see PUserFcn for a simple example implementation */ class PUserFcnBase : public TObject {