Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
370abe8
madspace: matrix elements with a fixed channel index
theoheimel Oct 14, 2026
8b581c2
madspace: integrands with discrete choices of the phase-space mapping
theoheimel Oct 14, 2026
f59cecc
madspace: LHE completion of subprocesses without diagrams
theoheimel Oct 14, 2026
cf7f1b7
mg7: split the launcher into modules
theoheimel Oct 14, 2026
636dead
mg7: weighting steps of each subprocess
theoheimel Oct 14, 2026
68714e6
interface: '[amplicol]' processes
theoheimel Oct 14, 2026
0b2f9c3
mg7: output of the [amplicol] processes
theoheimel Oct 14, 2026
40ce2e9
madspace: weighting step matrix elements with a fixed channel
theoheimel Oct 14, 2026
0e137ac
mg7: outputs with only [amplicol] processes
theoheimel Oct 14, 2026
62cc989
mg7: [amplicol] run card section
theoheimel Oct 14, 2026
b74dee3
mg7: subprocess hooks for other matrix-element backends
theoheimel Oct 14, 2026
6f2c76f
mg7: AmpliCol subprocesses
theoheimel Oct 14, 2026
a8bfeee
mg7: [amplicol] processes with flavour-grouped legs
theoheimel Oct 14, 2026
4e6d716
mg7: coupling orders of the [amplicol] processes
theoheimel Oct 14, 2026
327e2b5
mg7: refuse cuts that are not flavour blind for [amplicol]
theoheimel Oct 14, 2026
b38793b
tests: mg7 [amplicol] cross sections against madmatrix
theoheimel Oct 14, 2026
3ace557
mg7: [amplicol] fixes for quark processes and models without alpha_s
theoheimel Oct 14, 2026
f0dd992
mg7: no madmatrix files in outputs with only [amplicol] processes
theoheimel Oct 14, 2026
4de6aed
mg7: load the [amplicol] libraries once for all their channels
theoheimel Oct 15, 2026
d056758
mg7: mirrored beams of the [amplicol] processes
theoheimel Oct 15, 2026
8f04549
tests: [amplicol] p p > j j j and g g > t t~ g against madmatrix
theoheimel Oct 15, 2026
2d8f603
mg7: generate the [amplicol] artifacts in a worker process
theoheimel Oct 15, 2026
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
66 changes: 63 additions & 3 deletions madgraph/interface/madgraph_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,26 @@ def interference_mode_of(option, orders):
return ''


# '[amplicol]' takes the matrix elements of a process from AmpliCol, a
# colour-ordered generator, instead of MG diagrams (mg7 output only)
ME_BACKENDS = ('amplicol',)
_BACKEND_BRACKET = re.compile(r"\[\s*(?P<backend>\w+)\s*\]")


def split_backend_line(line):
"""(line without the bracket, backend) for a process with a matrix-element
backend bracket like '[amplicol]', (line, '') for any other line."""

match = _BACKEND_BRACKET.search(line)
if not match or match.group('backend').lower() not in ME_BACKENDS:
return line, ''
rest = line[:match.start()] + ' ' + line[match.end():]
if '[' in rest or ']' in rest:
raise madgraph.InvalidCmd(
"[%s] cannot be combined with another '[...]'." % match.group('backend'))
return ' '.join(rest.split()), match.group('backend').lower()


def split_interference_line(line):
"""Split a process definition of the form

Expand Down Expand Up @@ -2345,7 +2365,11 @@ def check_output(self, args, default='mg7'):
return


if not self._curr_amps:
if self._curr_amplicol_defs and self._export_format != 'mg7':
raise self.InvalidCmd(
'The [amplicol] processes are only supported by the mg7 output.')

if not self._curr_amps and not self._curr_amplicol_defs:
text = 'No processes generated. Please generate a process first.'
raise self.InvalidCmd(text)

Expand Down Expand Up @@ -3866,6 +3890,8 @@ def _tutorial_opts(self):
_curr_model = None #base_objects.Model()
_curr_amps = diagram_generation.AmplitudeList()
_curr_proc_defs = base_objects.ProcessDefinitionList()
# processes whose matrix elements come from AmpliCol, see split_backend_line
_curr_amplicol_defs = base_objects.ProcessDefinitionList()
_curr_matrix_elements = helas_objects.HelasMultiProcess()
_curr_helas_model = None
_curr_exporter = None
Expand Down Expand Up @@ -3954,6 +3980,7 @@ def setup(self):
self._done_export=False
self._curr_amps = diagram_generation.AmplitudeList()
self._curr_proc_defs = base_objects.ProcessDefinitionList()
self._curr_amplicol_defs = base_objects.ProcessDefinitionList()
self._curr_matrix_elements = helas_objects.HelasMultiProcess()

self._v4_export_formats = ['madevent', 'standalone_fortran','standalone_msP','standalone_msF',
Expand All @@ -3978,6 +4005,30 @@ def do_quit(self, line):

# Add a process to the existing multiprocess definition
# Generate a new amplitude
def add_backend_process(self, procdef, backend):
"""Keep a process whose matrix elements come from another backend than
MG diagrams. It is exported by the mg7 output, without diagrams."""

if procdef.get('decay_chains') or procdef.get('required_s_channels') or \
procdef.get('forbidden_onsh_s_channels') or \
procdef.get('forbidden_s_channels') or \
procdef.get('forbidden_particles') or procdef.get('orders') or \
procdef.get('squared_orders') or procdef.get('is_decay_chain') or \
any(leg.get('polarization') for leg in procdef.get('legs')) or \
procdef.get_ninitial() != 2:
raise self.InvalidCmd(
"[%s] supports 2 -> n processes without decay chains, coupling "
"orders, s-channel or particle restrictions and polarizations."
% backend)
initial = [amp.get_ninitial() for amp in self._curr_amps]
if initial and initial[0] != procdef.get_ninitial():
raise self.InvalidCmd(
"Can not mix processes with different number of initial states.")
self._curr_amplicol_defs.append(procdef)
self._done_export = False
logger.info("Process %s is computed by %s at output time" %
(procdef.nice_string(prefix=False), backend))

def do_add(self, line, counter=0):
"""Generate an amplitude for a given process and add to
existing amplitudes
Expand Down Expand Up @@ -4079,7 +4130,10 @@ def do_add(self, line, counter=0):
else:
nb_proc = len([l for l in self.history if l.startswith(('generate','add process'))])
nb_proc += counter
line, backend = split_backend_line(line)
myprocdef = self.extract_process(line, proc_number=nb_proc)
if backend:
return self.add_backend_process(myprocdef, backend)
# 'left [treextree] right': hidden order selecting the
# interference, minimal orders of the right-hand process
diagram_generation.prepare_interference_process(myprocdef)
Expand Down Expand Up @@ -6045,6 +6099,7 @@ def clean_process(self):
self._curr_amps = diagram_generation.AmplitudeList()
# Reset Process definition
self._curr_proc_defs = base_objects.ProcessDefinitionList()
self._curr_amplicol_defs = base_objects.ProcessDefinitionList()
# Reset Helas matrix elements
self._curr_matrix_elements = helas_objects.HelasMultiProcess()
self._generate_info = ""
Expand Down Expand Up @@ -7555,6 +7610,7 @@ def do_import(self, line, force=False, options={}):
self._curr_amps = diagram_generation.AmplitudeList()
# Reset proc defs
self._curr_proc_defs = base_objects.ProcessDefinitionList()
self._curr_amplicol_defs = base_objects.ProcessDefinitionList()
self._curr_matrix_elements = helas_objects.HelasMultiProcess()
process_checks.store_aloha = []
self.advise_neglected_masses()
Expand Down Expand Up @@ -10938,6 +10994,7 @@ def set2_group_subprocesses(self, args, log=True):
logger.info('Note that you need to regenerate all processes')
self._curr_amps = diagram_generation.AmplitudeList()
self._curr_proc_defs = base_objects.ProcessDefinitionList()
self._curr_amplicol_defs = base_objects.ProcessDefinitionList()
self._curr_matrix_elements = helas_objects.HelasMultiProcess()
if self.options[args[0]] == 'gpu':
export_v4.ProcessExporterFortranMEGroup.grouped_mode = 'gpu'
Expand Down Expand Up @@ -11113,6 +11170,7 @@ def set2_gauge(self, args, log=True):
self._curr_model = None
self._curr_amps = diagram_generation.AmplitudeList()
self._curr_proc_defs = base_objects.ProcessDefinitionList()
self._curr_amplicol_defs = base_objects.ProcessDefinitionList()
self._curr_matrix_elements = helas_objects.HelasMultiProcess()
self._curr_helas_model = None
self._curr_exporter = None
Expand Down Expand Up @@ -11592,6 +11650,7 @@ def set2_OLP(self, args, log=True):
# depend on this option
self._curr_amps = diagram_generation.AmplitudeList()
self._curr_proc_defs = base_objects.ProcessDefinitionList()
self._curr_amplicol_defs = base_objects.ProcessDefinitionList()
self._curr_matrix_elements = helas_objects.HelasMultiProcess()
self._curr_exporter = None
self._second_exporter = None
Expand Down Expand Up @@ -12485,7 +12544,7 @@ def do_output(self, line):
# have been defined with multiparticle labels, because then
# branching ratios necessitates to keep subprocesses independent.
# That applies only if there is more than one subprocess of course.
if self._curr_amps[0].get_ninitial() == 1 and \
if self._curr_amps and self._curr_amps[0].get_ninitial() == 1 and \
len(self._curr_amps)>1:

processes = [amp.get('process') for amp in self._curr_amps if 'process' in list(amp.keys())]
Expand Down Expand Up @@ -12881,7 +12940,7 @@ def generate_matrix_elements(self, group_processes=True):


matrix_elements = self._curr_matrix_elements.get_matrix_elements()
if any(l.get('flavor') is None for l in matrix_elements[0].get('processes')[0].get('legs')):
if matrix_elements and any(l.get('flavor') is None for l in matrix_elements[0].get('processes')[0].get('legs')):
[m.get_external_flavors() for m in matrix_elements] #precompute all flavors
# Just the matrix.f files
if self._export_format == 'matrix':
Expand Down Expand Up @@ -13544,6 +13603,7 @@ def do_decay_diagram(self, line, skip_2body=False, model=None):
# Reset amplitudes
self._curr_amps = diagram_generation.AmplitudeList()
self._curr_proc_defs = base_objects.ProcessDefinitionList()
self._curr_amplicol_defs = base_objects.ProcessDefinitionList()
# Reset Helas matrix elements
self._curr_matrix_elements = helas_objects.HelasMultiProcess()
# Reset _done_export, since we have new process
Expand Down
4 changes: 4 additions & 0 deletions madgraph/interface/master_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,10 @@ def extract_process_type(line):
space_before = re.compile(r"(?P<carac>\S)(?P<tag>[\\[\\]/\,\\$\\>|])(?P<carac2>\S)")
line2 = space_before.sub(r'\g<carac> \g<tag> \g<carac2>', line)

# a matrix-element backend bracket like [amplicol] is a tree process
if MGcmd.split_backend_line(re.split(r'\s\-\-', line, 1)[0])[1]:
return ('tree', None, [])

# Use regular expressions to extract the loop mode (if present) and its
# option, specified in the line with format [ option = loop_orders ] or
# [ loop_orders ] which implicitly select the 'all' option.
Expand Down
81 changes: 81 additions & 0 deletions madgraph/iolibs/amplicol_worker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
################################################################################
#
# Copyright (c) 2026 The MadGraph5_aMC@NLO Development team and Contributors
#
# This file is a part of the MadGraph5_aMC@NLO project, an application which
# automatically generates Feynman diagrams and matrix elements for arbitrary
# high-energy processes in the Standard Model and beyond.
#
# It is subject to the MadGraph5_aMC@NLO license which should accompany this
# distribution.
#
# For more information, visit madgraph.phys.ucl.ac.be and amcatnlo.web.cern.ch
#
################################################################################
"""Generation of the '[amplicol]' artifacts with pyamplicol, run in its own
process by export_amplicol.

Symbolica, which pyamplicol imports, holds a per-user lock until the process
exits, so it is never imported by MadGraph itself: sessions do not block each
other while they are open. Usage: python amplicol_worker.py JOB.json
"""

import json
import logging
import sys


def model_source(pyamplicol, model):
"""The model compiled for pyamplicol, cached in the output."""
import os
if not os.path.exists(model['cache']):
from pyamplicol.config import EvaluatorConfig, JITConfig
source = pyamplicol.ModelSource.from_path(model['path'],
restriction=model['restriction'])
source.compile(prepared_output=model['cache'],
evaluator=EvaluatorConfig(jit=JITConfig(optimization_level=2)))
return pyamplicol.ModelSource.from_path(model['cache'])


def generate_artifact(pyamplicol, artifact, accuracy, process, model):
from pyamplicol.config import resolve_config
# workers and cores are left to pyamplicol, which clamps them without a license
config = resolve_config({
'schema_version': 1,
'action': 'generate',
'color': {'accuracy': accuracy},
'process': {'multiparticles': process['multiparticles'],
'coupling_order_policy': 'explicit',
'max_coupling_orders': process['orders']},
'generation': {'emit_api_bundle': True,
'relation_discovery': {'mode': 'off'}},
'evaluator': {'backend': 'jit', 'execution_mode': 'recurrence',
'jit': {'optimization_level': 2}},
})
pyamplicol.Generator(config).generate(process['expression'], artifact,
model=model, mode='replace')


def main(job_path):
import os
logging.basicConfig(level=logging.WARNING, format='%(name)s: %(message)s')
with open(job_path) as stream:
job = json.load(stream)
import pyamplicol
from pyamplicol.artifacts import umami_reweight
model = model_source(pyamplicol, job['model'])
providers = []
for process in job['processes']:
path = process['path']
for accuracy in ('lc', 'full'):
generate_artifact(pyamplicol, os.path.join(path, accuracy), accuracy,
process, model)
providers.append(umami_reweight.write_reweight_provider(
os.path.join(path, 'lc'), os.path.join(path, 'full'),
os.path.join(path, 'reweight')))
with open(job['result'], 'w') as stream:
json.dump(providers, stream)


if __name__ == '__main__':
main(sys.argv[1])
Loading
Loading