Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 130 additions & 6 deletions madgraph/iolibs/export_cpp.py
Original file line number Diff line number Diff line change
Expand Up @@ -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++
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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'] = \
Expand Down Expand Up @@ -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)
Expand All @@ -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<double>' -> 'cxtype' rewrite
for independent couplings specifically), while parameters always
stay plain cxsmpl<double> 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<double> 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
Expand Down Expand Up @@ -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 = \
Expand Down Expand Up @@ -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"""

Expand Down Expand Up @@ -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
Expand All @@ -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)
Expand All @@ -3284,15 +3402,21 @@ 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())
plot = draw_svg.MultiSVGDiagramDrawer(diagrams, svg_stem,
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)
Expand Down
66 changes: 65 additions & 1 deletion madgraph/iolibs/template_files/cpp_model_parameters_cc.inc
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,21 @@
%(info_lines)s
//==========================================================================

#include <algorithm>
#include <cctype>
#include <iostream>
#include <iomanip>
#include <sstream>
#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<int> indices(2,0);
m_slha = slha;
registerParameters();
%(set_independent_parameters)s
}
void Parameters_%(model_name)s::setIndependentCouplings(){
Expand Down Expand Up @@ -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<std::string, std::string>::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<std::string, double*>::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<std::string, std::complex<double> *>::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();
}

43 changes: 43 additions & 0 deletions madgraph/iolibs/template_files/cpp_model_parameters_h.inc
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
#define Parameters_%(model_name)s_H

#include <complex>
#include <map>
#include <set>
#include <string>

#include "read_slha.h"
using namespace std;
Expand Down Expand Up @@ -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
// "<block> <index> [<index2>]" (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 "<block> <index> [<index2>]", 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<std::string, double*> m_real_params;
std::map<std::string, std::complex<double> *> m_complex_params;
std::set<std::string> m_settable_params;
std::map<std::string, double> m_param_overrides;
std::map<std::string, std::string> m_slha_key_to_name;
SLHAReader m_slha;
};

#endif // Parameters_%(model_name)s_H
6 changes: 6 additions & 0 deletions madgraph/iolibs/template_files/cpp_process_class.inc
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ public:

const vector<double>& 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; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading
Loading