From e5b2da256dc87694eb471f9424f38350f1e04e83 Mon Sep 17 00:00:00 2001 From: Zenny Date: Fri, 23 Oct 2026 11:28:38 +0200 Subject: [PATCH] MadMatrix: UMAMI parameter setters/getters and model/fptype-tagged libraries - umami_set_parameter / umami_get_parameter: runtime access to independent model parameters (by name or SLHA location) and to parton masses, for the MadMatrix backends (cpu/simd/gpu umami.cc) and the mg7 C++ api; the exporters register every scalar parameter/coupling in a name -> pointer table. Couplings and parameters are registered into separate maps so that FPTYPE=f builds compile (couplings are cxtype, parameters stay double). - Process and common libraries now carry the model name and FPTYPE (libmadmatrix____.so), so builds for different models/precisions out of the same directory never collide; me_path in subprocesses.json gains an {fptype} placeholder, resolved by mg7 launch.py and gridpack.py from [run] precision. - madmatrix.mk/madmatrix_src.mk pass FPTYPE through to the common library build and clean targets. Co-Authored-By: Claude Opus 5.5 --- madgraph/iolibs/export_cpp.py | 136 +++++++++++++++++- .../cpp_model_parameters_cc.inc | 66 ++++++++- .../template_files/cpp_model_parameters_h.inc | 43 ++++++ .../template_files/cpp_process_class.inc | 6 + .../cpp_process_function_definitions.inc | 13 +- .../madmatrix/backend/cpu/umami.cc | 36 +++++ .../madmatrix/backend/gpu/umami.cc | 36 +++++ .../madmatrix/backend/simd/umami.cc | 36 +++++ .../madmatrix/cpp_model_parameters_cc.inc | 93 ++++++++++++ .../madmatrix/cpp_model_parameters_h.inc | 56 ++++++++ .../template_files/madmatrix/madmatrix.mk | 39 +++-- .../template_files/madmatrix/madmatrix_src.mk | 13 +- .../madmatrix/process_class.inc | 15 ++ .../process_function_definitions.inc | 23 +++ madgraph/iolibs/template_files/mg7/api.cpp | 35 ++++- madgraph/iolibs/template_files/mg7/api.h | 28 +++- .../iolibs/template_files/mg7/gridpack.py | 17 ++- madgraph/iolibs/template_files/mg7/launch.py | 16 ++- madmatrix/model_handling.py | 62 +++++++- madmatrix/output.py | 13 +- madspace/include/madspace/umami.h | 10 +- tests/unit_tests/iolibs/test_export_cpp.py | 1 + 22 files changed, 754 insertions(+), 39 deletions(-) diff --git a/madgraph/iolibs/export_cpp.py b/madgraph/iolibs/export_cpp.py index e70cbfe98c..b0476553c7 100755 --- a/madgraph/iolibs/export_cpp.py +++ b/madgraph/iolibs/export_cpp.py @@ -121,7 +121,15 @@ def __init__(self, model, output_path, wanted_lorentz = [], self.params_dep = [] # base_objects.ModelVariable self.params_indep = [] # base_objects.ModelVariable self.coups_flv_dep = [] # (name, object, [couplings]) - self.coups_flv_indep = [] # (name, object, [couplings]) + self.coups_flv_indep = [] # (name, object, [couplings]) + self.params_ext_names = [] # names of independent parameters read directly + # from the SLHA card (as opposed to internal + # parameters/couplings derived from them); + # these are the only ones settable by name + # at runtime (e.g. via the UMAMI interface) + self.params_ext_slha = [] # (name, lhablock.lower(), lhacode) for the same + # parameters, so they can also be addressed by + # their SLHA card location (block + index) self.p_to_cpp = parsers.UFOExpressionParserCPP() # Prepare parameters and couplings for writeout in C++ @@ -184,6 +192,18 @@ def prepare_parameters(self): % (param.name, param.lhablock.lower(), param.value.real) else: raise MadGraph5Error("Only support for SLHA blocks with 1 or 2 indices") + # Track which independent parameters were read directly from the + # SLHA card (as opposed to internal parameters derived from them): + # these are the only ones settable by name at runtime (e.g. via + # the UMAMI set_parameter interface). NB: this list is consumed + # only by write_set_parameters() below, not by param.expr itself, + # since other UFOModelConverterCPP subclasses (e.g. the MadMatrix/ + # cudacpp backend) read param.expr directly for purposes (like + # generating hardcoded/constexpr parameter mirrors) that require + # it to remain a single plain assignment. + self.params_ext_names.append(param.name) + self.params_ext_slha.append((param.name, param.lhablock.lower(), + list(param.lhacode))) self.params_indep.insert(0, base_objects.ModelVariable(param.name, expression, @@ -314,6 +334,14 @@ def generate_parameters_class_files(self): replace_dict['set_flv_couplings'] = \ self.write_flv_couplings(self.coups_flv_indep) + replace_dict['register_parameters'] = \ + self.write_register_parameters( + self.params_indep + self.params_dep + \ + self.coups_indep + list(self.coups_dep.values()), + self.params_ext_names) + replace_dict['register_slha_keys'] = \ + self.write_register_slha_keys(self.params_ext_slha) + replace_dict['print_independent_parameters'] = \ self.write_print_parameters(self.params_indep) replace_dict['print_independent_couplings'] = \ @@ -360,11 +388,21 @@ def write_parameters(self, params): def write_set_parameters(self, params): """Write out the lines of independent parameters""" - # For each parameter, write name = expr; + # For each parameter, write name = expr;, unless a runtime override + # was set by name for this (necessarily independent, SLHA-card-level) + # parameter (e.g. via the UMAMI set_parameter interface), in which + # case the override takes precedence over the value read from the + # param card res_strings = [] for param in params: - res_strings.append("%s" % param.expr) + if param.name in self.params_ext_names: + res_strings.append( + "if (m_param_overrides.find(\"%s\") != m_param_overrides.end())\n" + "%s = m_param_overrides[\"%s\"];\nelse\n%s" % + (param.name, param.name, param.name, param.expr)) + else: + res_strings.append("%s" % param.expr) # Correct width sign for Majorana particles (where the width # and mass need to have the same sign) @@ -377,6 +415,55 @@ def write_set_parameters(self, params): return "\n".join(res_strings) + def write_register_parameters(self, params, ext_names, complex_map_name='m_complex_params'): + """Write out the code that registers name->pointer entries for every + scalar parameter and coupling (independent or dependent), and marks + which independent parameters were read directly from the SLHA card + (as opposed to being derived internally). This lookup-by-name table + backs runtime access to parameters/couplings, e.g. through the UMAMI + set_parameter/get_parameter interface. FLV_COUPLING entries are + skipped: they are not simple scalar values. + + complex_map_name: which C++ map a complex-valued param's pointer + gets registered into (default 'm_complex_params'). Model parameters + (mdl_*) and derived couplings (GC_*) are *not* interchangeable + here in the mg7/madmatrix backend: couplings are declared cxtype + (which the caller may have templated to a non-double precision -- + see model_handling.py's own 'cxsmpl' -> 'cxtype' rewrite + for independent couplings specifically), while parameters always + stay plain cxsmpl for numerical accuracy (matching + m_real_params' own always-double real-parameter pointers) -- so a + caller registering both groups needs two separate calls, one per + map, each with its own C++ declaration of matching pointer type; + passing both groups into a single call/map only compiles by + accident whenever cxtype happens to resolve to cxsmpl too + (FPTYPE=m/d), and fails to compile at FPTYPE=f.""" + + lines = [] + for param in params: + if not hasattr(param, 'type'): + continue + map_name = 'm_real_params' if param.type == 'real' \ + else complex_map_name + lines.append('%s["%s"] = &%s;' % (map_name, param.name, param.name)) + for name in ext_names: + lines.append('m_settable_params.insert("%s");' % name) + + return "\n".join(lines) + + def write_register_slha_keys(self, params_ext_slha): + """Write out the code that registers a lookup from SLHA (block, index[, + index2]) key strings to parameter names, so that a settable parameter + can also be addressed by its location in the SLHA param card (e.g. "mass + 6") rather than only by its model name (e.g. "mdl_MT").""" + + lines = [] + for name, block, lhacode in params_ext_slha: + key = block + "".join(" %d" % i for i in lhacode) + lines.append('m_slha_key_to_name["%s"] = "%s";' % (key, name)) + + return "\n".join(lines) + def _assert_flv_couplings_supported(self, params): """Refuse, with a clear and actionable message, the merged-flavor coupling structures the C++ (mg7/standalone) backend cannot yet @@ -1053,6 +1140,8 @@ def get_process_function_definitions(self, write=True): replace_dict['initProc_lines'] = \ self.get_initProc_lines(self.matrix_elements[0], color_amplitudes) + replace_dict['refreshMasses_lines'] = \ + self.get_refresh_masses_lines(self.matrix_elements[0]) replace_dict['reset_jamp_lines'] = \ self.get_reset_jamp_lines(color_amplitudes) replace_dict['sigmaKin_lines'], other_replace = \ @@ -1138,6 +1227,17 @@ def get_initProc_lines(self, matrix_element, color_amplitudes): return "\n".join(initProc_lines) + # Companion to get_initProc_lines: the same external-mass push_back list, without the + # jamp2 allocation (jamp2 is allocated once in the constructor and must not be + # reallocated every time CPPProcess::refreshMasses() re-populates mME). + def get_refresh_masses_lines(self, matrix_element): + """Get refreshMasses_lines for function definition for CPPProcess::refreshMasses""" + + lines = [] + for part in matrix_element.get_external_wavefunctions(): + lines.append("mME.push_back(pars.%s);" % part.get('mass')) + return "\n".join(lines) + def get_reset_jamp_lines(self, color_amplitudes): """Get lines to reset jamps""" @@ -3251,9 +3351,20 @@ class ProcessExporterMG7(ProcessExporterCPP): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.me_lib_format = args[1].get("me_lib_format", None) + self.me_lib_format = args[1].get("me_lib_format", None) or pjoin("SubProcesses", "{process_id}", "api.so") self.process_info = [] self.merged_subprocesses = defaultdict(list) + # Lazy import: madgraph.iolibs.export_mg7 imports export_cpp itself, so a top-level + # import here would be circular. Without this, oneprocessclass silently falls back + # to the base ProcessExporterCPP.oneprocessclass (OneProcessExporterCPP), which lacks + # the .name attribute (and other MG7-specific behaviour) generate_subprocess_directory + # needs. Only apply the fallback when nothing more specific was set: subclasses such + # as madmatrix.output.ProcessExporterMadMatrix set their own oneprocessclass (with the + # coloramps.h/epoch_process_id.h/color_sum.cc/makefile-symlink logic in + # OneProcessExporterMadMatrix.generate_process_files()) and that must not be clobbered. + if self.oneprocessclass is OneProcessExporterCPP: + from madgraph.iolibs import export_mg7 + self.oneprocessclass = export_mg7.OneProcessExporterMG7 def generate_subprocess_directory( self, matrix_element, cpp_helas_call_writer, proc_number=None @@ -3268,6 +3379,13 @@ def generate_subprocess_directory( # Create the directory PN_xx_xxxxx in the specified path proc_dir_name = process_exporter_mg7.name dirpath = pjoin(self.dir_path, 'SubProcesses', proc_dir_name) + # self.model/self.model_name aren't reliably set on this class by the + # time subprocess directories are generated (see eg + # write_check_sa_cpp()'s own "self.model may not be set yet" fallback + # above); the matrix element's own model is always available though, + # and is what %(model)s in template_Sub_make/template_src_make is + # substituted from too (via copy_template()'s model parameter). + model = matrix_element.get('processes')[0].get('model') try: os.mkdir(dirpath) @@ -3284,7 +3402,6 @@ def generate_subprocess_directory( # Generate SVG Feynman diagrams (diagrams.svg + diagrams.json) if not self.opt.get('output_options', {}).get('noeps') == 'True': svg_stem = pjoin(dirpath, 'diagrams') - model = matrix_element.get('processes')[0].get('model') diagrams = matrix_element.get('base_amplitude').get('diagrams') logger.info('Generating Feynman diagrams for %s' % matrix_element.get('processes')[0].nice_string()) @@ -3292,7 +3409,14 @@ def generate_subprocess_directory( model=model, amplitude=True) plot.draw() - me_lib_path = self.me_lib_format.format(process_id = proc_dir_name) + # me_lib_format may (eg madmatrix/output.py's ProcessExporterMadMatrix) + # carry a {model} placeholder alongside {process_id}, resolved here to + # the same sanitized model name template_Sub_make's %(model)s uses; + # harmless (str.format ignores unused kwargs) for exporters whose + # me_lib_format has no {model} placeholder at all. + me_lib_path = self.me_lib_format.format( + process_id=proc_dir_name, model=self.get_model_name(model.get('name')) + ) # Store the path relative to the process directory (like me_path): an # absolute path would break as soon as the process directory is moved. rel_dirpath = pjoin('SubProcesses', proc_dir_name) diff --git a/madgraph/iolibs/template_files/cpp_model_parameters_cc.inc b/madgraph/iolibs/template_files/cpp_model_parameters_cc.inc index 26ba3a3668..c3b0222785 100644 --- a/madgraph/iolibs/template_files/cpp_model_parameters_cc.inc +++ b/madgraph/iolibs/template_files/cpp_model_parameters_cc.inc @@ -3,16 +3,21 @@ %(info_lines)s //========================================================================== +#include +#include #include #include +#include #include "Parameters_%(model_name)s.h" void Parameters_%(model_name)s::setIndependentParameters(SLHAReader& slha){ // Define "zero" zero=0; ZERO=0; - // Prepare a vector for indices + // Prepare a vector for indices vector indices(2,0); + m_slha = slha; + registerParameters(); %(set_independent_parameters)s } void Parameters_%(model_name)s::setIndependentCouplings(){ @@ -44,3 +49,62 @@ %(print_dependent_couplings)s } + void Parameters_%(model_name)s::registerParameters(){ + %(register_parameters)s + %(register_slha_keys)s + } + + bool Parameters_%(model_name)s::resolveSlhaKey(const std::string& name, std::string& resolved) const { + std::istringstream iss(name); + std::string block; + if (!(iss >> block)) return false; + std::transform(block.begin(), block.end(), block.begin(), + [](unsigned char c){ return std::tolower(c); }); + std::string key = block; + bool any_index = false; + int index; + while (iss >> index) { + key += " " + std::to_string(index); + any_index = true; + } + if (!any_index) return false; + std::map::const_iterator it = m_slha_key_to_name.find(key); + if (it == m_slha_key_to_name.end()) return false; + resolved = it->second; + return true; + } + + bool Parameters_%(model_name)s::setParameterByName(std::string name, double real, double imag){ + (void)imag; // settable (SLHA-card-level) parameters are always real + std::string resolved; + if (resolveSlhaKey(name, resolved)) name = resolved; + if (m_settable_params.find(name) == m_settable_params.end()) + return false; + m_param_overrides[name] = real; + setIndependentParameters(m_slha); + setIndependentCouplings(); + return true; + } + + bool Parameters_%(model_name)s::getParameterByName(std::string name, double* real, double* imag){ + std::string resolved; + if (resolveSlhaKey(name, resolved)) name = resolved; + map::iterator real_it = m_real_params.find(name); + if (real_it != m_real_params.end()){ + *real = *(real_it->second); + if (imag) *imag = 0.; + return true; + } + map *>::iterator complex_it = m_complex_params.find(name); + if (complex_it != m_complex_params.end()){ + *real = complex_it->second->real(); + if (imag) *imag = complex_it->second->imag(); + return true; + } + return false; + } + + void Parameters_%(model_name)s::resetParameterOverrides(){ + m_param_overrides.clear(); + } + diff --git a/madgraph/iolibs/template_files/cpp_model_parameters_h.inc b/madgraph/iolibs/template_files/cpp_model_parameters_h.inc index 7fa3fb27e8..4f044b9f04 100644 --- a/madgraph/iolibs/template_files/cpp_model_parameters_h.inc +++ b/madgraph/iolibs/template_files/cpp_model_parameters_h.inc @@ -7,6 +7,9 @@ #define Parameters_%(model_name)s_H #include +#include +#include +#include #include "read_slha.h" using namespace std; @@ -48,6 +51,46 @@ void printIndependentCouplings(); void printDependentParameters(); // Print couplings that are changed event by event void printDependentCouplings(); + +// Set the value of an independent, SLHA-card-level parameter by name at +// runtime (overriding the value read from the param card), and recompute +// every parameter/coupling that depends on it. Returns false if "name" is +// not the name of a settable independent parameter (e.g. it names an +// internal parameter derived from others, or a coupling, neither of which +// can be set directly since they would just be recomputed from the +// underlying parameters on the next call). "name" may be either the +// parameter's model name (e.g. "mdl_MT") or its SLHA card location, given as +// " []" (e.g. "mass 6"); the block name is matched +// case-insensitively. +bool setParameterByName(std::string name, double real, double imag); +// Retrieve the current value of any parameter or coupling by name (real or +// complex, independent or dependent). As with setParameterByName, "name" may +// be either the parameter's model name or its SLHA card location. Returns +// false if "name" is unknown. +bool getParameterByName(std::string name, double* real, double* imag); +// Drop all overrides previously set via setParameterByName(), so that the +// next setIndependentParameters(slha) call starts from a clean slate (e.g. +// when (re-)initializing from a param card). Does not itself recompute +// anything; the caller is expected to call setIndependentParameters()/ +// setIndependentCouplings() afterward (as CPPProcess's constructor does). +void resetParameterOverrides(); + +private: + +// If "name" is of the form " []", resolve it (via +// m_slha_key_to_name) to the corresponding parameter's model name and return +// true; otherwise leave "resolved" untouched and return false. +bool resolveSlhaKey(const std::string& name, std::string& resolved) const; + +// Name-keyed lookup tables populated by registerParameters(), backing +// setParameterByName()/getParameterByName() above. +void registerParameters(); +std::map m_real_params; +std::map *> m_complex_params; +std::set m_settable_params; +std::map m_param_overrides; +std::map m_slha_key_to_name; +SLHAReader m_slha; }; #endif // Parameters_%(model_name)s_H diff --git a/madgraph/iolibs/template_files/cpp_process_class.inc b/madgraph/iolibs/template_files/cpp_process_class.inc index b64dfd4f2f..3739efb726 100644 --- a/madgraph/iolibs/template_files/cpp_process_class.inc +++ b/madgraph/iolibs/template_files/cpp_process_class.inc @@ -29,6 +29,12 @@ public: const vector& getMasses() const {return mME;} + // Recompute mME (external particle on-shell masses) from the current model parameters + // (pars). mME is only populated once, in the constructor; call this after overriding a + // mass via pars.setParameterByName() (e.g. through umami_set_parameter) so getMasses() + // reflects the new value. + void refreshMasses(); + const double* getAmp2() const { return amp2; } Parameters_%(model_name)s& getParameters() { return pars; } diff --git a/madgraph/iolibs/template_files/cpp_process_function_definitions.inc b/madgraph/iolibs/template_files/cpp_process_function_definitions.inc index db3a44fee6..79a3be1664 100644 --- a/madgraph/iolibs/template_files/cpp_process_function_definitions.inc +++ b/madgraph/iolibs/template_files/cpp_process_function_definitions.inc @@ -10,13 +10,22 @@ CPPProcess::CPPProcess(string param_card_name) : { // Instantiate the model class and set parameters that stay fixed during run SLHAReader slha(param_card_name, false); + pars.resetParameterOverrides(); pars.setIndependentParameters(slha); pars.setIndependentCouplings(); %(initProc_lines)s -} +} + +//-------------------------------------------------------------------------- +// Re-populate mME from the current model parameters (pars). See declaration in CPPProcess.h. + +void CPPProcess::refreshMasses() { + mME.clear(); + %(refreshMasses_lines)s +} //-------------------------------------------------------------------------- -// Evaluate |M|^2, part independent of incoming flavour. +// Evaluate |M|^2, part independent of incoming flavour. double CPPProcess::sigmaKin(int flavor_id) { // Set the parameters which change event by event diff --git a/madgraph/iolibs/template_files/madmatrix/backend/cpu/umami.cc b/madgraph/iolibs/template_files/madmatrix/backend/cpu/umami.cc index 8890a871b4..3b666c3453 100644 --- a/madgraph/iolibs/template_files/madmatrix/backend/cpu/umami.cc +++ b/madgraph/iolibs/template_files/madmatrix/backend/cpu/umami.cc @@ -14,6 +14,7 @@ #include #include +#include #include #include #include @@ -218,7 +219,36 @@ extern "C" static_cast( handle )->summed_helicity = parameter_real != 0; return UMAMI_SUCCESS; } + // Parameters are process-wide (Parameters::getInstance()), not per-handle, + // matching how umami_initialize itself never stores the CPPProcess it + // constructs: initProc() only ever populates the Parameters singleton. +#ifdef MGONGPU_HARDCODE_PARAM + // Hardcoded parameters are compile-time constants: there is nothing to set. return UMAMI_ERROR_NOT_IMPLEMENTED; +#else + if( !Parameters::getInstance()->setParameterByName( name, parameter_real, parameter_imag ) ) + return UMAMI_ERROR_UNKNOWN_PARAMETER; + // sigmaKin() and friends read cached copies of the independent parameters/couplings + // (cIPD/cIPC/cIPF/bsmIndepParam), not the Parameters singleton itself, for + // performance; refresh those caches now so the override actually takes effect. + CPPProcess::refreshIndependentParams(); + // Keep g_externalMasses in sync with the (possibly updated) singleton. + // Emit a one-time warning if a mass parameter was changed, since any phase-space + // generator that cached masses from a prior umami_get_meta(UMAMI_META_MASSES,...) call + // will need to re-query them. + { + std::vector updatedMasses; + CPPProcess::refreshExternalMasses( updatedMasses ); + bool changed = ( updatedMasses.size() != g_externalMasses.size() ); + for( size_t i = 0; !changed && i < updatedMasses.size(); ++i ) + changed = ( static_cast( updatedMasses[i] ) != g_externalMasses[i] ); + if( changed ) + { + g_externalMasses.assign( updatedMasses.begin(), updatedMasses.end() ); + } + } + return UMAMI_SUCCESS; +#endif } UmamiStatus umami_get_parameter( @@ -227,7 +257,13 @@ extern "C" [[maybe_unused]] double* parameter_real, [[maybe_unused]] double* parameter_imag ) { +#ifdef MGONGPU_HARDCODE_PARAM return UMAMI_ERROR_NOT_IMPLEMENTED; +#else + if( !Parameters::getInstance()->getParameterByName( name, parameter_real, parameter_imag ) ) + return UMAMI_ERROR_UNKNOWN_PARAMETER; + return UMAMI_SUCCESS; +#endif } UmamiStatus umami_matrix_element( diff --git a/madgraph/iolibs/template_files/madmatrix/backend/gpu/umami.cc b/madgraph/iolibs/template_files/madmatrix/backend/gpu/umami.cc index 5c0402444b..d86655a8e6 100644 --- a/madgraph/iolibs/template_files/madmatrix/backend/gpu/umami.cc +++ b/madgraph/iolibs/template_files/madmatrix/backend/gpu/umami.cc @@ -15,6 +15,7 @@ #include #include +#include #include #include #include @@ -295,7 +296,36 @@ extern "C" static_cast( handle )->summed_helicity = parameter_real != 0; return UMAMI_SUCCESS; } + // Parameters are process-wide (Parameters::getInstance()), not per-handle, + // matching how umami_initialize itself never stores the CPPProcess it + // constructs: initProc() only ever populates the Parameters singleton. +#ifdef MGONGPU_HARDCODE_PARAM + // Hardcoded parameters are compile-time constants: there is nothing to set. return UMAMI_ERROR_NOT_IMPLEMENTED; +#else + if( !Parameters::getInstance()->setParameterByName( name, parameter_real, parameter_imag ) ) + return UMAMI_ERROR_UNKNOWN_PARAMETER; + // sigmaKin() and friends read cached copies of the independent parameters/couplings + // (cIPD/cIPC/cIPF/bsmIndepParam), not the Parameters singleton itself, for + // performance; refresh those caches now so the override actually takes effect. + CPPProcess::refreshIndependentParams(); + // Keep g_externalMasses in sync with the (possibly updated) singleton. + // Emit a one-time warning if a mass parameter was changed, since any phase-space + // generator that cached masses from a prior umami_get_meta(UMAMI_META_MASSES,...) call + // will need to re-query them. + { + std::vector updatedMasses; + CPPProcess::refreshExternalMasses( updatedMasses ); + bool changed = ( updatedMasses.size() != g_externalMasses.size() ); + for( size_t i = 0; !changed && i < updatedMasses.size(); ++i ) + changed = ( static_cast( updatedMasses[i] ) != g_externalMasses[i] ); + if( changed ) + { + g_externalMasses.assign( updatedMasses.begin(), updatedMasses.end() ); + } + } + return UMAMI_SUCCESS; +#endif } UmamiStatus umami_get_parameter( @@ -304,7 +334,13 @@ extern "C" [[maybe_unused]] double* parameter_real, [[maybe_unused]] double* parameter_imag ) { +#ifdef MGONGPU_HARDCODE_PARAM return UMAMI_ERROR_NOT_IMPLEMENTED; +#else + if( !Parameters::getInstance()->getParameterByName( name, parameter_real, parameter_imag ) ) + return UMAMI_ERROR_UNKNOWN_PARAMETER; + return UMAMI_SUCCESS; +#endif } UmamiStatus umami_matrix_element( diff --git a/madgraph/iolibs/template_files/madmatrix/backend/simd/umami.cc b/madgraph/iolibs/template_files/madmatrix/backend/simd/umami.cc index a76fd1f7b3..4e33dc190c 100644 --- a/madgraph/iolibs/template_files/madmatrix/backend/simd/umami.cc +++ b/madgraph/iolibs/template_files/madmatrix/backend/simd/umami.cc @@ -14,6 +14,7 @@ #include #include +#include #include #include #include @@ -219,7 +220,36 @@ extern "C" static_cast( handle )->summed_helicity = parameter_real != 0; return UMAMI_SUCCESS; } + // Parameters are process-wide (Parameters::getInstance()), not per-handle, + // matching how umami_initialize itself never stores the CPPProcess it + // constructs: initProc() only ever populates the Parameters singleton. +#ifdef MGONGPU_HARDCODE_PARAM + // Hardcoded parameters are compile-time constants: there is nothing to set. return UMAMI_ERROR_NOT_IMPLEMENTED; +#else + if( !Parameters::getInstance()->setParameterByName( name, parameter_real, parameter_imag ) ) + return UMAMI_ERROR_UNKNOWN_PARAMETER; + // sigmaKin() and friends read cached copies of the independent parameters/couplings + // (cIPD/cIPC/cIPF/bsmIndepParam), not the Parameters singleton itself, for + // performance; refresh those caches now so the override actually takes effect. + CPPProcess::refreshIndependentParams(); + // Keep g_externalMasses in sync with the (possibly updated) singleton. + // Emit a one-time warning if a mass parameter was changed, since any phase-space + // generator that cached masses from a prior umami_get_meta(UMAMI_META_MASSES,...) call + // will need to re-query them. + { + std::vector updatedMasses; + CPPProcess::refreshExternalMasses( updatedMasses ); + bool changed = ( updatedMasses.size() != g_externalMasses.size() ); + for( size_t i = 0; !changed && i < updatedMasses.size(); ++i ) + changed = ( static_cast( updatedMasses[i] ) != g_externalMasses[i] ); + if( changed ) + { + g_externalMasses.assign( updatedMasses.begin(), updatedMasses.end() ); + } + } + return UMAMI_SUCCESS; +#endif } UmamiStatus umami_get_parameter( @@ -228,7 +258,13 @@ extern "C" [[maybe_unused]] double* parameter_real, [[maybe_unused]] double* parameter_imag ) { +#ifdef MGONGPU_HARDCODE_PARAM return UMAMI_ERROR_NOT_IMPLEMENTED; +#else + if( !Parameters::getInstance()->getParameterByName( name, parameter_real, parameter_imag ) ) + return UMAMI_ERROR_UNKNOWN_PARAMETER; + return UMAMI_SUCCESS; +#endif } UmamiStatus umami_matrix_element( diff --git a/madgraph/iolibs/template_files/madmatrix/cpp_model_parameters_cc.inc b/madgraph/iolibs/template_files/madmatrix/cpp_model_parameters_cc.inc index 5d465f831e..094964fea4 100644 --- a/madgraph/iolibs/template_files/madmatrix/cpp_model_parameters_cc.inc +++ b/madgraph/iolibs/template_files/madmatrix/cpp_model_parameters_cc.inc @@ -13,8 +13,11 @@ #include "Parameters.h" +#include +#include #include #include +#include using namespace madmatrix; @@ -38,6 +41,8 @@ Parameters::setIndependentParameters( SLHAReader& slha ) zero = 0; // define "zero" ZERO = 0; // define "zero" std::vector indices( 2, 0 ); // prepare a vector for indices + m_slha = slha; + registerParameters(); %(set_independent_parameters)s } @@ -48,6 +53,94 @@ Parameters::setIndependentCouplings() %(set_flv_couplings)s } +void +Parameters::registerParameters() +{ + %(register_parameters)s + %(register_slha_keys)s +} + +bool +Parameters::resolveSlhaKey( const std::string& name, std::string& resolved ) const +{ + std::istringstream iss( name ); + std::string block; + if( !( iss >> block ) ) return false; + std::transform( block.begin(), block.end(), block.begin(), + []( unsigned char c ) { return std::tolower( c ); } ); + std::string key = block; + bool any_index = false; + int index; + while( iss >> index ) + { + key += " " + std::to_string( index ); + any_index = true; + } + if( !any_index ) return false; + std::map::const_iterator it = m_slha_key_to_name.find( key ); + if( it == m_slha_key_to_name.end() ) return false; + resolved = it->second; + return true; +} + +bool +Parameters::setParameterByName( std::string name, double real, double imag ) +{ + (void)imag; // settable (SLHA-card-level) parameters are always real + std::string resolved; + if( resolveSlhaKey( name, resolved ) ) name = resolved; + if( m_settable_params.find( name ) == m_settable_params.end() ) + return false; + m_param_overrides[name] = real; + setIndependentParameters( m_slha ); + setIndependentCouplings(); + return true; +} + +bool +Parameters::getParameterByName( std::string name, double* real, double* imag ) +{ + std::string resolved; + if( resolveSlhaKey( name, resolved ) ) name = resolved; + std::map::iterator real_it = m_real_params.find( name ); + if( real_it != m_real_params.end() ) + { + *real = *( real_it->second ); + if( imag ) *imag = 0.; + return true; + } + // cxtype (not hardcoded cxsmpl): must match m_complex_params' + // own value_type (see its declaration's own comment) -- real()/imag() + // below still convert safely into *real/*imag (always plain double* + // here) whatever cxtype's own underlying precision is. + std::map::iterator complex_it = m_complex_params.find( name ); + if( complex_it != m_complex_params.end() ) + { + *real = complex_it->second->real(); + if( imag ) *imag = complex_it->second->imag(); + return true; + } + // Independent model parameters (as opposed to derived couplings just + // above) -- see m_complex_model_params' own declaration comment for why + // this is a second, separate map/type rather than sharing + // m_complex_params. + std::map*>::iterator complex_model_it = + m_complex_model_params.find( name ); + if( complex_model_it != m_complex_model_params.end() ) + { + *real = complex_model_it->second->real(); + if( imag ) *imag = complex_model_it->second->imag(); + return true; + } + return false; +} + +void +Parameters::resetParameterOverrides() +{ + m_param_overrides.clear(); +} + /* void Parameters::setDependentParameters() // now computed event-by-event (running alphas #373) diff --git a/madgraph/iolibs/template_files/madmatrix/cpp_model_parameters_h.inc b/madgraph/iolibs/template_files/madmatrix/cpp_model_parameters_h.inc index ded7bf84d5..8bcfa3e425 100644 --- a/madgraph/iolibs/template_files/madmatrix/cpp_model_parameters_h.inc +++ b/madgraph/iolibs/template_files/madmatrix/cpp_model_parameters_h.inc @@ -34,6 +34,10 @@ #include "read_slha.h" +#include +#include +#include + //One namespace. Split ber backend. namespace madmatrix { @@ -105,8 +109,60 @@ namespace madmatrix static constexpr int nBsmIndepParam = %(nbsmip)i; %(hasbsmip)sdouble mdl_bsmIndepParam[nBsmIndepParam]; + // Set the value of an independent, SLHA-card-level parameter by name at + // runtime (overriding the value read from the param card), and recompute + // every parameter/coupling that depends on it. Returns false if "name" is + // not the name of a settable independent parameter (e.g. it names an + // internal parameter derived from others, or a coupling, neither of which + // can be set directly since they would just be recomputed from the + // underlying parameters on the next call). + // "name" may be either the parameter's model name (e.g. "mdl_MT") or its + // SLHA card location, given as " []" (e.g. "mass + // 6"); the block name is matched case-insensitively. + bool setParameterByName( std::string name, double real, double imag ); + // Retrieve the current value of any independent parameter or coupling by + // name (real or complex; "name" accepts the same two forms as + // setParameterByName). NB: dependent (aS-dependent) parameters and + // couplings are not stored here at all in this backend (they are + // recomputed fresh every event batch by computeDependentCouplings()), so + // they cannot be retrieved by name; only independent ones can. Returns + // false if "name" is unknown. + bool getParameterByName( std::string name, double* real, double* imag ); + // Drop all overrides previously set via setParameterByName(), so that the + // next setIndependentParameters(slha) call starts from a clean slate (e.g. + // when (re-)initializing from a param card). Does not itself recompute + // anything; the caller is expected to call setIndependentParameters()/ + // setIndependentCouplings() afterward (as initProc() does). + void resetParameterOverrides(); + private: + // If "name" is of the form " []", resolve it (via + // m_slha_key_to_name) to the corresponding parameter's model name and + // return true; otherwise leave "resolved" untouched and return false. + bool resolveSlhaKey( const std::string& name, std::string& resolved ) const; + + // Name-keyed lookup tables populated by registerParameters(), backing + // setParameterByName()/getParameterByName() above. + void registerParameters(); + std::map m_real_params; + // Two separate maps, not one: independent *couplings* (eg GC_72 below) + // are declared cxtype, which resolves to cxsmpl at FPTYPE=f, so + // this map's value_type has to be cxtype too (a mismatched hardcoded + // cxsmpl here fails to compile the moment any coupling is + // actually registered below, eg complex effective couplings like + // ggH's) -- while independent model *parameters* (eg mdl_I1x33) always + // stay plain cxsmpl, matching m_real_params' own always-double + // real parameters, and go into m_complex_model_params below instead; + // see write_register_parameters()'s own doc comment (export_cpp.py) + // for why these can't share one map/type. + std::map m_complex_params; + std::map*> m_complex_model_params; + std::set m_settable_params; + std::map m_param_overrides; + std::map m_slha_key_to_name; + SLHAReader m_slha; + static Parameters* instance; }; diff --git a/madgraph/iolibs/template_files/madmatrix/madmatrix.mk b/madgraph/iolibs/template_files/madmatrix/madmatrix.mk index 9d1237ae24..a9027f1cb6 100644 --- a/madgraph/iolibs/template_files/madmatrix/madmatrix.mk +++ b/madgraph/iolibs/template_files/madmatrix/madmatrix.mk @@ -409,6 +409,9 @@ else endif export BACKENDDIR +# Export FPTYPE (used e.g. to name the common library, see MADMATRIX_COMMONLIB below) +export FPTYPE + #------------------------------------------------------------------------------- #=== Configure ccache for C++ and CUDA/HIP builds @@ -449,8 +452,13 @@ else ifeq ($(DEBUG),1) endif # Dependency on src directory -# The common library name carries the full BACKEND suffix so each vectorisation/GPU variant is distinct. -MADMATRIX_COMMONLIB = madmatrix_common_$(BACKEND) +# The common library name carries the model name and the full BACKEND suffix -- see the +# matching comment in madmatrix_src.mk for why the model name is required (dlopen +# collisions across process directories regenerated in different models) -- and, purely +# for naming consistency with MADMATRIX_LIB, the FPTYPE too; unlike MADMATRIX_LIB this +# isn't consumed by anything outside these madmatrix makefiles (subprocesses.json never +# references it directly), so no further propagation is needed for this one. +MADMATRIX_COMMONLIB = madmatrix_common_%(model)s_$(BACKEND)_$(FPTYPE) LIBFLAGS = -L$(LIBDIR) -l$(MADMATRIX_COMMONLIB) INCFLAGS += -I$(SRC) -I$(SRC)/rambo @@ -791,7 +799,16 @@ override RUNTIME = processid_short=$(shell basename $(CURDIR)) ###$(info processid_short=$(processid_short)) -MADMATRIX_LIB = madmatrix_$(processid_short)_$(BACKEND) +# The process library name carries the model name (same %(model)s substitution, +# and the same dlopen-collision rationale, as MADMATRIX_COMMONLIB above), the +# process id, the hardware backend, and the floating-point type -- so that +# libraries built for different models/precisions/backends out of the same +# process directory never collide, and so that a loader can tell, from the +# filename alone, exactly which of those a given library on +# disk was built with (subprocesses.json itself stays agnostic to BACKEND and +# FPTYPE -- see me_lib_format in madmatrix/output.py -- since a given process +# directory can be compiled for several of each without regenerating the JSON). +MADMATRIX_LIB = madmatrix_%(model)s_$(processid_short)_$(BACKEND)_$(FPTYPE) objects_lib=$(BUILDDIR)/CPPProcess.o $(BUILDDIR)/color_sum.o $(BUILDDIR)/MatrixElementKernels.o $(BUILDDIR)/CrossSectionKernels.o $(BUILDDIR)/umami.o $(BUILDDIR)/SigmaKin.o # Backend-owned sources @@ -868,7 +885,7 @@ ifeq ($(MADMATRIX_COMMONLIB_EXTERNAL),1) $(LIBDIR)/lib$(MADMATRIX_COMMONLIB).so: ; else $(LIBDIR)/lib$(MADMATRIX_COMMONLIB).so: $(SRC)/*.h $(SRC)/*.cc $(BUILDDIR)/.build.$(TAG) - $(MAKE) -C $(SRC) BACKEND=$(BACKEND) LIBDIR=$(LIBDIR) + $(MAKE) -C $(SRC) BACKEND=$(BACKEND) FPTYPE=$(FPTYPE) LIBDIR=$(LIBDIR) endif #------------------------------------------------------------------------------- @@ -971,20 +988,26 @@ $(addprefix commonlib.,$(BLDBACKENDS)): commonlib.%%: clean: ifeq ($(USEBUILDDIR),1) rm -rf $(BUILDDIR) +ifneq ($(MADMATRIX_COMMONLIB_EXTERNAL),1) + $(MAKE) -C $(SRC) clean BACKEND=$(BACKEND) FPTYPE=$(FPTYPE) LIBDIR=$(LIBDIR) +endif else rm -f $(BUILDDIR)/.build.* $(BUILDDIR)/*.o rm -f $(LIBDIR)/lib$(MADMATRIX_LIB).so rm -f $(BACKEND_LOG) ifneq ($(MADMATRIX_COMMONLIB_EXTERNAL),1) - $(MAKE) -C $(SRC) clean BACKEND=$(BACKEND) LIBDIR=$(LIBDIR) + $(MAKE) -C $(SRC) clean BACKEND=$(BACKEND) FPTYPE=$(FPTYPE) LIBDIR=$(LIBDIR) endif endif -# cleanall: remove objects and libraries for ALL backends. +# cleanall: remove objects and libraries for ALL backends and FPTYPEs. +# (NB: MADMATRIX_LIB itself only names one BACKEND/FPTYPE combination -- see +# its own definition above -- so this globs for all of them explicitly, +# rather than just removing $(MADMATRIX_LIB) as 'clean' above does.) cleanall: rm -rf build.* rm -f ./.build.* ./*.o - rm -f $(LIBDIR)/libmadmatrix_$(processid_short)_*.so + rm -f $(LIBDIR)/libmadmatrix_%(model)s_$(processid_short)_*.so ifneq ($(MADMATRIX_COMMONLIB_EXTERNAL),1) $(MAKE) -C $(SRC) cleanall LIBDIR=$(LIBDIR) endif @@ -993,7 +1016,7 @@ endif # for the SubProcesses dispatcher, which owns the common library while the P* # directories are cleaned in parallel with MADMATRIX_COMMONLIB_EXTERNAL=1. cleancommon: - $(MAKE) -C $(SRC) clean BACKEND=$(BACKEND) LIBDIR=$(LIBDIR) + $(MAKE) -C $(SRC) clean BACKEND=$(BACKEND) FPTYPE=$(FPTYPE) LIBDIR=$(LIBDIR) cleanallcommon: $(MAKE) -C $(SRC) cleanall LIBDIR=$(LIBDIR) diff --git a/madgraph/iolibs/template_files/madmatrix/madmatrix_src.mk b/madgraph/iolibs/template_files/madmatrix/madmatrix_src.mk index 336dcb26d5..d1d26bf6cc 100644 --- a/madgraph/iolibs/template_files/madmatrix/madmatrix_src.mk +++ b/madgraph/iolibs/template_files/madmatrix/madmatrix_src.mk @@ -66,8 +66,17 @@ endif #=== Makefile TARGETS and build rules below #=============================================================================== -# The common library name carries the full BACKEND suffix so each vectorisation/GPU variant is distinct. -MADMATRIX_COMMONLIB = madmatrix_common_$(BACKEND) +# The common library name carries the model name and the full BACKEND suffix, so each +# vectorisation/GPU variant is distinct AND libraries from different regenerated model +# directories (eg the same process regenerated in several models and loaded into one +# program) never collide: dlopen resolves a bare NEEDED entry by matching any +# already-loaded library of that name anywhere in the process, regardless of RUNPATH, +# so two "libmadmatrix_common_.so" from different models would otherwise +# silently alias -- the second model's process code ends up bound to the first +# model's Parameters implementation. FPTYPE is appended too, purely for naming +# consistency with MADMATRIX_LIB (SubProcesses/madmatrix.mk); FPTYPE is exported from +# there, same as BACKEND. +MADMATRIX_COMMONLIB = madmatrix_common_%(model)s_$(BACKEND)_$(FPTYPE) # Name recorded in the library itself, and hence in whatever links against it. # Without it the linker bakes in the absolute build path (LIBDIR), so the process diff --git a/madgraph/iolibs/template_files/madmatrix/process_class.inc b/madgraph/iolibs/template_files/madmatrix/process_class.inc index 51aecbc4dd..c43d0876e0 100644 --- a/madgraph/iolibs/template_files/madmatrix/process_class.inc +++ b/madgraph/iolibs/template_files/madmatrix/process_class.inc @@ -25,6 +25,21 @@ // Initialize process (read model parameters from file) virtual void initProc( const std::string& param_card_name ); +#ifndef MGONGPU_HARDCODE_PARAM + // Re-synchronize the cached independent parameters/couplings (cIPD/cIPC/cIPF) and the + // BSM-independent-parameter buffer (bsmIndepParam) with the current values in the + // Parameters singleton. sigmaKin() and friends read these cached copies, not the + // singleton itself, for performance, so updating the singleton alone (e.g. via + // Parameters::setParameterByName()) has no effect until this is called again; it is + // called once automatically by initProc(). + static void refreshIndependentParams(); + // Re-populate a caller-supplied mass vector from the current Parameters singleton, + // mirroring what initProc() stores in m_masses. Call after umami_set_parameter + // changes a mass parameter so callers of umami_get_meta(UMAMI_META_MASSES,...) see + // the updated values. + static void refreshExternalMasses( std::vector& masses ); +#endif + // PDG id for a given flavor combination index and particle index (0-based). // Useful for tools that need to enumerate flavor combinations without going through UMAMI. static int flavorPDG( int iflavor, int ipar ); diff --git a/madgraph/iolibs/template_files/madmatrix/process_function_definitions.inc b/madgraph/iolibs/template_files/madmatrix/process_function_definitions.inc index 2f2a273d02..3e3875927c 100644 --- a/madgraph/iolibs/template_files/madmatrix/process_function_definitions.inc +++ b/madgraph/iolibs/template_files/madmatrix/process_function_definitions.inc @@ -65,6 +65,12 @@ namespace madmatrix { // Instantiate the model class and set parameters that stay fixed during run m_pars = Parameters::getInstance(); + // Parameters is a process-wide singleton (not owned per-instance), so a fresh + // initProc() must explicitly drop any overrides left by a previous + // setParameterByName() call -- otherwise re-initializing from a (possibly + // different) param card would silently carry forward stale overrides instead + // of giving a clean slate. + m_pars->resetParameterOverrides(); SLHAReader slha( param_card_name, m_verbose ); m_pars->setIndependentParameters( slha ); m_pars->setIndependentCouplings(); @@ -79,6 +85,14 @@ namespace madmatrix } %(initProc_lines)s createNormalizedColorMatrix(); // no-op on cpu/simd, pushes to device memory on gpu + refreshIndependentParams(); + } + + // See declaration in CPPProcess.h for why this exists and when it must be called. + void + CPPProcess::refreshIndependentParams() + { + Parameters* m_pars = Parameters::getInstance(); // Read physics parameters like masses and couplings from user configuration files, // then hand them to the backend-owned storage via the setters (see SigmaKin.cc). %(cipdassign)s @@ -86,6 +100,15 @@ namespace madmatrix %(cipfassign)s %(bsmassign)s } + + // See declaration in CPPProcess.h for why this exists and when it must be called. + void + CPPProcess::refreshExternalMasses( std::vector& masses ) + { + Parameters* m_pars = Parameters::getInstance(); + masses.clear(); +%(refreshExternalMasses_lines)s + } #else // Initialize process (with hardcoded parameters) void diff --git a/madgraph/iolibs/template_files/mg7/api.cpp b/madgraph/iolibs/template_files/mg7/api.cpp index 6961afc7bd..d8198ce285 100644 --- a/madgraph/iolibs/template_files/mg7/api.cpp +++ b/madgraph/iolibs/template_files/mg7/api.cpp @@ -1,7 +1,17 @@ #include "CPPProcess.h" #include "api.h" +#include #include +namespace { + // Masses reported by umami_get_meta(UMAMI_META_MASSES, ...), which (unlike every other + // UMAMI entry point in this file) takes no handle: model parameters are stored per- + // instance in this backend (CPPProcess::pars), so this can only ever reflect the most + // recently initialized-or-modified instance, not a specific one. See the caveat on + // umami_get_meta() in api.h. + std::vector g_masses; +} + extern "C" { UmamiStatus umami_get_meta(UmamiMetaKey meta_key, void* result) { @@ -20,6 +30,12 @@ UmamiStatus umami_get_meta(UmamiMetaKey meta_key, void* result) { break; case UMAMI_META_COLOR_COUNT: return UMAMI_ERROR_UNSUPPORTED_META; + case UMAMI_META_MASSES: { + if (g_masses.size() != (size_t)CPPProcess::nexternal) return UMAMI_ERROR_UNINITIALIZED_META; + for (int i = 0; i < CPPProcess::nexternal; ++i) + static_cast(result)[i] = g_masses[i]; + break; + } default: return UMAMI_ERROR_UNSUPPORTED_META; } @@ -54,6 +70,7 @@ UmamiStatus umami_initialize(UmamiHandle* handle, char const* param_card_path) { CPPProcess* process = new CPPProcess(param_card_path); std::vector& momenta = process->getMomenta(); for (int i = 0; i < CPPProcess::nexternal; ++i) momenta.push_back(new double[4]()); + g_masses.assign(process->getMasses().begin(), process->getMasses().end()); *handle = process; return UMAMI_SUCCESS; } @@ -65,7 +82,18 @@ UmamiStatus umami_set_parameter( double parameter_real, double parameter_imag ) { - return UMAMI_ERROR_NOT_IMPLEMENTED; + CPPProcess* process = static_cast(handle); + if (!process->getParameters().setParameterByName(name, parameter_real, parameter_imag)) + return UMAMI_ERROR_UNKNOWN_PARAMETER; + process->refreshMasses(); + const std::vector& masses = process->getMasses(); + bool changed = (masses.size() != g_masses.size()); + for (size_t i = 0; !changed && i < masses.size(); ++i) + changed = (masses[i] != g_masses[i]); + if (changed) { + g_masses.assign(masses.begin(), masses.end()); + } + return UMAMI_SUCCESS; } @@ -75,7 +103,10 @@ UmamiStatus umami_get_parameter( double* parameter_real, double* parameter_imag ) { - return UMAMI_ERROR_NOT_IMPLEMENTED; + CPPProcess* process = static_cast(handle); + if (!process->getParameters().getParameterByName(name, parameter_real, parameter_imag)) + return UMAMI_ERROR_UNKNOWN_PARAMETER; + return UMAMI_SUCCESS; } UmamiStatus umami_matrix_element( diff --git a/madgraph/iolibs/template_files/mg7/api.h b/madgraph/iolibs/template_files/mg7/api.h index 426a342000..16fe3d514b 100644 --- a/madgraph/iolibs/template_files/mg7/api.h +++ b/madgraph/iolibs/template_files/mg7/api.h @@ -40,6 +40,8 @@ typedef enum { UMAMI_ERROR_UNSUPPORTED_OUTPUT, UMAMI_ERROR_UNSUPPORTED_META, UMAMI_ERROR_MISSING_INPUT, + UMAMI_ERROR_UNKNOWN_PARAMETER, + UMAMI_ERROR_UNINITIALIZED_META, } UmamiStatus; typedef enum { @@ -54,6 +56,7 @@ typedef enum { UMAMI_META_DIAGRAM_COUNT, UMAMI_META_HELICITY_COUNT, UMAMI_META_COLOR_COUNT, + UMAMI_META_MASSES, } UmamiMetaKey; typedef enum { @@ -97,7 +100,12 @@ typedef void* UmamiHandle; * pointer to an instance of the subprocess. Has to be cleaned up by * the caller with `free_subprocess`. * @return - * UMAMI_SUCCESS on success, error code otherwise + * UMAMI_SUCCESS on success, error code otherwise. UMAMI_META_MASSES returns + * UMAMI_ERROR_UNINITIALIZED_META if no instance has been created yet with + * umami_initialize(). NB: unlike other meta keys, UMAMI_META_MASSES can vary between + * instances (parameters are stored per-instance in this backend, and are settable via + * umami_set_parameter); since this function takes no handle, it always reports the + * masses of the most recently initialized-or-modified instance, not a specific one. */ UmamiStatus umami_get_meta(UmamiMetaKey meta_key, void* result); @@ -143,18 +151,24 @@ UmamiStatus umami_supported_outputs(bool const** supported, int* count); UmamiStatus umami_initialize(UmamiHandle* handle, char const* param_card_path); /** - * Sets the value of a model parameter + * Sets the value of a model parameter. Only independent, SLHA-card-level + * parameters (i.e. those normally read from the param card) can be set this + * way; parameters or couplings derived from them are recomputed + * automatically and cannot be overridden directly. * * @param handle * handle of a matrix element instance * @param name - * name of the parameter + * name of the parameter, given either as its model name (e.g. "mdl_MT") + * or as its SLHA card location, " []" (e.g. + * "mass 6"); the block name is matched case-insensitively * @param parameter_real * real part of the parameter value * @param parameter_imag * imaginary part of the parameter value. Ignored for real valued parameters. * @return - * UMAMI_SUCCESS on success, error code otherwise + * UMAMI_SUCCESS on success, UMAMI_ERROR_UNKNOWN_PARAMETER if "name" is not + * a settable parameter, error code otherwise */ UmamiStatus umami_set_parameter( UmamiHandle handle, @@ -169,14 +183,16 @@ UmamiStatus umami_set_parameter( * @param handle * handle of a matrix element instance * @param name - * name of the parameter + * name of the parameter; accepts the same two forms as + * umami_set_parameter (model name, or SLHA " []") * @param parameter_real * pointer to double to return real part of the parameter value * @param parameter_imag * pointer to double to return imaginary part of the parameter value. Ignored * for real-valued parameters (i.e. you may pass a null pointer) * @return - * UMAMI_SUCCESS on success, error code otherwise + * UMAMI_SUCCESS on success, UMAMI_ERROR_UNKNOWN_PARAMETER if "name" is not + * a known parameter or coupling, error code otherwise */ UmamiStatus umami_get_parameter( UmamiHandle handle, diff --git a/madgraph/iolibs/template_files/mg7/gridpack.py b/madgraph/iolibs/template_files/mg7/gridpack.py index faa066fee6..a23a74e563 100644 --- a/madgraph/iolibs/template_files/mg7/gridpack.py +++ b/madgraph/iolibs/template_files/mg7/gridpack.py @@ -36,6 +36,15 @@ import tomllib import argparse +_PRECISION_2_FPTYPE = {"all64": "d", "all32": "f", "color32": "m", "denom64": "v"} + + +def fptype_of(precision: str) -> str: + """[run] precision -> FPTYPE the matrix elements were compiled with (as + fptype_of() in launch.py, which this standalone script can't import).""" + return _PRECISION_2_FPTYPE.get(str(precision).lower(), str(precision).lower()) + + def resolve_verbosity(verbosity: str) -> str: """Resolve the run_card "auto" verbosity to "pretty"/"log" depending on whether stdout is attached to a terminal; other values pass through @@ -279,6 +288,8 @@ def resolve_dir(cli_value, card_value): # run-time matrix-element parameters (bwcutoff) of the run that made the # gridpack; gridpacks written before they were recorded used the default me_parameters = madspace_data.get("me_parameters", {}) + # the FPTYPE the matrix elements were compiled with ({fptype} in me_path) + fptype = fptype_of(run_args["precision"]) globals_path = os.path.join("data", "globals.tar") for context in contexts: context.load_globals(globals_path) @@ -292,7 +303,7 @@ def resolve_dir(cli_value, card_value): for context, backend in zip(contexts, backends): for me_path in madspace_data["matrix_elements"]: context.load_matrix_element( - me_path.format(device=backend), param_card_path, me_parameters + me_path.format(device=backend, fptype=fptype), param_card_path, me_parameters ) # set up generators @@ -448,7 +459,9 @@ def load_systematics(run_card, backends=(), param_card_path=None, if i not in need: matrix_elements.append(None) continue - lib = me_path.format(device=backend) + lib = me_path.format( + device=backend, fptype=fptype_of(run_card["run"]["precision"]) + ) if not os.path.exists(lib): print("WARNING systematics: %s not found, mu_R variations of the " "mixed-order subprocesses are dropped" % lib) diff --git a/madgraph/iolibs/template_files/mg7/launch.py b/madgraph/iolibs/template_files/mg7/launch.py index 574b5b3620..a50c638a5a 100644 --- a/madgraph/iolibs/template_files/mg7/launch.py +++ b/madgraph/iolibs/template_files/mg7/launch.py @@ -1140,7 +1140,8 @@ def build_systematics_matrix_elements(self, args, context): matrix_elements.append(None) continue api = context.load_matrix_element( - meta["me_path"].format(device=backend), self.param_card_path, + meta["me_path"].format(device=backend, fptype=self.fptype), + self.param_card_path, me_parameters(self.run_card)) matrix_elements.append(ms.MatrixElement( api, @@ -1361,14 +1362,14 @@ def compile_matrix_elements(self) -> list[str]: device_names = [device_names] cpu_mode = self.run_card["run"]["cpu_mode"] backends = [backend_of(name, cpu_mode) for name in device_names] + # FPTYPE is part of the library names ({fptype} in me_path) + fptype = self.fptype = fptype_of(self.run_card["run"]["precision"]) if not self.subprocess_data: return backends first_proc_path = self.subprocess_data[0]["path"] subproc_path = os.path.dirname(first_proc_path) - fptype = fptype_of(self.run_card["run"]["precision"]) - # Resolve cpu_mode='auto' once (the build rules pick the best SIMD # backend available here), so all subprocesses agree on the library names. auto_backend = None @@ -1388,7 +1389,9 @@ def compile_matrix_elements(self) -> list[str]: for backend in resolved: missing = [ meta for meta in self.subprocess_data - if not os.path.isfile(meta["me_path"].format(device=backend)) + if not os.path.isfile( + meta["me_path"].format(device=backend, fptype=fptype) + ) ] if not missing: continue @@ -2595,7 +2598,10 @@ def __init__( # The libraries were all built up front by MadgraphProcess.compile_matrix_elements all_api_paths = [ - [api_path_format.format(device=backend) for backend in self.process.backends] + [ + api_path_format.format(device=backend, fptype=self.process.fptype) + for backend in self.process.backends + ] for api_path_format in api_path_formats ] diff --git a/madmatrix/model_handling.py b/madmatrix/model_handling.py index 7282547d5a..fab792b1e8 100644 --- a/madmatrix/model_handling.py +++ b/madmatrix/model_handling.py @@ -1359,7 +1359,29 @@ def write_flv_couplings(self, params): # AV - overload export_cpp.UFOModelConverterCPP method (improve formatting) def write_set_parameters(self, params): - res = self.super_write_set_parameters_donotfixMajorana(params) + # Independent, SLHA-card-level parameters (self.params_ext_names) get + # an inline check for a runtime override (e.g. set by name via the + # UMAMI set_parameter interface) ahead of their normal SLHA-driven + # assignment; every other parameter/coupling is unaffected. NB: this + # must NOT be folded into super_write_set_parameters_donotfixMajorana, + # since write_hardcoded_parameters() calls that method directly and + # requires each parameter's expr to remain a single plain assignment. + res_strings = [] + for param in params: + # 'aS' is excluded here even though it is SLHA-card-level: its + # assignment is entirely commented out below (by name-matching on + # a plain " aS = ..." line), since this backend always retrieves + # alphaS event-by-event (as G) rather than from the param card. + # Wrapping it in an override check would break that name-match. + if param.name in self.params_ext_names and param.name != 'aS': + res_strings.append( + 'if (m_param_overrides.find("%s") != m_param_overrides.end())\n' + '%s = m_param_overrides["%s"];\nelse\n%s' % + (param.name, param.name, param.name, param.expr)) + else: + res_strings.append(param.expr) + res = "\n".join(res_strings) + res = res.replace('ABS(','std::abs(') # for SMEFT #614 and #616 res = res.replace('(','( ') res = res.replace(')',' )') res = res.replace('+',' + ') @@ -1571,6 +1593,31 @@ def super_generate_parameters_class_files(self): replace_dict['set_independent_couplings'] = self.write_set_parameters(self.coups_indep) replace_dict['set_dependent_parameters'] = self.write_set_parameters(self.params_dep) replace_dict['set_dependent_couplings'] = self.write_set_parameters(list(self.coups_dep.values())) + # NB: unlike the plain (mg7_v5) backend, dependent (aS-dependent) parameters + # and couplings are NOT persistent members of this Parameters class at all + # (their declarations are commented out above): they are recomputed fresh + # every event batch by computeDependentCouplings(), bypassing the Parameters + # singleton entirely. Only independent parameters/couplings (and never 'aS', + # whose declaration is likewise commented out) can be registered here. + # Two separate calls/maps, not one combined list into one map: model + # parameters (mdl_*) always stay plain cxsmpl (registered + # into m_complex_model_params below), while independent couplings + # (GC_*) are cxtype -- FPTYPE-templated, so possibly cxsmpl + # -- and go into m_complex_params instead. A single combined + # registration into one map only compiled by accident at FPTYPE=m/d + # (where cxtype happens to also be cxsmpl) and failed to + # compile at FPTYPE=f -- see write_register_parameters()'s own doc + # comment. + replace_dict['register_parameters'] = \ + self.write_register_parameters( + [p for p in self.params_indep if p.name != 'aS'], + [n for n in self.params_ext_names if n != 'aS'], + complex_map_name='m_complex_model_params') + \ + '\n' + \ + self.write_register_parameters(self.coups_indep, []) + replace_dict['register_slha_keys'] = \ + self.write_register_slha_keys( + [e for e in self.params_ext_slha if e[0] != 'aS']) print_params_indep = [ line.replace('std::cout','//std::cout') + ' // now retrieved event-by-event (as G) from Fortran (running alphas #373)' if '"aS =' in line else line for line in self.write_print_parameters(self.params_indep).split('\n') ] @@ -1955,6 +2002,8 @@ def get_process_function_definitions(self, write=True): # process-specific count (ProcessTables::nb_tmp_jamp) rather than # hardcoded per-process like the rest of calculate_jamps. self._nb_tmp_jamp = getattr(self.helas_call_writer, 'nb_tmp_jamp', 0) + replace_dict['refreshExternalMasses_lines'] = \ + self.get_refresh_external_masses_lines(self.matrix_elements[0]) replace_dict['nparams'] = len(self.params2order) replace_dict['coupling_list'] = ' ' replace_dict['hel_amps_cc'] = '#include \"HelAmps_%s.cc\"' % self.model_name # AV @@ -2626,6 +2675,17 @@ def get_hardcoded_initProc_lines(self, matrix_element): initProc_lines.append(' m_masses.push_back( Parameters::%s );' % part.get('mass')) return '\n'.join(initProc_lines) + # Companion to get_initProc_lines: the same external-mass list, but pushed into an + # arbitrary "masses" vector instead of m_masses, for CPPProcess::refreshExternalMasses() + # (called after umami_set_parameter() overrides a mass so UMAMI_META_MASSES stays in sync; + # see madgraph/iolibs/template_files/madmatrix/umami.cc). + def get_refresh_external_masses_lines(self, matrix_element): + """Get refreshExternalMasses_lines for function definition for CPPProcess::refreshExternalMasses""" + lines = [' // Set external particle masses for this matrix element'] + for part in matrix_element.get_external_wavefunctions(): + lines.append(' masses.push_back( m_pars->%s );' % part.get('mass')) + return '\n'.join(lines) + # AV - replace the export_cpp.OneProcessExporterCPP method (fix helicity order and improve formatting) def get_helicity_matrix(self, matrix_element): """Return the Helicity matrix definition lines for this matrix element""" diff --git a/madmatrix/output.py b/madmatrix/output.py index 6e9cc06a88..edc0d4cadd 100644 --- a/madmatrix/output.py +++ b/madmatrix/output.py @@ -188,7 +188,18 @@ class ProcessExporterMadMatrix(export_cpp.ProcessExporterMG7): # AV (default from OM's tutorial) - add a debug printout def __init__(self, *args, **kwargs): self.in_madevent_mode = False # see MR #747 - args[1]["me_lib_format"] = pjoin("lib", "libmadmatrix_{process_id}_{{device}}.so") + # {model} is resolved by ProcessExporterMG7.generate_subprocess_directory() + # alongside {process_id}, from the matrix element's own model -- same + # sanitized name (get_model_name()) used for MADMATRIX_LIB/ + # MADMATRIX_COMMONLIB's %(model)s in madmatrix.mk. {{device}}/{{fptype}} + # stay literal ("{device}"/"{fptype}") in the resulting me_path: which + # concrete BACKEND/FPTYPE a given process directory was compiled for is + # a per-build choice made when the libraries are compiled and loaded + # (see compile_matrix_elements() in mg7/launch.py), not something + # subprocesses.json commits to at generation time. + args[1]["me_lib_format"] = pjoin( + "lib", "libmadmatrix_{model}_{process_id}_{{device}}_{{fptype}}.so" + ) super().__init__(*args, **kwargs) # Honor the output command's --mask=True|False (flavor-mask # optimization for grouped/merged flavors). Default: enabled. diff --git a/madspace/include/madspace/umami.h b/madspace/include/madspace/umami.h index 91f3adc413..8510845910 100644 --- a/madspace/include/madspace/umami.h +++ b/madspace/include/madspace/umami.h @@ -51,6 +51,8 @@ typedef enum { UMAMI_ERROR_UNSUPPORTED_META, /** a mandatory matrix element input was not provided */ UMAMI_ERROR_MISSING_INPUT, + /** the provided parameter name is not a known, settable parameter */ + UMAMI_ERROR_UNKNOWN_PARAMETER, } UmamiStatus; typedef enum { @@ -222,7 +224,7 @@ UmamiStatus umami_supported_outputs(bool const** supported, int* count); * * @param handle * pointer to an instance of the subprocess. Has to be cleaned up by - * the caller with `free_subprocess`. + * the caller with `umami_free`. * @param param_card_path * path to the parameter file * @return @@ -242,7 +244,8 @@ UmamiStatus umami_initialize(UmamiHandle* handle, char const* param_card_path); * @param parameter_imag * imaginary part of the parameter value. Ignored for real valued parameters. * @return - * UMAMI_SUCCESS on success, error code otherwise + * UMAMI_SUCCESS on success, UMAMI_ERROR_UNKNOWN_PARAMETER if "name" is not + * a settable parameter, error code otherwise */ UmamiStatus umami_set_parameter( UmamiHandle handle, char const* name, double parameter_real, double parameter_imag @@ -261,7 +264,8 @@ UmamiStatus umami_set_parameter( * pointer to double to return imaginary part of the parameter value. Ignored * for real-valued parameters (i.e. you may pass a null pointer) * @return - * UMAMI_SUCCESS on success, error code otherwise + * UMAMI_SUCCESS on success, UMAMI_ERROR_UNKNOWN_PARAMETER if "name" is not + * a known parameter, error code otherwise */ UmamiStatus umami_get_parameter( UmamiHandle handle, char const* name, double* parameter_real, double* parameter_imag diff --git a/tests/unit_tests/iolibs/test_export_cpp.py b/tests/unit_tests/iolibs/test_export_cpp.py index 1f5369b615..f6961727b3 100755 --- a/tests/unit_tests/iolibs/test_export_cpp.py +++ b/tests/unit_tests/iolibs/test_export_cpp.py @@ -911,6 +911,7 @@ def test_cpp_export_decay_chain_broken_symmetry_metadata(self): 'process_lines': '', 'model_name': 'sm', 'initProc_lines': '', + 'refreshMasses_lines': '', 'reset_jamp_lines': '', 'sigmaKin_lines': '', 'sigmaHat_lines': 'return 0.;',