From 48b2c190f19b012e2ab13f88dcd4db13aee16bc8 Mon Sep 17 00:00:00 2001 From: Olivier Mattelaer Date: Tue, 11 Aug 2026 18:24:16 +0200 Subject: [PATCH 01/26] rename the launcher bin/mg5_aMC to bin/madgraph Hard rename: the old name is gone, no symlink and no compatibility shim. Every in-repo reference to the executable path is updated -- CI workflows, INSTALL, the release packer, the plugin launcher generator, the mg7 madevent runtime helper, the docs, the Template proc_card header and newprocess_mg5, and all test/comparator subprocess invocations. The ASCII banner header written by banner.py (and the shipped Template/LO/Cards/proc_card_mg5.dat) keeps its 61-column box: one padding space was dropped to absorb the longer name. Deliberately untouched: UpdateNotes.txt (changelog of past releases) and tests/input_files/{tt_banner.txt,madspin_event.lhe} (recorded LHE banners used as parser fixtures). The MG5_aMC / MadGraph5_aMC@NLO product name, python identifiers and config keys are out of scope. Co-Authored-By: Claude Opus 5 --- .github/workflows/acceptancetest.yml | 6 ++-- .github/workflows/acceptancetest_madevent.yml | 2 +- .github/workflows/aloha.yml | 2 +- .github/workflows/warm_cache.yml | 18 +++++------ INSTALL | 2 +- Template/LO/Cards/proc_card_mg5.dat | 2 +- Template/LO/bin/newprocess_mg5 | 2 +- bin/create_release.py | 2 +- bin/{mg5_aMC => madgraph} | 2 +- docs/standalone_flavor_python.md | 4 +-- input/.mg5_configuration_default.txt | 2 +- madgraph/interface/madgraph_interface.py | 6 ++-- madgraph/interface/tutorial_text.py | 2 +- .../iolibs/template_files/mg7/madevent.py | 8 ++--- madgraph/various/banner.py | 2 +- madmatrix/__init__.py | 2 +- .../create_acceptance_from_file.py | 2 +- .../acceptance_tests/test_simd_madevent.py | 8 ++--- tests/acceptance_tests/test_cmd.py | 24 +++++++------- tests/acceptance_tests/test_cmd_amcatnlo.py | 2 +- tests/acceptance_tests/test_cmd_madevent.py | 32 +++++++++---------- tests/acceptance_tests/test_cmd_madloop.py | 4 +-- tests/acceptance_tests/test_madspin.py | 10 +++--- tests/parallel_tests/madevent_comparator.py | 4 +-- tests/parallel_tests/madspin_comparator.py | 12 +++---- tests/parallel_tests/me_comparator.py | 4 +-- tests/test_manager.py | 2 +- 27 files changed, 84 insertions(+), 84 deletions(-) rename bin/{mg5_aMC => madgraph} (99%) diff --git a/.github/workflows/acceptancetest.yml b/.github/workflows/acceptancetest.yml index 3c4fa29029..4053439b04 100644 --- a/.github/workflows/acceptancetest.yml +++ b/.github/workflows/acceptancetest.yml @@ -427,7 +427,7 @@ jobs: # cd $GITHUB_WORKSPACE # echo "install lhapdf6" > cmd # echo "install pythia8" >> cmd -# ./bin/mg5_aMC cmd +# ./bin/madgraph cmd # # # Runs a set of commands using the runners shell @@ -448,7 +448,7 @@ jobs: cd $GITHUB_WORKSPACE cp input/.mg5_configuration_default.txt input/mg5_configuration.txt echo "install RunningCoupling" > cmd - ./bin/mg5_aMC cmd + ./bin/madgraph cmd ./tests/test_manager.py test_eft_running_nlo -pA -t0 -l INFO @@ -735,7 +735,7 @@ jobs: run: | cd $GITHUB_WORKSPACE echo "set acknowledged_v3.1_syntax True --global" > cmd - ./bin/mg5_aMC cmd + ./bin/madgraph cmd ./tests/test_manager.py test_amcatnlo_from_file -pA -t0 -l INFO diff --git a/.github/workflows/acceptancetest_madevent.yml b/.github/workflows/acceptancetest_madevent.yml index d173796936..4ee70fcecc 100644 --- a/.github/workflows/acceptancetest_madevent.yml +++ b/.github/workflows/acceptancetest_madevent.yml @@ -461,7 +461,7 @@ jobs: cd $GITHUB_WORKSPACE cp input/.mg5_configuration_default.txt input/mg5_configuration.txt echo "install RunningCoupling" > cmd - ./bin/mg5_aMC cmd + ./bin/madgraph cmd ./tests/test_manager.py test_eft_running -pA -t0 -l INFO diff --git a/.github/workflows/aloha.yml b/.github/workflows/aloha.yml index 29cd2afa01..c46ba3f9d2 100644 --- a/.github/workflows/aloha.yml +++ b/.github/workflows/aloha.yml @@ -47,7 +47,7 @@ jobs: run: | cd $GITHUB_WORKSPACE echo "import model MSSM_SLHA2" > cmd - ./bin/mg5_aMC cmd + ./bin/madgraph cmd ./tests/test_manager.py -pP -t0 test_aloha.* testIO_aloha.*-e test_short_mssm_subset_creation test_excluded: diff --git a/.github/workflows/warm_cache.yml b/.github/workflows/warm_cache.yml index 08241d0385..8f27c8f8b6 100644 --- a/.github/workflows/warm_cache.yml +++ b/.github/workflows/warm_cache.yml @@ -107,7 +107,7 @@ jobs: mkdir -p /home/runner/.cache/UFOMODEL echo "PYTHONPATH=$PYTHONPATH:$HOME/.cache/UFOMODEL" >> $GITHUB_ENV echo "import model 2HDM" >> cmd - ./bin/mg5_aMC cmd + ./bin/madgraph cmd - name: move UFO model to cache if: steps.cache-ufo.outputs.cache-hit != 'true' @@ -146,7 +146,7 @@ jobs: echo "heptools_install_dir=/home/runner/.cache/HEPtools" >> input/mg5_configuration.txt cp Template/LO/Source/.make_opts Template/LO/Source/make_opts echo "install lhapdf6" > cmd - ./bin/mg5_aMC cmd + ./bin/madgraph cmd - name: get pdf set for NLO if: steps.cache-lhapdf.outputs.cache-hit != 'true' @@ -201,12 +201,12 @@ jobs: echo "heptools_install_dir=/home/runner/.cache/HEPtools" >> input/mg5_configuration.txt cp Template/LO/Source/.make_opts Template/LO/Source/make_opts echo "install pythia8" > cmd - ./bin/mg5_aMC cmd + ./bin/madgraph cmd - name: Verify pythia8 landed in the cache if: steps.cache-pythia8.outputs.cache-hit != 'true' run: | - # mg5_aMC exits 0 even when "install pythia8" fails (e.g. a network + # madgraph exits 0 even when "install pythia8" fails (e.g. a network # timeout while downloading hepmc/pythia8 from the download server), # so guard the cache here. Without Pythia.h, MG5 nullifies # pythia8_path at startup, leaving the parton shower unavailable and @@ -251,7 +251,7 @@ jobs: echo "heptools_install_dir=/home/runner/.cache/HEPtools" >> input/mg5_configuration.txt cp Template/LO/Source/.make_opts Template/LO/Source/make_opts echo "install eMELA" > cmd - ./bin/mg5_aMC cmd + ./bin/madgraph cmd # delete_lhapdf_cache: # runs-on: ${{ matrix.os }} @@ -309,7 +309,7 @@ jobs: # YODA's python extension build needs Cython (no longer shipped by # default on the GitHub runner image); without it rivet aborts at # YODA, before FastJet is installed, leaving NLO builds broken. - # Install into the same interpreter mg5_aMC/YODA use (no sudo, so + # Install into the same interpreter madgraph/YODA use (no sudo, so # the system Python is left untouched for other steps). python3 -m pip install Cython mkdir -p /home/runner/.cache/HEPtools @@ -318,12 +318,12 @@ jobs: cp Template/LO/Source/.make_opts Template/LO/Source/make_opts echo "install rivet" > cmd echo "install contur" >> cmd - ./bin/mg5_aMC cmd + ./bin/madgraph cmd - name: Verify rivet/fastjet landed in the cache if: steps.cache-contur.outputs.cache-hit != 'true' run: | - # mg5_aMC exits 0 even when "install rivet" fails, so guard the + # madgraph exits 0 even when "install rivet" fails, so guard the # cache here: a missing fastjet-config means NLO subprocesses cannot # compile (fastjet/ClusterSequence.hh). Fail loudly instead of # saving an incomplete heptools cache. @@ -385,7 +385,7 @@ jobs: cp Template/LO/Source/.make_opts Template/LO/Source/make_opts echo "install collier " > cmd echo "install ninja " >> cmd - ./bin/mg5_aMC cmd + ./bin/madgraph cmd heptools_cache: if: always() && (github.ref_name == 'main' || github.ref_name == 'test_ci' || github.event_name == 'workflow_dispatch' || github.event_name == 'schedule') diff --git a/INSTALL b/INSTALL index 1294ef7ed0..aa76051e1f 100644 --- a/INSTALL +++ b/INSTALL @@ -48,7 +48,7 @@ On Windows: In order to run MG5 you need to 1) add the Python directory to the PATH -2) run $> python ./bin/mg5_aMC +2) run $> python ./bin/madgraph Note that most of the output (including madevent/aMCatNLO) are not compatible with Windows. We do recomend to use unix subsystem. diff --git a/Template/LO/Cards/proc_card_mg5.dat b/Template/LO/Cards/proc_card_mg5.dat index 5a2158341f..975cc6fc9a 100644 --- a/Template/LO/Cards/proc_card_mg5.dat +++ b/Template/LO/Cards/proc_card_mg5.dat @@ -17,7 +17,7 @@ #* * #* Command File for MadGraph5_aMC@NLO * #* * -#* run as ./bin/mg5_aMC filename * +#* run as ./bin/madgraph filename * #* * #************************************************************ import model sm diff --git a/Template/LO/bin/newprocess_mg5 b/Template/LO/bin/newprocess_mg5 index dba4767861..372ee8fb2c 100755 --- a/Template/LO/bin/newprocess_mg5 +++ b/Template/LO/bin/newprocess_mg5 @@ -75,7 +75,7 @@ else echo "neither proc_card.dat or proc_card_mg5.dat" echo "are present in directory Cards" echo "Prepare a card (see examples on the web) and" - echo "copy it there, or run directly ../bin/mg5_aMC." + echo "copy it there, or run directly ../bin/madgraph." exit fi diff --git a/bin/create_release.py b/bin/create_release.py index 8f5f70ca59..401857a77c 100755 --- a/bin/create_release.py +++ b/bin/create_release.py @@ -351,7 +351,7 @@ def get_first_contributions(repo_path): pass shutil.rmtree(path.join(filepath, '.git')) for data in glob.glob(path.join(filepath, 'bin', '*')): - if not data.endswith('mg5') and not data.endswith('mg5_aMC'): + if not data.endswith('mg5') and not data.endswith('madgraph'): if 'compile.py' not in data: os.remove(data) else: diff --git a/bin/mg5_aMC b/bin/madgraph similarity index 99% rename from bin/mg5_aMC rename to bin/madgraph index b424fee513..65e1973e72 100755 --- a/bin/mg5_aMC +++ b/bin/madgraph @@ -187,7 +187,7 @@ else: try: if options.file or args: if sys.platform == "darwin" and options.nosleep: - logging.getLogger('madgraph').warning("launching caffeinate to prevent idle sleep when MG5aMC is running. Run './bin/mg5_aMC -s' to prevent this.") + logging.getLogger('madgraph').warning("launching caffeinate to prevent idle sleep when MG5aMC is running. Run './bin/madgraph -s' to prevent this.") pid = os.getpid() subprocess.Popen(['caffeinate', '-i', '-w', str(pid)]) # They are an input file diff --git a/docs/standalone_flavor_python.md b/docs/standalone_flavor_python.md index 0c83258c5e..6aecece7f9 100644 --- a/docs/standalone_flavor_python.md +++ b/docs/standalone_flavor_python.md @@ -14,7 +14,7 @@ physical flavor combinations. Every standalone entry point therefore takes a ## 1. Generate the standalone output ``` -mg5_aMC +madgraph > generate p p > j j QCD=0 > output standalone /path/to/MYPROC --prefix=int ``` @@ -134,7 +134,7 @@ name-mangling/prefix. active), instead of a timing table: ``` -mg5_aMC +madgraph > launch /path/to/MYPROC --timings=21 --nb_run=0 ``` diff --git a/input/.mg5_configuration_default.txt b/input/.mg5_configuration_default.txt index a07569cac1..3f4ceb88c1 100644 --- a/input/.mg5_configuration_default.txt +++ b/input/.mg5_configuration_default.txt @@ -19,7 +19,7 @@ # Line starting with # should be uncommented if you want to modify the default # value. # Current value for all options can seen by typing "display options" -# after either ./bin/mg5_aMC or ./bin/madevent +# after either ./bin/madgraph or ./bin/madevent # # You can place this files in ~/.mg5/mg5_configuration.txt if you have more than # one version of MG5. diff --git a/madgraph/interface/madgraph_interface.py b/madgraph/interface/madgraph_interface.py index d7e1ef7fd0..154ddfcef3 100755 --- a/madgraph/interface/madgraph_interface.py +++ b/madgraph/interface/madgraph_interface.py @@ -6655,7 +6655,7 @@ def advanced_install(self, tool_to_install, logger.warning('Default installation of Madanalys5 failed.') logger.warning("MG5aMC will now attempt to reinstall it with the options '--no_MA5_further_install --no_root_in_MA5'.") logger.warning("This will however limit MA5 applicability for hadron-level analysis.") - logger.warning("If you would like to prevent MG5aMC to re-attempt MA5 installation, start MG5aMC with './bin/mg5_aMC --debug'.") + logger.warning("If you would like to prevent MG5aMC to re-attempt MA5 installation, start MG5aMC with './bin/madgraph --debug'.") for option in ['--no_MA5_further_install', '--no_root_in_MA5', '--force']: if option not in add_options: add_options.append(option) @@ -7160,7 +7160,7 @@ def do_install(self, line, paths=None, additional_options=[]): import os import sys root_path = os.path.split(os.path.dirname(os.path.realpath( __file__ )))[0] -exe_path = os.path.join(root_path,'bin','mg5_aMC') +exe_path = os.path.join(root_path,'bin','madgraph') sys.argv.pop(0) os.system('%s -tt %s %s --mode={0}' %(sys.executable, str(exe_path) , ' '.join(sys.argv) )) '''.format(name,'' if pyvers == 2 else pyvers) @@ -7169,7 +7169,7 @@ def do_install(self, line, paths=None, additional_options=[]): import os import sys root_path = os.path.split(os.path.dirname(os.path.realpath( __file__ )))[0] -exe_path = os.path.join(root_path,'bin','mg5_aMC') +exe_path = os.path.join(root_path,'bin','madgraph') sys.argv.pop(0) os.system('%s -O -W ignore::DeprecationWarning %s %s --mode={0}' %(sys.executable, str(exe_path) , ' '.join(sys.argv) )) '''.format(name,'' if pyvers == 2 else pyvers) diff --git a/madgraph/interface/tutorial_text.py b/madgraph/interface/tutorial_text.py index f12e7f4846..76841e51da 100755 --- a/madgraph/interface/tutorial_text.py +++ b/madgraph/interface/tutorial_text.py @@ -176,7 +176,7 @@ execute the commands in it, you can do: MG5_aMC>import command my_mg5_cmd.dat or from the shell: -./bin/mg5_aMC my_mg5_cmd.dat +./bin/madgraph my_mg5_cmd.dat It is also possible to display this file directly from MG5_aMC: MG5_aMC>open ./my_mg5_cmd.dat diff --git a/madgraph/iolibs/template_files/mg7/madevent.py b/madgraph/iolibs/template_files/mg7/madevent.py index 6114a4871b..63b40a4be8 100644 --- a/madgraph/iolibs/template_files/mg7/madevent.py +++ b/madgraph/iolibs/template_files/mg7/madevent.py @@ -1602,7 +1602,7 @@ def check_open(self, args): def do_compute_widths(self, line): # The interactive card editor delegates 'auto' width computation to - # the mother interface. Reuse the runtime helper (mg5_aMC subprocess + # the mother interface. Reuse the runtime helper (madgraph subprocess # + the model stored at output time). ``line`` looks like # " --path= [--nlo]"; we only need the card path. m = re.search(r'--path=(\S+)', line or "") @@ -2085,7 +2085,7 @@ def run_lhe_postprocessing(process) -> None: def compute_auto_widths(param_card_path=os.path.join("Cards", "param_card.dat")) -> None: - """Fill any width set to ``auto`` in the param_card, using mg5_aMC and the + """Fill any width set to ``auto`` in the param_card, using madgraph and the model stored at output time (``SubProcesses/model.txt``), and write the result back into the card. A no-op when the card has no ``auto`` width. @@ -2128,9 +2128,9 @@ def compute_auto_widths(param_card_path=os.path.join("Cards", "param_card.dat")) "current model, which may be inconsistent with the matrix " "element.", model) - mg5 = str(_MG_ROOT / "bin" / "mg5_aMC") + mg5 = str(_MG_ROOT / "bin" / "madgraph") if not os.path.exists(mg5): - logger.warning("Cannot find mg5_aMC at %s; 'auto' widths not computed.", mg5) + logger.warning("Cannot find madgraph at %s; 'auto' widths not computed.", mg5) return import tempfile diff --git a/madgraph/various/banner.py b/madgraph/various/banner.py index 5d5b0c63dd..13957043ac 100755 --- a/madgraph/various/banner.py +++ b/madgraph/various/banner.py @@ -803,7 +803,7 @@ class ProcCard(list): '#* *\n' + \ '#* Command File for MadGraph5_aMC@NLO *\n' + \ '#* *\n' + \ - '#* run as ./bin/mg5_aMC filename *\n' + \ + '#* run as ./bin/madgraph filename *\n' + \ '#* *\n' + \ '#************************************************************\n' diff --git a/madmatrix/__init__.py b/madmatrix/__init__.py index a76600e4a1..425d69d10f 100644 --- a/madmatrix/__init__.py +++ b/madmatrix/__init__.py @@ -40,7 +40,7 @@ new_cluster = {} # 3. Define a new interface (allows adding/modifying MG5 command). - # This can be activated via ./bin/mg5_aMC --mode=PLUGINNAME. + # This can be activated via ./bin/madgraph --mode=PLUGINNAME. # Put None if no dedicated command are required if PLUGIN_NAME.rsplit('.',1)[0] == 'MG5aMC_PLUGIN': import madgraph.interface.master_interface as interface diff --git a/madmatrix/contrib/acceptance_tests/create_acceptance_from_file.py b/madmatrix/contrib/acceptance_tests/create_acceptance_from_file.py index f012a3ca63..1373f75008 100644 --- a/madmatrix/contrib/acceptance_tests/create_acceptance_from_file.py +++ b/madmatrix/contrib/acceptance_tests/create_acceptance_from_file.py @@ -38,7 +38,7 @@ def test_%(name)s(self): open(pjoin(self.path, 'mg5_cmd'),'w').write(cmd) newenv = os.environ.copy() newenv[\"PYTHONPATH\"] = pjoin(MG5DIR, '..') - subprocess.call([sys.executable, pjoin(MG5DIR, 'bin','mg5_aMC'),'-m','CUDACPP_OUTPUT', + subprocess.call([sys.executable, pjoin(MG5DIR, 'bin','madgraph'),'-m','CUDACPP_OUTPUT', pjoin(self.path, 'mg5_cmd')], env=newenv, #cwd=self.path, stdout=stdout, stderr=stderr) diff --git a/madmatrix/contrib/acceptance_tests/test_simd_madevent.py b/madmatrix/contrib/acceptance_tests/test_simd_madevent.py index f93ec4ba5b..d5825a2a65 100644 --- a/madmatrix/contrib/acceptance_tests/test_simd_madevent.py +++ b/madmatrix/contrib/acceptance_tests/test_simd_madevent.py @@ -150,7 +150,7 @@ def test_simd_cpp_eemumua_float(self): open(pjoin(self.path, 'mg5_cmd'),'w').write(cmd) newenv = os.environ.copy() newenv["PYTHONPATH"] = pjoin(MG5DIR, '..') - subprocess.call([sys.executable, pjoin(MG5DIR, 'bin','mg5_aMC'),'-m','CUDACPP_OUTPUT', + subprocess.call([sys.executable, pjoin(MG5DIR, 'bin','madgraph'),'-m','CUDACPP_OUTPUT', pjoin(self.path, 'mg5_cmd')], env=newenv, #cwd=self.path, stdout=stdout, stderr=stderr) @@ -200,7 +200,7 @@ def test_simd_cpp_heft_ggh_double(self): open(pjoin(self.path, 'mg5_cmd'),'w').write(cmd) newenv = os.environ.copy() newenv["PYTHONPATH"] = pjoin(MG5DIR, '..') - subprocess.call([sys.executable, pjoin(MG5DIR, 'bin','mg5_aMC'),'-m','CUDACPP_OUTPUT', + subprocess.call([sys.executable, pjoin(MG5DIR, 'bin','madgraph'),'-m','CUDACPP_OUTPUT', pjoin(self.path, 'mg5_cmd')], env=newenv, #cwd=self.path, stdout=stdout, stderr=stderr) @@ -250,7 +250,7 @@ def test_simd_cpp_pptt_mixed(self): open(pjoin(self.path, 'mg5_cmd'),'w').write(cmd) newenv = os.environ.copy() newenv["PYTHONPATH"] = pjoin(MG5DIR, '..') - subprocess.call([sys.executable, pjoin(MG5DIR, 'bin','mg5_aMC'),'-m','CUDACPP_OUTPUT', + subprocess.call([sys.executable, pjoin(MG5DIR, 'bin','madgraph'),'-m','CUDACPP_OUTPUT', pjoin(self.path, 'mg5_cmd')], env=newenv, #cwd=self.path, stdout=stdout, stderr=stderr) @@ -307,7 +307,7 @@ def test_simd_cpp_vector_size(self): open(pjoin(self.path, 'mg5_cmd'),'w').write(cmd) newenv = os.environ.copy() newenv["PYTHONPATH"] = pjoin(MG5DIR, '..') - subprocess.call([sys.executable, pjoin(MG5DIR, 'bin','mg5_aMC'),'-m','CUDACPP_OUTPUT', + subprocess.call([sys.executable, pjoin(MG5DIR, 'bin','madgraph'),'-m','CUDACPP_OUTPUT', pjoin(self.path, 'mg5_cmd')], env=newenv, #cwd=self.path, stdout=stdout, stderr=stderr) diff --git a/tests/acceptance_tests/test_cmd.py b/tests/acceptance_tests/test_cmd.py index 0f9a64b0b5..bd160a04f6 100755 --- a/tests/acceptance_tests/test_cmd.py +++ b/tests/acceptance_tests/test_cmd.py @@ -2656,7 +2656,7 @@ def test_density_mode_user_interface(self): command_card.write(text) command_card.close() - subprocess.call([sys.executable,pjoin(MG5DIR,'bin','mg5_aMC'), + subprocess.call([sys.executable,pjoin(MG5DIR,'bin','madgraph'), pjoin(self.out_dir+'_density0', '..', 'mg5_cmd.txt')]) lhe_path = pjoin(self.out_dir+'_density0','Events','run_01','unweighted_events.lhe.gz') @@ -2719,7 +2719,7 @@ def test_density_mode_user_interface(self): logfile = 'test_density_mode_ttbar.log' - subprocess.call([sys.executable,pjoin(MG5DIR,'bin','mg5_aMC'), + subprocess.call([sys.executable,pjoin(MG5DIR,'bin','madgraph'), '/tmp/mg5_cmd.txt'], stdout=open(logfile, 'w'), stderr=subprocess.STDOUT) @@ -2791,7 +2791,7 @@ def test_density_mode_ttbar(self): command_card.close() logfile = 'test_density_mode_ttbar1.log' - subprocess.call([sys.executable,pjoin(MG5DIR,'bin','mg5_aMC'), + subprocess.call([sys.executable,pjoin(MG5DIR,'bin','madgraph'), pjoin(self.tmpdir, 'mg5_cmd.txt')], stdout=open(logfile, 'w'), stderr=subprocess.STDOUT) @@ -2812,7 +2812,7 @@ def test_density_mode_ttbar(self): command_card_rwgt.close() logfile = 'test_density_mode_ttbar2.log' - subprocess.call([sys.executable,pjoin(MG5DIR,'bin','mg5_aMC'), + subprocess.call([sys.executable,pjoin(MG5DIR,'bin','madgraph'), '/tmp/mg5_cmd_rwgt.txt'], stdout=open(logfile, 'w'), stderr=subprocess.STDOUT) #The lhe file has been reweighted, now we read density_check, the density matrix to compare to the reference @@ -2868,7 +2868,7 @@ def test_density_mode_wpwm(self): logfile = 'test_density_mode_wpwm1.log' - subprocess.call([sys.executable,pjoin(MG5DIR,'bin','mg5_aMC'), + subprocess.call([sys.executable,pjoin(MG5DIR,'bin','madgraph'), '/tmp/mg5_cmd2.txt'], stdout=open(logfile, 'w'), stderr=subprocess.STDOUT) @@ -2892,7 +2892,7 @@ def test_density_mode_wpwm(self): command_card_rwgt.close() logfile = 'test_density_mode_wpwm2.log' - subprocess.call([sys.executable,pjoin(MG5DIR,'bin','mg5_aMC'), + subprocess.call([sys.executable,pjoin(MG5DIR,'bin','madgraph'), '/tmp/mg5_cmd_rwgt2.txt'], stdout=open(logfile, 'w'), stderr=subprocess.STDOUT) #The lhe file has been reweighted, now we read density_check, the density matrix to compare to the reference @@ -2969,7 +2969,7 @@ def test_density_mode_decay1(self): command_card.close() logfile = 'test_density_mode_decay11.log' - subprocess.call([sys.executable,pjoin(MG5DIR,'bin','mg5_aMC'), + subprocess.call([sys.executable,pjoin(MG5DIR,'bin','madgraph'), '/tmp/mg5_cmd.txt'], stdout=open(logfile, 'w'), stderr=subprocess.STDOUT) @@ -2991,7 +2991,7 @@ def test_density_mode_decay1(self): command_card_rwgt.close() logfile = 'test_density_mode_decay12.log' - subprocess.call([sys.executable,pjoin(MG5DIR,'bin','mg5_aMC'), + subprocess.call([sys.executable,pjoin(MG5DIR,'bin','madgraph'), '/tmp/mg5_cmd_rwgt.txt'], stdout=open(logfile, 'w'), stderr=subprocess.STDOUT) #The lhe file has been reweighted, now we read density_check, the density matrix to compare to the reference @@ -3063,7 +3063,7 @@ def test_density_mode_decay2(self): command_card.close() logfile = 'test_density_mode_decay21.log' - subprocess.call([sys.executable,pjoin(MG5DIR,'bin','mg5_aMC'), + subprocess.call([sys.executable,pjoin(MG5DIR,'bin','madgraph'), '/tmp/mg5_cmd.txt'], stdout=open(logfile, 'w'), stderr=subprocess.STDOUT) @@ -3085,7 +3085,7 @@ def test_density_mode_decay2(self): command_card_rwgt.close() logfile = 'test_density_mode_decay22.log' - subprocess.call([sys.executable,pjoin(MG5DIR,'bin','mg5_aMC'), + subprocess.call([sys.executable,pjoin(MG5DIR,'bin','madgraph'), '/tmp/mg5_cmd_rwgt.txt'], stdout=open(logfile, 'w'), stderr=subprocess.STDOUT) #The lhe file has been reweighted, now we read density_check, the density matrix to compare to the reference @@ -3145,7 +3145,7 @@ def test_density_mode_doublettbar(self): command_card.close() logfile = 'test_density_mode_ttbar1.log' - subprocess.call([sys.executable,pjoin(MG5DIR,'bin','mg5_aMC'), + subprocess.call([sys.executable,pjoin(MG5DIR,'bin','madgraph'), '/tmp/mg5_cmd.txt'], stdout=open(logfile, 'w'), stderr=subprocess.STDOUT) @@ -3167,7 +3167,7 @@ def test_density_mode_doublettbar(self): command_card_rwgt.close() logfile = 'test_density_mode_ttbar2.log' - subprocess.call([sys.executable,pjoin(MG5DIR,'bin','mg5_aMC'), + subprocess.call([sys.executable,pjoin(MG5DIR,'bin','madgraph'), '/tmp/mg5_cmd_rwgt.txt'], stdout=open(logfile, 'w'), stderr=subprocess.STDOUT) diff --git a/tests/acceptance_tests/test_cmd_amcatnlo.py b/tests/acceptance_tests/test_cmd_amcatnlo.py index 54c8344120..e8ef9ee408 100755 --- a/tests/acceptance_tests/test_cmd_amcatnlo.py +++ b/tests/acceptance_tests/test_cmd_amcatnlo.py @@ -774,7 +774,7 @@ def test_amcatnlo_from_file(self): stderr=devnull - subprocess.call([sys.executable, pjoin(_file_path, os.path.pardir,'bin','mg5_aMC'), + subprocess.call([sys.executable, pjoin(_file_path, os.path.pardir,'bin','madgraph'), pjoin(_file_path, 'input_files','test_amcatnlo')], cwd=self.tmpdir, stdout=stdout,stderr=stderr) diff --git a/tests/acceptance_tests/test_cmd_madevent.py b/tests/acceptance_tests/test_cmd_madevent.py index e779bacac5..bb061a0005 100755 --- a/tests/acceptance_tests/test_cmd_madevent.py +++ b/tests/acceptance_tests/test_cmd_madevent.py @@ -208,7 +208,7 @@ def _run_mg7_postproc(test, setup_cmds, run_dir, datadir, switch_lines=None, # exec_cmd bypasses the history, so the proc_card would miss the model / # generate / output lines and MadSpin (which reads 'generate' from the # banner) would abort with "no generate line". This mirrors how a real user - # drives the output (bin/mg5_aMC command file / interactive session). + # drives the output (bin/madgraph command file / interactive session). for c in setup_cmds: mg.run_cmd(c) mg.run_cmd('output mg7 %s' % run_dir) @@ -341,7 +341,7 @@ def generate(self, process, model): #if not os.path.exists(pjoin(MG5DIR, 'MadAnalysis')): # print("install MadAnalysis") - # p = subprocess.Popen([pjoin(MG5DIR,'bin','mg5_aMC')], + # p = subprocess.Popen([pjoin(MG5DIR,'bin','madgraph')], # stdin=subprocess.PIPE, # stdout=stdout,stderr=stderr) # out = p.communicate('install MadAnalysis4'.encode()) @@ -2153,7 +2153,7 @@ def test_width_scan(self): stdout=devnull stderr=devnull - subprocess.call([pjoin(_file_path, os.path.pardir,'bin','mg5_aMC'), + subprocess.call([pjoin(_file_path, os.path.pardir,'bin','madgraph'), pjoin(self.path, 'cmd')], #cwd=pjoin(self.path), stdout=stdout,stderr=stdout) @@ -2389,7 +2389,7 @@ def test_add_time_of_flight(self): devnull =open(os.devnull,'w') stdout=devnull stderr=devnull - subprocess.call([sys.executable, pjoin(_file_path, os.path.pardir,'bin','mg5_aMC'), + subprocess.call([sys.executable, pjoin(_file_path, os.path.pardir,'bin','madgraph'), pjoin(self.path, 'mg5_cmd')], #cwd=self.path, stdout=stdout, stderr=stderr) @@ -2472,7 +2472,7 @@ def test_w_production_with_ms_decay(self): launch """) fsock.close() - subprocess.call([sys.executable, pjoin(_file_path, os.path.pardir,'bin','mg5_aMC'), + subprocess.call([sys.executable, pjoin(_file_path, os.path.pardir,'bin','madgraph'), pjoin(self.path, 'cmd')], cwd=pjoin(_file_path, os.path.pardir), stdout=stdout,stderr=stdout) @@ -2553,7 +2553,7 @@ def test_wj_production_with_ms_decay(self): launch """) fsock.close() - subprocess.call([sys.executable, pjoin(_file_path, os.path.pardir,'bin','mg5_aMC'), + subprocess.call([sys.executable, pjoin(_file_path, os.path.pardir,'bin','madgraph'), pjoin(self.path, 'cmd')], cwd=pjoin(_file_path, os.path.pardir), stdout=stdout,stderr=stdout) @@ -2629,7 +2629,7 @@ def test_w_production_with_PA_decay(self): launch """) fsock.close() - subprocess.call([sys.executable, pjoin(_file_path, os.path.pardir,'bin','mg5_aMC'), + subprocess.call([sys.executable, pjoin(_file_path, os.path.pardir,'bin','madgraph'), pjoin(self.path, 'cmd')], cwd=pjoin(_file_path, os.path.pardir), stdout=stdout,stderr=stdout) @@ -2715,7 +2715,7 @@ def test_w_production_with_PA_decay_inline_then_offline(self): launch """) fsock.close() - subprocess.call([sys.executable, pjoin(_file_path, os.path.pardir,'bin','mg5_aMC'), + subprocess.call([sys.executable, pjoin(_file_path, os.path.pardir,'bin','madgraph'), pjoin(self.path, 'cmd')], cwd=pjoin(_file_path, os.path.pardir), stdout=stdout,stderr=stdout) @@ -2774,7 +2774,7 @@ def test_DY_onejet(self): """ % {'path':self.run_dir}) command.close() - subprocess.call([sys.executable, pjoin(_file_path, os.path.pardir,'bin','mg5_aMC'), + subprocess.call([sys.executable, pjoin(_file_path, os.path.pardir,'bin','madgraph'), pjoin(self.path, 'cmd')], cwd=pjoin(_file_path, os.path.pardir), stdout=stdout,stderr=stdout) @@ -2835,7 +2835,7 @@ def test_generation_heft(self): """ % {'path':self.run_dir}) command.close() - subprocess.call([sys.executable, pjoin(_file_path, os.path.pardir,'bin','mg5_aMC'), + subprocess.call([sys.executable, pjoin(_file_path, os.path.pardir,'bin','madgraph'), pjoin(self.path, 'cmd')], cwd=pjoin(_file_path, os.path.pardir), stdout=stdout,stderr=stdout) @@ -2917,7 +2917,7 @@ def test_polarization_top_decay(self): """ % {'path':self.run_dir}) command.close() - subprocess.call([sys.executable, pjoin(_file_path, os.path.pardir,'bin','mg5_aMC'), + subprocess.call([sys.executable, pjoin(_file_path, os.path.pardir,'bin','madgraph'), pjoin(self.path, 'cmd')], cwd=pjoin(_file_path, os.path.pardir), stdout=stdout,stderr=stdout) @@ -2947,7 +2947,7 @@ def test_polarization_top_decay(self): """ % {'path':self.run_dir}) command.close() - subprocess.call([sys.executable, pjoin(_file_path, os.path.pardir,'bin','mg5_aMC'), + subprocess.call([sys.executable, pjoin(_file_path, os.path.pardir,'bin','madgraph'), pjoin(self.path, 'cmd')], cwd=pjoin(_file_path, os.path.pardir), stdout=stdout,stderr=stdout) @@ -2978,7 +2978,7 @@ def test_polarization_top_decay(self): """ % {'path':self.run_dir}) command.close() - subprocess.call([sys.executable, pjoin(_file_path, os.path.pardir,'bin','mg5_aMC'), + subprocess.call([sys.executable, pjoin(_file_path, os.path.pardir,'bin','madgraph'), pjoin(self.path, 'cmd')], cwd=pjoin(_file_path, os.path.pardir), stdout=stdout,stderr=stdout) @@ -3008,7 +3008,7 @@ def test_generation_from_file_1(self): fsock.write(open(pjoin(_file_path, 'input_files','test_mssm_generation')).read() % {'dir_name': self.run_dir, 'mg5_path':pjoin(_file_path, os.path.pardir)}) fsock.close() - subprocess.call([sys.executable, pjoin(_file_path, os.path.pardir,'bin','mg5_aMC'), + subprocess.call([sys.executable, pjoin(_file_path, os.path.pardir,'bin','madgraph'), pjoin(self.path, 'test_mssm_generation')], #cwd=pjoin(self.path), stdout=stdout,stderr=stdout) @@ -3086,7 +3086,7 @@ def test_contur_from_file(self): stdout= None - subprocess.call([pjoin(_file_path, os.path.pardir,'bin','mg5_aMC'), + subprocess.call([pjoin(_file_path, os.path.pardir,'bin','madgraph'), pjoin(_file_path, os.path.pardir, 'tests', 'input_files','rivet_contur_test.cmd')], cwd=pjoin(self.path), stdout=stdout,stderr=stdout) @@ -3144,7 +3144,7 @@ def test_rivet_from_file(self): devnull =open(os.devnull,'w') stdout=devnull stderr=devnull - subprocess.call([pjoin(_file_path, os.path.pardir,'bin','mg5_aMC'), + subprocess.call([pjoin(_file_path, os.path.pardir,'bin','madgraph'), pjoin(self.path, 'mg5_cmd')], #cwd=self.path, stdout=stdout, stderr=stderr) diff --git a/tests/acceptance_tests/test_cmd_madloop.py b/tests/acceptance_tests/test_cmd_madloop.py index ee46b977e8..e44d00d2ee 100755 --- a/tests/acceptance_tests/test_cmd_madloop.py +++ b/tests/acceptance_tests/test_cmd_madloop.py @@ -922,7 +922,7 @@ def test_density_mode_vs_standalone_LI1(self): command_card.close() logfile = 'test_density_vs_LI_standalone1.log' - subprocess.call([sys.executable,pjoin(MG5DIR,'bin','mg5_aMC'), + subprocess.call([sys.executable,pjoin(MG5DIR,'bin','madgraph'), '/tmp/mg5_cmd.txt']) lhe_path = pjoin(self.out_dir, "Events/run_01/unweighted_events.lhe.gz") @@ -969,7 +969,7 @@ def test_density_mode_vs_standalone_LI1(self): # command_card_bis.close() # logfile = 'test_density_vs_LI_standalone.log' - # subprocess.call([sys.executable,pjoin(MG5DIR,'bin','mg5_aMC'), + # subprocess.call([sys.executable,pjoin(MG5DIR,'bin','madgraph'), # '/tmp/mg5_cmd_bis.txt'], stdout=open(logfile, 'w'), stderr=subprocess.STDOUT) diff --git a/tests/acceptance_tests/test_madspin.py b/tests/acceptance_tests/test_madspin.py index 6f942d5517..9a8e90ec6a 100755 --- a/tests/acceptance_tests/test_madspin.py +++ b/tests/acceptance_tests/test_madspin.py @@ -267,7 +267,7 @@ def test_madspin_mixed_flavor_decay_log_summary(self): with open(log_path, 'w') as log_file: return_code = subprocess.call( - [sys.executable, pjoin(_file_path, os.path.pardir, 'bin', 'mg5_aMC'), cmd_path], + [sys.executable, pjoin(_file_path, os.path.pardir, 'bin', 'madgraph'), cmd_path], cwd=pjoin(_file_path, os.path.pardir), stdout=log_file, stderr=subprocess.STDOUT) self.assertEqual(return_code, 0) @@ -373,7 +373,7 @@ def test_madspin_mixed_flavor_decay_log_summary_mg7(self): with open(log_path, 'w') as log_file: try: return_code = subprocess.call( - [sys.executable, pjoin(_file_path, os.path.pardir, 'bin', 'mg5_aMC'), cmd_path], + [sys.executable, pjoin(_file_path, os.path.pardir, 'bin', 'madgraph'), cmd_path], cwd=pjoin(_file_path, os.path.pardir), stdin=subprocess.DEVNULL, stdout=log_file, stderr=subprocess.STDOUT, timeout=240) @@ -385,7 +385,7 @@ def test_madspin_mixed_flavor_decay_log_summary_mg7(self): # dir CI does not upload) so the reason is visible in the CI output. if return_code != 0 or not re.search(r'MadSpin\s+unweight\s+efficiency', log): print('\n===== test_madspin_mixed_flavor_decay_log_summary_mg7: ' - 'mg5_aMC log tail (rc=%s) =====\n%s' + 'madgraph log tail (rc=%s) =====\n%s' % (return_code, '\n'.join(log.splitlines()[-150:])), file=sys.stderr, flush=True) self.assertEqual(return_code, 0) @@ -417,7 +417,7 @@ def test_madspin_wplus_all_all_flavor_balance(self): with open(log_path, 'w') as log_file: return_code = subprocess.call( - [sys.executable, pjoin(_file_path, os.path.pardir, 'bin', 'mg5_aMC'), cmd_path], + [sys.executable, pjoin(_file_path, os.path.pardir, 'bin', 'madgraph'), cmd_path], cwd=pjoin(_file_path, os.path.pardir), stdout=log_file, stderr=subprocess.STDOUT) self.assertEqual(return_code, 0) @@ -479,7 +479,7 @@ def test_madspin_wplus_all_all_flavor_balance_2to1(self): with open(log_path, 'w') as log_file: return_code = subprocess.call( - [sys.executable, pjoin(_file_path, os.path.pardir, 'bin', 'mg5_aMC'), cmd_path], + [sys.executable, pjoin(_file_path, os.path.pardir, 'bin', 'madgraph'), cmd_path], cwd=pjoin(_file_path, os.path.pardir), stdout=log_file, stderr=subprocess.STDOUT) self.assertEqual(return_code, 0) diff --git a/tests/parallel_tests/madevent_comparator.py b/tests/parallel_tests/madevent_comparator.py index 0a660e0613..e3e4cf7afd 100755 --- a/tests/parallel_tests/madevent_comparator.py +++ b/tests/parallel_tests/madevent_comparator.py @@ -854,10 +854,10 @@ def run(self, proc_list, model, orders={}): devnull = open(os.devnull,'w') if logging.root.level >=20: - subprocess.call([pjoin(self.mg5_path,'bin','mg5_aMC'), proc_card_location], + subprocess.call([pjoin(self.mg5_path,'bin','madgraph'), proc_card_location], stdout=devnull, stderr=devnull) else: - subprocess.call([pjoin(self.mg5_path,'bin','mg5_aMC'), proc_card_location]) + subprocess.call([pjoin(self.mg5_path,'bin','madgraph'), proc_card_location]) os.remove(proc_card_location) values = self.get_values() diff --git a/tests/parallel_tests/madspin_comparator.py b/tests/parallel_tests/madspin_comparator.py index 90552c61c9..ffeaf14350 100644 --- a/tests/parallel_tests/madspin_comparator.py +++ b/tests/parallel_tests/madspin_comparator.py @@ -271,7 +271,7 @@ def __init__(self, name, production_process, decays, self._results = {} # ------------------------------------------------------------------ - # Production: run mg5_aMC once to generate events. + # Production: run madgraph once to generate events. # ------------------------------------------------------------------ def _write_mg5_script(self, script_path): lines = ['set automatic_html_opening False --no_save', @@ -300,7 +300,7 @@ def _write_mg5_script(self, script_path): fp.write('\n'.join(lines) + '\n') def produce_events(self): - """Run mg5_aMC once; cache the LHE file path.""" + """Run madgraph once; cache the LHE file path.""" if self.events_file: return self.events_file @@ -312,15 +312,15 @@ def produce_events(self): self.name, log_path) with open(log_path, 'w') as logf: ret = subprocess.call( - [pjoin(MG5DIR, 'bin', 'mg5_aMC'), '-f', script_path], + [pjoin(MG5DIR, 'bin', 'madgraph'), '-f', script_path], stdout=logf, stderr=subprocess.STDOUT, ) if ret != 0: raise RuntimeError( - 'mg5_aMC failed for factory %s (see %s)' % (self.name, log_path) + 'madgraph failed for factory %s (see %s)' % (self.name, log_path) ) - # mg5_aMC -f sometimes returns 0 even when an intermediate command + # madgraph -f sometimes returns 0 even when an intermediate command # (e.g. ``generate``) aborts -- the wrapper just skips the rest and # exits cleanly. Check the log for the unmistakable markers it emits # in that case so we surface the failure here instead of further down @@ -332,7 +332,7 @@ def produce_events(self): 'command not executed: launch'): if marker in log_text: raise RuntimeError( - 'mg5_aMC aborted mid-script for factory %s ' + 'madgraph aborted mid-script for factory %s ' '(marker %r in %s)' % (self.name, marker, log_path) ) diff --git a/tests/parallel_tests/me_comparator.py b/tests/parallel_tests/me_comparator.py index 83411edf5c..6a6abbbd97 100755 --- a/tests/parallel_tests/me_comparator.py +++ b/tests/parallel_tests/me_comparator.py @@ -557,10 +557,10 @@ def run(self, proc_list, model, orders={}, energy=1000): devnull = open(os.devnull,'w') if logging.root.level >=20: - subprocess.call([pjoin(self.mg5_path,'bin','mg5_aMC'), proc_card_location], + subprocess.call([pjoin(self.mg5_path,'bin','madgraph'), proc_card_location], stdout=devnull, stderr=devnull) else: - subprocess.call([pjoin(self.mg5_path,'bin','mg5_aMC'), proc_card_location]) + subprocess.call([pjoin(self.mg5_path,'bin','madgraph'), proc_card_location]) # Remove the temporary proc_card diff --git a/tests/test_manager.py b/tests/test_manager.py index 584d43e6e7..d0e4d87610 100755 --- a/tests/test_manager.py +++ b/tests/test_manager.py @@ -1132,7 +1132,7 @@ def bypass_for_py3(fct): pass if sys.platform == "darwin" and options.nosleep: - logging.getLogger('madgraph').warning("launching caffeinate to prevent idle sleep when MG5aMC is running. Run './bin/mg5_aMC -s' to prevent this.") + logging.getLogger('madgraph').warning("launching caffeinate to prevent idle sleep when MG5aMC is running. Run './bin/madgraph -s' to prevent this.") pid = os.getpid() subprocess.Popen(['caffeinate', '-i', '-w', str(pid)]) From 3828841c5180af7f105e30bba7a33956963ff59c Mon Sep 17 00:00:00 2001 From: Olivier Mattelaer Date: Tue, 11 Aug 2026 18:48:11 +0200 Subject: [PATCH 02/26] remove the dead Template/LO/bin/newprocess_mg5 script This script drove the pre-2.0 workflow of generating a process by editing Cards/proc_card_mg5.dat inside a Template copy and re-running the generator. It has been non-functional for years: it execs $mgdir/mg5, an executable removed long ago (UpdateNotes.txt:611, "OM: remove ./bin/mg5 executable"), so every invocation dies at "Error no mg5 executable found" before doing any work. The script prints its own deprecation notice announcing removal in version 2.0; we are well past that. Nothing references it: no Python exporter copies or names it, and the bin/newprocess and bin/newprocess_nlo calls in the parallel tests refer to separate scripts in external MG4/NLO trees. Because Template/LO is copied wholesale into generated process directories, this only served to ship a broken script into every output. Co-Authored-By: Claude Opus 5 --- Template/LO/bin/newprocess_mg5 | 124 --------------------------------- 1 file changed, 124 deletions(-) delete mode 100755 Template/LO/bin/newprocess_mg5 diff --git a/Template/LO/bin/newprocess_mg5 b/Template/LO/bin/newprocess_mg5 deleted file mode 100755 index 372ee8fb2c..0000000000 --- a/Template/LO/bin/newprocess_mg5 +++ /dev/null @@ -1,124 +0,0 @@ -#!/usr/bin/env bash -# -# First get to main level directory for this process -# - -if [[ ! -d ./bin || ! -d ./SubProcesses ]]; then - cd ../ - if [[ ! -d ./bin || ! -d ./SubProcesses ]]; then - echo "Error: newprocess_mg5 must be executed from the main, or bin directory" - exit - fi -fi -echo -e "\033[1;31m WARNING This out-dated method of using MG5 will be remove in version 2.0\033[0m" -echo -e "\033[1;31m A proc_card example is present in the main directory of MG5 \033[0m" -echo -e "\033[1;31m you can run it like ./bin/mg5 proc_card.dat \033[0m" - -if [[ -e SubProcesses/subproc.mg ]]; then - echo "Error: newprocess_mg5 can only be used in a clean copy of Template." - echo " This directory already contains a generated process, please" - echo " open the index.html file to see information about the process." - echo "If you want to generate events from this process directory, run" - echo " bin/generate_events" - echo "or" - echo " bin/madevent" - echo "and type \"help\" to get started." - exit -fi - -# Check that mg5 exists -main=`pwd` -mgdir=$main/../bin -if [[ ! -x $mgdir/mg5 ]]; then - mgdir=$main/../madgraph5/bin -fi -if [[ ! -x $mgdir/mg5 ]]; then - echo "Error no mg5 executable $mgdir/mg5 found" - exit -fi - - -dirbin=$main/bin/internal -bin=$main/bin - -# Local versions to work on the cluster -tag='' -while [[ "$1" != "" ]]; do - if [[ "$1" == "--web" ]]; then - mgdir=$MADGRAPH_BASE/MG_ME/mg5 - dirbin=$MADGRAPH_BASE/MG_ME/WebBin - bin =$MADGRAPH_BASE/MG_ME/WebBin - tag="--web" - fi - shift 1 -done - -# some external executable -gen_card=$dirbin/gen_cardhtml-pl -gen_info=$dirbin/gen_infohtml-pl - -# Check if we have to use MG4 proc_card.dat or MG5 proc_card.dat -if [[ -e ./Cards/proc_card_mg5.dat ]]; then - if [[ -e ./Cards/proc_card.dat ]]; then - if [[ $tag != '--web' ]]; then - echo "both mg4 and mg5 proc_card.dat are detected. Which one do you want to use? [4/5]" - read card_use - else - card_use='5' - fi - else - card_use='5' - fi -elif [[ -e ./Cards/proc_card.dat ]]; then - card_use='4' -else - echo "neither proc_card.dat or proc_card_mg5.dat" - echo "are present in directory Cards" - echo "Prepare a card (see examples on the web) and" - echo "copy it there, or run directly ../bin/madgraph." - exit -fi - -echo $$ > generating -rm proc_log.txt >& /dev/null -echo "Started `date`" > ./proc_log.txt -echo "Running..." - -cp -p HTML/thinking.gif HTML/card.jpg -rm SubProcesses/done >& /dev/null - -$gen_card >& /dev/null - -# launch the mg5 program -if [[ $card_use == '4' ]]; then -# Run ./mg5 with stderr going to both the file proc_log.txt and to terminal -# output. See http://acs.lbl.gov/~ksb/Scratch/sh_redir_pipe.html - ((echo import proc_v4 ./Cards/proc_card.dat | $mgdir/mg5 3>&1 1>&2 2>&3) | tee /dev/tty) >> proc_log.txt 2>&1 -elif [[ $card_use == '5' ]]; then -# Run ./mg5 with stderr going to both the file proc_log.txt and to terminal -# output. See http://acs.lbl.gov/~ksb/Scratch/sh_redir_pipe.html - (($mgdir/mg5 ./Cards/proc_card_mg5.dat 3>&1 1>&2 2>&3) | tee /dev/tty) >> proc_log.txt 2>&1 -else - echo 'not recognized mode' -fi - -echo "Finished `date` " >> ./proc_log.txt -echo "`date` " >& ./CREATED -cd SubProcesses -touch done -cd .. - -if [[ $tag == "--web" ]]; then - touch Online -fi -rm generating -echo 'done' - -# Check that generation worked -if [[ ! -e SubProcesses/subproc.mg ]]; then - - cp -p HTML/stop.jpg HTML/card.jpg - echo "No diagrams for this process!" -fi - -$gen_card >& /dev/null From db84be2f9f6a8e5b3e7a5683ea4fdbb885227d39 Mon Sep 17 00:00:00 2001 From: Olivier Mattelaer Date: Tue, 11 Aug 2026 23:55:39 +0200 Subject: [PATCH 03/26] rename the Fortran export format standalone -> standalone_fortran First half of the standalone/standalone_mg7 swap: free the name `standalone` by renaming the Fortran standalone exporter to `standalone_fortran`. Nothing yet answers to `standalone`; the second commit gives that name to the current `standalone_mg7`. Renamed everywhere the token names the Fortran exporter: the MG5 _v4_export_formats / _export_formats lists and the do_output config dict, find_output_type, get_default_path, the launch completion and dispatch, the citation "runnable" list, ProcessExporterFortranSA's `format` attribute and its default, the UFO_model_to_mg4 makefile selection, and the MadLoop (ML5) interface, whose `standalone` is the same Fortran standalone family. Internal callers that mean the Fortran exporter are repointed too: MadSpin's matrix-element generation and the Fortran arm of the Fortran-vs-C++ language check in process_checks. find_output_type cannot distinguish standalone_msP/msF/rw from standalone_fortran on disk, so check_launch now accepts those explicitly instead of relying on the old common `standalone` prefix. Co-Authored-By: Claude Opus 5 --- MadSpin/decay.py | 2 +- docs/standalone_flavor_python.md | 2 +- madgraph/interface/amcatnlo_interface.py | 2 +- madgraph/interface/loop_interface.py | 10 +-- madgraph/interface/madgraph_interface.py | 39 +++++---- madgraph/interface/master_interface.py | 2 +- madgraph/iolibs/export_cpp.py | 4 +- madgraph/iolibs/export_v4.py | 10 +-- madgraph/various/process_checks.py | 2 +- tests/acceptance_tests/test_cmd.py | 84 +++++++++---------- tests/acceptance_tests/test_cmd_madloop.py | 8 +- .../test_model_equivalence.py | 2 +- .../test_standalone_madevent_consistency.py | 2 +- tests/parallel_tests/loop_me_comparator.py | 4 +- tests/parallel_tests/me_comparator.py | 10 +-- .../unit_tests/various/test_process_checks.py | 2 +- 16 files changed, 96 insertions(+), 89 deletions(-) diff --git a/MadSpin/decay.py b/MadSpin/decay.py index ca9ab1edc0..299879bae1 100755 --- a/MadSpin/decay.py +++ b/MadSpin/decay.py @@ -5127,7 +5127,7 @@ def generate_all_matrix_element(self): # remove decay with 0 branching ratio. #mgcmd.remove_pointless_decay(self.banner.param_card) # - commandline = 'output standalone %s --prefix=int' % pjoin(path_me, ms_me_subdir) + commandline = 'output standalone_fortran %s --prefix=int' % pjoin(path_me, ms_me_subdir) logger.info(commandline) mgcmd.exec_cmd(commandline, precmd=True) logger.info('Done %.4g' % (time.time()-start)) diff --git a/docs/standalone_flavor_python.md b/docs/standalone_flavor_python.md index 6aecece7f9..7f50931a43 100644 --- a/docs/standalone_flavor_python.md +++ b/docs/standalone_flavor_python.md @@ -16,7 +16,7 @@ physical flavor combinations. Every standalone entry point therefore takes a ``` madgraph > generate p p > j j QCD=0 -> output standalone /path/to/MYPROC --prefix=int +> output standalone_fortran /path/to/MYPROC --prefix=int ``` This writes one subprocess directory per group, e.g. diff --git a/madgraph/interface/amcatnlo_interface.py b/madgraph/interface/amcatnlo_interface.py index f2c6e2b8d8..5702527689 100755 --- a/madgraph/interface/amcatnlo_interface.py +++ b/madgraph/interface/amcatnlo_interface.py @@ -145,7 +145,7 @@ def check_output(self, args): else: self._export_format = 'NLO' - forbidden_formats = ['madevent', 'standalone'] + forbidden_formats = ['madevent', 'standalone_fortran'] if not hasattr(self, '_fks_multi_proc') or not self._fks_multi_proc: diff --git a/madgraph/interface/loop_interface.py b/madgraph/interface/loop_interface.py index f3e53c7a5d..2b819b7c27 100755 --- a/madgraph/interface/loop_interface.py +++ b/madgraph/interface/loop_interface.py @@ -90,7 +90,7 @@ def check_add(self, args): mg_interface.MadGraphCmd.check_add(self,args) - def check_output(self, args, default='standalone'): + def check_output(self, args, default='standalone_fortran'): """ Check the arguments of the output command in the context of the Loop interface.""" @@ -389,7 +389,7 @@ def validate_model(self, loop_type='virtual',coupling_type=['QCD'], stop=True): class LoopInterface(CheckLoop, CompleteLoop, HelpLoop, CommonLoopInterface): - supported_ML_format = ['standalone', 'standalone_rw', 'matchbox'] + supported_ML_format = ['standalone_fortran', 'standalone_rw', 'matchbox'] def __init__(self, mgme_dir = '', *completekey, **stdin): """ Special init tasks for the Loop Interface """ @@ -412,7 +412,7 @@ def setup(self): self._curr_amps = diagram_generation.AmplitudeList() self._curr_matrix_elements = helas_objects.HelasMultiProcess() self._v4_export_formats = [] - self._export_formats = [ 'matrix', 'standalone' ] + self._export_formats = [ 'matrix', 'standalone_fortran' ] self._nlo_modes_for_completion = ['virt'] self.validate_model() # Set where to look for CutTools installation. @@ -502,11 +502,11 @@ def do_output(self, line): noclean, output_type=output_type, group_subprocesses=False, cmd_options=line_options) - if self._export_format in ['standalone', 'matchbox']: + if self._export_format in ['standalone_fortran', 'matchbox']: self._curr_exporter.copy_template(self._curr_model) if self._export_format == "standalone_rw": - self._export_format = "standalone" + self._export_format = "standalone_fortran" self._curr_exporter.copy_template(self._curr_model) self._export_format = "standalone_rw" diff --git a/madgraph/interface/madgraph_interface.py b/madgraph/interface/madgraph_interface.py index 154ddfcef3..ed68b725de 100755 --- a/madgraph/interface/madgraph_interface.py +++ b/madgraph/interface/madgraph_interface.py @@ -497,7 +497,7 @@ def help_output(self): logger.info(" - For MadLoop and aMC@NLO runs, there is only one mode and") logger.info(" it is set by default.") logger.info(" - If mode is madevent, create a MadEvent process directory.") - logger.info(" - If mode is standalone, create a Standalone directory") + logger.info(" - If mode is standalone_fortran, create a Fortran Standalone directory") logger.info(" - If mode is matrix, output the matrix.f files for all") logger.info(" generated processes in directory \"path\".") logger.info(" - If mode is standalone_cpp, create a standalone C++") @@ -521,14 +521,14 @@ def help_output(self): logger.info(" -nojpeg: no jpeg diagrams will be generated.") logger.info(" --noeps=True: no jpeg and eps diagrams will be generated.") logger.info(" -name: the postfix of the main file in pythia8 mode.") - logger.info(" --jamp_optim=[True|False]: [madevent(default:True)|standalone(default:False)] allows a more efficient code computing the color-factor.") + logger.info(" --jamp_optim=[True|False]: [madevent(default:True)|standalone_fortran(default:False)] allows a more efficient code computing the color-factor.") logger.info(" --t_strategy: [madevent] allows to change ordering strategy for t-channel.") logger.info(" --hel_recycling=False: [madevent] forbids helicity recycling optimization") - logger.info(" --mask=False: [madevent|standalone] disable flavor-mask optimization for grouped/merged flavors (default:True).") - logger.info(" --prefix=int|proc: [standalone] prefix matrix-element routine names (int: M_, proc: process name); generates f2py python-linkable routines.") + logger.info(" --mask=False: [madevent|standalone_fortran] disable flavor-mask optimization for grouped/merged flavors (default:True).") + logger.info(" --prefix=int|proc: [standalone_fortran] prefix matrix-element routine names (int: M_, proc: process name); generates f2py python-linkable routines.") logger.info(" Examples:",'$MG:color:GREEN') logger.info(" output",'$MG:color:GREEN') - logger.info(" output standalone MYRUN -f",'$MG:color:GREEN') + logger.info(" output standalone_fortran MYRUN -f",'$MG:color:GREEN') logger.info(" output pythia8 ../pythia8/ -name qcdprocs",'$MG:color:GREEN') def help_check(self): @@ -1447,6 +1447,13 @@ def check_launch(self, args, options): elif self._done_export[1].startswith(mode): args.append(self._done_export[1]) args.append(self._done_export[0]) + elif mode == 'standalone_fortran' and self._done_export[1] in \ + ('standalone_msP', 'standalone_msF', 'standalone_rw'): + # find_output_type cannot tell the Fortran standalone + # variants apart on disk: they all report + # 'standalone_fortran'. Accept the recorded format. + args.append(self._done_export[1]) + args.append(self._done_export[0]) else: raise self.InvalidCmd('%s not valid directory for launch' % self._done_export[0]) return @@ -1545,7 +1552,7 @@ def find_output_type(self, path): elif os.path.isfile(pjoin(bin_path,'aMCatNLO')): return 'aMC@NLO' elif os.path.isdir(card_path): - return 'standalone' + return 'standalone_fortran' raise self.InvalidCmd('%s : Not a valid directory' % path) @@ -1816,7 +1823,7 @@ def check_output(self, args, default='mg7'): raise self.InvalidCmd('%s is not allowed in the output path' % char) # Check for special directory treatment if path == 'auto' and self._export_format in \ - ['madevent', 'standalone', 'standalone_cpp', 'matchbox_cpp', + ['madevent', 'standalone_fortran', 'standalone_cpp', 'matchbox_cpp', 'matchbox', 'plugin', 'me7', 'mg7', 'mg7_v5', 'standalone_mg7']: self.get_default_path() if '-noclean' not in args and os.path.exists(self._export_dir): @@ -1940,7 +1947,7 @@ def check_compute_widths(self, args): def get_default_path(self): """Set self._export_dir to the default (\'auto\') path""" - if self._export_format in ['madevent', 'standalone']: + if self._export_format in ['madevent', 'standalone_fortran']: # Detect if this script is launched from a valid copy of the Template, # if so store this position as standard output directory if 'TemplateVersion.txt' in os.listdir('.'): @@ -2719,7 +2726,7 @@ def complete_output(self, text, line, begidx, endidx, return self.aloha_complete_output(text, line, begidx, endidx) except Exception as error: print(error) - if 'standalone' in args: + if 'standalone_fortran' in args: possible_options_full = list(possible_options_full) + ['--prefix=int', '--prefix=proc', '--density='] # Directory continuation @@ -3136,7 +3143,7 @@ class MadGraphCmd(HelpToCmd, CheckValidForCmd, CompleteForCmd, CmdExtended): _install_opts.extend(_advanced_install_opts) - _v4_export_formats = ['madevent', 'standalone', 'standalone_msP','standalone_msF', + _v4_export_formats = ['madevent', 'standalone_fortran', 'standalone_msP','standalone_msF', 'matrix', 'standalone_rw'] _export_formats = _v4_export_formats + ['standalone_cpp', 'aloha', 'matchbox_cpp', 'matchbox', 'mg7_v5', 'mg7', @@ -3334,7 +3341,7 @@ def setup(self): self._curr_proc_defs = base_objects.ProcessDefinitionList() self._curr_matrix_elements = helas_objects.HelasMultiProcess() - self._v4_export_formats = ['madevent', 'standalone','standalone_msP','standalone_msF', + self._v4_export_formats = ['madevent', 'standalone_fortran','standalone_msP','standalone_msF', 'matrix', 'standalone_rw'] self._export_formats = self._v4_export_formats + ['standalone_cpp', 'mg7_v5', 'mg7', 'standalone_mg7'] self._nlo_modes_for_completion = ['all','virt','real'] @@ -9639,7 +9646,7 @@ def do_output(self, line): config = {} config['madevent'] = {'check': True, 'exporter': 'v4', 'output':'Template'} config['matrix'] = {'check': False, 'exporter': 'v4', 'output':'dir'} - config['standalone'] = {'check': True, 'exporter': 'v4', 'output':'Template'} + config['standalone_fortran'] = {'check': True, 'exporter': 'v4', 'output':'Template'} config['standalone_msF'] = {'check': False, 'exporter': 'v4', 'output':'Template'} config['standalone_msP'] = {'check': False, 'exporter': 'v4', 'output':'Template'} config['standalone_rw'] = {'check': False, 'exporter': 'v4', 'output':'Template'} @@ -10237,7 +10244,7 @@ def finalize(self, nojpeg, online = False, flaglist=[]): # into its final citations.bib. self.write_generation_citations() - if self._export_format in ['madevent', 'standalone', 'standalone_cpp', 'matchbox', 'mg7']: + if self._export_format in ['madevent', 'standalone_fortran', 'standalone_cpp', 'matchbox', 'mg7']: logger.info('Output to directory ' + self._export_dir + ' done.') if self._export_format in ['madevent', 'NLO']: @@ -10252,7 +10259,7 @@ def write_generation_citations(self): routines. Writes citations.log (machine-readable, collected by every run) plus a ready-to-use citations.bib and a citations.md summary. """ - runnable = ['madevent', 'standalone', 'standalone_cpp', 'NLO', + runnable = ['madevent', 'standalone_fortran', 'standalone_cpp', 'NLO', 'madweight', 'matchbox', 'mg7', 'mg7_v5', 'standalone_mg7'] if self._export_format not in runnable or not self._export_dir: return @@ -10850,9 +10857,9 @@ class MadGraphCmdWeb(CheckValidForCmdWeb, MadGraphCmd): _launch_parser.add_option("-M", "--madspin", default=False, action='store_true', help="Run the madspin package") _launch_parser.add_option("", "--timings", default=0, type='int', - help="[standalone] Number of SMATRIX calls per flavor per run for timing analysis (0=disabled)") + help="[standalone_fortran] Number of SMATRIX calls per flavor per run for timing analysis (0=disabled)") _launch_parser.add_option("", "--nb_run", default=1, type='int', - help="[standalone] Number of timing repetitions for statistics (used with --timings); 0 = good-helicity check (print matrix-element values instead of a timing table)") + help="[standalone_fortran] Number of timing repetitions for statistics (used with --timings); 0 = good-helicity check (print matrix-element values instead of a timing table)") #=============================================================================== # Interface for customize question. diff --git a/madgraph/interface/master_interface.py b/madgraph/interface/master_interface.py index b18de52528..6fa1184e03 100755 --- a/madgraph/interface/master_interface.py +++ b/madgraph/interface/master_interface.py @@ -504,7 +504,7 @@ def do_launch(self, line, *argss, **opts): # if there is a path, find what output has been done if path: type = self.cmd.find_output_type(self, path) - if type in ['standalone', 'standalone_cpp', 'pythia8', 'madevent']: + if type in ['standalone_fortran', 'standalone_cpp', 'pythia8', 'madevent']: self.change_principal_cmd('MadGraph') elif type == 'aMC@NLO': self.change_principal_cmd('aMC@NLO') diff --git a/madgraph/iolibs/export_cpp.py b/madgraph/iolibs/export_cpp.py index 354815b369..3302b9f797 100755 --- a/madgraph/iolibs/export_cpp.py +++ b/madgraph/iolibs/export_cpp.py @@ -395,7 +395,7 @@ def _assert_flv_couplings_supported(self, params): * a vertex with more than two merged-flavor legs (never seen so far). - The Fortran 'madevent'/'standalone' output supports the remaining cases. + The Fortran 'madevent'/'standalone_fortran' output supports the remaining cases. See docs/mg7_merged_flavor_mssm_design.md. """ for coupl in params: @@ -407,7 +407,7 @@ def _assert_flv_couplings_supported(self, params): "merged-flavor C++ output (mg7/standalone_mg7) does not yet " "support this process: flavor coupling %s connects %d " "merged-flavor legs; only one or two are supported. Use " - "'output madevent' or 'output standalone' for this process. " + "'output madevent' or 'output standalone_fortran' for this process. " "See docs/mg7_merged_flavor_mssm_design.md for details." % (coupl.name, nb_merged)) diff --git a/madgraph/iolibs/export_v4.py b/madgraph/iolibs/export_v4.py index 1fb735ea1b..3bba426cb4 100755 --- a/madgraph/iolibs/export_v4.py +++ b/madgraph/iolibs/export_v4.py @@ -3364,7 +3364,7 @@ def __init__(self, *args,**opts): self.format = opts['format'] del opts['format'] else: - self.format = 'standalone' + self.format = 'standalone_fortran' self.prefix_info = {} ProcessExporterFortran.__init__(self, *args, **opts) @@ -3919,7 +3919,7 @@ def color_dim_from_particle(p): fsock.close() #important to put that first - if self.format == 'standalone': + if self.format == 'standalone_fortran': filename2 = pjoin(dirpath, 'check_sa.f') self.write_check_sa(writers.FortranWriter(filename2), matrix_element, proc_prefix) @@ -3993,7 +3993,7 @@ def color_dim_from_particle(p): plot.draw() linkfiles = ['check_sa.f', 'coupl.inc'] - if self.format == 'standalone': + if self.format == 'standalone_fortran': linkfiles = ['coupl.inc'] @@ -4400,7 +4400,7 @@ def write_matrix_element_v4(self, writer, matrix_element, fortran_model, #=========================================================================== def write_check_sa(self, writer, matrix_element, proc_prefix=''): - if self.format != 'standalone': + if self.format != 'standalone_fortran': return # Density-mode defaults (overridden if 'density' is in cmd_options). @@ -8660,7 +8660,7 @@ def copy_standard_file(self): text = open(path).read() text = text.replace('madevent','aMCatNLO').replace('../vector.inc', '') open(path, 'w').writelines(text) - elif self.opt['export_format'] in ['standalone', 'standalone_msP','standalone_msF', + elif self.opt['export_format'] in ['standalone_fortran', 'standalone_msP','standalone_msF', 'madloop','madloop_optimized', 'standalone_rw', 'madweight','matchbox','madloop_matchbox', 'plugin']: cp( MG5DIR + '/models/template_files/fortran/makefile_standalone', diff --git a/madgraph/various/process_checks.py b/madgraph/various/process_checks.py index 423ec5d603..e461dad157 100755 --- a/madgraph/various/process_checks.py +++ b/madgraph/various/process_checks.py @@ -4314,7 +4314,7 @@ def _parse_sa_output(text, target_pdgs=None): parent_f = tempfile.mkdtemp(prefix='mg5_langcheck_f_') sa_dir_f = pjoin(parent_f, 'sa_f') try: - opt_f = {'sa_symmetry': False, 'export_format': 'standalone', + opt_f = {'sa_symmetry': False, 'export_format': 'standalone_fortran', 'mp': False, 'v5_model': True, 'output_options': {'noeps': 'True'}} exporter_f = export_v4.ProcessExporterFortranSA(sa_dir_f, opt_f) diff --git a/tests/acceptance_tests/test_cmd.py b/tests/acceptance_tests/test_cmd.py index bd160a04f6..261fcc9962 100755 --- a/tests/acceptance_tests/test_cmd.py +++ b/tests/acceptance_tests/test_cmd.py @@ -542,7 +542,7 @@ def test_output_standalone_directory(self): self.do('set group_subprocesses False') self.do('import model sm') self.do('generate e+ e- > e+ e-') - self.do('output standalone %s' % self.out_dir) + self.do('output standalone_fortran %s' % self.out_dir) self.do('set group_subprocesses True') self.assertTrue(os.path.exists(self.out_dir)) self.assertTrue(os.path.isfile(os.path.join(self.out_dir, 'lib', 'libdhelas.a'))) @@ -561,7 +561,7 @@ def test_custom_propa(self): path = os.path.join(MG5DIR, 'tests', 'input_files', 'sm_with_custom_propa') self.do('import model %s' % path) self.do('generate g g > t t~') - self.do('output standalone %s ' % self.out_dir) + self.do('output standalone_fortran %s ' % self.out_dir) files = ['aloha_file.inc', 'aloha_functions.f','FFV1_0.f', 'FFV1_1.f', 'FFV1_2.f', 'makefile', 'VVV1PV2_1.f'] @@ -615,7 +615,7 @@ def test_ufo_aloha_merged(self): self.do('import model sm') self.do('generate e+ e- > e+ e-') - self.do('output standalone %s ' % self.out_dir) + self.do('output standalone_fortran %s ' % self.out_dir) # Check that the needed ALOHA subroutines are generated files = ['FFV6_3.f', 'aloha_object.mod', 'FFV2_3.f', 'aloha_file.inc', 'makefile', 'FFV6_0.f', 'FFV1P0_3.f', 'FFV2_0.f', 'FFV1_0.f', 'aloha_functions.f'] for f in files: @@ -673,7 +673,7 @@ def test_ufo_aloha(self): self.do('set apply_flavor_grouping False') self.do('import model sm') self.do('generate e+ e- > e+ e-') - self.do('output standalone %s ' % self.out_dir) + self.do('output standalone_fortran %s ' % self.out_dir) # Check that the needed ALOHA subroutines are generated files = ['aloha_file.inc', #'FFS1C1_2.f', 'FFS1_0.f', @@ -745,7 +745,7 @@ def test_standalone_spin2_loop_smgrav(self): model_path = pjoin(MG5DIR, 'tests', 'input_files', 'loop_smgrav') self.do('import model %s' % model_path) self.do('generate p p > w+ y') - self.do('output standalone %s ' % self.out_dir) + self.do('output standalone_fortran %s ' % self.out_dir) # Pin down whichever P*_udx_wpy directory the exporter chose # (depends on flavor-grouping defaults). @@ -796,7 +796,7 @@ def test_standalone_wwjj(self): shutil.rmtree(self.out_dir) self.do('generate p p > w+ w- j j QCD=0') - self.do('output standalone %s ' % self.out_dir) + self.do('output standalone_fortran %s ' % self.out_dir) sub_root = os.path.join(self.out_dir, 'SubProcesses') proc_candidates = [d for d in os.listdir(sub_root) @@ -873,7 +873,7 @@ def test_standalone_merged_flavor_uq_zuq(self): self.do('import model sm') self.do('define q = u d') self.do('generate u q > Z u q QCD=0') - output_cmd = 'output standalone %s' % self.out_dir + output_cmd = 'output standalone_fortran %s' % self.out_dir if mask_flag: output_cmd += ' ' + mask_flag self.do(output_cmd + ' -f') @@ -1017,7 +1017,7 @@ def assert_backend(text, known_flavor, zero_flavor): # 'mask for %s should be all-on' % (zero_flavor,)) # ---- Fortran standalone ------------------------------------- - self.do('output standalone %s -f' % self.out_dir) + self.do('output standalone_fortran %s -f' % self.out_dir) proc_dir = find_qqx(pjoin(self.out_dir, 'SubProcesses')) check_f = pjoin(proc_dir, 'check_sa.f') @@ -1207,7 +1207,7 @@ def run_check(proc_dir, nb_try): if define_cmd: self.do(define_cmd) self.do('generate %s' % proc) - self.do('output standalone %s -f' % self.out_dir) + self.do('output standalone_fortran %s -f' % self.out_dir) subprocess.call(['make'], stdout=devnull, stderr=devnull, cwd=pjoin(self.out_dir, 'Source')) sub_root = pjoin(self.out_dir, 'SubProcesses') @@ -1287,7 +1287,7 @@ def get_values(output_format, check_exe, build_source=False): proc_dir = pjoin(proc_root, d) # standalone uses 'make check' + ./check; standalone_mg7 ships a # UMAMI check_sa.exe whose 'matrix' mode == the Fortran driver. - target = ['make', 'check'] if output_format == 'standalone' \ + target = ['make', 'check'] if output_format == 'standalone_fortran' \ else ['make'] subprocess.call(target, stdout=devnull, stderr=devnull, cwd=proc_dir) @@ -1304,7 +1304,7 @@ def get_values(output_format, check_exe, build_source=False): self.do('import model sm') self.do('generate u u~ > j j QCD=0') mg7 = get_values('standalone_mg7', './check_sa.exe') - standalone = get_values('standalone', './check', build_source=True) + standalone = get_values('standalone_fortran', './check', build_source=True) self.assertTrue(any(v != 0.0 for v in standalone), 'all matrix elements vanished for u u~ > j j') self._assert_me_lists_close(mg7, standalone, atol=1e-7) @@ -1426,7 +1426,7 @@ def get_values(): original = get_values() #step 1 standalone output shutil.rmtree(self.out_dir) - self.do('output standalone %s -f' % self.out_dir) + self.do('output standalone_fortran %s -f' % self.out_dir) shutil.rmtree(self.out_dir) self.do('output standalone_cpp %s -f' % self.out_dir) new = get_values() @@ -1478,7 +1478,7 @@ def get_values(output_format): proc_dir = os.path.join(self.out_dir, 'SubProcesses') directories = sorted([d for d in os.listdir(proc_dir) if d.startswith('P')]) self.assertTrue(directories) - if output_format == 'standalone': + if output_format == 'standalone_fortran': subprocess.call(['make'], stdout=devnull, stderr=devnull, cwd=os.path.join(self.out_dir, 'Source')) @@ -1486,7 +1486,7 @@ def get_values(output_format): logfile = os.path.join(proc_dir, oneproc, 'check.log') # standalone uses 'make check' + ./check; standalone_mg7 ships a # UMAMI check_sa.exe whose 'matrix' mode == the Fortran driver. - if output_format == 'standalone': + if output_format == 'standalone_fortran': target = ['make', 'check'] check_exe = './check %s' % energy elif output_format == 'standalone_mg7': @@ -1511,7 +1511,7 @@ def get_values(output_format): standalone_mg7 = get_values('standalone_mg7') shutil.rmtree(self.out_dir) - standalone = get_values('standalone') + standalone = get_values('standalone_fortran') # atol: this process's matrix elements are O(1e-20), i.e. at the # floating-point noise floor, where the Fortran and cudacpp backends @@ -1527,7 +1527,7 @@ def get_values(output_format): shutil.rmtree(self.out_dir) standalone_mg7_no_fd = get_values('standalone_mg7') shutil.rmtree(self.out_dir) - standalone_no_fd = get_values('standalone') + standalone_no_fd = get_values('standalone_fortran') self._assert_me_lists_close(standalone_mg7_no_fd, standalone_no_fd, atol=1e-7) @@ -1611,7 +1611,7 @@ def get_values(output_format, check_exe, build_source=False): values = [] for d in dirs: proc_dir = os.path.join(proc_root, d) - target = ['make', 'check'] if output_format == 'standalone' \ + target = ['make', 'check'] if output_format == 'standalone_fortran' \ else ['make'] subprocess.call(target, stdout=devnull, stderr=devnull, cwd=proc_dir) @@ -1628,7 +1628,7 @@ def get_values(output_format, check_exe, build_source=False): self.do('import model MSSM_SLHA2') self.do('generate p p > n1 n1 QCD=0') mg7 = get_values('standalone_mg7', './check_sa.exe') - standalone = get_values('standalone', './check', build_source=True) + standalone = get_values('standalone_fortran', './check', build_source=True) self.assertTrue(any(v != 0.0 for v in standalone), 'all matrix elements vanished for p p > n1 n1') self._assert_me_lists_close(mg7, standalone, rtol=1e-4) @@ -1672,7 +1672,7 @@ def get_values(output_format, check_exe, build_source=False): values = [] for d in dirs: proc_dir = os.path.join(proc_root, d) - target = ['make', 'check'] if output_format == 'standalone' \ + target = ['make', 'check'] if output_format == 'standalone_fortran' \ else ['make'] subprocess.call(target, stdout=devnull, stderr=devnull, cwd=proc_dir) @@ -1689,7 +1689,7 @@ def get_values(output_format, check_exe, build_source=False): self.do('import model MSSM_SLHA2') self.do('generate p p > go go') mg7 = get_values('standalone_mg7', './check_sa.exe') - standalone = get_values('standalone', './check', build_source=True) + standalone = get_values('standalone_fortran', './check', build_source=True) self.assertTrue(any(v != 0.0 for v in standalone), 'all matrix elements vanished for p p > go go') self._assert_me_lists_close(mg7, standalone, rtol=1e-4) @@ -1721,7 +1721,7 @@ def test_standalone_density(self): shutil.rmtree(self.out_dir) self.do('generate p p > j t t~ ') - self.do('output standalone %s --density=4,5 -f' % self.out_dir) + self.do('output standalone_fortran %s --density=4,5 -f' % self.out_dir) devnull = open(os.devnull,'w') logfile = os.path.join(self.out_dir,'SubProcesses', 'P0_gg_gttx', @@ -1770,7 +1770,7 @@ def test_standalone_density(self): ### check case with polarization vectors ######################################################################## self.do('generate u u~ > z{0} z{T} g') - self.do('output standalone %s --density=3,4,5 -f ' % self.out_dir) + self.do('output standalone_fortran %s --density=3,4,5 -f ' % self.out_dir) devnull = open(os.devnull,'w') logfile = os.path.join(self.out_dir,'SubProcesses', 'P0_uux_z0zTg', @@ -1827,7 +1827,7 @@ def test_standalone_density(self): ### check Z > t t~ case ######################################################################## self.do('generate z > b b~') - self.do('output standalone %s --density=1 -f ' % self.out_dir) + self.do('output standalone_fortran %s --density=1 -f ' % self.out_dir) devnull = open(os.devnull,'w') logfile = os.path.join(self.out_dir,'SubProcesses', 'P0_z_bbx', @@ -1879,7 +1879,7 @@ def test_standalone_density(self): ### check case with interference computation ######################################################################## self.do('generate u u~ > t t~ QCD^2==2') - self.do('output standalone %s --density=3,4 -f ' % self.out_dir) + self.do('output standalone_fortran %s --density=3,4 -f ' % self.out_dir) devnull = open(os.devnull,'w') logfile = os.path.join(self.out_dir,'SubProcesses', 'P0_uux_ttx', @@ -1937,12 +1937,12 @@ def test_standalone_density_uu(self): # testing case u u~ > z z, z > e+ e- ############################################################################ self.do('generate u u~ > z z') - self.do('output standalone %s_prod --density=3,4 -f ' % self.out_dir) + self.do('output standalone_fortran %s_prod --density=3,4 -f ' % self.out_dir) self.do('generate u u~ > z z, z > e+ e-') - self.do('output standalone %s_full -f ' % self.out_dir) + self.do('output standalone_fortran %s_full -f ' % self.out_dir) self.do('generate z > e+ e- --standalone') # --standalone allow mix 2>1 and 2>2 processes - self.do('output standalone %s_dec1 --density=1 -f ' % self.out_dir) - self.do('output standalone %s_dec2 --density=1 -f ' % self.out_dir) + self.do('output standalone_fortran %s_dec1 --density=1 -f ' % self.out_dir) + self.do('output standalone_fortran %s_dec2 --density=1 -f ' % self.out_dir) # Read a test event for u u~ > z z, z > e+ e- @@ -2077,12 +2077,12 @@ def test_standalone_density_dd(self): # testing case d d~ > z z, z > e+ e- ############################################################################ self.do('generate d d~ > z z') - self.do('output standalone %s_prod --prefix=int --density=3,4 -f ' % self.out_dir) + self.do('output standalone_fortran %s_prod --prefix=int --density=3,4 -f ' % self.out_dir) self.do('generate d d~ > z z, z > e+ e-') - self.do('output standalone %s_full -f ' % self.out_dir) + self.do('output standalone_fortran %s_full -f ' % self.out_dir) self.do('generate z > e+ e- --standalone') # --standalone allow mix 2>1 and 2>2 processes - self.do('output standalone %s_dec1 --density=1 -f ' % self.out_dir) - self.do('output standalone %s_dec2 --density=1 -f ' % self.out_dir) + self.do('output standalone_fortran %s_dec1 --density=1 -f ' % self.out_dir) + self.do('output standalone_fortran %s_dec2 --density=1 -f ' % self.out_dir) # Read a test event for u u~ > z z, z > e+ e- text_lhe = """ 8 1 +9.3182000e+00 1.00474800e+02 7.54677100e-03 1.27930100e-01 @@ -2497,12 +2497,12 @@ def test_standalone_density_f2py(self): # testing case d d~ > z z, z > e+ e- ############################################################################ self.do('generate d d~ > z z') - self.do('output standalone %s_prod --prefix=int --density=3,4 -f ' % self.out_dir) + self.do('output standalone_fortran %s_prod --prefix=int --density=3,4 -f ' % self.out_dir) self.do('generate d d~ > z z, z > e+ e-') - #self.do('output standalone %s_full -f ' % self.out_dir) + #self.do('output standalone_fortran %s_full -f ' % self.out_dir) #self.do('generate z > e+ e- --standalone') # --standalone allow mix 2>1 and 2>2 processes - #self.do('output standalone %s_dec1 --density=1 -f ' % self.out_dir) - #self.do('output standalone %s_dec2 --density=1 -f ' % self.out_dir) + #self.do('output standalone_fortran %s_dec1 --density=1 -f ' % self.out_dir) + #self.do('output standalone_fortran %s_dec2 --density=1 -f ' % self.out_dir) # Read a test event for u u~ > z z, z > e+ e- text_lhe = """ 8 1 +9.3182000e+00 1.00474800e+02 7.54677100e-03 1.27930100e-01 @@ -3265,7 +3265,7 @@ def notest_v4_heft(self): self.do('import model heft', force=True) self.do('generate g g > h g g') - self.do('output standalone %s ' % self.out_dir) + self.do('output standalone_fortran %s ' % self.out_dir) devnull = open(os.devnull,'w') # Check that the Model and Aloha output compile @@ -3771,7 +3771,7 @@ def test_complex_mass_SA(self): self.do('import model sm --noprefix') self.do('set complex_mass_scheme') self.do('generate e+ e- > e+ e-') - self.do('output standalone %s ' % self.out_dir) + self.do('output standalone_fortran %s ' % self.out_dir) misc.compile(cwd=os.path.join(self.out_dir,'SubProcesses', 'P0_epem_epem')) p = subprocess.Popen(['./check'], cwd=os.path.join(self.out_dir,'SubProcesses', 'P0_epem_epem'), stdout=subprocess.PIPE) @@ -3787,7 +3787,7 @@ def test_complex_mass_SA(self): self.do('import model sm') self.do('set complex_mass_scheme') self.do('generate e+ e- > e+ e-') - self.do('output standalone %s -f' % self.out_dir) + self.do('output standalone_fortran %s -f' % self.out_dir) misc.compile(cwd=os.path.join(self.out_dir,'SubProcesses', 'P0_epem_epem')) p = subprocess.Popen(['./check'], cwd=os.path.join(self.out_dir,'SubProcesses', 'P0_epem_epem'), stdout=subprocess.PIPE) @@ -3806,7 +3806,7 @@ def test_complex_mass_SA_merged(self): self.do('import model sm --noprefix') self.do('set complex_mass_scheme') self.do('generate e+ e- > e+ e-') - self.do('output standalone %s ' % self.out_dir) + self.do('output standalone_fortran %s ' % self.out_dir) subdir = os.path.join(self.out_dir, 'SubProcesses', 'P0_epem_epem') misc.compile(cwd=subdir) p = subprocess.Popen(['./check'], cwd=subdir, stdout=subprocess.PIPE) @@ -3821,7 +3821,7 @@ def test_complex_mass_SA_merged(self): self.do('import model sm') self.do('set complex_mass_scheme') self.do('generate e+ e- > e+ e-') - self.do('output standalone %s -f' % self.out_dir) + self.do('output standalone_fortran %s -f' % self.out_dir) subdir = os.path.join(self.out_dir, 'SubProcesses', 'P0_epem_epem') misc.compile(cwd=subdir) p = subprocess.Popen(['./check'], cwd=subdir, stdout=subprocess.PIPE) @@ -4393,7 +4393,7 @@ def run_standalone(self,commands): for command in commands: self.do(command) - self.do('output standalone %s -f' % self.out_dir) + self.do('output standalone_fortran %s -f' % self.out_dir) Pdir = None for pdir in misc.glob('P*', pjoin(self.out_dir, 'SubProcesses')): Pdir = pdir diff --git a/tests/acceptance_tests/test_cmd_madloop.py b/tests/acceptance_tests/test_cmd_madloop.py index e44d00d2ee..91c8c93d50 100755 --- a/tests/acceptance_tests/test_cmd_madloop.py +++ b/tests/acceptance_tests/test_cmd_madloop.py @@ -637,7 +637,7 @@ def test_density_mode_loop_induced_standalone1(self): self.do('import model loop_sm') self.do('generate g g > h [sqrvirt=QCD]') - self.run_cmd(f'output standalone {short_path} --density=1,2 -f') # we need run_cmd here, else HelicityFilterLevel is not set to 1. + self.run_cmd(f'output standalone_fortran {short_path} --density=1,2 -f') # we need run_cmd here, else HelicityFilterLevel is not set to 1. self.run_cmd(f'launch {short_path} -f ') devnull = open(os.devnull,'w') @@ -695,7 +695,7 @@ def test_density_mode_loop_induced_standalone2(self): self.do('import model loop_sm') self.do('generate g g > w+ w- [sqrvirt=QCD]') - self.run_cmd(f'output standalone {short_path} --density=3,4 -f') # we need run_cmd here, else HelicityFilterLevel is not set to 1. + self.run_cmd(f'output standalone_fortran {short_path} --density=3,4 -f') # we need run_cmd here, else HelicityFilterLevel is not set to 1. self.run_cmd(f'launch {short_path} -f ') devnull = open(os.devnull,'w') @@ -760,7 +760,7 @@ def test_density_mode_loop_induced_standalone3(self): self.do('import model loop_sm') self.do('generate p p > h j [sqrvirt=QCD]') - self.run_cmd(f'output standalone {short_path} --density=2,4 -f') # we need run_cmd here, else HelicityFilterLevel is not set to 1. + self.run_cmd(f'output standalone_fortran {short_path} --density=2,4 -f') # we need run_cmd here, else HelicityFilterLevel is not set to 1. self.run_cmd(f'launch {short_path} -f ') # the result of the run is stored inside the file result.dat @@ -950,7 +950,7 @@ def test_density_mode_vs_standalone_LI1(self): # self.do('import model loop_sm') # self.do('generate g g > w+ w- [sqrvirt=QCD]') - # self.run_cmd(f'output standalone {short_path2} --density=3,4 -f') # we need run_cmd here, else HelicityFilterLevel is not set to 1. + # self.run_cmd(f'output standalone_fortran {short_path2} --density=3,4 -f') # we need run_cmd here, else HelicityFilterLevel is not set to 1. # path_PS_card = pjoin(short_path2, "SubProcesses/P0_gg_wpwm/PS.input") # with open(path_PS_card, 'w') as psinput: # psinput.write(str(p_all[0]).strip("[],") + "\n") diff --git a/tests/acceptance_tests/test_model_equivalence.py b/tests/acceptance_tests/test_model_equivalence.py index 5025b69fd9..2c28bfee14 100755 --- a/tests/acceptance_tests/test_model_equivalence.py +++ b/tests/acceptance_tests/test_model_equivalence.py @@ -346,7 +346,7 @@ def setUp(self): # model = save_load_object.load_from_file(picklefile) misc.sprint(self.output_path, os.path.exists(self.output_path)) - export_v4.UFO_model_to_mg4(model, self.output_path,opt = {'export_format': 'standalone', 'mp':False}).build() + export_v4.UFO_model_to_mg4(model, self.output_path,opt = {'export_format': 'standalone_fortran', 'mp':False}).build() # tearDown = CheckFileCreate.clean_files diff --git a/tests/acceptance_tests/test_standalone_madevent_consistency.py b/tests/acceptance_tests/test_standalone_madevent_consistency.py index 0c9dd01983..d717c1af33 100644 --- a/tests/acceptance_tests/test_standalone_madevent_consistency.py +++ b/tests/acceptance_tests/test_standalone_madevent_consistency.py @@ -73,7 +73,7 @@ def check_process(self, process, model='sm', tolerance=1e-6): self.do('import model %s' % model) self.do('generate %s' % process) generated_process = self.cmd._curr_amps[0].get('process') - self.do('output standalone %s -f' % self.standalone_dir) + self.do('output standalone_fortran %s -f' % self.standalone_dir) self.do('output madevent %s -f' % self.madevent_dir) standalone_dir = self._get_single_subprocess_dir( diff --git a/tests/parallel_tests/loop_me_comparator.py b/tests/parallel_tests/loop_me_comparator.py index 62d2d43b0b..0fd4eda927 100755 --- a/tests/parallel_tests/loop_me_comparator.py +++ b/tests/parallel_tests/loop_me_comparator.py @@ -236,7 +236,7 @@ def format_mg5_proc_card(self, proc_list, model): v5_string += 'add process ' + proc + ' ' + born_couplings + \ ' [virt=' + perturbations + '] ' + squared_couplings + \ (' @%i\n'%i) - v5_string += "output standalone %s -f\n"%\ + v5_string += "output standalone_fortran %s -f\n"%\ os.path.join(self.mg5_path, self.temp_dir_name) return v5_string @@ -448,7 +448,7 @@ def format_mg5_proc_card(self, proc_list, model): v5_string += 'add process ' + proc + ' ' + born_couplings + \ ' [virt=' + perturbations + '] ' + squared_couplings + \ (' @%i\n'%i) - v5_string += "output standalone %s -f\n"%\ + v5_string += "output standalone_fortran %s -f\n"%\ os.path.join(self.mg5_path, self.temp_dir_name) v5_string += 'set complex_mass_scheme False \n' diff --git a/tests/parallel_tests/me_comparator.py b/tests/parallel_tests/me_comparator.py index 6a6abbbd97..20c9b54641 100755 --- a/tests/parallel_tests/me_comparator.py +++ b/tests/parallel_tests/me_comparator.py @@ -443,7 +443,7 @@ def format_mg5_proc_card(self, proc_list, model, orders): for i, proc in enumerate(proc_list): v5_string += 'add process ' + proc + ' ' + couplings + \ '@%i' % i + '\n' - v5_string += "output standalone %s -f\n" % \ + v5_string += "output standalone_fortran %s -f\n" % \ os.path.join(self.mg4_path, self.temp_dir_name) return v5_string @@ -466,7 +466,7 @@ def format_mg5_proc_card(self, proc_list, model, orders): for i, proc in enumerate(proc_list): v5_string += 'add process ' + proc + ' ' + couplings + \ '@%i' % i + '\n' - v5_string += "output standalone %s -f\n" % \ + v5_string += "output standalone_fortran %s -f\n" % \ os.path.join(self.mg4_path, self.temp_dir_name) misc.sprint("proc_card.dat content:\n%s" % v5_string) return v5_string @@ -496,7 +496,7 @@ def format_mg5_proc_card(self, proc_list, model, orders): for i, proc in enumerate(proc_list): v5_string += 'add process ' + proc + ' ' + couplings + \ '@%i' % i + '\n' - v5_string += "output standalone %s -f\n" % \ + v5_string += "output standalone_fortran %s -f\n" % \ os.path.join(self.mg4_path, self.temp_dir_name) v5_string += 'set complex_mass_scheme False \n' @@ -546,7 +546,7 @@ def run(self, proc_list, model, orders={}, energy=1000): v5_string = "import model %s \n" % model proc_card_file.write(v5_string) proc_card_file.write(self.pass_proc) - proc_card_file.write("\n output standalone %s -f\n" % dir_name) + proc_card_file.write("\n output standalone_fortran %s -f\n" % dir_name) proc_card_file.close() logging.info("proc_card.dat file for %i processes successfully created in %s" % \ @@ -592,7 +592,7 @@ def format_mg5_proc_card(self, proc_list, model, orders): for i, proc in enumerate(proc_list): v5_string += 'add process ' + proc + ' ' + couplings + \ '@%i' % i + '\n' - v5_string += "output standalone %s -f\n" % \ + v5_string += "output standalone_fortran %s -f\n" % \ os.path.join(self.mg4_path, self.temp_dir_name) return v5_string diff --git a/tests/unit_tests/various/test_process_checks.py b/tests/unit_tests/various/test_process_checks.py index 012d1350ed..a47ea0cd30 100755 --- a/tests/unit_tests/various/test_process_checks.py +++ b/tests/unit_tests/various/test_process_checks.py @@ -860,7 +860,7 @@ def test_python_vs_fortran_epem_aa(self): parent = tempfile.mkdtemp(prefix='mg5_test_f_') sa_dir = os.path.join(parent, 'sa_f') try: - opt = {'sa_symmetry': False, 'export_format': 'standalone', + opt = {'sa_symmetry': False, 'export_format': 'standalone_fortran', 'mp': False, 'v5_model': True, 'output_options': {'noeps': 'True'}} exporter = ev4.ProcessExporterFortranSA(sa_dir, opt) From 02f0c0d37c8b7c5030a3c4737f68891c799e952f Mon Sep 17 00:00:00 2001 From: Olivier Mattelaer Date: Tue, 11 Aug 2026 23:59:09 +0200 Subject: [PATCH 04/26] rename the export format standalone_mg7 -> standalone Second half of the swap. `output standalone` now produces the MadMatrix (C++/CUDA) standalone tree that `output standalone_mg7` produced before; the Fortran one, renamed in the previous commit, is `standalone_fortran`. Renamed in the export-format lists, the do_output config dict, find_output_type, get_default_path (PROCMG7_SA_*), the launch dispatch, the citation lists, the ExportCPPFactory dispatch and the MG7 arm of the language check in process_checks. Two guards needed a decision rather than a rename: * complete_launch excluded the madmatrix export from the SALauncher option set with `mode != 'standalone_mg7'`. That exclusion now reads `mode != 'standalone'`, which looks like a tautology next to the `startswith('standalone')` on the same line but is not; a comment spells that out. * the MadLoop interface has no madmatrix output, so `output standalone` there would have been silently taken as the output *path*. It now raises and points at standalone_fortran. The aMC@NLO interface's forbidden-format list gained `standalone` for the same reason. Test methods named after the old format are renamed to test_madmatrix_*, together with the three workflows that invoke them by name. Co-Authored-By: Claude Opus 5 --- .github/workflows/acceptancetest.yml | 2 +- .github/workflows/acceptancetest_madevent.yml | 2 +- .github/workflows/acceptancetest_mg7.yml | 11 ++-- madgraph/interface/amcatnlo_interface.py | 2 +- madgraph/interface/loop_interface.py | 11 +++- madgraph/interface/madgraph_interface.py | 37 ++++++----- madgraph/iolibs/export_cpp.py | 6 +- madgraph/iolibs/export_v4.py | 5 ++ .../madmatrix/generate_events_standalone | 2 +- .../madmatrix/madmatrix_standalone.mk | 2 +- .../iolibs/template_files/madmatrix/rambo.h | 2 +- madgraph/various/process_checks.py | 8 +-- tests/acceptance_tests/test_cmd.py | 64 +++++++++---------- tests/acceptance_tests/test_cmd_madevent.py | 4 +- 14 files changed, 90 insertions(+), 68 deletions(-) diff --git a/.github/workflows/acceptancetest.yml b/.github/workflows/acceptancetest.yml index 4053439b04..e96bd37c50 100644 --- a/.github/workflows/acceptancetest.yml +++ b/.github/workflows/acceptancetest.yml @@ -327,7 +327,7 @@ jobs: run: | cd $GITHUB_WORKSPACE ./tests/test_manager.py test_standalone_cpp_output_consistency -pA -t0 -l INFO - ./tests/test_manager.py test_standalone_mg7_vs_cpp -pA -t0 -l INFO + ./tests/test_manager.py test_madmatrix_vs_cpp -pA -t0 -l INFO diff --git a/.github/workflows/acceptancetest_madevent.yml b/.github/workflows/acceptancetest_madevent.yml index 4ee70fcecc..bd3ae593c2 100644 --- a/.github/workflows/acceptancetest_madevent.yml +++ b/.github/workflows/acceptancetest_madevent.yml @@ -985,7 +985,7 @@ jobs: acceptancetest_mssm_gogo: # Fortran madevent supports MSSM p p > go go (merged-flavor squark/gluino # vertices); the mg7/madmatrix counterpart now also supports it and is - # checked per-flavor (test_standalone_mg7_mssm_gogo in acceptancetest_mg7.yml). + # checked per-flavor (test_madmatrix_mssm_gogo in acceptancetest_mg7.yml). runs-on: ubuntu-24.04 if: github.event_name == 'push' || github.event.pull_request.head.repo.fork == true steps: diff --git a/.github/workflows/acceptancetest_mg7.yml b/.github/workflows/acceptancetest_mg7.yml index fe8579fe7b..2361e1c0ad 100644 --- a/.github/workflows/acceptancetest_mg7.yml +++ b/.github/workflows/acceptancetest_mg7.yml @@ -290,8 +290,9 @@ jobs: acceptancetest_mg7_mssm_gogo: # mg7/madmatrix now generates the MSSM merged-flavor squark/gluino vertices - # (single-merged-leg / event-by-event flavored couplings); standalone_mg7 - # reproduces the Fortran standalone per-flavor |M|^2 (~1e-4). This is a + # (single-merged-leg / event-by-event flavored couplings); `output + # standalone` reproduces the standalone_fortran per-flavor |M|^2 (~1e-4). + # This is a # matrix-element (standalone) check only -- full mg7 event generation for # merged-flavor processes is not wired up yet (see test_madevent_merged_ # flavor_uq_mg7). The madevent counterpart is test_madevent_mssm_gogo in @@ -303,15 +304,15 @@ jobs: - uses: actions/checkout@v4 - uses: ./.github/actions/checkout_mg5 - uses: ./.github/actions/restore-pip-cache - - name: test one of the test test_standalone_mg7_mssm_gogo + - name: test one of the test test_madmatrix_mssm_gogo run: | cd $GITHUB_WORKSPACE - ./tests/test_manager.py test_standalone_mg7_mssm_gogo -pA -t0 -l INFO + ./tests/test_manager.py test_madmatrix_mssm_gogo -pA -t0 -l INFO acceptancetest_mg7_mssm_gogo_xsec: # Pins the mg7 (madspace) cross-section for MSSM p p > go go to the madevent # reference (5.024 pb, run_01 of test_generation_from_file_1), on top of the - # per-flavor |M|^2 check in test_standalone_mg7_mssm_gogo. This job used to + # per-flavor |M|^2 check in test_madmatrix_mssm_gogo. This job used to # be red at random rather than for a physics reason: the 1% assertion sat # below the ~1.1% statistical error of a 2000-event run, so it failed on # roughly 40% of runs. _run_mg7_xsec now runs 50000 events (error ~0.08%). diff --git a/madgraph/interface/amcatnlo_interface.py b/madgraph/interface/amcatnlo_interface.py index 5702527689..32a95c5e3c 100755 --- a/madgraph/interface/amcatnlo_interface.py +++ b/madgraph/interface/amcatnlo_interface.py @@ -145,7 +145,7 @@ def check_output(self, args): else: self._export_format = 'NLO' - forbidden_formats = ['madevent', 'standalone_fortran'] + forbidden_formats = ['madevent', 'standalone', 'standalone_fortran'] if not hasattr(self, '_fks_multi_proc') or not self._fks_multi_proc: diff --git a/madgraph/interface/loop_interface.py b/madgraph/interface/loop_interface.py index 2b819b7c27..42da33e224 100755 --- a/madgraph/interface/loop_interface.py +++ b/madgraph/interface/loop_interface.py @@ -93,7 +93,16 @@ def check_add(self, args): def check_output(self, args, default='standalone_fortran'): """ Check the arguments of the output command in the context of the Loop interface.""" - + + # `standalone` is the MadMatrix (C++/CUDA) export, which MadLoop does + # not support. Reject it here: otherwise the generic check_output would + # not recognise it as a format and would silently use it as the output + # *path*. The MadLoop standalone output is `standalone_fortran`. + if args and args[0] == 'standalone': + raise self.InvalidCmd( + "'output standalone' (MadMatrix) is not available for MadLoop; " + "use 'output standalone_fortran' instead.") + mg_interface.MadGraphCmd.check_output(self,args, default=default) if self._export_format not in self.supported_ML_format: diff --git a/madgraph/interface/madgraph_interface.py b/madgraph/interface/madgraph_interface.py index ed68b725de..ec94f067b9 100755 --- a/madgraph/interface/madgraph_interface.py +++ b/madgraph/interface/madgraph_interface.py @@ -497,6 +497,8 @@ def help_output(self): logger.info(" - For MadLoop and aMC@NLO runs, there is only one mode and") logger.info(" it is set by default.") logger.info(" - If mode is madevent, create a MadEvent process directory.") + logger.info(" - If mode is standalone, create a Standalone directory") + logger.info(" using the MadMatrix (C++/CUDA) matrix elements.") logger.info(" - If mode is standalone_fortran, create a Fortran Standalone directory") logger.info(" - If mode is matrix, output the matrix.f files for all") logger.info(" generated processes in directory \"path\".") @@ -1540,9 +1542,10 @@ def find_output_type(self, path): if os.path.isfile(pjoin(bin_path,'madevent')): return 'madevent' elif os.path.isfile(pjoin(subproc_path, 'madmatrix.mk')): - # standalone_mg7 writes SubProcesses/madmatrix.mk explicitly - # (the regular mg7 export does not). - return 'standalone_mg7' + # the `standalone` (madmatrix) export writes + # SubProcesses/madmatrix.mk explicitly (the regular mg7 export + # does not). + return 'standalone' elif os.path.isfile(pjoin(card_path, 'run_card.toml')): return 'mg7' elif os.path.isdir(src_path): @@ -1824,7 +1827,7 @@ def check_output(self, args, default='mg7'): # Check for special directory treatment if path == 'auto' and self._export_format in \ ['madevent', 'standalone_fortran', 'standalone_cpp', 'matchbox_cpp', - 'matchbox', 'plugin', 'me7', 'mg7', 'mg7_v5', 'standalone_mg7']: + 'matchbox', 'plugin', 'me7', 'mg7', 'mg7_v5', 'standalone']: self.get_default_path() if '-noclean' not in args and os.path.exists(self._export_dir): args.append('-noclean') @@ -1989,7 +1992,7 @@ def get_default_path(self): (self._curr_model['name'], i) auto_path = lambda i: pjoin(self.writing_dir, name_dir(i)) - elif self._export_format == 'standalone_mg7': + elif self._export_format == 'standalone': name_dir = lambda i: 'PROCMG7_SA_%s_%s' % \ (self._curr_model['name'], i) auto_path = lambda i: pjoin(self.writing_dir, @@ -2605,8 +2608,12 @@ def complete_launch(self, text, line, begidx, endidx,formatting=True): mode = self.find_launch_mode(args) - if mode and mode.startswith('standalone') and mode != 'standalone_mg7': - # standalone outputs are run through SALauncher/MadLoopLauncher: + if mode and mode.startswith('standalone') and mode != 'standalone': + # NB: `mode != 'standalone'` deliberately EXCLUDES the plain + # `standalone` (madmatrix) output, which is launched through its own + # bin/generate_events, not through SALauncher. It is not a typo: + # every *other* standalone_* mode (standalone_fortran, _cpp, _msP, + # _msF, _rw) is run through SALauncher/MadLoopLauncher, for which # only force + the timing analysis options are relevant. opt = ['-f', '--force', '--timings=', '--nb_run='] out['Options'] = self.list_completion(text, opt, line) @@ -3147,7 +3154,7 @@ class MadGraphCmd(HelpToCmd, CheckValidForCmd, CompleteForCmd, CmdExtended): 'matrix', 'standalone_rw'] _export_formats = _v4_export_formats + ['standalone_cpp', 'aloha', 'matchbox_cpp', 'matchbox', 'mg7_v5', 'mg7', - 'standalone_mg7'] + 'standalone'] _set_options = ['group_subprocesses', 'ignore_six_quark_processes', 'stdout_level', @@ -3343,7 +3350,7 @@ def setup(self): self._v4_export_formats = ['madevent', 'standalone_fortran','standalone_msP','standalone_msF', 'matrix', 'standalone_rw'] - self._export_formats = self._v4_export_formats + ['standalone_cpp', 'mg7_v5', 'mg7', 'standalone_mg7'] + self._export_formats = self._v4_export_formats + ['standalone_cpp', 'mg7_v5', 'mg7', 'standalone'] self._nlo_modes_for_completion = ['all','virt','real'] def do_quit(self, line): @@ -7911,7 +7918,7 @@ def do_launch(self, line): options = options.__dict__ # args is now MODE PATH - if args[0] == 'standalone_mg7': + if args[0] == 'standalone': class ext_program: @staticmethod def run(): @@ -9656,7 +9663,7 @@ def do_output(self, line): config['matchbox'] = {'check': True, 'exporter': 'v4', 'output': 'Template'} config['mg7_v5'] = {'check': True, 'exporter': 'cpp', 'output': 'Template'} config['mg7'] = {'check': True, 'exporter': 'cpp', 'output': 'Template'} - config['standalone_mg7'] = {'check': True, 'exporter': 'cpp', 'output': 'Template'} + config['standalone'] = {'check': True, 'exporter': 'cpp', 'output': 'Template'} if self._export_format == 'plugin': options = {'check': self._export_plugin.check, 'exporter':self._export_plugin.exporter, 'output':self._export_plugin.output} @@ -10260,7 +10267,7 @@ def write_generation_citations(self): run) plus a ready-to-use citations.bib and a citations.md summary. """ runnable = ['madevent', 'standalone_fortran', 'standalone_cpp', 'NLO', - 'madweight', 'matchbox', 'mg7', 'mg7_v5', 'standalone_mg7'] + 'madweight', 'matchbox', 'mg7', 'mg7_v5', 'standalone'] if self._export_format not in runnable or not self._export_dir: return try: @@ -10286,13 +10293,13 @@ def write_generation_citations(self): polarization=getattr(self, '_uses_polarization', False), taudecay=getattr(self, '_uses_taudecay', False)) - # MadSpace + MadNIS: used by the mg7 / standalone_mg7 integration engine - if self._export_format in ('mg7', 'mg7_v5', 'standalone_mg7'): + # MadSpace + MadNIS: used by the mg7 / standalone integration engine + if self._export_format in ('mg7', 'mg7_v5', 'standalone'): pairs += [('Heimel:2026hgp', 'phase-space integration with MadSpace'), ('Heimel:2023ngj', 'normalising flows for integration (MadNIS)')] - if self._export_format == 'standalone_mg7': + if self._export_format == 'standalone': pairs.append(('Hagebock:2025jyk', 'data-parallel matrix-element evaluation (MadMatrix)')) diff --git a/madgraph/iolibs/export_cpp.py b/madgraph/iolibs/export_cpp.py index 3302b9f797..e1b1927bdf 100755 --- a/madgraph/iolibs/export_cpp.py +++ b/madgraph/iolibs/export_cpp.py @@ -378,7 +378,7 @@ def write_set_parameters(self, params): def _assert_flv_couplings_supported(self, params): """Refuse, with a clear and actionable message, the merged-flavor - coupling structures the C++ (mg7/standalone_mg7) backend cannot yet + coupling structures the C++ (mg7/standalone) backend cannot yet generate correctly, instead of crashing or emitting wrong/uncompilable code. @@ -404,7 +404,7 @@ def _assert_flv_couplings_supported(self, params): if nb_merged in (1, 2): continue raise InvalidCmd( - "merged-flavor C++ output (mg7/standalone_mg7) does not yet " + "merged-flavor C++ output (mg7/standalone) does not yet " "support this process: flavor coupling %s connects %d " "merged-flavor legs; only one or two are supported. Use " "'output madevent' or 'output standalone_fortran' for this process. " @@ -3477,7 +3477,7 @@ def ExportCPPFactory(cmd, group_subprocesses=False, cmd_options={}): elif cformat == 'mg7': from madmatrix.output import ProcessExporterMadMatrix return ProcessExporterMadMatrix(cmd._export_dir, opt) - elif cformat == 'standalone_mg7': + elif cformat == 'standalone': from madmatrix.output import ProcessExporterMadMatrixStandalone return ProcessExporterMadMatrixStandalone(cmd._export_dir, opt) else: diff --git a/madgraph/iolibs/export_v4.py b/madgraph/iolibs/export_v4.py index 3bba426cb4..d3bf098971 100755 --- a/madgraph/iolibs/export_v4.py +++ b/madgraph/iolibs/export_v4.py @@ -11612,6 +11612,11 @@ def ExportV4Factory(cmd, noclean, output_type='default', group_subprocesses=True if format in ['madevent']: opt['madanalysis5'] = cmd.options['madanalysis5_path'] + # Every standalone_* format that reaches the *v4* factory is + # Fortran-family (standalone_fortran, standalone_msP/msF/rw). The plain + # `standalone` (MadMatrix) and `standalone_cpp` are declared with + # exporter 'cpp' in MadGraphCmd.do_output and go to ExportCPPFactory + # instead, so they never get here despite matching the prefix. if format == 'matrix' or format.startswith('standalone'): return ProcessExporterFortranSA(cmd._export_dir, opt, format=format) diff --git a/madgraph/iolibs/template_files/madmatrix/generate_events_standalone b/madgraph/iolibs/template_files/madmatrix/generate_events_standalone index f86c1d676e..a54009dc9a 100644 --- a/madgraph/iolibs/template_files/madmatrix/generate_events_standalone +++ b/madgraph/iolibs/template_files/madmatrix/generate_events_standalone @@ -2,7 +2,7 @@ # Copyright (C) 2020-2026 CERN and UCLouvain. # Licensed under the GNU Lesser General Public License (version 3 or later). # Integrated with the MadGraph7 project in Feb 2026. -"""Standalone driver for the MadGraph7 standalone_mg7 mode. +"""Standalone driver for the MadGraph7 `output standalone` (madmatrix) mode. Orchestrates compilation and run of `check_sa.exe matrix` in each SubProcesses/P*/ folder. diff --git a/madgraph/iolibs/template_files/madmatrix/madmatrix_standalone.mk b/madgraph/iolibs/template_files/madmatrix/madmatrix_standalone.mk index 618cb9e42a..ce7ebec20a 100644 --- a/madgraph/iolibs/template_files/madmatrix/madmatrix_standalone.mk +++ b/madgraph/iolibs/template_files/madmatrix/madmatrix_standalone.mk @@ -2,7 +2,7 @@ # Licensed under the GNU Lesser General Public License (version 3 or later). # Integrated with the MadGraph7 project in Feb 2026. # -# Standalone (standalone_mg7) Makefile. +# Standalone (`output standalone`, madmatrix) Makefile. # Extends the non-standalone (madmatrix) Makefile by including madmatrix.mk and # adding a recipe for check_sa.exe (the standalone driver). Running `make` here # will build both the process library AND the standalone executable. diff --git a/madgraph/iolibs/template_files/madmatrix/rambo.h b/madgraph/iolibs/template_files/madmatrix/rambo.h index 00679a5255..1d404629ef 100644 --- a/madgraph/iolibs/template_files/madmatrix/rambo.h +++ b/madgraph/iolibs/template_files/madmatrix/rambo.h @@ -1,7 +1,7 @@ // Copyright (C) 2020-2026 CERN and UCLouvain. // Licensed under the GNU Lesser General Public License (version 3 or later). //========================================================================== -// RAMBO phase-space generator for the standalone_mg7 driver. +// RAMBO phase-space generator for the standalone (madmatrix) driver. // Ported verbatim from the MG5aMC standalone_cpp output (rambo.cc/rambo.h), so // that check_sa generates the SAME phase-space point as the Fortran/C++ 'check' // drivers, INCLUDING the particle masses. The only changes are: diff --git a/madgraph/various/process_checks.py b/madgraph/various/process_checks.py index e461dad157..00dd7eb6ce 100755 --- a/madgraph/various/process_checks.py +++ b/madgraph/various/process_checks.py @@ -4084,7 +4084,7 @@ def check_language(process_definition, param_card=None, options=None, cpp_compiler = (hasattr(cmd, 'options') and cmd.options.get('cpp_compiler')) or 'g++' - # MG7 (standalone_mg7 / madmatrix) availability: needs the madmatrix + # MG7 (standalone / madmatrix) availability: needs the madmatrix # package, a C++ compiler and make. Its check_sa.exe "matrix" mode # evaluates the same phase-space point as the Fortran/C++ drivers and # prints the per-flavor PDG / matrix-element lines in the same format. @@ -4277,7 +4277,7 @@ def _parse_sa_output(text, target_pdgs=None): # tuple. All individual-flavor procs share the same matrix element code. sa_f_output_cache = {} sa_cpp_output_cache = {} - # Cache of MG7 (standalone_mg7) check_sa.exe matrix-mode output text. + # Cache of MG7 (standalone / madmatrix) check_sa.exe matrix-mode output text. sa_mg7_output_cache = {} energy_str = str(energy) @@ -4402,7 +4402,7 @@ def _parse_sa_output(text, target_pdgs=None): out_cpp_text = sa_cpp_output_cache.get(sa_key) - # ── MG7 SA (standalone_mg7 / madmatrix) ────────────────────────────── + # ── MG7 SA (standalone / madmatrix) ───────────────────────────────── out_mg7_text = None if has_mg7: if sa_key not in sa_mg7_output_cache: @@ -4410,7 +4410,7 @@ def _parse_sa_output(text, target_pdgs=None): parent_mg7 = tempfile.mkdtemp(prefix='mg5_langcheck_mg7_') sa_dir_mg7 = pjoin(parent_mg7, 'sa_mg7') try: - opt_mg7 = {'export_format': 'standalone_mg7', + opt_mg7 = {'export_format': 'standalone', 'mp': False, 'v5_model': True, 'cpp_compiler': cpp_compiler, 'output_options': {}} diff --git a/tests/acceptance_tests/test_cmd.py b/tests/acceptance_tests/test_cmd.py index 261fcc9962..1a6b29262a 100755 --- a/tests/acceptance_tests/test_cmd.py +++ b/tests/acceptance_tests/test_cmd.py @@ -1241,19 +1241,19 @@ def run_check(proc_dir, nb_try): self.assertTrue(saw_nonzero, 'all matrix elements vanished for %s' % proc) - def test_standalone_mg7_goodhel_filter(self): - """The standalone_mg7 (cudacpp) good-helicity filter must reproduce the + def test_madmatrix_goodhel_filter(self): + """The standalone (madmatrix/cudacpp) good-helicity filter must reproduce the per-flavor matrix element of every flavor served by a merged matrix element. - standalone_mg7 computes a single global good-helicity list once, as the + the standalone (madmatrix) export computes a single global good-helicity list once, as the union over all flavor combinations (see sigmaKin_getGoodHel). A flavor-blind filter -- one that seeds the good helicities from only the flavor(s) of the first sampled events -- would drop a helicity that vanishes for the seeding flavor but contributes for another merged flavor, giving a too-small |M|^2 for that other flavor. - We compare the standalone_mg7 per-flavor values (check_sa.exe 'matrix' + We compare the standalone (madmatrix) per-flavor values (check_sa.exe 'matrix' mode) against the Fortran standalone ones, which test_standalone_goodhel_filter independently validates as filter-invariant. ``u u~ > j j QCD=0`` is a single merged matrix element @@ -1285,7 +1285,7 @@ def get_values(output_format, check_exe, build_source=False): values = [] for d in dirs: proc_dir = pjoin(proc_root, d) - # standalone uses 'make check' + ./check; standalone_mg7 ships a + # standalone_fortran uses 'make check' + ./check; standalone ships a # UMAMI check_sa.exe whose 'matrix' mode == the Fortran driver. target = ['make', 'check'] if output_format == 'standalone_fortran' \ else ['make'] @@ -1303,7 +1303,7 @@ def get_values(output_format, check_exe, build_source=False): self.do('import model sm') self.do('generate u u~ > j j QCD=0') - mg7 = get_values('standalone_mg7', './check_sa.exe') + mg7 = get_values('standalone', './check_sa.exe') standalone = get_values('standalone_fortran', './check', build_source=True) self.assertTrue(any(v != 0.0 for v in standalone), 'all matrix elements vanished for u u~ > j j') @@ -1349,7 +1349,7 @@ def test_standalone_cpp(self): self.assertTrue(me_groups) self.assertAlmostEqual(float(me_groups.group('value')), 6.4739191,5) - # Cross-check standalone_mg7 (madmatrix) against standalone_cpp for this + # Cross-check standalone (madmatrix) against standalone_cpp for this # massive BSM process. The Fortran/C++ ./check auto-bumps the CM energy # to 2*total_mass for the heavy gluinos, but check_sa.exe does not, so # evaluate BOTH at the same explicit above-threshold energy. @@ -1362,7 +1362,7 @@ def test_standalone_cpp(self): self.assertTrue(cpp_me) shutil.rmtree(self.out_dir) - self.do('output standalone_mg7 %s -f' % self.out_dir) + self.do('output standalone %s -f' % self.out_dir) mg7_root = os.path.join(self.out_dir, 'SubProcesses') mg7_cand = [d for d in os.listdir(mg7_root) if d.endswith('_gg_gogo') and @@ -1376,7 +1376,7 @@ def test_standalone_cpp(self): stdout=open(mg7_log, 'w'), stderr=subprocess.STDOUT, cwd=mg7_dir, shell=True) mg7_me = me_re.search(open(mg7_log).read()) - self.assertTrue(mg7_me, 'standalone_mg7 produced no matrix element') + self.assertTrue(mg7_me, 'standalone (madmatrix) produced no matrix element') self._assert_me_lists_close([float(mg7_me.group('value'))], [float(cpp_me.group('value'))]) @@ -1440,7 +1440,7 @@ def _assert_me_lists_close(self, a, b, rtol=1e-5, atol=0.0): (|x-y| <= atol + rtol*max(|x|,|y|)). Backends print with different precision (standalone_cpp 7 sig figs vs - standalone_mg7 full double) and may emit the per-flavour values in a + standalone (madmatrix) full double) and may emit the per-flavour values in a different order, so compare sorted rather than index-by-index / exact. `atol` lets callers treat numerically-tiny (vanishing-flavour) values as zero, where the different floating-point arithmetic of the Fortran vs @@ -1454,11 +1454,11 @@ def _assert_me_lists_close(self, a, b, rtol=1e-5, atol=0.0): 'matrix-element mismatch: %s vs %s' % (x, y)) def test_standalone_cpp_fd_output_consistency(self): - """test standalone_mg7 in FD gauge against standalone + """test the standalone (madmatrix) output in FD gauge against standalone_fortran - The standalone_mg7 (madmatrix) matrix elements must agree with the + The standalone (madmatrix) matrix elements must agree with the Fortran standalone ones, both in FD gauge and in unitary gauge (and FD - vs unitary, i.e. gauge invariance). standalone_mg7 ships a UMAMI-based + vs unitary, i.e. gauge invariance). madmatrix ships a UMAMI-based check_sa.exe whose 'matrix' mode is by design identical to the Fortran check driver; the per-flavour values are compared as sorted multisets (the backends may order flavours differently and print at different @@ -1484,12 +1484,12 @@ def get_values(output_format): cwd=os.path.join(self.out_dir, 'Source')) for oneproc in directories: logfile = os.path.join(proc_dir, oneproc, 'check.log') - # standalone uses 'make check' + ./check; standalone_mg7 ships a + # standalone_fortran uses 'make check' + ./check; standalone ships a # UMAMI check_sa.exe whose 'matrix' mode == the Fortran driver. if output_format == 'standalone_fortran': target = ['make', 'check'] check_exe = './check %s' % energy - elif output_format == 'standalone_mg7': + elif output_format == 'standalone': target = ['make'] check_exe = './check_sa.exe %s' % energy else: @@ -1509,7 +1509,7 @@ def get_values(output_format): values.extend(float(value) for value in me_groups) return values - standalone_mg7 = get_values('standalone_mg7') + madmatrix = get_values('standalone') shutil.rmtree(self.out_dir) standalone = get_values('standalone_fortran') @@ -1517,7 +1517,7 @@ def get_values(output_format): # floating-point noise floor, where the Fortran and cudacpp backends # differ; only require agreement above an absolute floor (the original # cpp-vs-standalone check used assertAlmostEqual, equally lenient here). - self._assert_me_lists_close(standalone_mg7, standalone, atol=1e-7) + self._assert_me_lists_close(madmatrix, standalone, atol=1e-7) self.do('set gauge unitary') self.do('generate _quark _quark > h _quark _quark _quark _anti_quark QCD=0') @@ -1525,24 +1525,24 @@ def get_values(output_format): energy = '1000' shutil.rmtree(self.out_dir) - standalone_mg7_no_fd = get_values('standalone_mg7') + madmatrix_no_fd = get_values('standalone') shutil.rmtree(self.out_dir) standalone_no_fd = get_values('standalone_fortran') - self._assert_me_lists_close(standalone_mg7_no_fd, standalone_no_fd, + self._assert_me_lists_close(madmatrix_no_fd, standalone_no_fd, atol=1e-7) # gauge invariance: unitary-gauge values must also match the FD ones. - self._assert_me_lists_close(standalone_mg7_no_fd, standalone, atol=1e-7) + self._assert_me_lists_close(madmatrix_no_fd, standalone, atol=1e-7) - def test_standalone_mg7_vs_cpp(self): - """Cross-check that standalone_mg7 (madmatrix) reproduces the + def test_madmatrix_vs_cpp(self): + """Cross-check that standalone (madmatrix) reproduces the standalone_cpp matrix elements for p p > e+ e- QCD=0. Uses a massless final state so both check drivers evaluate the same default 1000 GeV phase-space point (no energy auto-bump mismatch), and compares the per-flavour matrix elements as sorted multisets (the two backends may emit them in a different order and at different printed - precision). standalone_mg7 ships a UMAMI-based check_sa.exe whose + precision). madmatrix ships a UMAMI-based check_sa.exe whose 'matrix' mode is by design identical to the Fortran/C++ check drivers. """ energy = '1000' @@ -1577,12 +1577,12 @@ def get_values(output_format, check_exe): self.do('import model sm') self.do('generate p p > e+ e- QCD=0') cpp = get_values('standalone_cpp', './check') - mg7 = get_values('standalone_mg7', './check_sa.exe') + mg7 = get_values('standalone', './check_sa.exe') self._assert_me_lists_close(mg7, cpp) - def test_standalone_mg7_mssm_single_leg(self): + def test_madmatrix_mssm_single_leg(self): """Single-merged-leg flavored couplings must give the same per-flavor - |M|^2 in standalone_mg7 (madmatrix) as in the Fortran standalone. + |M|^2 in standalone (madmatrix) as in the Fortran standalone. p p > n1 n1 QCD=0 is a t-channel-squark process with single-merged-leg vertices (one merged light quark + an unmerged neutralino + a squark) @@ -1627,15 +1627,15 @@ def get_values(output_format, check_exe, build_source=False): self.do('import model MSSM_SLHA2') self.do('generate p p > n1 n1 QCD=0') - mg7 = get_values('standalone_mg7', './check_sa.exe') + mg7 = get_values('standalone', './check_sa.exe') standalone = get_values('standalone_fortran', './check', build_source=True) self.assertTrue(any(v != 0.0 for v in standalone), 'all matrix elements vanished for p p > n1 n1') self._assert_me_lists_close(mg7, standalone, rtol=1e-4) - def test_standalone_mg7_mssm_gogo(self): + def test_madmatrix_mssm_gogo(self): """Dependent (event-by-event, running-alphas) flavored couplings must - give the same per-flavor |M|^2 in standalone_mg7 (madmatrix) as in the + give the same per-flavor |M|^2 in standalone (madmatrix) as in the Fortran standalone. MSSM 'p p > go go' has single-merged-leg squark/gluino-quark vertices @@ -1645,7 +1645,7 @@ def test_standalone_mg7_mssm_gogo(self): addressable as fixed value[] pointers, so they are gathered event-by- event into cDPF_* / flvCOUPs_dep (Step 3 of docs/mg7_merged_flavor_mssm_design.md). This is the dependent-coupling - counterpart of test_standalone_mg7_mssm_single_leg (independent flavored + counterpart of test_madmatrix_mssm_single_leg (independent flavored couplings) and the consistency check matching test_madevent_mssm_gogo. The energy (sqrt(s)) is chosen above the gluino-pair threshold (Mgo ~ @@ -1688,7 +1688,7 @@ def get_values(output_format, check_exe, build_source=False): self.do('import model MSSM_SLHA2') self.do('generate p p > go go') - mg7 = get_values('standalone_mg7', './check_sa.exe') + mg7 = get_values('standalone', './check_sa.exe') standalone = get_values('standalone_fortran', './check', build_source=True) self.assertTrue(any(v != 0.0 for v in standalone), 'all matrix elements vanished for p p > go go') @@ -1698,7 +1698,7 @@ def test_madevent_mssm_gogo(self): """The Fortran madevent output supports MSSM 'p p > go go' (merged-flavor squark/gluino vertices with single-merged-leg / event-by-event flavored couplings). The mg7/madmatrix C++ output now also supports it and is - checked to agree per-flavor in test_standalone_mg7_mssm_gogo; this acts + checked to agree per-flavor in test_madmatrix_mssm_gogo; this acts as the madevent counterpart. """ self.do('import model MSSM_SLHA2') diff --git a/tests/acceptance_tests/test_cmd_madevent.py b/tests/acceptance_tests/test_cmd_madevent.py index bb061a0005..053356018f 100755 --- a/tests/acceptance_tests/test_cmd_madevent.py +++ b/tests/acceptance_tests/test_cmd_madevent.py @@ -3041,8 +3041,8 @@ def test_generation_from_file_1_mg7(self): """mg7 (madspace) cross-section for MSSM p p > go go, pinned to the madevent reference from test_generation_from_file_1. - standalone_mg7 reproduces the per-flavor |M|^2 for p p > go go - (test_standalone_mg7_mssm_gogo, ~1e-4) and the madspace integrator now + the standalone (madmatrix) export reproduces the per-flavor |M|^2 for + p p > go go (test_madmatrix_mssm_gogo, ~1e-4) and the madspace integrator now lands on the madevent cross-section as well, so this pins the mg7 result to the madevent reference (run_01 of test_generation_from_file_1). From 74e9844a43e8b14a7feed62e608738ed7d01ccc9 Mon Sep 17 00:00:00 2001 From: Olivier Mattelaer Date: Wed, 12 Aug 2026 04:21:50 +0200 Subject: [PATCH 05/26] rename the default process directory prefix PROCMG7_ to PROC_ mg7 is the default output format, so its auto-named process directories now take the plain PROC_ prefix instead of PROCMG7_, and the madmatrix standalone takes PROC_SA_ instead of PROCMG7_SA_. Both prefixes are now shared with a legacy backend: PROC_ with madevent and PROC_SA_ with the Fortran standalone. That is deliberate. auto_path() hands out the first unused index, so the two backends never collide on disk, they just interleave -- verified by running all four formats into one directory and getting PROC_sm_0 (mg7), PROC_sm_1 (madevent), PROC_SA_sm_0 (madmatrix) and PROC_SA_sm_1 (standalone_fortran). Comments record the intent, since the branches now build identical names and would otherwise look like a bug. Nothing matches these directories by name, so output-type detection, which works off directory contents, is unaffected. Co-Authored-By: Claude Opus 5 --- madgraph/interface/madgraph_interface.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/madgraph/interface/madgraph_interface.py b/madgraph/interface/madgraph_interface.py index ec94f067b9..750db66395 100755 --- a/madgraph/interface/madgraph_interface.py +++ b/madgraph/interface/madgraph_interface.py @@ -1977,7 +1977,11 @@ def get_default_path(self): auto_path = lambda i: pjoin(self.writing_dir, name_dir(i)) elif self._export_format in ['mg7', 'mg7_v5']: - name_dir = lambda i: 'PROCMG7_%s_%s' % \ + # mg7 is the default output format, so it takes the plain PROC_ + # prefix. This deliberately shares a namespace with madevent + # below: auto_path() picks the first free index, so the two never + # collide on disk, they just interleave. + name_dir = lambda i: 'PROC_%s_%s' % \ (self._curr_model['name'], i) auto_path = lambda i: pjoin(self.writing_dir, name_dir(i)) @@ -1993,7 +1997,10 @@ def get_default_path(self): auto_path = lambda i: pjoin(self.writing_dir, name_dir(i)) elif self._export_format == 'standalone': - name_dir = lambda i: 'PROCMG7_SA_%s_%s' % \ + # As above: the madmatrix standalone is the default standalone, + # so it takes the plain PROC_SA_ prefix and shares the namespace + # with the Fortran standalone in the else branch. + name_dir = lambda i: 'PROC_SA_%s_%s' % \ (self._curr_model['name'], i) auto_path = lambda i: pjoin(self.writing_dir, name_dir(i)) From e6afaf231b79272aaf543df3eb91cccc71fcbaca Mon Sep 17 00:00:00 2001 From: Olivier Mattelaer Date: Wed, 12 Aug 2026 06:05:30 +0200 Subject: [PATCH 06/26] draw the merged-flavor particles with short labels The merged-flavor particles print their internal model names ('_quark', '_anti_quark', '_lepton', ...) in drawn Feynman diagrams, which is unreadable. Substitute short labels at drawing time: 81 -> Q -81 -> Qx 82 -> L- -82 -> L+ 83 -> v -83 -> vx The lepton signs follow the model: the merged '_lepton' is built from the electron (PDG 11, charge -1), so the particle is L- and the antiparticle L+. The map is applied in FeynmanLine.get_name, the single point through which both the EPS and the SVG/JSON drawers fetch a line label, and only for the 'name' lookup, so the polarization suffix path still yields Q_R / L-_L. The particle name/antiname themselves are deliberately NOT renamed: they feed model['name2pdg'] and name2part, i.e. how the interface resolves user input ('define p = g _quark _anti_quark'). Process strings are unchanged. Co-Authored-By: Claude Opus 5 --- madgraph/core/drawing.py | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/madgraph/core/drawing.py b/madgraph/core/drawing.py index 31c7f8ed77..b1dfd678c1 100755 --- a/madgraph/core/drawing.py +++ b/madgraph/core/drawing.py @@ -48,6 +48,25 @@ import madgraph.core.base_objects as base_objects import madgraph.loop.loop_base_objects as loop_objects import madgraph.various.misc as misc + +#=============================================================================== +# Display labels for the merged-flavor particles +#=============================================================================== +# The merged-flavor particles created by Model.define_merge_particle_for carry +# internal model names ('_quark', '_anti_quark', '_lepton', ...). Those names +# are what the user types on the interface (multiparticle definitions, process +# generation) and what model['name2pdg'] resolves, so they must NOT be renamed. +# In a *drawn* diagram those underscore-prefixed names are however unreadable, +# so the labels below are substituted at drawing time only. +# The sign convention follows the model: the merged '_lepton' is built from the +# electron (PDG 11, charge -1), hence the particle is 'L-' and its antiparticle +# 'L+'. The generic '_merged%d' particles (PDG 90+n) have no canonical short +# label -- their content depends on which PDGs were merged -- so they keep +# their model name. +MERGED_PARTICLE_DRAW_NAME = {81: 'Q', -81: 'Qx', + 82: 'L-', -82: 'L+', + 83: 'v', -83: 'vx'} + #=============================================================================== # FeynmanLine #=============================================================================== @@ -198,7 +217,12 @@ def get_name(self, name='name',add_pol=True): name += '_pol' return name - + # merged-flavor particles are drawn with a short readable label + # (the model name '_quark', ... stays untouched: the interface needs it) + # ('name' already means 'antiname' for a negative pid, see below) + if name == 'name' and pid in MERGED_PARTICLE_DRAW_NAME: + return MERGED_PARTICLE_DRAW_NAME[pid] + if pid > 0: return model_info.get(name) elif model_info: From c3588d37064184710edf5c6c7d5c090ac4b7f985 Mon Sep 17 00:00:00 2001 From: Olivier Mattelaer Date: Wed, 12 Aug 2026 07:55:11 +0200 Subject: [PATCH 07/26] rename the mg7 template module madevent.py to launch.py The mg7 output is no longer called madevent7, so naming its driver module madevent.py only invited confusion with the legacy madevent tooling it reuses. The module is imported, not copied, so this is a plain module rename: the generated bin/generate_events now does "from madgraph.iolibs.template_files.mg7.launch import main". Logger names ("madevent", via logging.getLogger) are deliberately left alone: those are shared with the legacy madevent tool drivers that the mg7 launcher reuses, and renaming them would detach the log handlers. Verified end to end: the module imports, a generated process directory has a bin/generate_events pointing at the new module and it runs, and test_relaunch_switch_defaults_mg7 (which imports the module by dotted path) passes. Co-Authored-By: Claude Opus 5 --- .github/actions/install_lhapdf_pdfset/action.yml | 2 +- madgraph/iolibs/export_cpp.py | 2 +- madgraph/iolibs/template_files/mg7/{madevent.py => launch.py} | 0 tests/acceptance_tests/test_cmd_madevent.py | 2 +- 4 files changed, 3 insertions(+), 3 deletions(-) rename madgraph/iolibs/template_files/mg7/{madevent.py => launch.py} (100%) diff --git a/.github/actions/install_lhapdf_pdfset/action.yml b/.github/actions/install_lhapdf_pdfset/action.yml index a0aafe0a6c..e98cefb008 100644 --- a/.github/actions/install_lhapdf_pdfset/action.yml +++ b/.github/actions/install_lhapdf_pdfset/action.yml @@ -2,7 +2,7 @@ name: Install LHAPDF PDF set description: > Make an LHAPDF PDF grid available to the mg7 (madspace) runtime and export LHAPDF_DATA_PATH so it is found. The mg7 runtime reads the grid file directly - (mg7/madevent.py: ms.PdfGrid on $LHAPDF_DATA_PATH//_0000.dat), so it + (mg7/launch.py: ms.PdfGrid on $LHAPDF_DATA_PATH//_0000.dat), so it only needs the grid data -- not the LHAPDF C++ library and not a pre-baked HEPTools cache. The set is installed with the lhapdf-management pip package into a cached, writable directory, so the download happens at most once. diff --git a/madgraph/iolibs/export_cpp.py b/madgraph/iolibs/export_cpp.py index e1b1927bdf..4b8bdbab29 100755 --- a/madgraph/iolibs/export_cpp.py +++ b/madgraph/iolibs/export_cpp.py @@ -3248,7 +3248,7 @@ def copy_template(self, model): "#! /usr/bin/env python3\n" "import sys, os\n" f"sys.path.append('{MG5DIR}')\n" - "from madgraph.iolibs.template_files.mg7.madevent import main\n" + "from madgraph.iolibs.template_files.mg7.launch import main\n" "if __name__ == '__main__':\n" " os.chdir(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))\n" " try:\n" diff --git a/madgraph/iolibs/template_files/mg7/madevent.py b/madgraph/iolibs/template_files/mg7/launch.py similarity index 100% rename from madgraph/iolibs/template_files/mg7/madevent.py rename to madgraph/iolibs/template_files/mg7/launch.py diff --git a/tests/acceptance_tests/test_cmd_madevent.py b/tests/acceptance_tests/test_cmd_madevent.py index 053356018f..c948b4471c 100755 --- a/tests/acceptance_tests/test_cmd_madevent.py +++ b/tests/acceptance_tests/test_cmd_madevent.py @@ -1150,7 +1150,7 @@ def test_relaunch_switch_defaults_mg7(self): mg.exec_cmd('output mg7 %s' % out_dir) launcher = importlib.import_module( - 'madgraph.iolibs.template_files.mg7.madevent') + 'madgraph.iolibs.template_files.mg7.launch') tool_cards = ('pythia8_card.dat', 'madspin_card.dat', 'delphes_card.dat', 'reweight_card.dat', 'rivet_card.dat', 'madanalysis5_parton_card.dat', From 50b086c454db2f2cf09a5f35a253f463c6a9118b Mon Sep 17 00:00:00 2001 From: Olivier Mattelaer Date: Wed, 12 Aug 2026 09:38:05 +0200 Subject: [PATCH 08/26] remove the dead simd/ and generate_events templates from mg7 Two leftovers in madgraph/iolibs/template_files/mg7/ are no longer reachable by any code path: - simd/{api_simd.cpp,cudacpp.mk,Makefile} was wired to an `output --simd=` option added in 3e452ad92. That option, along with the exporter attributes it drove (matrix_element_path / from_template_simd / to_link_simd), has since been removed from ProcessExporterMG7; the only surviving references were the commented-out from_template_simd/to_link_simd block, which is dropped here so no comment points at files that no longer exist. Nothing else copies, links or includes these three files: madmatrix builds its own cudacpp.mk from template_files/madmatrix/madmatrix.mk, and the `simd_vector_size` run_card key is a madspace runtime setting (ms.set_simd_vector_size), unrelated to this directory. - generate_events did `from internal.madevent import main`. No exporter has ever produced a bin/internal layout for mg7 (dirs_to_create is bin/src/lib/Cards/ SubProcesses), the string `internal.madevent` occurs nowhere else in the tree, and the file appears in no from_template list. The real bin/generate_events of an mg7 output is written inline by ProcessExporterMG7.copy_template and points at madgraph.iolibs.template_files.mg7.launch. gridpack.py and train_madnis.py were also examined and are kept: gridpack.py is copied into the gridpack bin/generate_events by launch.py, and train_madnis.py is imported by launch.py as `from .train_madnis import train_madnis`. Co-Authored-By: Claude Opus 5 --- madgraph/iolibs/export_cpp.py | 7 - .../iolibs/template_files/mg7/generate_events | 10 - .../iolibs/template_files/mg7/simd/Makefile | 33 - .../template_files/mg7/simd/api_simd.cpp | 254 ------- .../iolibs/template_files/mg7/simd/cudacpp.mk | 658 ------------------ 5 files changed, 962 deletions(-) delete mode 100755 madgraph/iolibs/template_files/mg7/generate_events delete mode 100644 madgraph/iolibs/template_files/mg7/simd/Makefile delete mode 100644 madgraph/iolibs/template_files/mg7/simd/api_simd.cpp delete mode 100644 madgraph/iolibs/template_files/mg7/simd/cudacpp.mk diff --git a/madgraph/iolibs/export_cpp.py b/madgraph/iolibs/export_cpp.py index 4b8bdbab29..2b2bbecaa3 100755 --- a/madgraph/iolibs/export_cpp.py +++ b/madgraph/iolibs/export_cpp.py @@ -3187,13 +3187,6 @@ class ProcessExporterMG7(ProcessExporterCPP): from_template = {'src': [s+'read_slha.h', s+'read_slha.cc', s+'mg7/api.h'], 'SubProcesses': [s+'mg7/api.cpp'], 'Cards': []} - #from_template_simd = [ - # s+"mg7/api.h", - # s+"mg7/simd/api_simd.cpp", - # s+"mg7/simd/cudacpp.mk", - # s+"mg7/simd/Makefile", - #] - #to_link_simd = ["api.h", "api_simd.cpp", "cudacpp.mk", "Makefile"] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) diff --git a/madgraph/iolibs/template_files/mg7/generate_events b/madgraph/iolibs/template_files/mg7/generate_events deleted file mode 100755 index 57316e48a7..0000000000 --- a/madgraph/iolibs/template_files/mg7/generate_events +++ /dev/null @@ -1,10 +0,0 @@ -#! /usr/bin/env python3 -import sys, os -from internal.madevent import main - -if __name__ == '__main__': - os.chdir(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))) - try: - main() - except KeyboardInterrupt: - pass diff --git a/madgraph/iolibs/template_files/mg7/simd/Makefile b/madgraph/iolibs/template_files/mg7/simd/Makefile deleted file mode 100644 index 5daffc1a48..0000000000 --- a/madgraph/iolibs/template_files/mg7/simd/Makefile +++ /dev/null @@ -1,33 +0,0 @@ -include config.mk - -SUBPROC_PATH=$(PROC_PATH)/SubProcesses/P$(SUBPROC_NUMBER)_$(SUBPROC_NAME) -COMMON_LIB_NAME=libmg5amc_common_cpp.so -SUBPROC_LIB_NAME=libmg5amc_$(SUBPROC_NAME)_cpp.so - -include $(PROC_PATH)/src/cudacpp_config.mk -include cudacpp.mk - -INCDIR_SP=$(SUBPROC_PATH) -INCDIR_SRC=$(PROC_PATH)/src -COMMON_LIB=$(PROC_PATH)/lib/$(COMMON_LIB_NAME) -SUBPROC_LIB=$(PROC_PATH)/lib/$(SUBPROC_LIB_NAME) -MAIN=api.so - -all: $(MAIN) - -$(COMMON_LIB_NAME): $(COMMON_LIB) - cp $(COMMON_LIB) $(COMMON_LIB_NAME) - -$(SUBPROC_LIB_NAME): $(SUBPROC_LIB) - cp $(SUBPROC_LIB) $(SUBPROC_LIB_NAME) - if [ "$(shell uname)" = "Darwin" ]; then\ - install_name_tool -id @rpath/$(SUBPROC_LIB_NAME) $(SUBPROC_LIB_NAME);\ - fi - -$(MAIN): api_simd.cpp $(COMMON_LIB_NAME) $(SUBPROC_LIB_NAME) - $(CXX) -shared $(CXXFLAGS) -I$(INCDIR_SP) -I$(INCDIR_SRC) -I. -Wl,-rpath $(shell pwd) -o $@ \ - api_simd.cpp $(COMMON_LIB_NAME) $(SUBPROC_LIB_NAME) - -.PHONY: clean -clean: - rm -f $(MAIN) $(COMMON_LIB_NAME) $(SUBPROC_LIB_NAME) diff --git a/madgraph/iolibs/template_files/mg7/simd/api_simd.cpp b/madgraph/iolibs/template_files/mg7/simd/api_simd.cpp deleted file mode 100644 index 41f3dab40e..0000000000 --- a/madgraph/iolibs/template_files/mg7/simd/api_simd.cpp +++ /dev/null @@ -1,254 +0,0 @@ -#include "api.h" - -#include "CPPProcess.h" -#include "MemoryAccessMomenta.h" -#include - -using namespace mg5amcCpu; - -namespace { - -void* initialize_impl( - const fptype* momenta, - const fptype* couplings, - fptype* matrix_elements, - fptype* numerators, - fptype* denominators, - std::size_t count -) { - bool is_good_hel[CPPProcess::ncomb]; - sigmaKin_getGoodHel( - momenta, couplings, matrix_elements, numerators, denominators, is_good_hel, count - ); - sigmaKin_setGoodHel(is_good_hel); - return nullptr; -} - -void initialize( - const fptype* momenta, - const fptype* couplings, - fptype* matrix_elements, - fptype* numerators, - fptype* denominators, - std::size_t count -) { - // static local initialization is called exactly once in a thread-safe way - static void* dummy = initialize_impl( - momenta, couplings, matrix_elements, numerators, denominators, count - ); -} - -void transpose_momenta( - const double* momenta_in, fptype* momenta_out, std::size_t i_event, std::size_t stride -) { - std::size_t page_size = MemoryAccessMomentaBase::neppM; - std::size_t i_page = i_event / page_size; - std::size_t i_vector = i_event % page_size; - - for (std::size_t i_part = 0; i_part < CPPProcess::npar; ++i_part) { - for(std::size_t i_mom = 0; i_mom < 4; ++i_mom) { - momenta_out[ - i_page * CPPProcess::npar * 4 * page_size + - i_part * 4 * page_size + i_mom * page_size + i_vector - ] = momenta_in[ - stride * (CPPProcess::npar * i_mom + i_part) + i_event - ]; - } - } -} - -} - -extern "C" { - -const SubProcessInfo* subprocess_info() { - static SubProcessInfo info = { - /* on_gpu = */ false, - /* particle_count = */ CPPProcess::npar, - /* diagram_count = */ CPPProcess::ndiagrams, - /* helicity_count = */ CPPProcess::ncomb - }; - return &info; -} - -void* init_subprocess(const char* param_card_path) { - CPPProcess process; - process.initProc(param_card_path); - // We don't actually need the CPPProcess instance for anything as it initializes a - // global variable. So here we just return a boolean that is used to store whether - // the good helicities are initialized - return new bool(false); -} - -void compute_matrix_element( - void* subprocess, - size_t count, - size_t stride, - const double* momenta_in, - const int* flavor_in, - double* m2_out -) { - // need to round to round to double page size for some reason - std::size_t page_size2 = 2 * MemoryAccessMomentaBase::neppM; - std::size_t rounded_count = (count + page_size2 - 1) / page_size2 * page_size2; - - std::vector momenta(rounded_count * CPPProcess::npar * 4); - std::vector couplings( - rounded_count * mg5amcCpu::Parameters_sm_dependentCouplings::ndcoup * 2 - ); - // alpha s from the paramcard is discarded, so we just use 0.118 for now - std::vector g_s(rounded_count, 1.2177157847767195); // sqrt(4 pi alpha_s) - std::vector helicity_random(rounded_count, 0.5); - std::vector color_random(rounded_count, 0.5); - std::vector matrix_elements(rounded_count); - std::vector channel_index(rounded_count, 2); - std::vector numerators(rounded_count * CPPProcess::ndiagrams); - std::vector denominators(rounded_count); - std::vector helicity_index(rounded_count); - std::vector color_index(rounded_count); - - for (std::size_t i_event = 0; i_event < count; ++i_event) { - transpose_momenta(momenta_in, momenta.data(), i_event, stride); - } - computeDependentCouplings( - g_s.data(), couplings.data(), rounded_count - ); - - bool& is_initialized = *static_cast(subprocess); - if (!is_initialized) { - initialize( - momenta.data(), - couplings.data(), - matrix_elements.data(), - numerators.data(), - denominators.data(), - rounded_count - ); - is_initialized = true; - } - - sigmaKin( - momenta.data(), - couplings.data(), - helicity_random.data(), - color_random.data(), - matrix_elements.data(), - channel_index.data(), - numerators.data(), - denominators.data(), - color_index.data(), - helicity_index.data(), - rounded_count - ); - - std::size_t page_size = MemoryAccessMomentaBase::neppM; - for (std::size_t i_event = 0; i_event < count; ++i_event) { - std::size_t i_page = i_event / page_size; - std::size_t i_vector = i_event % page_size; - - double denominator = denominators[i_event]; - double factor = denominator / numerators[ - i_page * page_size * CPPProcess::ndiagrams + - 1 * page_size + i_vector - ]; - m2_out[i_event] = factor * matrix_elements[i_event]; - } -} - -void compute_matrix_element_multichannel( - void* subprocess, - size_t count, - size_t stride, - const double* momenta_in, - const double* alpha_s_in, - const double* random_in, - const int* flavor_in, - double* m2_out, - double* amp2_out, - int* diagram_out, - int* color_out, - int* helicity_out -) { - // need to round to round to double page size for some reason - std::size_t page_size2 = 2 * MemoryAccessMomentaBase::neppM; - std::size_t rounded_count = (count + page_size2 - 1) / page_size2 * page_size2; - - std::vector momenta(rounded_count * CPPProcess::npar * 4); - std::vector couplings( - rounded_count * mg5amcCpu::Parameters_sm_dependentCouplings::ndcoup * 2 - ); - std::vector g_s(rounded_count); - std::vector helicity_random(rounded_count); - std::vector color_random(rounded_count); - std::vector matrix_elements(rounded_count); - std::vector channel_index(rounded_count, 2); - std::vector numerators(rounded_count * CPPProcess::ndiagrams); - std::vector denominators(rounded_count); - std::vector helicity_index(rounded_count); - std::vector color_index(rounded_count); - - for (std::size_t i_event = 0; i_event < count; ++i_event) { - transpose_momenta(momenta_in, momenta.data(), i_event, stride); - helicity_random[i_event] = random_in[i_event]; - color_random[i_event] = random_in[i_event + stride]; - g_s[i_event] = sqrt(4 * M_PI * alpha_s_in[i_event]); - } - computeDependentCouplings( - g_s.data(), couplings.data(), rounded_count - ); - - bool& is_initialized = *static_cast(subprocess); - if (!is_initialized) { - initialize( - momenta.data(), - couplings.data(), - matrix_elements.data(), - numerators.data(), - denominators.data(), - rounded_count - ); - is_initialized = true; - } - - sigmaKin( - momenta.data(), - couplings.data(), - helicity_random.data(), - color_random.data(), - matrix_elements.data(), - channel_index.data(), - numerators.data(), - denominators.data(), - color_index.data(), - helicity_index.data(), - rounded_count - ); - - std::size_t page_size = MemoryAccessMomentaBase::neppM; - for (std::size_t i_event = 0; i_event < count; ++i_event) { - std::size_t i_page = i_event / page_size; - std::size_t i_vector = i_event % page_size; - - double denominator = denominators[i_event]; - double factor = denominator / numerators[ - i_page * page_size * CPPProcess::ndiagrams + - 1 * page_size + i_vector - ]; - m2_out[i_event] = factor * matrix_elements[i_event]; - for (std::size_t i_diag = 0; i_diag < CPPProcess::ndiagrams; ++i_diag) { - amp2_out[stride * i_diag + i_event] = numerators[ - i_page * page_size * CPPProcess::ndiagrams + - i_diag * page_size + i_vector - ] / denominator; - } - diagram_out[i_event] = 0; - color_out[i_event] = color_index[i_event] - 1; - helicity_out[i_event] = helicity_index[i_event] - 1; - } -} - -void free_subprocess(void* subprocess) { - delete static_cast(subprocess); -} - -} diff --git a/madgraph/iolibs/template_files/mg7/simd/cudacpp.mk b/madgraph/iolibs/template_files/mg7/simd/cudacpp.mk deleted file mode 100644 index 0fd17ab80a..0000000000 --- a/madgraph/iolibs/template_files/mg7/simd/cudacpp.mk +++ /dev/null @@ -1,658 +0,0 @@ -# Copyright (C) 2020-2024 CERN and UCLouvain. -# Licensed under the GNU Lesser General Public License (version 3 or later). -# Created by: S. Roiser (Feb 2020) for the MG5aMC CUDACPP plugin. -# Further modified by: S. Hageboeck, O. Mattelaer, S. Roiser, J. Teig, A. Valassi (2020-2024) for the MG5aMC CUDACPP plugin. - -#=== Determine the name of this makefile (https://ftp.gnu.org/old-gnu/Manuals/make-3.80/html_node/make_17.html) -#=== NB: use ':=' to ensure that the value of CUDACPP_MAKEFILE is not modified further down after including make_opts -#=== NB: use 'override' to ensure that the value can not be modified from the outside -override CUDACPP_MAKEFILE := $(word $(words $(MAKEFILE_LIST)),$(MAKEFILE_LIST)) -###$(info CUDACPP_MAKEFILE='$(CUDACPP_MAKEFILE)') - -#=== NB: different names (e.g. cudacpp.mk and cudacpp_src.mk) are used in the Subprocess and src directories -override CUDACPP_SRC_MAKEFILE = cudacpp_src.mk - -#------------------------------------------------------------------------------- - -#=== Include cudacpp_config.mk - -# Check that the user-defined choices of BACKEND, FPTYPE, HELINL, HRDCOD are supported (and configure defaults if no user-defined choices exist) -# Stop with an error if BACKEND=cuda and nvcc is missing or if BACKEND=hip and hipcc is missing. -# Determine CUDACPP_BUILDDIR from a DIRTAG based on BACKEND, FPTYPE, HELINL, HRDCOD and from the user-defined choice of USEBUILDDIR -# include ../../src/cudacpp_config.mk - -# Export CUDACPP_BUILDDIR (so that there is no need to check/define it again in cudacpp_src.mk) -export CUDACPP_BUILDDIR - -#------------------------------------------------------------------------------- - -#=== Use bash in the Makefile (https://www.gnu.org/software/make/manual/html_node/Choosing-the-Shell.html) - -SHELL := /bin/bash - -#------------------------------------------------------------------------------- - -#=== Detect O/S and architecture (assuming uname is available, https://en.wikipedia.org/wiki/Uname) - -# Detect O/S kernel (Linux, Darwin...) -UNAME_S := $(shell uname -s) -###$(info UNAME_S='$(UNAME_S)') - -# Detect architecture (x86_64, ppc64le...) -UNAME_P := $(shell uname -p) -###$(info UNAME_P='$(UNAME_P)') - -#------------------------------------------------------------------------------- - -#=== Include the common MG5aMC Makefile options - -# OM: including make_opts is crucial for MG5aMC flag consistency/documentation -# AV: disable the inclusion of make_opts if the file has not been generated (standalone cudacpp) -ifneq ($(wildcard ../../Source/make_opts),) - include ../../Source/make_opts -endif - -#------------------------------------------------------------------------------- - -#=== Redefine BACKEND if the current value is 'cppauto' - -# Set the default BACKEND choice corresponding to 'cppauto' (the 'best' C++ vectorization available: eventually use native instead?) -ifeq ($(BACKEND),cppauto) - ifeq ($(UNAME_P),ppc64le) - override BACKEND = cppsse4 - else ifeq ($(UNAME_P),arm) - override BACKEND = cppsse4 - else ifeq ($(wildcard /proc/cpuinfo),) - override BACKEND = cppnone - ###$(warning Using BACKEND='$(BACKEND)' because host SIMD features cannot be read from /proc/cpuinfo) - else ifeq ($(shell grep -m1 -c avx512vl /proc/cpuinfo)$(shell $(CXX) --version | grep ^clang),1) - override BACKEND = cpp512y - else - override BACKEND = cppavx2 - ###ifneq ($(shell grep -m1 -c avx512vl /proc/cpuinfo),1) - ### $(warning Using BACKEND='$(BACKEND)' because host does not support avx512vl) - ###else - ### $(warning Using BACKEND='$(BACKEND)' because this is faster than avx512vl for clang) - ###endif - endif - $(info BACKEND=$(BACKEND) (was cppauto)) -else - $(info BACKEND='$(BACKEND)') -endif - -#------------------------------------------------------------------------------- - -#=== Configure the C++ compiler - -CXXFLAGS = $(OPTFLAGS) -std=c++17 -Wall -Wshadow -Wextra -ifeq ($(shell $(CXX) --version | grep ^nvc++),) - CXXFLAGS += -ffast-math # see issue #117 -endif -###CXXFLAGS+= -Ofast # performance is not different from --fast-math -###CXXFLAGS+= -g # FOR DEBUGGING ONLY - -# Optionally add debug flags to display the full list of flags (eg on Darwin) -###CXXFLAGS+= -v - -# Note: AR, CXX and FC are implicitly defined if not set externally -# See https://www.gnu.org/software/make/manual/html_node/Implicit-Variables.html - -# Add -mmacosx-version-min=11.3 to avoid "ld: warning: object file was built for newer macOS version than being linked" -ifneq ($(shell $(CXX) --version | egrep '^Apple clang'),) - CXXFLAGS += -mmacosx-version-min=11.3 -endif - -# Export CXXFLAGS (so that there is no need to check/define it again in cudacpp_src.mk) -export CXXFLAGS - -#------------------------------------------------------------------------------- - -#=== Configure the GPU compiler (CUDA or HIP) -#=== (note, this is done also for C++, as NVTX and CURAND/ROCRAND are also needed by the C++ backends) - -# Set CUDA_HOME from the path to nvcc, if it exists -override CUDA_HOME = $(patsubst %/bin/nvcc,%,$(shell which nvcc 2>/dev/null)) - -# Set HIP_HOME from the path to hipcc, if it exists -override HIP_HOME = $(patsubst %/bin/hipcc,%,$(shell which hipcc 2>/dev/null)) - -# Configure CUDA_INC (for CURAND and NVTX) and NVTX if a CUDA installation exists (see #965) -ifeq ($(CUDA_HOME),) - # CUDA_HOME is empty (nvcc not found) - override CUDA_INC= -else ifeq ($(wildcard $(CUDA_HOME)/include/),) - # CUDA_HOME is defined (nvcc was found) but $(CUDA_HOME)/include/ does not exist? - override CUDA_INC= -else - CUDA_INC = -I$(CUDA_HOME)/include/ -endif -###$(info CUDA_INC=$(CUDA_INC)) - -# Configure NVTX if a CUDA include directory exists and NVTX headers exist (see #965) -ifeq ($(CUDA_INC),) - # $(CUDA_HOME)/include/ does not exist - override USE_NVTX= -else ifeq ($(wildcard $(CUDA_HOME)/include/nvtx3/nvToolsExt.h),) - # $(CUDA_HOME)/include/ exists but NVTX headers do not exist? - override USE_NVTX= -else - # $(CUDA_HOME)/include/nvtx.h exists: use NVTX - # (NB: the option to disable NVTX if 'USE_NVTX=' is defined has been removed) - override USE_NVTX=-DUSE_NVTX -endif -###$(info USE_NVTX=$(USE_NVTX)) - -# NB: NEW LOGIC FOR ENABLING AND DISABLING CUDA OR HIP BUILDS (AV Feb-Mar 2024) -# - In the old implementation, by default the C++ targets for one specific AVX were always built together with either CUDA or HIP. -# If both CUDA and HIP were installed, then CUDA took precedence over HIP, and the only way to force HIP builds was to disable -# CUDA builds by setting CUDA_HOME to an invalid value (as CUDA_HOME took precdence over PATH to find the installation of nvcc). -# Similarly, C++-only builds could be forced by setting CUDA_HOME and/or HIP_HOME to invalid values. A check for an invalid nvcc -# in CUDA_HOME or an invalid hipcc HIP_HOME was necessary to ensure this logic, and had to be performed at the very beginning. -# - In the new implementation (PR #798), separate individual builds are performed for one specific C++/AVX mode, for CUDA or -# for HIP. The choice of the type of build is taken depending on the value of the BACKEND variable (replacing the AVX variable). -# Unlike what happened in the past, nvcc and hipcc must have already been added to PATH. Using 'which nvcc' and 'which hipcc', -# their existence and their location is checked, and the variables CUDA_HOME and HIP_HOME are internally set by this makefile. -# This must be still done before backend-specific customizations, e.g. because CURAND and NVTX are also used in C++ builds. -# Note also that a preliminary check for nvcc and hipcc if BACKEND is cuda or hip is performed in cudacpp_config.mk. -# - Note also that the REQUIRE_CUDA variable (which was used in the past, e.g. for CI tests on GPU #443) is now (PR #798) no -# longer necessary, as it is now equivalent to BACKEND=cuda. Similarly, there is no need to introduce a REQUIRE_HIP variable. - -#=== Configure the CUDA or HIP compiler (only for the CUDA and HIP backends) -#=== (NB: throughout all makefiles, an empty GPUCC is used to indicate that this is a C++ build, i.e. that BACKEND is neither cuda nor hip!) - -ifeq ($(BACKEND),cuda) - - # If CXX is not a single word (example "clang++ --gcc-toolchain...") then disable CUDA builds (issue #505) - # This is because it is impossible to pass this to "GPUFLAGS += -ccbin " below - ifneq ($(words $(subst ccache ,,$(CXX))),1) # allow at most "CXX=ccache " from outside - $(error BACKEND=$(BACKEND) but CUDA builds are not supported for multi-word CXX "$(CXX)") - endif - - # Set GPUCC as $(CUDA_HOME)/bin/nvcc (it was already checked above that this exists) - GPUCC = $(CUDA_HOME)/bin/nvcc - XCOMPILERFLAG = -Xcompiler - GPULANGUAGE = cu - GPUSUFFIX = cuda - - # Optimization flags - GPUFLAGS = $(foreach opt, $(OPTFLAGS), $(XCOMPILERFLAG) $(opt)) - - # NVidia CUDA architecture flags - # See https://docs.nvidia.com/cuda/cuda-compiler-driver-nvcc/index.html - # See https://arnon.dk/matching-sm-architectures-arch-and-gencode-for-various-nvidia-cards/ - # Default: use compute capability 70 for V100 (CERN lxbatch, CERN itscrd, Juwels Cluster). - # This will embed device code for 70, and PTX for 70+. - # One may pass MADGRAPH_CUDA_ARCHITECTURE (comma-separated list) to the make command to use another value or list of values (see #533). - # Examples: use 60 for P100 (Piz Daint), 80 for A100 (Juwels Booster, NVidia raplab/Curiosity). - MADGRAPH_CUDA_ARCHITECTURE ?= 70 - ###GPUARCHFLAGS = -gencode arch=compute_$(MADGRAPH_CUDA_ARCHITECTURE),code=compute_$(MADGRAPH_CUDA_ARCHITECTURE) -gencode arch=compute_$(MADGRAPH_CUDA_ARCHITECTURE),code=sm_$(MADGRAPH_CUDA_ARCHITECTURE) # Older implementation (AV): go back to this one for multi-GPU support #533 - ###GPUARCHFLAGS = --gpu-architecture=compute_$(MADGRAPH_CUDA_ARCHITECTURE) --gpu-code=sm_$(MADGRAPH_CUDA_ARCHITECTURE),compute_$(MADGRAPH_CUDA_ARCHITECTURE) # Newer implementation (SH): cannot use this as-is for multi-GPU support #533 - comma:=, - GPUARCHFLAGS = $(foreach arch,$(subst $(comma), ,$(MADGRAPH_CUDA_ARCHITECTURE)),-gencode arch=compute_$(arch),code=compute_$(arch) -gencode arch=compute_$(arch),code=sm_$(arch)) - GPUFLAGS += $(GPUARCHFLAGS) - - # Other NVidia-specific flags - CUDA_OPTFLAGS = -lineinfo - GPUFLAGS += $(CUDA_OPTFLAGS) - - # NVCC version - ###GPUCC_VERSION = $(shell $(GPUCC) --version | grep 'Cuda compilation tools' | cut -d' ' -f5 | cut -d, -f1) - - # Fast math - GPUFLAGS += -use_fast_math - - # Extra build warnings - GPUFLAGS += $(XCOMPILERFLAG) -Wunused-parameter - ###GPUFLAGS += $(XCOMPILERFLAG) -Wall $(XCOMPILERFLAG) -Wextra $(XCOMPILERFLAG) -Wshadow - - # CUDA includes and NVTX - GPUFLAGS += $(CUDA_INC) $(USE_NVTX) - - # C++ standard - GPUFLAGS += -std=c++17 # need CUDA >= 11.2 (see #333): this is enforced in mgOnGpuConfig.h - - # For nvcc, use -maxrregcount to control the maximum number of registries (this does not exist in hipcc) - # Without -maxrregcount: baseline throughput: 6.5E8 (16384 32 12) up to 7.3E8 (65536 128 12) - ###GPUFLAGS+= --maxrregcount 160 # improves throughput: 6.9E8 (16384 32 12) up to 7.7E8 (65536 128 12) - ###GPUFLAGS+= --maxrregcount 128 # improves throughput: 7.3E8 (16384 32 12) up to 7.6E8 (65536 128 12) - ###GPUFLAGS+= --maxrregcount 96 # degrades throughput: 4.1E8 (16384 32 12) up to 4.5E8 (65536 128 12) - ###GPUFLAGS+= --maxrregcount 64 # degrades throughput: 1.7E8 (16384 32 12) flat at 1.7E8 (65536 128 12) - - # Set the host C++ compiler for nvcc via "-ccbin " - # (NB issue #505: this must be a single word, "clang++ --gcc-toolchain..." is not supported) - GPUFLAGS += -ccbin $(shell which $(subst ccache ,,$(CXX))) - - # Allow newer (unsupported) C++ compilers with older versions of CUDA if ALLOW_UNSUPPORTED_COMPILER_IN_CUDA is set (#504) - ifneq ($(origin ALLOW_UNSUPPORTED_COMPILER_IN_CUDA),undefined) - GPUFLAGS += -allow-unsupported-compiler - endif - -else ifeq ($(BACKEND),hip) - - # Set GPUCC as $(HIP_HOME)/bin/hipcc (it was already checked above that this exists) - GPUCC = $(HIP_HOME)/bin/hipcc - XCOMPILERFLAG = - GPULANGUAGE = hip - GPUSUFFIX = hip - - # Optimization flags - override OPTFLAGS = -O2 # work around "Memory access fault" in gq_ttq for HIP #806: disable hipcc -O3 optimizations - GPUFLAGS = $(foreach opt, $(OPTFLAGS), $(XCOMPILERFLAG) $(opt)) - - # DEBUG FLAGS (for #806: see https://hackmd.io/@gmarkoma/lumi_finland) - ###GPUFLAGS += -ggdb # FOR DEBUGGING ONLY - - # AMD HIP architecture flags - GPUARCHFLAGS = --offload-arch=gfx90a - GPUFLAGS += $(GPUARCHFLAGS) - - # Other AMD-specific flags - GPUFLAGS += -target x86_64-linux-gnu -DHIP_PLATFORM=amd - - # Fast math (is -DHIP_FAST_MATH equivalent to -ffast-math?) - GPUFLAGS += -DHIP_FAST_MATH - - # Extra build warnings - ###GPUFLAGS += $(XCOMPILERFLAG) -Wall $(XCOMPILERFLAG) -Wextra $(XCOMPILERFLAG) -Wshadow - - # HIP includes - HIP_INC = -I$(HIP_HOME)/include/ - GPUFLAGS += $(HIP_INC) - - # C++ standard - GPUFLAGS += -std=c++17 - -else - - # Backend is neither cuda nor hip - override GPUCC= - override GPUFLAGS= - - # Sanity check, this should never happen: if GPUCC is empty, then this is a C++ build, i.e. BACKEND is neither cuda nor hip. - # In practice, in the following, "ifeq ($(GPUCC),)" is equivalent to "ifneq ($(findstring cpp,$(BACKEND)),)". - # Conversely, note that GPUFLAGS is non-empty also for C++ builds, but it is never used in that case. - ifeq ($(findstring cpp,$(BACKEND)),) - $(error INTERNAL ERROR! Unknown backend BACKEND='$(BACKEND)': supported backends are $(foreach backend,$(SUPPORTED_BACKENDS),'$(backend)')) - endif - -endif - -# Export GPUCC, GPUFLAGS, GPULANGUAGE, GPUSUFFIX (so that there is no need to check/define them again in cudacpp_src.mk) -export GPUCC -export GPUFLAGS -export GPULANGUAGE -export GPUSUFFIX - -#------------------------------------------------------------------------------- - -#=== Configure ccache for C++ and CUDA/HIP builds - -# Enable ccache only if USECCACHE=1 -ifeq ($(USECCACHE)$(shell echo $(CXX) | grep ccache),1) - override CXX:=ccache $(CXX) -endif -#ifeq ($(USECCACHE)$(shell echo $(AR) | grep ccache),1) -# override AR:=ccache $(AR) -#endif -ifneq ($(GPUCC),) - ifeq ($(USECCACHE)$(shell echo $(GPUCC) | grep ccache),1) - override GPUCC:=ccache $(GPUCC) - endif -endif - -#------------------------------------------------------------------------------- - -#=== Configure common compiler flags for C++ and CUDA/HIP - -INCFLAGS = -I. -OPTFLAGS = -O3 # this ends up in GPUFLAGS too (should it?), cannot add -Ofast or -ffast-math here - -# Dependency on src directory -ifeq ($(GPUCC),) -MG5AMC_COMMONLIB = mg5amc_common_cpp -else -MG5AMC_COMMONLIB = mg5amc_common_$(GPUSUFFIX) -endif -LIBFLAGS = -L$(LIBDIR) -l$(MG5AMC_COMMONLIB) -INCFLAGS += -I../../src - -# Compiler-specific googletest build directory (#125 and #738) -ifneq ($(shell $(CXX) --version | grep '^Intel(R) oneAPI DPC++/C++ Compiler'),) - override CXXNAME = icpx$(shell $(CXX) --version | head -1 | cut -d' ' -f5) -else ifneq ($(shell $(CXX) --version | egrep '^clang'),) - override CXXNAME = clang$(shell $(CXX) --version | head -1 | cut -d' ' -f3) -else ifneq ($(shell $(CXX) --version | grep '^g++ (GCC)'),) - override CXXNAME = gcc$(shell $(CXX) --version | head -1 | cut -d' ' -f3) -else - override CXXNAME = unknown -endif -###$(info CXXNAME=$(CXXNAME)) -override CXXNAMESUFFIX = _$(CXXNAME) - -# Export CXXNAMESUFFIX (so that there is no need to check/define it again in cudacpp_test.mk) -export CXXNAMESUFFIX - -# Dependency on test directory -# Within the madgraph4gpu git repo: by default use a common gtest installation in /test (optionally use an external or local gtest) -# Outside the madgraph4gpu git repo: by default do not build the tests (optionally use an external or local gtest) -# Do not build the tests if USEGTEST is equal to 0 (default inside launch_plugin.py, see https://github.com/madgraph5/madgraph4gpu/issues/878) -###GTEST_ROOT = /cvmfs/sft.cern.ch/lcg/releases/gtest/1.11.0-21e8c/x86_64-centos8-gcc11-opt/# example of an external gtest installation -###LOCALGTEST = yes# comment this out (or use make LOCALGTEST=yes) to build tests using a local gtest installation -TESTDIRCOMMON = ../../../../../test -TESTDIRLOCAL = ../../test -ifeq ($(USEGTEST),0) - TESTDIR= - GTEST_ROOT= -else ifneq ($(wildcard $(GTEST_ROOT)),) - TESTDIR= -else ifneq ($(LOCALGTEST),) - TESTDIR=$(TESTDIRLOCAL) - GTEST_ROOT=$(TESTDIR)/googletest/install$(CXXNAMESUFFIX) -else ifneq ($(wildcard ../../../../../epochX/cudacpp/CODEGEN),) - TESTDIR=$(TESTDIRCOMMON) - GTEST_ROOT= $(TESTDIR)/googletest/install$(CXXNAMESUFFIX) -else - TESTDIR= -endif -ifneq ($(GTEST_ROOT),) - GTESTLIBDIR = $(GTEST_ROOT)/lib64/ - GTESTLIBS = $(GTESTLIBDIR)/libgtest.a - GTESTINC = -I$(GTEST_ROOT)/include -else - GTESTLIBDIR = - GTESTLIBS = - GTESTINC = -endif -###$(info GTEST_ROOT = $(GTEST_ROOT)) -###$(info LOCALGTEST = $(LOCALGTEST)) -###$(info TESTDIR = $(TESTDIR)) - -#------------------------------------------------------------------------------- - -#=== Configure PowerPC-specific compiler flags for C++ and CUDA/HIP - -# PowerPC-specific CXX compiler flags (being reviewed) -ifeq ($(UNAME_P),ppc64le) - CXXFLAGS+= -mcpu=power9 -mtune=power9 # gains ~2-3% both for cppnone and cppsse4 - # Throughput references without the extra flags below: cppnone=1.41-1.42E6, cppsse4=2.15-2.19E6 - ###CXXFLAGS+= -DNO_WARN_X86_INTRINSICS # no change - ###CXXFLAGS+= -fpeel-loops # no change - ###CXXFLAGS+= -funroll-loops # gains ~1% for cppnone, loses ~1% for cppsse4 - ###CXXFLAGS+= -ftree-vectorize # no change - ###CXXFLAGS+= -flto # would increase to cppnone=4.08-4.12E6, cppsse4=4.99-5.03E6! -else - ###CXXFLAGS+= -flto # also on Intel this would increase throughputs by a factor 2 to 4... - ######CXXFLAGS+= -fno-semantic-interposition # no benefit (neither alone, nor combined with -flto) -endif - -# PowerPC-specific CUDA/HIP compiler flags (to be reviewed!) -ifeq ($(UNAME_P),ppc64le) - GPUFLAGS+= $(XCOMPILERFLAG) -mno-float128 -endif - -#------------------------------------------------------------------------------- - -#=== Configure defaults for OMPFLAGS - -# Disable OpenMP by default: enable OpenMP only if USEOPENMP=1 (#758) -ifeq ($(USEOPENMP),1) - ###$(info USEOPENMP==1: will build with OpenMP if possible) - ifneq ($(findstring hipcc,$(GPUCC)),) - override OMPFLAGS = # disable OpenMP MT when using hipcc #802 - else ifneq ($(shell $(CXX) --version | egrep '^Intel'),) - override OMPFLAGS = -fopenmp - ###override OMPFLAGS = # disable OpenMP MT on Intel (was ok without GPUCC but not ok with GPUCC before #578) - else ifneq ($(shell $(CXX) --version | egrep '^clang version 16'),) - ###override OMPFLAGS = # disable OpenMP on clang16 #904 - $(error OpenMP is not supported by cudacpp on clang16 - issue #904) - else ifneq ($(shell $(CXX) --version | egrep '^clang version 17'),) - ###override OMPFLAGS = # disable OpenMP on clang17 #904 - $(error OpenMP is not supported by cudacpp on clang17 - issue #904) - else ifneq ($(shell $(CXX) --version | egrep '^(clang)'),) - override OMPFLAGS = -fopenmp - ###override OMPFLAGS = # disable OpenMP MT on clang (was not ok without or with nvcc before #578) - ###else ifneq ($(shell $(CXX) --version | egrep '^(Apple clang)'),) # AV for Mac (Apple clang compiler) - else ifeq ($(UNAME_S),Darwin) # OM for Mac (any compiler) - override OMPFLAGS = # AV disable OpenMP MT on Apple clang (builds fail in the CI #578) - ###override OMPFLAGS = -fopenmp # OM reenable OpenMP MT on Apple clang? (AV Oct 2023: this still fails in the CI) - else - override OMPFLAGS = -fopenmp # enable OpenMP MT by default on all other platforms - ###override OMPFLAGS = # disable OpenMP MT on all other platforms (default before #575) - endif -else - ###$(info USEOPENMP!=1: will build without OpenMP) - override OMPFLAGS = -endif - -#------------------------------------------------------------------------------- - -#=== Configure defaults and check if user-defined choices exist for RNDGEN (legacy!), HASCURAND, HASHIPRAND - -# If the legacy RNDGEN exists, this take precedence over any HASCURAND choice (but a warning is printed out) -###$(info RNDGEN=$(RNDGEN)) -ifneq ($(RNDGEN),) - $(warning Environment variable RNDGEN is no longer supported, please use HASCURAND instead!) - ifeq ($(RNDGEN),hasCurand) - override HASCURAND = $(RNDGEN) - else ifeq ($(RNDGEN),hasNoCurand) - override HASCURAND = $(RNDGEN) - else ifneq ($(RNDGEN),hasNoCurand) - $(error Unknown RNDGEN='$(RNDGEN)': only 'hasCurand' and 'hasNoCurand' are supported - but use HASCURAND instead!) - endif -endif - -# Set the default HASCURAND (curand random number generator) choice, if no prior choice exists for HASCURAND -# (NB: allow HASCURAND=hasCurand even if $(GPUCC) does not point to nvcc: assume CUDA_HOME was defined correctly...) -ifeq ($(HASCURAND),) - ifeq ($(GPUCC),) # CPU-only build - ifeq ($(CUDA_INC),) - # $(CUDA_HOME)/include/ does not exist (see #965) - override HASCURAND = hasNoCurand - else ifeq ($(wildcard $(CUDA_HOME)/include/curand.h),) - # $(CUDA_HOME)/include/ exists but CURAND headers do not exist? (see #965) - override HASCURAND = hasNoCurand - else - # By default, assume that curand is installed if a CUDA installation exists - override HASCURAND = hasCurand - endif - else ifeq ($(findstring nvcc,$(GPUCC)),nvcc) # Nvidia GPU build - # By default, assume that curand is installed if a CUDA build is requested - override HASCURAND = hasCurand - else # non-Nvidia GPU build - override HASCURAND = hasNoCurand - endif -endif - -# Set the default HASHIPRAND (hiprand random number generator) choice, if no prior choice exists for HASHIPRAND -# (NB: allow HASHIPRAND=hasHiprand even if $(GPUCC) does not point to hipcc: assume HIP_HOME was defined correctly...) -ifeq ($(HASHIPRAND),) - ifeq ($(GPUCC),) # CPU-only build - override HASHIPRAND = hasNoHiprand - else ifeq ($(findstring hipcc,$(GPUCC)),hipcc) # AMD GPU build - override HASHIPRAND = hasHiprand - else # non-AMD GPU build - override HASHIPRAND = hasNoHiprand - endif -endif - -#------------------------------------------------------------------------------- - -#=== Set the CUDA/HIP/C++ compiler flags appropriate to user-defined choices of AVX, FPTYPE, HELINL, HRDCOD - -# Set the build flags appropriate to OMPFLAGS -$(info OMPFLAGS=$(OMPFLAGS)) -CXXFLAGS += $(OMPFLAGS) - -# Set the build flags appropriate to each BACKEND choice (example: "make BACKEND=cppnone") -# [NB MGONGPU_PVW512 is needed because "-mprefer-vector-width=256" is not exposed in a macro] -# [See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=96476] -ifeq ($(UNAME_P),ppc64le) - ifeq ($(BACKEND),cppsse4) - override AVXFLAGS = -D__SSE4_2__ # Power9 VSX with 128 width (VSR registers) - else ifeq ($(BACKEND),cppavx2) - $(error Invalid SIMD BACKEND='$(BACKEND)': only 'cppnone' and 'cppsse4' are supported on PowerPC for the moment) - else ifeq ($(BACKEND),cpp512y) - $(error Invalid SIMD BACKEND='$(BACKEND)': only 'cppnone' and 'cppsse4' are supported on PowerPC for the moment) - else ifeq ($(BACKEND),cpp512z) - $(error Invalid SIMD BACKEND='$(BACKEND)': only 'cppnone' and 'cppsse4' are supported on PowerPC for the moment) - endif -else ifeq ($(UNAME_P),arm) - ifeq ($(BACKEND),cppsse4) - override AVXFLAGS = -D__SSE4_2__ # ARM NEON with 128 width (Q/quadword registers) - else ifeq ($(BACKEND),cppavx2) - $(error Invalid SIMD BACKEND='$(BACKEND)': only 'cppnone' and 'cppsse4' are supported on ARM for the moment) - else ifeq ($(BACKEND),cpp512y) - $(error Invalid SIMD BACKEND='$(BACKEND)': only 'cppnone' and 'cppsse4' are supported on ARM for the moment) - else ifeq ($(BACKEND),cpp512z) - $(error Invalid SIMD BACKEND='$(BACKEND)': only 'cppnone' and 'cppsse4' are supported on ARM for the moment) - endif -else ifneq ($(shell $(CXX) --version | grep ^nvc++),) # support nvc++ #531 - ifeq ($(BACKEND),cppnone) - override AVXFLAGS = -mno-sse3 # no SIMD - else ifeq ($(BACKEND),cppsse4) - override AVXFLAGS = -mno-avx # SSE4.2 with 128 width (xmm registers) - else ifeq ($(BACKEND),cppavx2) - override AVXFLAGS = -march=haswell # AVX2 with 256 width (ymm registers) [DEFAULT for clang] - else ifeq ($(BACKEND),cpp512y) - override AVXFLAGS = -march=skylake -mprefer-vector-width=256 # AVX512 with 256 width (ymm registers) [DEFAULT for gcc] - else ifeq ($(BACKEND),cpp512z) - override AVXFLAGS = -march=skylake -DMGONGPU_PVW512 # AVX512 with 512 width (zmm registers) - endif -else - ifeq ($(BACKEND),cppnone) - override AVXFLAGS = -march=x86-64 # no SIMD (see #588) - else ifeq ($(BACKEND),cppsse4) - override AVXFLAGS = -march=nehalem # SSE4.2 with 128 width (xmm registers) - else ifeq ($(BACKEND),cppavx2) - override AVXFLAGS = -march=haswell # AVX2 with 256 width (ymm registers) [DEFAULT for clang] - else ifeq ($(BACKEND),cpp512y) - override AVXFLAGS = -march=skylake-avx512 -mprefer-vector-width=256 # AVX512 with 256 width (ymm registers) [DEFAULT for gcc] - else ifeq ($(BACKEND),cpp512z) - override AVXFLAGS = -march=skylake-avx512 -DMGONGPU_PVW512 # AVX512 with 512 width (zmm registers) - endif -endif -# For the moment, use AVXFLAGS everywhere (in C++ builds): eventually, use them only in encapsulated implementations? -ifeq ($(GPUCC),) - CXXFLAGS+= $(AVXFLAGS) -endif - -# Set the build flags appropriate to each FPTYPE choice (example: "make FPTYPE=f") -$(info FPTYPE='$(FPTYPE)') -ifeq ($(FPTYPE),d) - CXXFLAGS += -DMGONGPU_FPTYPE_DOUBLE -DMGONGPU_FPTYPE2_DOUBLE - GPUFLAGS += -DMGONGPU_FPTYPE_DOUBLE -DMGONGPU_FPTYPE2_DOUBLE -else ifeq ($(FPTYPE),f) - CXXFLAGS += -DMGONGPU_FPTYPE_FLOAT -DMGONGPU_FPTYPE2_FLOAT - GPUFLAGS += -DMGONGPU_FPTYPE_FLOAT -DMGONGPU_FPTYPE2_FLOAT -else ifeq ($(FPTYPE),m) - CXXFLAGS += -DMGONGPU_FPTYPE_DOUBLE -DMGONGPU_FPTYPE2_FLOAT - GPUFLAGS += -DMGONGPU_FPTYPE_DOUBLE -DMGONGPU_FPTYPE2_FLOAT -else - $(error Unknown FPTYPE='$(FPTYPE)': only 'd', 'f' and 'm' are supported) -endif - -# Set the build flags appropriate to each HELINL choice (example: "make HELINL=1") -$(info HELINL='$(HELINL)') -ifeq ($(HELINL),1) - CXXFLAGS += -DMGONGPU_INLINE_HELAMPS - GPUFLAGS += -DMGONGPU_INLINE_HELAMPS -else ifneq ($(HELINL),0) - $(error Unknown HELINL='$(HELINL)': only '0' and '1' are supported) -endif - -# Set the build flags appropriate to each HRDCOD choice (example: "make HRDCOD=1") -$(info HRDCOD='$(HRDCOD)') -ifeq ($(HRDCOD),1) - CXXFLAGS += -DMGONGPU_HARDCODE_PARAM - GPUFLAGS += -DMGONGPU_HARDCODE_PARAM -else ifneq ($(HRDCOD),0) - $(error Unknown HRDCOD='$(HRDCOD)': only '0' and '1' are supported) -endif - -#=== Set the CUDA/HIP/C++ compiler and linker flags appropriate to user-defined choices of HASCURAND, HASHIPRAND - -$(info HASCURAND=$(HASCURAND)) -$(info HASHIPRAND=$(HASHIPRAND)) -override RNDCXXFLAGS= -override RNDLIBFLAGS= - -# Set the RNDCXXFLAGS and RNDLIBFLAGS build flags appropriate to each HASCURAND choice (example: "make HASCURAND=hasNoCurand") -ifeq ($(HASCURAND),hasNoCurand) - override RNDCXXFLAGS += -DMGONGPU_HAS_NO_CURAND -else ifeq ($(HASCURAND),hasCurand) - override RNDLIBFLAGS += -L$(CUDA_HOME)/lib64/ -lcurand # NB: -lcuda is not needed here! -else - $(error Unknown HASCURAND='$(HASCURAND)': only 'hasCurand' and 'hasNoCurand' are supported) -endif - -# Set the RNDCXXFLAGS and RNDLIBFLAGS build flags appropriate to each HASHIPRAND choice (example: "make HASHIPRAND=hasNoHiprand") -ifeq ($(HASHIPRAND),hasNoHiprand) - override RNDCXXFLAGS += -DMGONGPU_HAS_NO_HIPRAND -else ifeq ($(HASHIPRAND),hasHiprand) - override RNDLIBFLAGS += -L$(HIP_HOME)/lib/ -lhiprand -else ifneq ($(HASHIPRAND),hasHiprand) - $(error Unknown HASHIPRAND='$(HASHIPRAND)': only 'hasHiprand' and 'hasNoHiprand' are supported) -endif - -#$(info RNDCXXFLAGS=$(RNDCXXFLAGS)) -#$(info RNDLIBFLAGS=$(RNDLIBFLAGS)) - -#------------------------------------------------------------------------------- - -#=== Configure Position-Independent Code -CXXFLAGS += -fPIC -GPUFLAGS += $(XCOMPILERFLAG) -fPIC - -#------------------------------------------------------------------------------- - -#=== Configure channelid debugging -ifneq ($(MG5AMC_CHANNELID_DEBUG),) - CXXFLAGS += -DMGONGPU_CHANNELID_DEBUG - GPUFLAGS += -DMGONGPU_CHANNELID_DEBUG -endif - -#------------------------------------------------------------------------------- - -#=== Configure build directories and build lockfiles === - -# Build lockfile "full" tag (defines full specification of build options that cannot be intermixed) -# (Rationale: avoid mixing of builds with different random number generators) -override TAG = $(patsubst cpp%,%,$(BACKEND))_$(FPTYPE)_inl$(HELINL)_hrd$(HRDCOD)_$(HASCURAND)_$(HASHIPRAND) - -# Export TAG (so that there is no need to check/define it again in cudacpp_src.mk) -export TAG - -# Build directory: current directory by default, or build.$(DIRTAG) if USEBUILDDIR==1 -override BUILDDIR = $(CUDACPP_BUILDDIR) -ifeq ($(USEBUILDDIR),1) - override LIBDIR = ../../lib/$(BUILDDIR) - override LIBDIRRPATH = '$$ORIGIN/../$(LIBDIR)' - $(info Building in BUILDDIR=$(BUILDDIR) for tag=$(TAG) (USEBUILDDIR == 1)) -else - override LIBDIR = ../../lib - override LIBDIRRPATH = '$$ORIGIN/$(LIBDIR)' - $(info Building in BUILDDIR=$(BUILDDIR) for tag=$(TAG) (USEBUILDDIR != 1)) -endif -###override INCDIR = ../../include -###$(info Building in BUILDDIR=$(BUILDDIR) for tag=$(TAG)) - -# On Linux, set rpath to LIBDIR to make it unnecessary to use LD_LIBRARY_PATH -# Use relative paths with respect to the executables or shared libraries ($ORIGIN on Linux) -# On Darwin, building libraries with absolute paths in LIBDIR makes this unnecessary -ifeq ($(UNAME_S),Darwin) - override CXXLIBFLAGSRPATH = - override GPULIBFLAGSRPATH = - override CXXLIBFLAGSRPATH2 = - override GPULIBFLAGSRPATH2 = -else - # RPATH to gpu/cpp libs when linking executables - override CXXLIBFLAGSRPATH = -Wl,-rpath=$(LIBDIRRPATH) - override GPULIBFLAGSRPATH = -Xlinker -rpath=$(LIBDIRRPATH) - # RPATH to common lib when linking gpu/cpp libs - override CXXLIBFLAGSRPATH2 = -Wl,-rpath='$$ORIGIN' - override GPULIBFLAGSRPATH2 = -Xlinker -rpath='$$ORIGIN' -endif - -# Setting LD_LIBRARY_PATH or DYLD_LIBRARY_PATH in the RUNTIME is no longer necessary (neither on Linux nor on Mac) -override RUNTIME = - From 865563c63982a867bb17362a2f8beea50b6760fc Mon Sep 17 00:00:00 2001 From: Olivier Mattelaer Date: Wed, 12 Aug 2026 14:41:29 +0200 Subject: [PATCH 09/26] accept standalone as an alias of standalone_fortran in ML5 and aMC@NLO Since standalone was given to the MadMatrix (C++/CUDA) exporter, the MadLoop and aMC@NLO interfaces stopped recognising it as a format at all, so the generic check_output would have taken it as the output *path* and quietly created a directory named standalone. The previous commit guarded that by refusing the format outright; accept it as an alias instead. ML5: output standalone now generates the MadLoop Fortran standalone, exactly as output standalone_fortran does. Verified by diffing the full recursive file lists of two generated trees for g g > t t~ [virt=QCD]: identical. aMC@NLO: standalone_fortran is itself a forbidden format there (an NLO process cannot be exported standalone in any form), so the alias is applied before the forbidden-format check and standalone is dropped from that list. The result is the single canonical error, "You generated a NLO process, which cannot be exported in standalone_fortran mode", rather than a second parallel message that could drift from it. Unit suite: 892 tests, only the two known pre-existing failures (testIO_UnitProcOutputIOTests, test_DensityMatrixObservables22 / no scipy). Co-Authored-By: Claude Opus 5 --- madgraph/interface/amcatnlo_interface.py | 9 ++++++++- madgraph/interface/loop_interface.py | 14 +++++++------- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/madgraph/interface/amcatnlo_interface.py b/madgraph/interface/amcatnlo_interface.py index 32a95c5e3c..f1c27ad719 100755 --- a/madgraph/interface/amcatnlo_interface.py +++ b/madgraph/interface/amcatnlo_interface.py @@ -145,7 +145,14 @@ def check_output(self, args): else: self._export_format = 'NLO' - forbidden_formats = ['madevent', 'standalone', 'standalone_fortran'] + # Elsewhere `standalone` names the MadMatrix (C++/CUDA) export. Here it + # is accepted as an alias for `standalone_fortran`, so that it is + # reported as a forbidden *format* below rather than being silently + # taken as the output path. + if args and args[0] == 'standalone': + args[0] = 'standalone_fortran' + + forbidden_formats = ['madevent', 'standalone_fortran'] if not hasattr(self, '_fks_multi_proc') or not self._fks_multi_proc: diff --git a/madgraph/interface/loop_interface.py b/madgraph/interface/loop_interface.py index 42da33e224..33d78d1ac6 100755 --- a/madgraph/interface/loop_interface.py +++ b/madgraph/interface/loop_interface.py @@ -94,14 +94,14 @@ def check_output(self, args, default='standalone_fortran'): """ Check the arguments of the output command in the context of the Loop interface.""" - # `standalone` is the MadMatrix (C++/CUDA) export, which MadLoop does - # not support. Reject it here: otherwise the generic check_output would - # not recognise it as a format and would silently use it as the output - # *path*. The MadLoop standalone output is `standalone_fortran`. + # Elsewhere `standalone` names the MadMatrix (C++/CUDA) export, which + # MadLoop does not support. Here it is accepted as an alias for + # `standalone_fortran`, the MadLoop standalone output, so that + # `output standalone` keeps working in the ML5 interface. Without this + # the generic check_output would not recognise it as a format and would + # silently use it as the output *path*. if args and args[0] == 'standalone': - raise self.InvalidCmd( - "'output standalone' (MadMatrix) is not available for MadLoop; " - "use 'output standalone_fortran' instead.") + args[0] = 'standalone_fortran' mg_interface.MadGraphCmd.check_output(self,args, default=default) From bc499a4e710475dffea120829a6424e4c743c419 Mon Sep 17 00:00:00 2001 From: Olivier Mattelaer Date: Wed, 12 Aug 2026 18:06:55 +0200 Subject: [PATCH 10/26] repoint the madmatrix cross-checks from standalone_cpp to standalone_fortran The two acceptance tests that validate the madmatrix (`standalone`) C++/CUDA output against an independent backend used `standalone_cpp` as the reference. Repoint them at `standalone_fortran` so the coverage survives the upcoming removal of the user-facing `standalone_cpp` format. - test_madmatrix_vs_cpp -> test_madmatrix_vs_fortran (p p > e+ e- QCD=0): the reference arm now builds Source + `make check` and runs ./check, the same convention the other Fortran cross-checks use. - test_standalone_cpp: the MSSM `g g > go go` cross-check now takes its 5000 GeV reference from standalone_fortran, and additionally asserts the hard-coded 6.4739191 reference against the Fortran backend at the auto-bumped default energy. Verified by hand that standalone_fortran reproduces the C++ numbers exactly for MSSM g g > go go: 6.4739191277160320 (default) vs the C++ 6.4739191e+00, and 5.7733902125148244 vs 5.7733902e+00 at 5000 GeV. Co-Authored-By: Claude Opus 5 --- .github/workflows/acceptancetest.yml | 2 +- tests/acceptance_tests/test_cmd.py | 65 +++++++++++++++++++++------- 2 files changed, 51 insertions(+), 16 deletions(-) diff --git a/.github/workflows/acceptancetest.yml b/.github/workflows/acceptancetest.yml index e96bd37c50..958cc3efa3 100644 --- a/.github/workflows/acceptancetest.yml +++ b/.github/workflows/acceptancetest.yml @@ -327,7 +327,7 @@ jobs: run: | cd $GITHUB_WORKSPACE ./tests/test_manager.py test_standalone_cpp_output_consistency -pA -t0 -l INFO - ./tests/test_manager.py test_madmatrix_vs_cpp -pA -t0 -l INFO + ./tests/test_manager.py test_madmatrix_vs_fortran -pA -t0 -l INFO diff --git a/tests/acceptance_tests/test_cmd.py b/tests/acceptance_tests/test_cmd.py index 1a6b29262a..bacff6bb38 100755 --- a/tests/acceptance_tests/test_cmd.py +++ b/tests/acceptance_tests/test_cmd.py @@ -1349,17 +1349,42 @@ def test_standalone_cpp(self): self.assertTrue(me_groups) self.assertAlmostEqual(float(me_groups.group('value')), 6.4739191,5) - # Cross-check standalone (madmatrix) against standalone_cpp for this - # massive BSM process. The Fortran/C++ ./check auto-bumps the CM energy + # Cross-check standalone (madmatrix) against standalone_fortran for + # this massive BSM process. The Fortran ./check auto-bumps the CM energy # to 2*total_mass for the heavy gluinos, but check_sa.exe does not, so # evaluate BOTH at the same explicit above-threshold energy. energy = '5000' - cpp_e_log = os.path.join(proc_dir, 'check_e.log') + + shutil.rmtree(self.out_dir) + self.do('output standalone_fortran %s -f' % self.out_dir) + subprocess.call(['make'], stdout=devnull, stderr=devnull, + cwd=os.path.join(self.out_dir, 'Source')) + f_root = os.path.join(self.out_dir, 'SubProcesses') + f_cand = [d for d in os.listdir(f_root) + if d.endswith('_gg_gogo') and + os.path.isdir(os.path.join(f_root, d))] + self.assertEqual(len(f_cand), 1, + 'expected one gg_gogo Fortran subprocess, got %s' + % f_cand) + f_dir = os.path.join(f_root, f_cand[0]) + subprocess.call(['make', 'check'], stdout=devnull, stderr=devnull, + cwd=f_dir) + # Same hard-coded reference at the auto-bumped default energy. + f_log = os.path.join(f_dir, 'check.log') + subprocess.call('./check', + stdout=open(f_log, 'w'), stderr=subprocess.STDOUT, + cwd=f_dir, shell=True) + f_default = me_re.search(open(f_log).read()) + self.assertTrue(f_default, + 'standalone_fortran produced no matrix element') + self.assertAlmostEqual(float(f_default.group('value')), 6.4739191, 5) + # Reference value at the explicit above-threshold energy. + f_e_log = os.path.join(f_dir, 'check_e.log') subprocess.call('./check %s' % energy, - stdout=open(cpp_e_log, 'w'), stderr=subprocess.STDOUT, - cwd=proc_dir, shell=True) - cpp_me = me_re.search(open(cpp_e_log).read()) - self.assertTrue(cpp_me) + stdout=open(f_e_log, 'w'), stderr=subprocess.STDOUT, + cwd=f_dir, shell=True) + f_me = me_re.search(open(f_e_log).read()) + self.assertTrue(f_me) shutil.rmtree(self.out_dir) self.do('output standalone %s -f' % self.out_dir) @@ -1378,7 +1403,7 @@ def test_standalone_cpp(self): mg7_me = me_re.search(open(mg7_log).read()) self.assertTrue(mg7_me, 'standalone (madmatrix) produced no matrix element') self._assert_me_lists_close([float(mg7_me.group('value'))], - [float(cpp_me.group('value'))]) + [float(f_me.group('value'))]) def test_standalone_cpp_output_consistency(self): @@ -1534,24 +1559,27 @@ def get_values(output_format): # gauge invariance: unitary-gauge values must also match the FD ones. self._assert_me_lists_close(madmatrix_no_fd, standalone, atol=1e-7) - def test_madmatrix_vs_cpp(self): + def test_madmatrix_vs_fortran(self): """Cross-check that standalone (madmatrix) reproduces the - standalone_cpp matrix elements for p p > e+ e- QCD=0. + standalone_fortran matrix elements for p p > e+ e- QCD=0. Uses a massless final state so both check drivers evaluate the same default 1000 GeV phase-space point (no energy auto-bump mismatch), and compares the per-flavour matrix elements as sorted multisets (the two backends may emit them in a different order and at different printed precision). madmatrix ships a UMAMI-based check_sa.exe whose - 'matrix' mode is by design identical to the Fortran/C++ check drivers. + 'matrix' mode is by design identical to the Fortran check driver. """ energy = '1000' devnull = open(os.devnull, 'w') - def get_values(output_format, check_exe): + def get_values(output_format, check_exe, build_source=False): if os.path.isdir(self.out_dir): shutil.rmtree(self.out_dir) self.do('output %s %s' % (output_format, self.out_dir)) + if build_source: + subprocess.call(['make'], stdout=devnull, stderr=devnull, + cwd=os.path.join(self.out_dir, 'Source')) proc_root = os.path.join(self.out_dir, 'SubProcesses') dirs = sorted(d for d in os.listdir(proc_root) if d.startswith('P') and @@ -1562,7 +1590,12 @@ def get_values(output_format, check_exe): re.IGNORECASE) for d in dirs: proc_dir = os.path.join(proc_root, d) - subprocess.call(['make'], stdout=devnull, stderr=devnull, + # standalone_fortran uses 'make check' + ./check; standalone + # ships a UMAMI check_sa.exe whose 'matrix' mode == the Fortran + # driver. + target = ['make', 'check'] \ + if output_format == 'standalone_fortran' else ['make'] + subprocess.call(target, stdout=devnull, stderr=devnull, cwd=proc_dir) log = os.path.join(proc_dir, 'check.log') subprocess.call('%s %s' % (check_exe, energy), @@ -1576,9 +1609,11 @@ def get_values(output_format, check_exe): self.do('import model sm') self.do('generate p p > e+ e- QCD=0') - cpp = get_values('standalone_cpp', './check') + fortran = get_values('standalone_fortran', './check', build_source=True) + self.assertTrue(any(v != 0.0 for v in fortran), + 'all matrix elements vanished for p p > e+ e- QCD=0') mg7 = get_values('standalone', './check_sa.exe') - self._assert_me_lists_close(mg7, cpp) + self._assert_me_lists_close(mg7, fortran) def test_madmatrix_mssm_single_leg(self): """Single-merged-leg flavored couplings must give the same per-flavor From ea8593da34bfa2d14bda9e235a23a26e0f7a3c2a Mon Sep 17 00:00:00 2001 From: Olivier Mattelaer Date: Wed, 12 Aug 2026 18:25:25 +0200 Subject: [PATCH 11/26] remove the user-facing standalone_cpp export format `output standalone_cpp` is gone; `standalone` (MadMatrix C++/CUDA) and `standalone_fortran` cover the two standalone outputs a user wants. The exporter classes are NOT removed: ProcessExporterCPP / OneProcessExporterCPP / UFOModelConverterCPP in madgraph/iolibs/export_cpp.py are the base classes of the madmatrix exporter, and `check language` drives ProcessExporterCPP directly through the internal API. Both keep working unchanged. Interface: - check_output now raises InvalidCmd for `output standalone_cpp` with a message pointing at `standalone` / `standalone_fortran`, the same way `pythia8` and `madweight` already do. Without that guard the removed name would silently be taken as the output *path*. - dropped from _export_formats (class level and setup()), from the do_output config table, from the `path == 'auto'` whitelist, from the get_default_path prefix table (the PROC_SA_CPP_ branch; the remaining standalone branches all produce PROC_SA_ and are collapsed), from the 'Output to directory ... done' and write_generation_citations lists, and from the do_output/launch help. - ExportCPPFactory loses its 'standalone_cpp' branch. find_output_type's `os.path.isdir(src_path)` catch-all is *not* deleted: it is what makes `launch ` work for any C++-family tree with a src/ directory, matchbox_cpp in particular (which is reachable from ./bin/madgraph -- the class-level _export_formats includes it, and setup() only runs when switching back from ML5/aMC@NLO). It now returns 'standalone_cpp_family', with master_interface.do_launch updated in the same commit; the name still starts with 'standalone' so do_launch keeps routing it to the SALauncher. group_subprocs.DecayChainSubProcessGroup.group_amplitudes normalised its `criteria` against a list of *output format* names, but criteria is an exporter's grouped_mode -- only False / 'madevent' / 'madweight' ever reach it. Replaced by the same `if not criteria` normalisation its non-decay sibling uses. Tests: - test_standalone_cpp, test_standalone_cpp_output, test_standalone_cpp_output_consistency and the C++ arm of test_standalone_flavor_mask now go through a new TestCmdShell2._output_standalone_cpp helper that drives ProcessExporterCPP through its internal API, so the scalar-C++ coverage (including the only scalar-C++ flavor-mask coverage) survives. The helper reuses cmd._curr_matrix_elements the way do_output does: rebuilding a HelasMultiProcess from the same _curr_amps does not give the same matrix elements back (decay chains are lost), which is what test_standalone_cpp_output_consistency checks. - tests/parallel_tests/me_comparator.MG5_CPP_Runner is deleted: it drives an out-of-process `output standalone_cpp` proc_card, which cannot be converted to the internal API. Its only caller, sample_script.py, constructed it and never passed it to set_me_runners, and no workflow runs that script, so no live coverage is lost. Test names are unchanged, so no workflow step needed updating. Co-Authored-By: Claude Opus 5 --- madgraph/interface/madgraph_interface.py | 56 +++++++------- madgraph/interface/master_interface.py | 3 +- madgraph/iolibs/export_cpp.py | 2 - madgraph/iolibs/export_v4.py | 6 +- madgraph/iolibs/group_subprocs.py | 6 +- tests/acceptance_tests/test_cmd.py | 93 ++++++++++++++++++++---- tests/parallel_tests/me_comparator.py | 41 ----------- tests/parallel_tests/sample_script.py | 4 - 8 files changed, 115 insertions(+), 96 deletions(-) diff --git a/madgraph/interface/madgraph_interface.py b/madgraph/interface/madgraph_interface.py index 750db66395..948496fab6 100755 --- a/madgraph/interface/madgraph_interface.py +++ b/madgraph/interface/madgraph_interface.py @@ -502,8 +502,6 @@ def help_output(self): logger.info(" - If mode is standalone_fortran, create a Fortran Standalone directory") logger.info(" - If mode is matrix, output the matrix.f files for all") logger.info(" generated processes in directory \"path\".") - logger.info(" - If mode is standalone_cpp, create a standalone C++") - logger.info(" directory in \"path\".") logger.info(" - If mode is pythia8, output all files needed to generate") logger.info(" the processes using Pythia 8. The files are written in") logger.info(" the Pythia 8 directory (default).") @@ -1525,7 +1523,7 @@ def find_import_type(self, path): def find_output_type(self, path): """ identify the type of output of a given directory: - valid output: madevent/standalone/standalone_cpp""" + valid output: madevent/standalone/standalone_fortran/mg7/...""" card_path = pjoin(path,'Cards') bin_path = pjoin(path,'bin') @@ -1549,7 +1547,13 @@ def find_output_type(self, path): elif os.path.isfile(pjoin(card_path, 'run_card.toml')): return 'mg7' elif os.path.isdir(src_path): - return 'standalone_cpp' + # Catch-all for the C++-family standalone trees, i.e. anything + # written by a ProcessExporterCPP descendant that ships a src/ + # directory and is not one of the more specific cases above -- + # today that is `matchbox_cpp` and `mg7_v5`. This is not a + # user-facing `output` format name; it only has to start with + # 'standalone' so that do_launch routes it to the SALauncher. + return 'standalone_cpp_family' elif os.path.isdir(mw_path): return 'madweight' elif os.path.isfile(pjoin(bin_path,'aMCatNLO')): @@ -1778,6 +1782,12 @@ def check_output(self, args, default='mg7'): if args and args[0] == 'pythia8': raise self.InvalidCmd('output pythia8 is no longer supported; please use a different output mode') + if args and args[0] == 'standalone_cpp': + raise self.InvalidCmd('output standalone_cpp is no longer supported; ' + 'use \'standalone\' for the MadMatrix (C++/CUDA) ' + 'standalone or \'standalone_fortran\' for the ' + 'Fortran one') + if args and args[0] in self._export_formats: self._export_format = args.pop(0) elif args: @@ -1826,7 +1836,7 @@ def check_output(self, args, default='mg7'): raise self.InvalidCmd('%s is not allowed in the output path' % char) # Check for special directory treatment if path == 'auto' and self._export_format in \ - ['madevent', 'standalone_fortran', 'standalone_cpp', 'matchbox_cpp', + ['madevent', 'standalone_fortran', 'matchbox_cpp', 'matchbox', 'plugin', 'me7', 'mg7', 'mg7_v5', 'standalone']: self.get_default_path() if '-noclean' not in args and os.path.exists(self._export_dir): @@ -1991,24 +2001,13 @@ def get_default_path(self): auto_path = lambda i: pjoin(self.writing_dir, name_dir(i)) elif self._export_format.startswith('standalone'): - if self._export_format == 'standalone_cpp': - name_dir = lambda i: 'PROC_SA_CPP_%s_%s' % \ - (self._curr_model['name'], i) - auto_path = lambda i: pjoin(self.writing_dir, - name_dir(i)) - elif self._export_format == 'standalone': - # As above: the madmatrix standalone is the default standalone, - # so it takes the plain PROC_SA_ prefix and shares the namespace - # with the Fortran standalone in the else branch. - name_dir = lambda i: 'PROC_SA_%s_%s' % \ - (self._curr_model['name'], i) - auto_path = lambda i: pjoin(self.writing_dir, - name_dir(i)) - else: - name_dir = lambda i: 'PROC_SA_%s_%s' % \ - (self._curr_model['name'], i) - auto_path = lambda i: pjoin(self.writing_dir, - name_dir(i)) + # The madmatrix standalone is the default standalone, so it takes + # the plain PROC_SA_ prefix and shares the auto-name namespace with + # the Fortran standalone (and its msP/msF/rw variants). + name_dir = lambda i: 'PROC_SA_%s_%s' % \ + (self._curr_model['name'], i) + auto_path = lambda i: pjoin(self.writing_dir, + name_dir(i)) elif self._export_format in ['matchbox_cpp', 'matchbox']: name_dir = lambda i: 'PROC_MATCHBOX_%s_%s' % \ (self._curr_model['name'], i) @@ -3159,7 +3158,7 @@ class MadGraphCmd(HelpToCmd, CheckValidForCmd, CompleteForCmd, CmdExtended): _v4_export_formats = ['madevent', 'standalone_fortran', 'standalone_msP','standalone_msF', 'matrix', 'standalone_rw'] - _export_formats = _v4_export_formats + ['standalone_cpp', 'aloha', + _export_formats = _v4_export_formats + ['aloha', 'matchbox_cpp', 'matchbox', 'mg7_v5', 'mg7', 'standalone'] _set_options = ['group_subprocesses', @@ -3357,7 +3356,7 @@ def setup(self): self._v4_export_formats = ['madevent', 'standalone_fortran','standalone_msP','standalone_msF', 'matrix', 'standalone_rw'] - self._export_formats = self._v4_export_formats + ['standalone_cpp', 'mg7_v5', 'mg7', 'standalone'] + self._export_formats = self._v4_export_formats + ['mg7_v5', 'mg7', 'standalone'] self._nlo_modes_for_completion = ['all','virt','real'] def do_quit(self, line): @@ -9664,7 +9663,6 @@ def do_output(self, line): config['standalone_msF'] = {'check': False, 'exporter': 'v4', 'output':'Template'} config['standalone_msP'] = {'check': False, 'exporter': 'v4', 'output':'Template'} config['standalone_rw'] = {'check': False, 'exporter': 'v4', 'output':'Template'} - config['standalone_cpp'] = {'check': False, 'exporter': 'cpp', 'output': 'Template'} config['pythia8'] = {'check': False, 'exporter': 'cpp', 'output':'dir'} config['matchbox_cpp'] = {'check': True, 'exporter': 'cpp', 'output': 'Template'} config['matchbox'] = {'check': True, 'exporter': 'v4', 'output': 'Template'} @@ -10258,7 +10256,7 @@ def finalize(self, nojpeg, online = False, flaglist=[]): # into its final citations.bib. self.write_generation_citations() - if self._export_format in ['madevent', 'standalone_fortran', 'standalone_cpp', 'matchbox', 'mg7']: + if self._export_format in ['madevent', 'standalone_fortran', 'matchbox', 'mg7']: logger.info('Output to directory ' + self._export_dir + ' done.') if self._export_format in ['madevent', 'NLO']: @@ -10273,7 +10271,7 @@ def write_generation_citations(self): routines. Writes citations.log (machine-readable, collected by every run) plus a ready-to-use citations.bib and a citations.md summary. """ - runnable = ['madevent', 'standalone_fortran', 'standalone_cpp', 'NLO', + runnable = ['madevent', 'standalone_fortran', 'NLO', 'madweight', 'matchbox', 'mg7', 'mg7_v5', 'standalone'] if self._export_format not in runnable or not self._export_dir: return @@ -10847,7 +10845,7 @@ class MadGraphCmdWeb(CheckValidForCmdWeb, MadGraphCmd): help="forbid to display the generate file and only generate the eps file") # LAUNCH PROGRAM _launch_usage = "launch [DIRPATH] [options]\n" + \ - "-- execute the madevent/standalone/standalone_cpp/pythia8/NLO output present in DIRPATH\n" + \ + "-- execute the madevent/standalone/standalone_fortran/pythia8/NLO output present in DIRPATH\n" + \ " By default DIRPATH is the latest created directory \n" + \ " (for pythia8, it should be the Pythia 8 main directory) \n" + \ " Example: launch PROC_sm_1 --name=run2 \n" + \ diff --git a/madgraph/interface/master_interface.py b/madgraph/interface/master_interface.py index 6fa1184e03..6f5a8e8fa9 100755 --- a/madgraph/interface/master_interface.py +++ b/madgraph/interface/master_interface.py @@ -504,7 +504,8 @@ def do_launch(self, line, *argss, **opts): # if there is a path, find what output has been done if path: type = self.cmd.find_output_type(self, path) - if type in ['standalone_fortran', 'standalone_cpp', 'pythia8', 'madevent']: + if type in ['standalone_fortran', 'standalone_cpp_family', + 'pythia8', 'madevent']: self.change_principal_cmd('MadGraph') elif type == 'aMC@NLO': self.change_principal_cmd('aMC@NLO') diff --git a/madgraph/iolibs/export_cpp.py b/madgraph/iolibs/export_cpp.py index 2b2bbecaa3..d464d0cc62 100755 --- a/madgraph/iolibs/export_cpp.py +++ b/madgraph/iolibs/export_cpp.py @@ -3461,8 +3461,6 @@ def ExportCPPFactory(cmd, group_subprocesses=False, cmd_options={}): if cformat == 'pythia8': return ProcessExporterPythia8(cmd._export_dir, opt) - elif cformat == 'standalone_cpp': - return ProcessExporterCPP(cmd._export_dir, opt) elif cformat == 'matchbox_cpp': return ProcessExporterMatchbox(cmd._export_dir, opt) elif cformat == 'mg7_v5': diff --git a/madgraph/iolibs/export_v4.py b/madgraph/iolibs/export_v4.py index d3bf098971..4ebd5b26ee 100755 --- a/madgraph/iolibs/export_v4.py +++ b/madgraph/iolibs/export_v4.py @@ -11614,9 +11614,9 @@ def ExportV4Factory(cmd, noclean, output_type='default', group_subprocesses=True # Every standalone_* format that reaches the *v4* factory is # Fortran-family (standalone_fortran, standalone_msP/msF/rw). The plain - # `standalone` (MadMatrix) and `standalone_cpp` are declared with - # exporter 'cpp' in MadGraphCmd.do_output and go to ExportCPPFactory - # instead, so they never get here despite matching the prefix. + # `standalone` (MadMatrix) is declared with exporter 'cpp' in + # MadGraphCmd.do_output and goes to ExportCPPFactory instead, so it + # never gets here despite matching the prefix. if format == 'matrix' or format.startswith('standalone'): return ProcessExporterFortranSA(cmd._export_dir, opt, format=format) diff --git a/madgraph/iolibs/group_subprocs.py b/madgraph/iolibs/group_subprocs.py index 335dd9ada6..7fd3b8bb78 100755 --- a/madgraph/iolibs/group_subprocs.py +++ b/madgraph/iolibs/group_subprocs.py @@ -776,7 +776,11 @@ def group_amplitudes(decay_chain_amps, criteria='madevent', matrix_elements_opts assert isinstance(decay_chain_amps, diagram_generation.DecayChainAmplitudeList), \ "Argument to group_amplitudes must be DecayChainAmplitudeList" - if criteria in ['matrix', 'standalone','pythia8','standalone_cpp', False]: + # `criteria` is an exporter's grouped_mode, not an output format name: + # the only values ever passed are False, 'madevent' and 'madweight' + # ('gpu' is mapped to 'madevent' by the caller). Normalise the falsy + # case the same way SubProcessGroup.group_amplitudes does. + if not criteria: criteria = 'madevent' assert criteria in ['madevent', 'madweight'] diff --git a/tests/acceptance_tests/test_cmd.py b/tests/acceptance_tests/test_cmd.py index bacff6bb38..4dc23e282f 100755 --- a/tests/acceptance_tests/test_cmd.py +++ b/tests/acceptance_tests/test_cmd.py @@ -921,8 +921,10 @@ def test_standalone_flavor_mask(self): """Acceptance test for the per-flavor masking optimization. Generates p p > j j QCD=0 and, for the q q~ > q q~ subprocess, - exercises both the Fortran (standalone) and C++ (standalone_cpp) - backends. The check_sa driver is patched to also evaluate two + exercises both the Fortran (standalone_fortran) and scalar C++ + (export_cpp.ProcessExporterCPP, driven through its internal API by + _output_standalone_cpp) backends. + The check_sa driver is patched to also evaluate two non-representative flavors -- s c~ > s c~ (flavor 3 4 3 4) and s c~ > c c~ (flavor 3 4 4 4) -- and the matrix-element source is patched to print the runtime flavor mask that gates the HELAS @@ -1060,8 +1062,7 @@ def assert_backend(text, known_flavor, zero_flavor): assert_backend(run_check(proc_dir), (3, 4, 3, 4), (3, 4, 4, 4)) # ---- C++ standalone ----------------------------------------- - shutil.rmtree(self.out_dir) - self.do('output standalone_cpp %s -f' % self.out_dir) + self._output_standalone_cpp(self.out_dir, force=True) proc_dir = find_qqx(pjoin(self.out_dir, 'SubProcesses')) def extend_flavor_2d_array(text, name, dim_old, dim_new, extra_rows): @@ -1310,17 +1311,22 @@ def get_values(output_format, check_exe, build_source=False): self._assert_me_lists_close(mg7, standalone, atol=1e-7) def test_standalone_cpp(self): - """test that standalone cpp is working""" + """test that the scalar C++ standalone exporter is working + + `output standalone_cpp` is no longer a user-facing format, so the C++ + arm drives export_cpp.ProcessExporterCPP through its internal API + (_output_standalone_cpp) instead of the command. + """ if os.path.isdir(self.out_dir): shutil.rmtree(self.out_dir) self.do('import model MSSM_SLHA2-full') self.do('generate g g > go go QED=2') - self.do('output standalone_cpp %s ' % self.out_dir) + self._output_standalone_cpp(self.out_dir) devnull = open(os.devnull,'w') - # Locate the subprocess directory: the merge shortened the standalone_cpp + # Locate the subprocess directory: the merge shortened the C++ # directory name (e.g. P0_Sigma_MSSM_SLHA2_full_gg_gogo -> P1_gg_gogo), # so discover it rather than hard-coding the number/prefix. proc_root = os.path.join(self.out_dir, 'SubProcesses') @@ -1414,11 +1420,11 @@ def test_standalone_cpp_output_consistency(self): #step 0 cpp output self.do('generate p p > t t~, t > b mu+ vm, t~ > b~ mu- vm~') - self.do('output standalone_cpp %s ' % self.out_dir) + self._output_standalone_cpp(self.out_dir) devnull = open(os.devnull,'w') - # Discover the subprocess directories: the merge shortened the - # standalone_cpp directory names (e.g. P0_Sigma_sm_gg_bmupvmbxmumvmx -> + # Discover the subprocess directories: the merge shortened the C++ + # directory names (e.g. P0_Sigma_sm_gg_bmupvmbxmumvmx -> # P1_gg_bmupvmbxmumvmx), so list them rather than hard-coding. def get_values(): proc_root = os.path.join(self.out_dir, 'SubProcesses') @@ -1452,19 +1458,76 @@ def get_values(): #step 1 standalone output shutil.rmtree(self.out_dir) self.do('output standalone_fortran %s -f' % self.out_dir) - shutil.rmtree(self.out_dir) - self.do('output standalone_cpp %s -f' % self.out_dir) + self._output_standalone_cpp(self.out_dir, force=True) new = get_values() for i,_ in enumerate(original): self.assertEqual(original[i], new[i]) + def _output_standalone_cpp(self, out_dir, force=False): + """Write a scalar C++ standalone output for the processes currently + held by the interface, driving export_cpp.ProcessExporterCPP through + its internal API. + + `output standalone_cpp` is no longer a user-facing format, but the + exporter class itself is very much alive: it is the base class of the + madmatrix (`standalone`) export, and `check language` drives it exactly + this way (see madgraph/various/process_checks.py and + tests/unit_tests/various/test_process_checks.py). Going through the API + keeps the scalar-C++ coverage of these tests without the command. + """ + import madgraph.iolibs.export_cpp as export_cpp + import madgraph.iolibs.helas_call_writers as helas_call_writers + import madgraph.core.helas_objects as helas_objects + + cmd = self.cmd + model = cmd._curr_model + + if force and os.path.isdir(out_dir): + shutil.rmtree(out_dir) + + opt = dict(cmd.options) + opt['output_options'] = {} + opt.update({'sa_symmetry': False, 'export_format': 'standalone_cpp', + 'mp': False, 'v5_model': True}) + exporter = export_cpp.ProcessExporterCPP(out_dir, opt) + + # Reuse the helas objects the interface already built, exactly like + # do_output does: building a second HelasMultiProcess from the same + # _curr_amps does NOT give the same matrix elements back (decay chains + # in particular are lost), so the cache is what makes repeated exports + # of one `generate` consistent. + multi_me = cmd._curr_matrix_elements + if not isinstance(multi_me, helas_objects.HelasMultiProcess) or \ + not multi_me.get_matrix_elements(): + # do_output sets this global from the exporter before building the + # helas objects; mirror it. + helas_objects.HelasMatrixElement.enumerate_all_flavors = \ + not getattr(exporter, 'use_flavor_mask', True) + multi_me = helas_objects.HelasMultiProcess(cmd._curr_amps) + for uid, me in enumerate(multi_me.get_matrix_elements()): + me.get('processes')[0].set('uid', uid + 1) + cmd._curr_matrix_elements = multi_me + matrix_elements = multi_me.get_matrix_elements() + self.assertTrue(matrix_elements, 'no matrix element to export') + + cpp_writer = helas_call_writers.CPPUFOHelasCallWriter(model) + exporter.copy_template(model) + for me_number, me in enumerate(matrix_elements): + exporter.generate_subprocess_directory(me, cpp_writer, me_number) + exporter.convert_model(model, multi_me.get_used_lorentz(), + multi_me.get_used_couplings()) + # ProcessExporterCPP.finalize() ignores its arguments and compiles src. + exporter.finalize({'matrix_elements': matrix_elements}, '', + cmd.options, ['nojpeg']) + return out_dir + def _assert_me_lists_close(self, a, b, rtol=1e-5, atol=0.0): """Assert two matrix-element value lists agree as multisets (sorted), within a combined relative/absolute tolerance (|x-y| <= atol + rtol*max(|x|,|y|)). - Backends print with different precision (standalone_cpp 7 sig figs vs + Backends print with different precision (the scalar C++ one 7 sig figs vs standalone (madmatrix) full double) and may emit the per-flavour values in a different order, so compare sorted rather than index-by-index / exact. `atol` lets callers treat numerically-tiny (vanishing-flavour) values as @@ -4625,7 +4688,7 @@ def test_standalone_cpp_output(self): self.do('import model sm') self.do('generate e+ e- > e+ e- @2') - self.do('output standalone_cpp %s' % self.out_dir) + self._output_standalone_cpp(self.out_dir) # Check that all needed src files are generated files = ['HelAmps_sm.h', 'HelAmps_sm.cc', 'Makefile', @@ -4643,7 +4706,7 @@ def test_standalone_cpp_output(self): self.assertTrue(os.path.exists(os.path.join(self.out_dir, 'lib', 'libmodel_sm.a'))) - # Locate the subprocess directory: the merge shortened the standalone_cpp + # Locate the subprocess directory: the merge shortened the C++ # directory name (P2_Sigma_sm_epem_epem -> P2_epem_epem), so discover it. proc_root = os.path.join(self.out_dir, 'SubProcesses') candidates = [d for d in os.listdir(proc_root) diff --git a/tests/parallel_tests/me_comparator.py b/tests/parallel_tests/me_comparator.py index 20c9b54641..a775a63a05 100755 --- a/tests/parallel_tests/me_comparator.py +++ b/tests/parallel_tests/me_comparator.py @@ -598,47 +598,6 @@ def format_mg5_proc_card(self, proc_list, model, orders): return v5_string -class MG5_CPP_Runner(MG5Runner): - """Runner object for the MG5 C++ Standalone output.""" - - mg5_path = "" - - type='cpp' - name = 'MG5-C++' - compilator ='g++' - - def format_mg5_proc_card(self, proc_list, model, orders): - """Create a proc_card.dat string following v5 conventions.""" - - v5_string = "import model %s \n" % model - v5_string += "set automatic_html_opening False\n" - couplings = MERunner.get_coupling_definitions(orders) - - for i, proc in enumerate(proc_list): - v5_string += 'add process ' + proc + ' ' + couplings + \ - '@%i' % i + '\n' - v5_string += "output standalone_cpp %s -f\n" % \ - os.path.join(self.mg4_path, self.temp_dir_name) - - return v5_string - - def fix_energy_in_check(self, dir_name, energy): - """Replace the hard coded collision energy in check_sa.cpp by the given - energy, assuming a working dir dir_name""" - - for check_sa_path in glob.glob( - os.path.join(dir_name, 'SubProcesses', '*', 'check_sa.cpp')): - - file = open(check_sa_path, 'r') - check_sa = file.read() - file.close() - - file = open(check_sa_path, 'w') - file.write(re.sub(r"double energy = [\d.]+;", - "double energy = %s;" % str(float(energy)), - check_sa)) - file.close() - class PickleRunner(MERunner): """Runner object for the stored comparison results.""" diff --git a/tests/parallel_tests/sample_script.py b/tests/parallel_tests/sample_script.py index bb038c7212..2dc89c352c 100755 --- a/tests/parallel_tests/sample_script.py +++ b/tests/parallel_tests/sample_script.py @@ -71,10 +71,6 @@ my_mg5_ufo = me_comparator.MG5_UFO_Runner() my_mg5_ufo.setup(mg5_path, mg4_path) - # Create a MERunner object for C++ - my_mg5_cpp = me_comparator.MG5_CPP_Runner() - my_mg5_cpp.setup(mg5_path, mg4_path) - # Create and setup a comparator my_comp = me_comparator.MEComparator() my_comp.set_me_runners(my_mg5, my_mg5_ufo) From 329f22b0a3f62dcad7c484f16441fdb86fd8ae68 Mon Sep 17 00:00:00 2001 From: Olivier Mattelaer Date: Wed, 12 Aug 2026 20:34:58 +0200 Subject: [PATCH 12/26] give MG7 its own command-history file, mg7history bin/madgraph wrote its readline history to mg5history in the shared state directory ($XDG_STATE_HOME, ~/.local/state, or the legacy ~/.mg5). An MG5_aMC install writes bin/mg5_aMC history to that same path, so anyone with both installed had the two command sets interleaved into one file, each run truncating the other to the last 100 entries. MG7 now writes mg7history instead. mg5history is read once as a fallback when mg7history does not yet exist, so an existing MG5 user does not start from an empty history, and mg5history is never written to again. Left alone deliberately: the me5history and mw5history files used by Template/{LO,NLO}/bin and MadWeight. Those launchers are madevent, aMCatNLO and MadWeight, which are the same tools in an MG5 and an MG7 install, so sharing a history between them is correct rather than a collision. Co-Authored-By: Claude Opus 5 --- bin/madgraph | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/bin/madgraph b/bin/madgraph index 65e1973e72..017be5079e 100755 --- a/bin/madgraph +++ b/bin/madgraph @@ -114,8 +114,18 @@ else: else: state_dir = os.getenv('XDG_STATE_HOME', os.path.join(os.environ['HOME'], '.local', 'state')) - history_file = os.path.join(state_dir, "mg5history") - readline.read_history_file(history_file) + # MG7 keeps its own history file. An MG5_aMC install writes mg5history + # into this same shared state directory, so sharing the name would + # interleave the two command sets. Read mg5history as a one-time + # fallback so an existing MG5 user does not start from an empty + # history; writes always go to mg7history. + history_file = os.path.join(state_dir, "mg7history") + if os.path.exists(history_file): + readline.read_history_file(history_file) + else: + legacy_history_file = os.path.join(state_dir, "mg5history") + if os.path.exists(legacy_history_file): + readline.read_history_file(legacy_history_file) except: pass From 836b96d9c516887c0c3249ea7e02143bdd6aed98 Mon Sep 17 00:00:00 2001 From: Olivier Mattelaer Date: Wed, 12 Aug 2026 22:33:20 +0200 Subject: [PATCH 13/26] default the mg7 run_card to the dynamical HT/2 scale The mg7 run_card already defaulted dynamical_scale_choice to half_transverse_mass, but that setting was inert: fixed_ren_scale and fixed_fact_scale both defaulted to true, so every default mg7 run used a fixed scale of 91.188 GeV. The launcher passes these through as ren_scale_fixed / fact_scale_fixed, which gate whether the dynamical choice is consulted at all. Flip both defaults to false so HT/2 is what a default run actually uses. The 91.188 values stay as the fallback for users who turn either switch back on. A lepton-collider process still pins fixed_fact_scale (set in check_validity): with no PDF there is no meaningful factorisation scale. The renormalisation scale goes dynamical there too. The cross-section references in check_xsec_processes_reference.json were generated with the fixed scale. They remain valid because test_check_xsec_processes_mg7._edit_run_card rewrites the two switches back to true before running -- until now that rewrite matched nothing and was dead defensive code, and it is what keeps roughly forty reference values honest, so its comments are updated to say so. The mg7 cross-section tests in test_cmd_madevent.py are unaffected: their helper already forced both switches to false, so their reference values were always taken with HT/2 and that rewrite simply becomes a no-op. Co-Authored-By: Claude Opus 5 --- madgraph/various/banner.py | 8 ++++++-- .../check_xsec_processes_reference.json | 2 +- .../test_check_xsec_processes_mg7.py | 17 ++++++++++------- 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/madgraph/various/banner.py b/madgraph/various/banner.py index 13957043ac..831ba8bc24 100755 --- a/madgraph/various/banner.py +++ b/madgraph/various/banner.py @@ -6477,8 +6477,12 @@ def default_setup(self): self.add_toml_param('beam', 'e_cm', 13000.0) self.add_toml_param('beam', 'leptonic', False) self.add_toml_param('beam', 'pdf', "NNPDF23_lo_as_0130_qed") - self.add_toml_param('beam', 'fixed_ren_scale', True) - self.add_toml_param('beam', 'fixed_fact_scale', True) + # Default to the dynamical scale set by dynamical_scale_choice below + # (half_transverse_mass, i.e. HT/2) rather than to the fixed ren_scale + # / fact_scale values. Those fixed values are kept as the fallback used + # when a user turns either of these back on. + self.add_toml_param('beam', 'fixed_ren_scale', False) + self.add_toml_param('beam', 'fixed_fact_scale', False) self.add_toml_param('beam', 'ren_scale', 91.188) self.add_toml_param('beam', 'fact_scale1', 91.188) self.add_toml_param('beam', 'fact_scale2', 91.188) diff --git a/tests/acceptance_tests/check_xsec_processes_reference.json b/tests/acceptance_tests/check_xsec_processes_reference.json index 30c7316e27..3f63de8625 100644 --- a/tests/acceptance_tests/check_xsec_processes_reference.json +++ b/tests/acceptance_tests/check_xsec_processes_reference.json @@ -1,5 +1,5 @@ { - "_comment": "Reference cross-sections (pb) for systematic MG7 testing. Source of truth: #28. Reference runs used fixed scale (mu = 91.188 GeV, the run_card.toml default), e_cm = 13000 GeV, NNPDF23_lo_as_0130_qed, 1M events.", + "_comment": "Reference cross-sections (pb) for systematic MG7 testing. Source of truth: #28. Reference runs used fixed scale (mu = 91.188 GeV, pinned by the test; the run_card.toml default is now dynamical HT/2), e_cm = 13000 GeV, NNPDF23_lo_as_0130_qed, 1M events.", "defines": [], "sections": { "3jets": [ diff --git a/tests/acceptance_tests/test_check_xsec_processes_mg7.py b/tests/acceptance_tests/test_check_xsec_processes_mg7.py index 3da2a80730..f5a13ddd65 100644 --- a/tests/acceptance_tests/test_check_xsec_processes_mg7.py +++ b/tests/acceptance_tests/test_check_xsec_processes_mg7.py @@ -24,10 +24,11 @@ ``_anti_quark`` merged-flavor particles are used directly from the sm model), 2. ``output mg7``s it, - 3. edits ``Cards/run_card.toml`` -- fixed scale is already the template - default (mu = 91.188 GeV, e_cm = 13000 GeV, NNPDF23_lo_as_0130_qed); here - we only set the event count and, for the hadronic tt~ decays, neutralise - the jet cuts (see CLAUDE.md), + 3. edits ``Cards/run_card.toml`` -- it pins the fixed scale the references + were generated with (mu = 91.188 GeV, e_cm = 13000 GeV, + NNPDF23_lo_as_0130_qed), since the template now defaults to the dynamical + HT/2 scale; it also sets the event count and, for the hadronic tt~ decays, + neutralises the jet cuts (see CLAUDE.md), 4. runs ``bin/generate_events -f`` and reads the cross-section from the madspace ``Events/*/info.json`` (``process.mean`` / ``process.error``), 5. asserts the relative difference to the reference stays within a tolerance. @@ -135,9 +136,11 @@ def _mg7_datadir_or_skip(test): def _edit_run_card(toml_path, events, disable_jet_cuts): """Set the event count and (optionally) neutralise the jet cuts. - Fixed renormalisation/factorisation scales are already the template - default; we only force them back on if a template change ever flipped - them, to keep the reference configuration honest.""" + The reference cross-sections were produced with FIXED scales + (mu = 91.188 GeV). The run_card.toml template now defaults to the + dynamical HT/2 scale instead, so these replacements are what pins the + configuration back to the one the references were generated with. They + are load-bearing: drop them and every reference value below goes stale.""" t = open(toml_path).read() t = t.replace('fixed_ren_scale = false', 'fixed_ren_scale = true') t = t.replace('fixed_fact_scale = false', 'fixed_fact_scale = true') From 37b97d9bb8afc757b506cc4242a1c0765f404e6f Mon Sep 17 00:00:00 2001 From: Olivier Mattelaer Date: Wed, 12 Aug 2026 22:44:51 +0200 Subject: [PATCH 14/26] fix the mg7 transverse_energy scale mapping to the legacy code 1 _dyn_scale_legacy translates an mg7 dynamical_scale_choice name back to the legacy integer code used by the madevent-side tools. It mapped transverse_energy to 3, which is HT/2, rather than to 1, the sum of transverse energy. The inverse map _LO_DYNSCALE_MAP has always had 1 -> transverse_energy, so an LO run_card carrying dynamical_scale_choice = 1 round-tripped through mg7 and came back as 3, silently changing the scale. The comment above the map said "mapping to confirm"; it is now confirmed against the codes documented on the LO run_card and both maps round-trip for all four values. This was latent until the previous commit: with fixed scales as the mg7 default, _legacy_compat returned -1 before ever consulting this map. Now that a default mg7 run is dynamical, the map is live. Co-Authored-By: Claude Opus 5 --- madgraph/various/banner.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/madgraph/various/banner.py b/madgraph/various/banner.py index 831ba8bc24..1d1b45e3ae 100755 --- a/madgraph/various/banner.py +++ b/madgraph/various/banner.py @@ -6647,14 +6647,18 @@ def __getitem__(self, name): 'dparameter', 'lhaid', 'iseed', 'python_seed', } - # mg7 dynamical_scale_choice name -> legacy integer code - # NOTE: mapping to confirm; only relevant for a dynamical-scale run (fixed - # scales, the mg7 default, are handled through the event scale directly). + # mg7 dynamical_scale_choice name -> legacy integer code. This is the + # inverse of _LO_DYNSCALE_MAP below and must stay consistent with it, and + # with the legacy codes documented on the LO run_card's + # dynamical_scale_choice: 1 = sum of transverse energy, 2 = HT (sum of + # transverse mass), 3 = HT/2, 4 = partonic centre-of-mass energy. + # Only consulted for a dynamical-scale run; a fixed-scale run returns -1 + # before reaching here and is handled through the event scale directly. _dyn_scale_legacy = { 'partonic_energy': 4, 'transverse_mass': 2, 'half_transverse_mass': 3, - 'transverse_energy': 3, + 'transverse_energy': 1, } def _legacy_compat(self, key): From 4ad794d049ee36bd094233c43718ac3725f90d6b Mon Sep 17 00:00:00 2001 From: Olivier Mattelaer Date: Thu, 13 Aug 2026 00:35:29 +0200 Subject: [PATCH 15/26] gzip the mg7 LHE output by default madspace writes events.lhe uncompressed. LHE is large and very compressible, and madevent has always stored its events gzipped, so compress the file once combine_to_lhe has produced it. On a 2000-event e+ e- > mu+ mu- run this takes 1569291 bytes down to 135001, a factor 11.6, and the archive passes gzip -t with all 2000 events intact. Nothing downstream needed changing: _find_event_file already looked for both events.lhe and events.lhe.gz, the time-of-flight post-processing already keys off need_zip = lhe_path.endswith('.gz') to re-compress what it rewrites, and the mg7 acceptance tests either gunzip the .gz when the plain file is absent or iterate over both names. misc.gzip is used rather than a local implementation so that files over 256 MB go through the external multithreaded tool, as elsewhere in the codebase. Verified: test_systematics_mg7 and test_add_time_of_flight_mg7 both pass with this change; the latter exercises the gunzip/rewrite/re-gzip round trip. Known interaction, not introduced here but now on the default path: reweight_interface.do_import gunzips a .gz input WITHOUT keep=True, so it consumes the event file it was given, and if the banner then comes back empty it prompts with default 0 under timeout=0 and fails on open('0'). That branch used to be rare for mg7 output and is now the normal one. test_mass_reweighting_mg7 cannot arbitrate this locally: it fails on this machine both with and without this change (the known macOS f2py/lhapdf breakage), differing only in which symptom it reports. Co-Authored-By: Claude Opus 5 --- madgraph/iolibs/template_files/mg7/launch.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/madgraph/iolibs/template_files/mg7/launch.py b/madgraph/iolibs/template_files/mg7/launch.py index 63b40a4be8..5900585a95 100644 --- a/madgraph/iolibs/template_files/mg7/launch.py +++ b/madgraph/iolibs/template_files/mg7/launch.py @@ -713,10 +713,18 @@ def generate_events(self) -> None: ) elif output_format == "lhe": self.lhe_completer = self.build_lhe_completer() + lhe_path = os.path.join(self.run_path, "events.lhe") self.event_generator.combine_to_lhe( - os.path.join(self.run_path, "events.lhe"), self.lhe_completer, + lhe_path, self.lhe_completer, self.build_lhe_meta(), ) + # Ship the LHE compressed by default. These files are large and + # very compressible, madevent has always stored its events + # gzipped, and every consumer here already accepts either form + # (see _find_event_file). misc.gzip replaces events.lhe with + # events.lhe.gz, and switches to an external multithreaded tool + # above 256 MB. + misc.gzip(lhe_path) else: raise ValueError("Unknown output format") self.save_gridpack() From f035e1888fb0d351b88de6606046b6abe864048c Mon Sep 17 00:00:00 2001 From: Olivier Mattelaer Date: Thu, 13 Aug 2026 08:02:43 +0200 Subject: [PATCH 16/26] do not double-gzip the mg7 event file for the madevent tools MG7RunCmd._prepare_run_dir materialises the unweighted_events.lhe.gz name that the madevent post-processing tools expect, by gzipping the mg7 event file. That assumed the source was the plain events.lhe. Since events.lhe is now written gzipped, it produced a doubly-gzipped file: each consumer decompressed it once, got gzip bytes rather than LHE text, and parsed zero events. lhe_parser.split then raised UnboundLocalError: cannot access local variable 'i' because its event loop never ran. This took out every tool that reads the events -- Pythia8, Rivet, reweighting and both MadSpin jobs. Copy the file when the source is already compressed, and keep gzipping only a plain source, so both forms still work. Verified by driving _prepare_run_dir directly on both source forms: each produces an unweighted_events.lhe.gz that lhe_parser.EventFile reads as a 9271-byte banner and 2000 events. Reproduced the original fault the same way first: the doubly-gzipped file gives banner length 0 and 0 events. Co-Authored-By: Claude Opus 5 --- .../iolibs/template_files/mg7/run_interface.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/madgraph/iolibs/template_files/mg7/run_interface.py b/madgraph/iolibs/template_files/mg7/run_interface.py index 6f3007fcb4..2491c3c2c2 100644 --- a/madgraph/iolibs/template_files/mg7/run_interface.py +++ b/madgraph/iolibs/template_files/mg7/run_interface.py @@ -64,13 +64,23 @@ def __init__(self, me_dir, options, run_name, lhe_path): # ------------------------------------------------------------------ def _prepare_run_dir(self): """Provide the file names the madevent tools look for: a gzipped - ``unweighted_events.lhe.gz`` next to the mg7 ``events.lhe``.""" + ``unweighted_events.lhe.gz`` next to the mg7 event file. + + The mg7 event file may already be gzipped (``events.lhe.gz``, the + default) or plain (``events.lhe``). Compressing an already-gzipped + source would produce a doubly-gzipped file: every consumer would + decompress it once, get gzip bytes instead of LHE text, and read zero + events. So copy when the source is already compressed and only gzip a + plain source.""" run_dir = os.path.dirname(self._mg7_lhe_path) gz = pjoin(run_dir, 'unweighted_events.lhe.gz') if not os.path.exists(gz) and os.path.exists(self._mg7_lhe_path): - with open(self._mg7_lhe_path, 'rb') as fin, \ - gzip.open(gz, 'wb') as fout: - shutil.copyfileobj(fin, fout) + if self._mg7_lhe_path.endswith('.gz'): + shutil.copyfile(self._mg7_lhe_path, gz) + else: + with open(self._mg7_lhe_path, 'rb') as fin, \ + gzip.open(gz, 'wb') as fout: + shutil.copyfileobj(fin, fout) def load_results_db(self): """Fresh results database without recreating old runs from banners: the From 3137b3f5bbc38c971ffd59658798172a7706c445 Mon Sep 17 00:00:00 2001 From: Olivier Mattelaer Date: Thu, 13 Aug 2026 10:12:34 +0200 Subject: [PATCH 17/26] pin the PDF set in the mg7 cross-section reference test The ~40 reference cross-sections in check_xsec_processes_reference.json were produced with NNPDF23_lo_as_0130_qed, but the test only pinned the scale and took the PDF from whatever the run_card.toml template defaults to. Changing that default therefore silently invalidated every reference value. Pin the set explicitly into each generated Cards/run_card.toml, next to the existing fixed-scale pins, and route the skip guard through the same _REFERENCE_PDF constant so the set the test requires on disk and the set it runs with cannot drift apart. No reference value changes: the pinned set is what the template still defaults to, so this is a no-op today and pure protection for tomorrow. Co-Authored-By: Claude Opus 5 --- .../check_xsec_processes_reference.json | 2 +- .../test_check_xsec_processes_mg7.py | 43 ++++++++++++------- 2 files changed, 29 insertions(+), 16 deletions(-) diff --git a/tests/acceptance_tests/check_xsec_processes_reference.json b/tests/acceptance_tests/check_xsec_processes_reference.json index 3f63de8625..0f19dd0782 100644 --- a/tests/acceptance_tests/check_xsec_processes_reference.json +++ b/tests/acceptance_tests/check_xsec_processes_reference.json @@ -1,5 +1,5 @@ { - "_comment": "Reference cross-sections (pb) for systematic MG7 testing. Source of truth: #28. Reference runs used fixed scale (mu = 91.188 GeV, pinned by the test; the run_card.toml default is now dynamical HT/2), e_cm = 13000 GeV, NNPDF23_lo_as_0130_qed, 1M events.", + "_comment": "Reference cross-sections (pb) for systematic MG7 testing. Source of truth: #28. Reference runs used fixed scale (mu = 91.188 GeV, pinned by the test; the run_card.toml default is now dynamical HT/2), e_cm = 13000 GeV, NNPDF23_lo_as_0130_qed (also pinned by the test, so these values stay valid if the run_card.toml default PDF changes), 1M events.", "defines": [], "sections": { "3jets": [ diff --git a/tests/acceptance_tests/test_check_xsec_processes_mg7.py b/tests/acceptance_tests/test_check_xsec_processes_mg7.py index f5a13ddd65..627a7a7de9 100644 --- a/tests/acceptance_tests/test_check_xsec_processes_mg7.py +++ b/tests/acceptance_tests/test_check_xsec_processes_mg7.py @@ -24,11 +24,12 @@ ``_anti_quark`` merged-flavor particles are used directly from the sm model), 2. ``output mg7``s it, - 3. edits ``Cards/run_card.toml`` -- it pins the fixed scale the references - were generated with (mu = 91.188 GeV, e_cm = 13000 GeV, - NNPDF23_lo_as_0130_qed), since the template now defaults to the dynamical - HT/2 scale; it also sets the event count and, for the hadronic tt~ decays, - neutralises the jet cuts (see CLAUDE.md), + 3. edits ``Cards/run_card.toml`` -- it pins the configuration the references + were generated with: the fixed scale (mu = 91.188 GeV, e_cm = 13000 GeV), + since the template now defaults to the dynamical HT/2 scale, and the PDF + set (``NNPDF23_lo_as_0130_qed``), so the references stay valid no matter + which set the template defaults to; it also sets the event count and, for + the hadronic tt~ decays, neutralises the jet cuts (see CLAUDE.md), 4. runs ``bin/generate_events -f`` and reads the cross-section from the madspace ``Events/*/info.json`` (``process.mean`` / ``process.error``), 5. asserts the relative difference to the reference stays within a tolerance. @@ -75,6 +76,13 @@ _HERE = os.path.dirname(os.path.abspath(__file__)) _REFERENCE = pjoin(_HERE, 'check_xsec_processes_reference.json') +# PDF set the reference cross-sections were generated with. The test pins it +# into every run_card.toml (see _edit_run_card) instead of relying on the +# template default, so that changing the default PDF of the mg7 run_card does +# not silently invalidate all ~40 reference values. It is also the set +# _mg7_datadir_or_skip requires to be installed. +_REFERENCE_PDF = 'NNPDF23_lo_as_0130_qed' + # Environment-tunable knobs (see module docstring). Kept as module globals so # the dynamically generated test methods pick up the CI-provided values. _TOLERANCE = float(os.environ.get('MG7_XSEC_TOLERANCE', 0.01)) @@ -105,9 +113,9 @@ def _tail(path, n=60): def _mg7_datadir_or_skip(test): - """Return an LHAPDF data dir that contains the NNPDF23_lo_as_0130_qed set, - or ``skipTest`` (on *test*) when the mg7 runtime stack (madspace + LHAPDF + - the run_card.toml default PDF) is unavailable.""" + """Return an LHAPDF data dir that contains the ``_REFERENCE_PDF`` set, or + ``skipTest`` (on *test*) when the mg7 runtime stack (madspace + LHAPDF + + the PDF set the references were generated with) is unavailable.""" try: import madspace has_mg7 = hasattr(madspace, 'ChannelEventGenerator') @@ -127,23 +135,28 @@ def _mg7_datadir_or_skip(test): except Exception: pass for d in candidates: - if d and os.path.isdir(d) and glob.glob(pjoin(d, 'NNPDF23_lo_as_0130_qed*')): + if d and os.path.isdir(d) and glob.glob(pjoin(d, '%s*' % _REFERENCE_PDF)): return d - test.skipTest('NNPDF23_lo_as_0130_qed LHAPDF data not found ' - '(set $LHAPDF_DATA_PATH)') + test.skipTest('%s LHAPDF data not found (set $LHAPDF_DATA_PATH)' + % _REFERENCE_PDF) def _edit_run_card(toml_path, events, disable_jet_cuts): """Set the event count and (optionally) neutralise the jet cuts. The reference cross-sections were produced with FIXED scales - (mu = 91.188 GeV). The run_card.toml template now defaults to the - dynamical HT/2 scale instead, so these replacements are what pins the - configuration back to the one the references were generated with. They - are load-bearing: drop them and every reference value below goes stale.""" + (mu = 91.188 GeV) and with the NNPDF23_lo_as_0130_qed PDF set. The + run_card.toml template now defaults to the dynamical HT/2 scale instead, + and its default PDF set is free to change, so these replacements are what + pins the configuration back to the one the references were generated with. + They are load-bearing: drop them and every reference value below goes + stale. The PDF pin in particular decouples the references from the + template default -- ``_mg7_datadir_or_skip`` already guarantees the pinned + set is the one present on disk.""" t = open(toml_path).read() t = t.replace('fixed_ren_scale = false', 'fixed_ren_scale = true') t = t.replace('fixed_fact_scale = false', 'fixed_fact_scale = true') + t = re.sub(r'(?m)^pdf = ".*"$', 'pdf = "%s"' % _REFERENCE_PDF, t) t = re.sub(r'events = \d+', 'events = %d' % events, t) if disable_jet_cuts: # jet cuts must be disabled for the hadronic tt~ decay processes to From f861e7437f9330e5b12294ec045ad36c517831d7 Mon Sep 17 00:00:00 2001 From: Olivier Mattelaer Date: Thu, 13 Aug 2026 12:42:58 +0200 Subject: [PATCH 18/26] switch the mg7 default PDF to NNPDF40MC_lo_as_01180 The mg7 run_card.toml shipped NNPDF23_lo_as_0130_qed, a 2012 LO set with alpha_s(M_Z) = 0.130. Move the default to NNPDF4.0 LO. The MC-generator variant NNPDF40MC_lo_as_01180 (lhaid 338500) is used rather than NNPDF40_lo_as_01180: it is a single member and ~0.7 MB against ~54 MB, which also keeps the CI grid cheap. It is 5-flavour; NNPDF4.0 has no 4-flavour LO counterpart, so this is used unconditionally and no scheme-dependent choice is made anywhere. tests/parallel_tests/madevent_comparator.py pins lhaid so the madevent side of the mg7-aligned comparison uses the identical set; it moves to 338500 and must stay in sync with the banner.py default. Three acceptance references move. alpha_s(M_Z) drops 9%, and the NNPDF4.0 quark and gluon luminosities are smaller than NNPDF2.3's at the x these processes probe, so every one drops. Each was checked with a same-code A/B (one output directory, one run per PDF set) so the shift is attributable to the PDF alone and not to a regression: u u > u u 1.277e+06 -> 7.76e+05 -39% A/B 1.272e+06 -> 7.79e+05 u q > u q QCD=0 4428 -> 3730 -16% A/B 4380 -> 3688 g g > b b~ HEFT 3.754e+08 -> 1.820e+08 -51% A/B 3.708e+08 -> 1.820e+08 u q > u q has QCD=0, so its -16% is pure PDF. u u > u u carries alpha_s^2 (-18%) on top of the smaller u density. The HEFT process is gg-initiated at a ~20 GeV dynamical scale, where the gluon luminosity is down ~25% and alpha_s^2 down ~22%. test_generation_from_file_1_mg7 (p p > go go) is NOT re-referenced: with this set the integration returns nan from the first survey iteration and never terminates. mg7 divides by the per-flavour parton density when it weights the initial-state flavour channels, and NNPDF40MC returns exactly 0.0 for c/b below their thresholds and at large x, giving 0/0; NNPDF23's floor there is ~6.5e-15, which is why this never showed. Bisected on the grid file: replacing every literal 0.0 with 1e-30 makes the process integrate cleanly. The reference is left at the old value with the diagnosis recorded next to it, so the failure stays visible. The mg7 tests guard on the default set being installed, and the heptools cache predates this change, so restore_heptools_lhapdf now fetches the set when it is absent (~0.7 MB, non-fatal, a no-op once cached); warm_cache.yml bakes it in alongside NNPDF23, which check_xsec_processes_mg7 still pins deliberately. Co-Authored-By: Claude Opus 5 --- .../actions/install_lhapdf_pdfset/action.yml | 5 ++ .../restore_heptools_lhapdf/action.yml | 21 ++++- .github/workflows/acceptancetest_mg7.yml | 6 +- .github/workflows/warm_cache.yml | 4 + madgraph/various/banner.py | 7 +- tests/acceptance_tests/test_cmd_madevent.py | 83 +++++++++++++------ tests/parallel_tests/madevent_comparator.py | 13 +-- 7 files changed, 102 insertions(+), 37 deletions(-) diff --git a/.github/actions/install_lhapdf_pdfset/action.yml b/.github/actions/install_lhapdf_pdfset/action.yml index e98cefb008..06455d7b87 100644 --- a/.github/actions/install_lhapdf_pdfset/action.yml +++ b/.github/actions/install_lhapdf_pdfset/action.yml @@ -11,6 +11,11 @@ inputs: pdfset: description: Name of the LHAPDF set to install required: false + # NNPDF23_lo_as_0130_qed on purpose: this action's only consumer is + # check_xsec_processes_mg7.yml, whose ~40 reference cross-sections are + # pinned to that set (_REFERENCE_PDF, written into every run_card.toml). + # It is deliberately NOT the mg7 run_card default, which is now + # NNPDF40MC_lo_as_01180 and comes from the heptools cache. default: NNPDF23_lo_as_0130_qed runs: diff --git a/.github/actions/restore_heptools_lhapdf/action.yml b/.github/actions/restore_heptools_lhapdf/action.yml index 6dac678e13..4748184386 100644 --- a/.github/actions/restore_heptools_lhapdf/action.yml +++ b/.github/actions/restore_heptools_lhapdf/action.yml @@ -38,4 +38,23 @@ runs: if: ${{ env.CACHE_KEY == 'lhapdf-ubuntu24' }} run: | echo "PYTHONPATH=$PYTHONPATH:/home/runner/.cache/HEPtools/lhapdf6_py3/lib/python3.12/dist-packages" >> $GITHUB_ENV - shell: bash + shell: bash + + # The mg7 run_card.toml default PDF (NNPDF40MC_lo_as_01180). warm_cache.yml + # bakes it into the lhapdf cache, but that cache is only rebuilt when + # warm_cache runs with a reset, so a branch that changes the default would + # otherwise face a cache that predates it: the mg7 tests guard on the set + # being present and would all self-skip (test_manager.py counts a skip as a + # failure). Fetching it here costs ~0.7 MB and is a no-op once the cache + # carries it. Non-fatal on failure: the tests skip as they did before. + - name: ensure the mg7 default PDF set is present + run: | + DATADIR=/home/runner/.cache/HEPtools/lhapdf6_py3/share/LHAPDF + SET=NNPDF40MC_lo_as_01180 + if [ -d "$DATADIR" ] && [ ! -d "$DATADIR/$SET" ]; then + ( cd "$DATADIR" \ + && wget -q https://lhapdfsets.web.cern.ch/current/$SET.tar.gz \ + && tar -xzpf $SET.tar.gz \ + && rm -f $SET.tar.gz ) || echo "::warning::could not fetch $SET" + fi + shell: bash diff --git a/.github/workflows/acceptancetest_mg7.yml b/.github/workflows/acceptancetest_mg7.yml index 2361e1c0ad..ba18a39d86 100644 --- a/.github/workflows/acceptancetest_mg7.yml +++ b/.github/workflows/acceptancetest_mg7.yml @@ -119,8 +119,8 @@ jobs: acceptancetest_mg7_madspin_mixed_flavor: needs: build_madspace # mg7 + MadSpin density-summary check driven through the madevent-style - # launch command interface. restore_heptools provides LHAPDF/NNPDF23 for the - # u u~ > z g generation. + # launch command interface. restore_heptools provides LHAPDF and the mg7 + # default PDF set for the u u~ > z g generation. runs-on: ubuntu-24.04 if: github.event_name == 'push' || github.event.pull_request.head.repo.fork == true steps: @@ -184,7 +184,7 @@ jobs: acceptancetest_mg7_group_subprocess: needs: build_madspace # mg7 cross-section run (u u > u u). madspace is installed by checkout_mg5; - # LHAPDF (incl. NNPDF23_lo_as_0130_qed) comes from the heptools cache. + # LHAPDF (incl. NNPDF40MC_lo_as_01180) comes from the heptools cache. # The test self-skips if that stack is unavailable. runs-on: ubuntu-24.04 if: github.event_name == 'push' || github.event.pull_request.head.repo.fork == true diff --git a/.github/workflows/warm_cache.yml b/.github/workflows/warm_cache.yml index 8f27c8f8b6..20161157d8 100644 --- a/.github/workflows/warm_cache.yml +++ b/.github/workflows/warm_cache.yml @@ -157,6 +157,10 @@ jobs: wget https://lhapdfsets.web.cern.ch/lhapdfsets/current/cteq6l1.tar.gz tar -xzpvf cteq6l1.tar.gz # LO set used by the mg7 run_card.toml default (madspace integration) + wget https://lhapdfsets.web.cern.ch/current/NNPDF40MC_lo_as_01180.tar.gz + tar -xzpvf NNPDF40MC_lo_as_01180.tar.gz + # previous mg7 default; still pinned by the mg7 cross-section + # reference tests (tests/acceptance_tests/test_check_xsec_processes_mg7.py) wget https://lhapdfsets.web.cern.ch/current/NNPDF23_lo_as_0130_qed.tar.gz tar -xzpvf NNPDF23_lo_as_0130_qed.tar.gz diff --git a/madgraph/various/banner.py b/madgraph/various/banner.py index 1d1b45e3ae..3f8620757d 100755 --- a/madgraph/various/banner.py +++ b/madgraph/various/banner.py @@ -6476,7 +6476,12 @@ def default_setup(self): # ----------------------------- [beam] ------------------------- self.add_toml_param('beam', 'e_cm', 13000.0) self.add_toml_param('beam', 'leptonic', False) - self.add_toml_param('beam', 'pdf', "NNPDF23_lo_as_0130_qed") + # NNPDF4.0 LO, 5-flavour scheme, alpha_s(M_Z) = 0.118. This is the + # MC-generator-oriented variant of the NNPDF4.0 LO set: a single member + # and ~0.7 MB, versus ~54 MB for NNPDF40_lo_as_01180. NNPDF4.0 has no + # 4-flavour LO counterpart, so there is no scheme-dependent choice to + # make here: this one set is used whatever the b-quark treatment. + self.add_toml_param('beam', 'pdf', "NNPDF40MC_lo_as_01180") # Default to the dynamical scale set by dynamical_scale_choice below # (half_transverse_mass, i.e. HT/2) rather than to the fixed ren_scale # / fact_scale values. Those fixed values are kept as the fallback used diff --git a/tests/acceptance_tests/test_cmd_madevent.py b/tests/acceptance_tests/test_cmd_madevent.py index c948b4471c..d1355244c8 100755 --- a/tests/acceptance_tests/test_cmd_madevent.py +++ b/tests/acceptance_tests/test_cmd_madevent.py @@ -96,8 +96,8 @@ def _mg7_datadir_or_skip(test): misc.sprint(datadir) if not has_mg7 or not datadir or not os.path.isdir(datadir): test.skipTest('mg7 runtime stack (madspace + LHAPDF data) unavailable') - if not glob.glob(pjoin(datadir, 'NNPDF23_lo_as_0130_qed*')): - test.skipTest('NNPDF23_lo_as_0130_qed PDF set not available') + if not glob.glob(pjoin(datadir, 'NNPDF40MC_lo_as_01180*')): + test.skipTest('NNPDF40MC_lo_as_01180 PDF set not available') return datadir @@ -722,8 +722,8 @@ def test_group_subprocess_mg7(self): Runs the mg7 (madspace) integrator with group_subprocesses on and off and checks the two cross-sections agree (grouping consistency). It also pins the absolute value to the mg7-native result obtained with the - run_card.toml defaults (NNPDF23_lo_as_0130_qed + dynamical HT/2 scale, - events=2000) ~ 1.277e+06 pb. + run_card.toml defaults (NNPDF40MC_lo_as_01180 + dynamical HT/2 scale, + events=2000) ~ 7.76e+05 pb. NOTE: this is NOT the madevent reference (1.31e6 pb in test_group_subprocess); but it would be if true lhapdf were used in madevent @@ -746,8 +746,8 @@ def test_group_subprocess_mg7(self): if not has_mg7 or not datadir or not os.path.isdir(datadir): self.skipTest('mg7 runtime stack (madspace + LHAPDF data) unavailable') # the mg7 run_card.toml default PDF must be present in the data dir - if not glob.glob(pjoin(datadir, 'NNPDF23_lo_as_0130_qed*')): - self.skipTest('NNPDF23_lo_as_0130_qed PDF set not available') + if not glob.glob(pjoin(datadir, 'NNPDF40MC_lo_as_01180*')): + self.skipTest('NNPDF40MC_lo_as_01180 PDF set not available') def run_mg7(group): run_dir = pjoin(self.path, 'MG7_%s' % ('grp' if group else 'ungrp')) @@ -786,9 +786,13 @@ def run_mg7(group): self.assertLess(abs(val1 - val2) / (err1 + err2 + 1e-30), 5, 'mg7 grouped (%s +- %s) vs ungrouped (%s +- %s) disagree' % (val1, err1, val2, err2)) - # NOT the madevent 1.31e6 value for internal pdf but the one for - # lhapdf NNPDF23_lo_as_0130_qed + dynamical HT/2 scale - target = 1.277e+06 + # NOT the madevent 1.31e6 value for internal pdf but the one for + # lhapdf NNPDF40MC_lo_as_01180 + dynamical HT/2 scale. Was 1.277e+06 + # with the old default NNPDF23_lo_as_0130_qed; a same-code A/B gives + # 1.272e+06 (NNPDF23) vs 7.79e+05, so the -39% is the PDF change alone: + # alpha_s^2 (-18%) times the smaller NNPDF4.0 u-quark luminosity. + # Spread over 6 runs at events=2000: 7.66e5-7.84e5, mean 7.76e5. + target = 7.76e+05 self.assertLess(abs(val2 - target) / target, 0.10, 'mg7 u u > u u cross-section %s far from mg7 reference %s' % (val2, target)) @@ -1192,8 +1196,9 @@ def launch_select_nothing(): def test_madevent_merged_flavor_uq_mg7(self): """mg7 equivalent of test_madevent_merged_flavor_uq (u q > u q QCD=0, - q = u d): the merged-flavor path must reproduce the 4428 pb obtained by - running u u > u u and u d > u d as separate single-flavor processes. + q = u d): the merged-flavor path must reproduce the cross-section + obtained by running u u > u u and u d > u d as separate single-flavor + processes. This used to come out too large because the mg7 exporter mirrored the mixed u d initial flavor -- leg 1 is a fixed u, so the beam-swapped @@ -1219,8 +1224,8 @@ def test_madevent_merged_flavor_uq_mg7(self): datadir = None if not has_mg7 or not datadir or not os.path.isdir(datadir): self.skipTest('mg7 runtime stack (madspace + LHAPDF data) unavailable') - if not glob.glob(pjoin(datadir, 'NNPDF23_lo_as_0130_qed*')): - self.skipTest('NNPDF23_lo_as_0130_qed PDF set not available') + if not glob.glob(pjoin(datadir, 'NNPDF40MC_lo_as_01180*')): + self.skipTest('NNPDF40MC_lo_as_01180 PDF set not available') run_dir = pjoin(self.path, 'MG7_uq') if os.path.isdir(run_dir): @@ -1253,8 +1258,14 @@ def test_madevent_merged_flavor_uq_mg7(self): info = json.load(open(infos[-1]))['process'] cross = float(info['mean']) error = float(info.get('error') or 0.0) - # physical reference (same as the madevent test); mg7 must reproduce it - self.assertAlmostEqual(cross, 4428.0, delta=max(30.0, 5 * error)) + # mg7 reference with the default PDF NNPDF40MC_lo_as_01180. This no + # longer equals the 4428 pb of the madevent test above: that one runs on + # madevent's internal (nn23lo1) PDF while mg7 convolutes with the LHAPDF + # grid named in run_card.toml. QCD=0, so there is no alpha_s here at all + # and the -16% shift is purely the smaller NNPDF4.0 valence-quark + # luminosity; a same-code A/B gives 4380 pb (NNPDF23) vs 3688 pb. + # Spread over 7 runs at events=2000: 3675-3775, mean 3731. + self.assertAlmostEqual(cross, 3730.0, delta=max(30.0, 5 * error)) def test_flavor_grouping_consistency(self): """Check that the four combinations of 'apply_flavor_grouping' and @@ -2846,22 +2857,23 @@ def test_generation_heft(self): def test_generation_heft_mg7(self): """mg7 equivalent of test_generation_heft for g g > b b~ HIW<=1 (HEFT). - KNOWN-FAILING, intentionally NOT marked xfail: mg7 runs this HEFT process - but its cross-section comes out ~257x below the physical value (~1.6e6 pb - vs the madevent 4.117e8 pb) -- a large mg7 normalisation discrepancy for - the effective ggH coupling. The test asserts the physical reference and is - expected to fail until that is resolved; left undecorated to keep the - discrepancy visible. Self-skips where the mg7 runtime stack is unavailable. + Pins the mg7-native cross-section obtained with the run_card.toml + defaults and the dynamical HT/2 scale that _run_mg7_xsec selects. + Self-skips where the mg7 runtime stack is unavailable. """ datadir = _mg7_datadir_or_skip(self) - cross, error = _run_mg7_xsec(self, + cross, error = _run_mg7_xsec(self, ['set automatic_html_opening False --no_save', 'import model heft', 'generate g g > b b~ HIW<=1'], pjoin(self.path, 'MG7_heft'), datadir) - # physical reference (same as test_generation_heft) - target = 4.117e8 # HT/2 - target = 3.754e+08 # fixed scale MZ + # mg7 reference with the default PDF NNPDF40MC_lo_as_01180 (NNPDF4.0 LO, + # alpha_s(M_Z) = 0.118). The previous 3.754e+08 was the same run with the + # old default NNPDF23_lo_as_0130_qed: a same-code A/B on this process + # gives 3.708e+08 (NNPDF23) vs 1.820e+08 (NNPDF40MC), i.e. the -51% is + # entirely the PDF change -- gg luminosity (-25% at these x) times + # alpha_s^2 (-22% at the ~20 GeV dynamical scale). + target = 1.820e+08 self.assertLess(abs(cross - target) / target, 0.10, 'mg7 HEFT cross-section %s far from physical reference %s' % (cross, target)) @@ -3062,6 +3074,24 @@ def test_generation_from_file_1_mg7(self): 'generate p p > go go'], pjoin(self.path, 'MG7_mssm_gogo'), datadir) # madevent reference (run_01 in test_generation_from_file_1) + # + # KNOWN BROKEN with the current default PDF NNPDF40MC_lo_as_01180: the + # integration returns nan from the first survey iteration and then never + # terminates, so generate_events is killed and this test fails before it + # can quote a cross-section. The reference below is therefore left at the + # NNPDF23_lo_as_0130_qed value on purpose -- it is NOT a stale number to + # be refreshed, and updating it would hide the failure. + # + # Cause (bisected on the grid file itself): mg7 divides by the + # per-flavour parton density when it weights the initial-state flavour + # channels, and NNPDF40MC returns *exactly* 0.0 for c/b below their + # thresholds and at large x (b from x >= 0.933 at Q = 600), giving 0/0. + # p p > go go is forced to large x by the ~600 GeV gluino pair, so it hits + # it immediately; the lighter mg7 processes only avoid it by luck. + # Rewriting the .dat with every literal 0.0 replaced by 1e-30 makes this + # process integrate cleanly and give 3.71 pb. Not the interpolation: + # ms.PartonDensity reproduces LHAPDF xfxQ2 exactly for this set. The fix + # belongs in madspace; until then this test cannot be re-referenced. target = 5.024 # no cut madevent with lhapdf (not internal pdf) (relative error from madevent: 1e-4) self.assertLess(abs(cross - target) / target, 0.01, 'mg7 p p > go go cross-section %s far from madevent reference %s' @@ -3160,7 +3190,8 @@ def test_rivet_from_file(self): # tool-selection question -> run_selected_tools -> MG7RunCmd), so that at # least one test exercises the command interface + chaining of every tool # with the new (default) output. They self-skip when the mg7 runtime stack - # (madspace + LHAPDF + NNPDF23) or the external tool is unavailable. + # (madspace + LHAPDF + the default PDF set) or the external tool is + # unavailable. #========================================================================== def test_add_time_of_flight_mg7(self): """time-of-flight LHE post-processing chained on the mg7 output. diff --git a/tests/parallel_tests/madevent_comparator.py b/tests/parallel_tests/madevent_comparator.py index e3e4cf7afd..c196252ed1 100755 --- a/tests/parallel_tests/madevent_comparator.py +++ b/tests/parallel_tests/madevent_comparator.py @@ -749,8 +749,8 @@ class MG5RunnerMG7Aligned(MG5Runner): run_card.toml defaults, so its cross-section is directly comparable with :class:`MG7Runner`. - Matched settings: e_cm = 13 TeV (ebeam 6500 each), PDF NNPDF23_lo_as_0130_qed - (lhaid 247000), the dynamical HT/2 scale (dynamical_scale_choice=3, which is + Matched settings: e_cm = 13 TeV (ebeam 6500 each), PDF NNPDF40MC_lo_as_01180 + (lhaid 338500), the dynamical HT/2 scale (dynamical_scale_choice=3, which is the madevent equivalent of mg7's ``half_transverse_mass``), and the mg7 jet cuts (pt>20, |eta|<5, dR>0.4). Returns the total cross-section under the 'cross' key so the comparison is total-to-total. @@ -758,9 +758,10 @@ class MG5RunnerMG7Aligned(MG5Runner): name = 'MadGraph madevent (mg7-aligned)' type = 'v5_mg7aligned' - # lhaid for NNPDF23_lo_as_0130_qed (the mg7 run_card.toml default PDF), so - # both sides use exactly the same LHAPDF set. - lhaid = 247000 + # lhaid for NNPDF40MC_lo_as_01180 (the mg7 run_card.toml default PDF), so + # both sides use exactly the same LHAPDF set. Must be kept in sync with the + # [beam] pdf default in banner.py. + lhaid = 338500 def format_mg5_proc_card(self, proc_list, model, orders): if model != 'mssm': @@ -781,7 +782,7 @@ def format_mg5_proc_card(self, proc_list, model, orders): # --- align with the mg7 run_card.toml ------------------------------- v5_string += "set ebeam1 6500\n" v5_string += "set ebeam2 6500\n" - # Use exactly the mg7 run_card.toml PDF (NNPDF23_lo_as_0130_qed) via + # Use exactly the mg7 run_card.toml PDF (NNPDF40MC_lo_as_01180) via # LHAPDF, now that the AlphaS_FlavorScheme metadata hotfix patches the # source set in pdfsets_dir. v5_string += "set pdlabel lhapdf\n" From 621c9c907039f8103d7a7d29ae97d9bcdc1541f4 Mon Sep 17 00:00:00 2001 From: Olivier Mattelaer Date: Thu, 13 Aug 2026 13:35:10 +0200 Subject: [PATCH 19/26] guard the discrete-sampling divisions against zero parton densities p p > go go returned integral: nan(nan) from the first survey iteration and then never terminated, killing test_generation_from_file_1_mg7. The trigger was the switch of the mg7 default PDF to NNPDF40MC_lo_as_01180, which returns exactly 0.0 for c/b below their thresholds and at large x (b from x >= 0.933 at Q = 600 GeV) where NNPDF2.3 still had a ~1e-15 floor. The ~600 GeV gluino pair forces large x, so the process hits it immediately. kernel_sample_discrete_probs weights the initial-state flavour channels by the parton densities and divides by them twice, both unguarded: (A) probs[i] / prob_norm, which is 0/0 -> NaN when every density is zero; (B) det = 1 / prob_out, which is 1/0 -> inf when the selected option has probability zero. Reachable even when other options are non-zero, since cum_prob accumulates in floating point and can end just below r, which selects a trailing zero-probability option. A channel whose density is exactly zero cannot contribute, so its correct weight is zero, not inf and not NaN. Both divisions now fall back to a unit divisor and the result is forced to zero, which is what the integrator needs since these dets are multiplied into the event weight. The same case-(A) guard is applied to kernel_sample_discrete_probs_inverse (whose det is the probability itself, so zero already means zero weight) and to the matching backward kernel, whose gradient is zero where the forward det is identically zero. The guards test "!= 0" rather than "> 0" so that every non-zero divisor, including a negative one, divides exactly as before and keeps the sign of its weight; they are inert unless the divisor is literally zero. Nothing is done to the input densities: flooring those would alter the physics everywhere else. A "> 0" variant was tried first and changed the NNPDF2.3 result badly enough to abort the run. Everything is branch-free and type-generic, so it holds for the scalar CPU, SIMD, CUDA and HIP instantiations alike. Re-references test_generation_from_file_1_mg7 to the default PDF. Measured over 6 runs: 3.7864 +- 0.0011, single-run error ~0.003, so the 1% tolerance is a ~12 sigma check; a 1e-30-patched grid independently gives 3.7887. The old 5.024 was the NNPDF2.3 madevent value, and pinning NNPDF2.3 in the run_card still gives 5.0235 with this fix in place, i.e. 0.003 sigma from the same result before it. Co-Authored-By: Claude Opus 5 --- madspace/src/kernels/discrete.hpp | 59 ++++++++++++++++++--- tests/acceptance_tests/test_cmd_madevent.py | 42 ++++++--------- 2 files changed, 68 insertions(+), 33 deletions(-) diff --git a/madspace/src/kernels/discrete.hpp b/madspace/src/kernels/discrete.hpp index 98c8968b7d..e90c96c4fe 100644 --- a/madspace/src/kernels/discrete.hpp +++ b/madspace/src/kernels/discrete.hpp @@ -26,6 +26,29 @@ KERNELSPEC void kernel_sample_discrete_inverse( det = 1. / opt_count_f; } +// An option whose probability is exactly zero cannot be produced, so its +// correct contribution is a zero-weight event -- not inf and not NaN. Exact +// zeros are routine here: the probabilities are parton densities (NNPDF4.0 +// returns exactly 0.0 for c/b below threshold and at large x, where NNPDF2.3 +// had a ~1e-15 floor) optionally multiplied by an active-flavour mask. Two +// distinct divisions have to be guarded: +// +// (A) every probability is zero -> prob_norm == 0 -> 0/0 -> NaN in every +// lane. The whole point is kinematically inaccessible. +// (B) the *selected* option has probability zero -> 1/0 -> inf. This is +// reachable even when other options are non-zero: cum_prob accumulates +// in floating point and can end just below r, which selects a trailing +// zero-probability option. +// +// The guards sit on the divisions, never on the input probabilities: flooring +// the densities would silently alter the physics everywhere else. They test +// "!= 0" rather than "> 0" on purpose, so that every non-zero divisor -- including +// a negative one -- divides exactly as it did before and keeps the sign of its +// weight. That makes the guards provably inert unless the divisor is literally +// zero. This is not academic: a "> 0" version of these guards changed results +// for NNPDF2.3 badly enough to abort the run, while the "!= 0" version below +// reproduces it to 0.003 sigma. + template KERNELSPEC void kernel_sample_discrete_probs( FIn r, FIn probs, IOut output, FOut det @@ -34,17 +57,26 @@ KERNELSPEC void kernel_sample_discrete_probs( for (std::size_t i = 0; i < probs.size(); ++i) { prob_norm = prob_norm + probs[i]; } + // (A) fall back to a unit norm so the ratios below are 0/1 = 0 instead of + // 0/0; prob_out then stays zero and the det guard turns the point into a + // zero-weight event. + auto norm_ok = prob_norm != 0.; + auto norm_safe = where(norm_ok, prob_norm, FVal(1.)); FVal cum_prob(0.), prob_out(0.); IVal option(0); for (std::size_t i = 0; i < probs.size(); ++i) { - auto prob = probs[i] / prob_norm; + auto prob = probs[i] / norm_safe; auto mask = r < cum_prob; cum_prob = cum_prob + prob; option = where(mask, option, IVal(i)); prob_out = where(mask, prob_out, prob); } - output = option; - det = 1. / prob_out; + // (B) zero probability -> zero weight. Also pins the option to 0 in case + // (A), where the loop would otherwise return the last index by default. + auto prob_ok = prob_out != 0.; + auto prob_safe = where(prob_ok, prob_out, FVal(1.)); + output = where(norm_ok, option, IVal(0)); + det = where(prob_ok, FVal(1.) / prob_safe, FVal(0.)); } template @@ -55,9 +87,15 @@ KERNELSPEC void kernel_sample_discrete_probs_inverse( for (std::size_t i = 0; i < probs.size(); ++i) { prob_norm = prob_norm + probs[i]; } + // Same guard as case (A) above. Here det is the probability itself rather + // than its reciprocal, so a zero probability already means zero weight and + // case (B) needs no guard: with a unit fallback norm every ratio is 0, + // giving r = 0 and det = 0, i.e. a deterministic zero-weight point instead + // of NaN. + auto norm_safe = where(prob_norm != 0., prob_norm, FVal(1.)); FVal cum_prob(0.), random(0.), prob_out(0.); for (std::size_t i = 0; i < probs.size(); ++i) { - auto prob = probs[i] / prob_norm; + auto prob = probs[i] / norm_safe; cum_prob = cum_prob + prob; auto mask = index == i; random = where(mask, cum_prob + 0.5 * prob, random); @@ -80,11 +118,18 @@ KERNELSPEC void backward_kernel_sample_discrete_probs_inverse( for (std::size_t i = 0; i < probs.size(); ++i) { prob_norm = prob_norm + probs[i]; } + // Matches the forward guard: where prob_norm is zero the forward det is + // identically 0 (a constant), so its gradient is 0 rather than NaN. + auto norm_ok = prob_norm != 0.; + auto norm_safe = where(norm_ok, prob_norm, FVal(1.)); FVal det_grad_out(0.); - auto prob = probs.gather(index) / prob_norm; + auto prob = probs.gather(index) / norm_safe; for (std::size_t i = 0; i < probs.size(); ++i) { - probs_grad[i] = - (where(index == i, FVal(1.), 0.) - prob) / prob_norm * det_grad; + probs_grad[i] = where( + norm_ok, + (where(index == i, FVal(1.), 0.) - prob) / norm_safe * det_grad, + FVal(0.) + ); } } diff --git a/tests/acceptance_tests/test_cmd_madevent.py b/tests/acceptance_tests/test_cmd_madevent.py index d1355244c8..dda209a8d9 100755 --- a/tests/acceptance_tests/test_cmd_madevent.py +++ b/tests/acceptance_tests/test_cmd_madevent.py @@ -3050,13 +3050,14 @@ def test_generation_from_file_1(self): event.check() def test_generation_from_file_1_mg7(self): - """mg7 (madspace) cross-section for MSSM p p > go go, pinned to the - madevent reference from test_generation_from_file_1. + """mg7 (madspace) cross-section for MSSM p p > go go. the standalone (madmatrix) export reproduces the per-flavor |M|^2 for - p p > go go (test_madmatrix_mssm_gogo, ~1e-4) and the madspace integrator now - lands on the madevent cross-section as well, so this pins the mg7 result - to the madevent reference (run_01 of test_generation_from_file_1). + p p > go go (test_madmatrix_mssm_gogo, ~1e-4) and the madspace integrator + lands on the madevent cross-section as well: with NNPDF23_lo_as_0130_qed + pinned in the run_card this setup gives 5.0235, against the madevent + reference of 5.024 (run_01 of test_generation_from_file_1). The target + below is for the default PDF instead -- see the comment on it. This used to be red at random rather than for a physics reason: the assertion is at 1%, but with the old 2000-event target a single run @@ -3073,28 +3074,17 @@ def test_generation_from_file_1_mg7(self): 'import model MSSM_SLHA2', 'generate p p > go go'], pjoin(self.path, 'MG7_mssm_gogo'), datadir) - # madevent reference (run_01 in test_generation_from_file_1) - # - # KNOWN BROKEN with the current default PDF NNPDF40MC_lo_as_01180: the - # integration returns nan from the first survey iteration and then never - # terminates, so generate_events is killed and this test fails before it - # can quote a cross-section. The reference below is therefore left at the - # NNPDF23_lo_as_0130_qed value on purpose -- it is NOT a stale number to - # be refreshed, and updating it would hide the failure. - # - # Cause (bisected on the grid file itself): mg7 divides by the - # per-flavour parton density when it weights the initial-state flavour - # channels, and NNPDF40MC returns *exactly* 0.0 for c/b below their - # thresholds and at large x (b from x >= 0.933 at Q = 600), giving 0/0. - # p p > go go is forced to large x by the ~600 GeV gluino pair, so it hits - # it immediately; the lighter mg7 processes only avoid it by luck. - # Rewriting the .dat with every literal 0.0 replaced by 1e-30 makes this - # process integrate cleanly and give 3.71 pb. Not the interpolation: - # ms.PartonDensity reproduces LHAPDF xfxQ2 exactly for this set. The fix - # belongs in madspace; until then this test cannot be re-referenced. - target = 5.024 # no cut madevent with lhapdf (not internal pdf) (relative error from madevent: 1e-4) + # Reference for the default PDF NNPDF40MC_lo_as_01180, measured over 6 + # runs: 3.7864 +- 0.0011 (single-run error ~0.003, i.e. ~0.08%), so the + # 1% tolerance here is a ~12 sigma check. The value shifts from the old + # NNPDF23_lo_as_0130_qed reference of 5.024 (madevent, no cuts, lhapdf) + # purely because of the PDF change: p p > go go is forced to large x by + # the ~600 GeV gluino pair, where the two sets differ a lot. With + # NNPDF23 pinned in the run_card this same setup still gives 5.0235, + # matching that madevent reference to 0.01%. + target = 3.786 self.assertLess(abs(cross - target) / target, 0.01, - 'mg7 p p > go go cross-section %s far from madevent reference %s' + 'mg7 p p > go go cross-section %s far from reference %s' % (cross, target)) def test_contur_from_file(self): From 24e252f56e1e183869fa18eef2a6d6f32342007f Mon Sep 17 00:00:00 2001 From: Olivier Mattelaer Date: Fri, 14 Aug 2026 13:49:58 +0200 Subject: [PATCH 20/26] rename the madmatrix CPU/SIMD backends to cpu_* names The CPU/SIMD backend names of the madmatrix (cudacpp) backend are renamed: cppauto -> cpu cppnone -> cpu_scalar cppsse4 -> cpu_128b cppavx2 -> cpu_256b cpp512y -> cpu_512b_y cpp512z -> cpu_512b cuda and hip are unchanged. The bare 'cpp' token stays as it was: an alias for the auto backend accepted by the Python option lists (launch_plugin, trex) and resolved to BACKEND=cpu before make is invoked. It is still not a member of the makefile's SUPPORTED_BACKENDS, exactly as before. Structural changes beyond the literal rename, in madmatrix.mk and in the MadtRex driver/runner makefiles: - DIRTAG and TAG stripped the literal 'cpp' prefix of the backend to build the build-lockfile tag ('cppavx2' -> 'avx2'). They now strip 'cpu_' instead ('cpu_256b' -> '256b', 'cpu_scalar' -> 'scalar'), which keeps the previous meaning: the tag is the short backend name, with cuda and hip left unchanged. Without this the patsubst would silently stop stripping and the tag would change meaning. DIRTAG has no consumer in this repository (the MadtRex makefiles only mention it in comments and derive their build directory from CUDACPP_BUILDDIR); TAG is the one that actually names .build. lockfiles and the all. default goal. - The "sanity check" that a non-GPU backend really is a CPU backend used $(findstring cpp,$(BACKEND)); it now uses $(findstring cpu,$(BACKEND)), otherwise every C++ build would hit the INTERNAL ERROR branch. Build directories become build.cpu_scalar, build.cpu_128b, ... (they are derived from $(BACKEND)); nothing in this repository greps for the old build.cpp* names. The make target aliases (bldnone, bldsse4, bld512y, ...) and the $(PROG)_cpp program suffix are deliberately left untouched: they are not backend names. The mg7 run_card 'devices' default changes from ["cppnone"] to ["cpu_scalar"]. This is a user-facing default change: existing generated directories whose run_card.toml still names an old backend will now fail at build time with the makefile's supported-backends error. No deprecation alias layer is added. Co-Authored-By: Claude Opus 5 --- .../madmatrix/cudacpp_overlay.mk | 14 +- .../madmatrix/generate_events_standalone | 4 +- .../template_files/madmatrix/madmatrix.mk | 131 +++++++++--------- .../template_files/madmatrix/madmatrix_src.mk | 2 +- .../template_files/madmatrix/mgOnGpuConfig.h | 2 +- madgraph/iolibs/template_files/mg7/launch.py | 8 +- .../iolibs/template_files/mg7/run_card.toml | 2 +- madgraph/various/banner.py | 4 +- madgraph/various/process_checks.py | 2 +- madmatrix/MadtRex/makefiles/cudacpp_driver.mk | 114 +++++++-------- madmatrix/MadtRex/makefiles/cudacpp_runner.mk | 106 +++++++------- madmatrix/launch_plugin.py | 12 +- madmatrix/trex.py | 4 +- tests/acceptance_tests/test_cmd.py | 4 +- tests/acceptance_tests/test_cmd_madevent.py | 2 +- tests/input_files/mg7_run_card.toml | 4 +- 16 files changed, 209 insertions(+), 206 deletions(-) diff --git a/madgraph/iolibs/template_files/madmatrix/cudacpp_overlay.mk b/madgraph/iolibs/template_files/madmatrix/cudacpp_overlay.mk index d54b2a03a5..4ef75c82c2 100644 --- a/madgraph/iolibs/template_files/madmatrix/cudacpp_overlay.mk +++ b/madgraph/iolibs/template_files/madmatrix/cudacpp_overlay.mk @@ -211,12 +211,12 @@ madevent_hip_link: ln -s $(CUDACPP_BUILDDIR)/$(PROG)_hip $(PROG) madevent_cpp_link: - $(MAKE) USEGTEST=0 BACKEND=cppauto $(CUDACPP_BUILDDIR)/$(PROG)_cpp + $(MAKE) USEGTEST=0 BACKEND=cpu $(CUDACPP_BUILDDIR)/$(PROG)_cpp rm -f $(PROG) ln -s $(CUDACPP_BUILDDIR)/$(PROG)_cpp $(PROG) # Variant AVX builds for cpp backend -override SUPPORTED_AVXS := cppnone cppsse4 cppavx2 cpp512y cpp512z cppauto +override SUPPORTED_AVXS := cpu_scalar cpu_128b cpu_256b cpu_512b_y cpu_512b cpu madevent_%_link: @if [ '$(words $(filter $*, $(SUPPORTED_AVXS)))' != '1' ]; then \ echo "ERROR! Invalid target '$@' (supported: $(foreach avx,$(SUPPORTED_AVXS),madevent_$(avx)_link))"; exit 1; fi @@ -257,23 +257,23 @@ bldhip: $(PROG)_fortran $(DSIG_cudacpp) bldnone: $(PROG)_fortran $(DSIG_cudacpp) @echo - $(MAKE) USEBUILDDIR=1 BACKEND=cppnone + $(MAKE) USEBUILDDIR=1 BACKEND=cpu_scalar bldsse4: $(PROG)_fortran $(DSIG_cudacpp) @echo - $(MAKE) USEBUILDDIR=1 BACKEND=cppsse4 + $(MAKE) USEBUILDDIR=1 BACKEND=cpu_128b bldavx2: $(PROG)_fortran $(DSIG_cudacpp) @echo - $(MAKE) USEBUILDDIR=1 BACKEND=cppavx2 + $(MAKE) USEBUILDDIR=1 BACKEND=cpu_256b bld512y: $(PROG)_fortran $(DSIG_cudacpp) @echo - $(MAKE) USEBUILDDIR=1 BACKEND=cpp512y + $(MAKE) USEBUILDDIR=1 BACKEND=cpu_512b_y bld512z: $(PROG)_fortran $(DSIG_cudacpp) @echo - $(MAKE) USEBUILDDIR=1 BACKEND=cpp512z + $(MAKE) USEBUILDDIR=1 BACKEND=cpu_512b # Clean (NB: 'make clean' in Source calls 'make clean' in all P*) clean: # Clean builds: fortran in this Pn; cudacpp executables for one AVX in this Pn diff --git a/madgraph/iolibs/template_files/madmatrix/generate_events_standalone b/madgraph/iolibs/template_files/madmatrix/generate_events_standalone index a54009dc9a..7dce42351c 100644 --- a/madgraph/iolibs/template_files/madmatrix/generate_events_standalone +++ b/madgraph/iolibs/template_files/madmatrix/generate_events_standalone @@ -17,9 +17,9 @@ import os import subprocess import sys -BACKENDS = ['cppauto', 'cppnone', 'cppsse4', 'cppavx2', 'cpp512y', 'cpp512z', 'cuda', 'hip'] +BACKENDS = ['cpu', 'cpu_scalar', 'cpu_128b', 'cpu_256b', 'cpu_512b_y', 'cpu_512b', 'cuda', 'hip'] -DEFAULT_BACKEND = 'cppauto' +DEFAULT_BACKEND = 'cpu' def list_p_dirs(subproc): diff --git a/madgraph/iolibs/template_files/madmatrix/madmatrix.mk b/madgraph/iolibs/template_files/madmatrix/madmatrix.mk index c82234690e..c930868ed9 100644 --- a/madgraph/iolibs/template_files/madmatrix/madmatrix.mk +++ b/madgraph/iolibs/template_files/madmatrix/madmatrix.mk @@ -9,7 +9,7 @@ # Set the default BACKEND (CUDA, HIP or C++/SIMD) choice ifeq ($(BACKEND),) - override BACKEND = cppauto + override BACKEND = cpu endif # Set the default FPTYPE (floating point type) choice @@ -33,7 +33,7 @@ endif # Check that the user-defined choices of BACKEND, FPTYPE, HELINL, HRDCOD are supported # (NB: use 'filter' and 'words' instead of 'findstring' because they properly handle whitespace-separated words) -override SUPPORTED_BACKENDS = cuda hip cppnone cppsse4 cppavx2 cpp512y cpp512z cppauto +override SUPPORTED_BACKENDS = cuda hip cpu_scalar cpu_128b cpu_256b cpu_512b_y cpu_512b cpu ifneq ($(words $(filter $(BACKEND), $(SUPPORTED_BACKENDS))),1) $(error Invalid backend BACKEND='$(BACKEND)': supported backends are $(foreach backend,$(SUPPORTED_BACKENDS),'$(backend)')) endif @@ -70,10 +70,12 @@ endif #=== Configure MADMATRIX_BUILDDIR # Build directory "full" tag (used for build lockfiles to prevent mixing builds with different options) -override DIRTAG := $(patsubst cpp%%,%%,$(BACKEND))_$(FPTYPE)_inl$(HELINL)_hrd$(HRDCOD) +# NB: the 'cpu_' prefix of the CPU/SIMD backends is stripped, so the tag is the short backend name +# ('cpu_128b' -> '128b', 'cpu_scalar' -> 'scalar'); 'cuda' and 'hip' are left unchanged. +override DIRTAG := $(patsubst cpu_%%,%%,$(BACKEND))_$(FPTYPE)_inl$(HELINL)_hrd$(HRDCOD) # Build directory: current directory by default, or build. if USEBUILDDIR==1 -# NB: using '=' (not ':=') ensures BACKEND is evaluated lazily after potential cppauto resolution +# NB: using '=' (not ':=') ensures BACKEND is evaluated lazily after the potential 'cpu' auto-resolution ifeq ($(USEBUILDDIR),1) override MADMATRIX_BUILDDIR = build.$(BACKEND) else @@ -118,38 +120,38 @@ $(info Building objects in BUILDDIR=$(BUILDDIR), libraries in LIBDIR=$(LIBDIR)) #------------------------------------------------------------------------------- -#=== Redefine BACKEND if the current value is 'cppauto' +#=== Redefine BACKEND if the current value is 'cpu' -# Set the default BACKEND choice corresponding to 'cppauto' (the 'best' C++ vectorization available) +# Set the default BACKEND choice corresponding to 'cpu' (the 'best' C++ vectorization available) BACKEND_ORIG := $(BACKEND) -ifeq ($(BACKEND),cppauto) +ifeq ($(BACKEND),cpu) ifeq ($(UNAME_P),ppc64le) - override BACKEND = cppsse4 + override BACKEND = cpu_128b else ifneq (,$(filter $(UNAME_M),arm64 aarch64)) - override BACKEND = cppsse4 + override BACKEND = cpu_128b else ifeq ($(wildcard /proc/cpuinfo),) - override BACKEND = cppnone + override BACKEND = cpu_scalar ###$(warning Using BACKEND='$(BACKEND)' because host SIMD features cannot be read from /proc/cpuinfo) else ifeq ($(shell grep -m1 -c avx512vl /proc/cpuinfo)$(shell $(CXX) --version | grep ^clang),1) - override BACKEND = cpp512y + override BACKEND = cpu_512b_y else ifeq ($(shell grep -m1 -c avx2 /proc/cpuinfo),1) - override BACKEND = cppavx2 + override BACKEND = cpu_256b ###ifneq ($(shell grep -m1 -c avx512vl /proc/cpuinfo),1) ### $(warning Using BACKEND='$(BACKEND)' because host does not support avx512vl) ###else ### $(warning Using BACKEND='$(BACKEND)' because this is faster than avx512vl for clang) ###endif else ifeq ($(shell grep -m1 -c sse4_2 /proc/cpuinfo),1) - override BACKEND = cppsse4 + override BACKEND = cpu_128b else - override BACKEND = cppnone + override BACKEND = cpu_scalar endif - $(info BACKEND=$(BACKEND) (was cppauto)) + $(info BACKEND=$(BACKEND) (was cpu)) else $(info BACKEND='$(BACKEND)') endif -# Create file with the resolved backend in case user chooses 'cppauto' +# Create file with the resolved backend in case user chooses 'cpu' BACKEND_LOG ?= .resolved-backend ifneq ($(BACKEND_ORIG),$(BACKEND)) $(file >$(BACKEND_LOG),$(BACKEND)) @@ -361,9 +363,9 @@ else override GPUFLAGS= # Sanity check, this should never happen: if GPUCC is empty, then this is a C++ build, i.e. BACKEND is neither cuda nor hip. - # In practice, in the following, "ifeq ($(GPUCC),)" is equivalent to "ifneq ($(findstring cpp,$(BACKEND)),)". + # In practice, in the following, "ifeq ($(GPUCC),)" is equivalent to "ifneq ($(findstring cpu,$(BACKEND)),)". # Conversely, note that GPUFLAGS is non-empty also for C++ builds, but it is never used in that case. - ifeq ($(findstring cpp,$(BACKEND)),) + ifeq ($(findstring cpu,$(BACKEND)),) $(error INTERNAL ERROR! Unknown backend BACKEND='$(BACKEND)': supported backends are $(foreach backend,$(SUPPORTED_BACKENDS),'$(backend)')) endif @@ -375,7 +377,7 @@ export GPUFLAGS export GPULANGUAGE export GPUSUFFIX -# Export BACKEND (resolved from cppauto above if needed; used e.g. to name the common library) +# Export BACKEND (resolved from cpu above if needed; used e.g. to name the common library) export BACKEND #------------------------------------------------------------------------------- @@ -429,13 +431,13 @@ INCFLAGS += -I$(SRC) # PowerPC-specific CXX compiler flags (being reviewed) ifeq ($(UNAME_P),ppc64le) - CXXFLAGS+= -mcpu=power9 -mtune=power9 # gains ~2-3%% both for cppnone and cppsse4 - # Throughput references without the extra flags below: cppnone=1.41-1.42E6, cppsse4=2.15-2.19E6 + CXXFLAGS+= -mcpu=power9 -mtune=power9 # gains ~2-3%% both for cpu_scalar and cpu_128b + # Throughput references without the extra flags below: cpu_scalar=1.41-1.42E6, cpu_128b=2.15-2.19E6 ###CXXFLAGS+= -DNO_WARN_X86_INTRINSICS # no change ###CXXFLAGS+= -fpeel-loops # no change - ###CXXFLAGS+= -funroll-loops # gains ~1%% for cppnone, loses ~1%% for cppsse4 + ###CXXFLAGS+= -funroll-loops # gains ~1%% for cpu_scalar, loses ~1%% for cpu_128b ###CXXFLAGS+= -ftree-vectorize # no change - ###CXXFLAGS+= -flto # would increase to cppnone=4.08-4.12E6, cppsse4=4.99-5.03E6! + ###CXXFLAGS+= -flto # would increase to cpu_scalar=4.08-4.12E6, cpu_128b=4.99-5.03E6! else ###CXXFLAGS+= -flto # also on Intel this would increase throughputs by a factor 2 to 4... ######CXXFLAGS+= -fno-semantic-interposition # no benefit (neither alone, nor combined with -flto) @@ -516,66 +518,66 @@ endif $(info OMPFLAGS=$(OMPFLAGS)) CXXFLAGS += $(OMPFLAGS) -# Set the build flags appropriate to each BACKEND choice (example: "make BACKEND=cppnone") +# Set the build flags appropriate to each BACKEND choice (example: "make BACKEND=cpu_scalar") # [NB MGONGPU_PVW512 is needed because "-mprefer-vector-width=256" is not exposed in a macro] # [See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=96476] # [Use 'g++ -E -dM - < /dev/null' to check which #define's are enabled] ifeq ($(UNAME_P),ppc64le) - ifeq ($(BACKEND),cppsse4) + ifeq ($(BACKEND),cpu_128b) override AVXFLAGS = -D__SSE4_2__ # Power9 VSX with 128 width (VSR registers) - else ifeq ($(BACKEND),cppavx2) - $(error Invalid SIMD BACKEND='$(BACKEND)': only 'cppnone' and 'cppsse4' are supported on PowerPC for the moment) - else ifeq ($(BACKEND),cpp512y) - $(error Invalid SIMD BACKEND='$(BACKEND)': only 'cppnone' and 'cppsse4' are supported on PowerPC for the moment) - else ifeq ($(BACKEND),cpp512z) - $(error Invalid SIMD BACKEND='$(BACKEND)': only 'cppnone' and 'cppsse4' are supported on PowerPC for the moment) + else ifeq ($(BACKEND),cpu_256b) + $(error Invalid SIMD BACKEND='$(BACKEND)': only 'cpu_scalar' and 'cpu_128b' are supported on PowerPC for the moment) + else ifeq ($(BACKEND),cpu_512b_y) + $(error Invalid SIMD BACKEND='$(BACKEND)': only 'cpu_scalar' and 'cpu_128b' are supported on PowerPC for the moment) + else ifeq ($(BACKEND),cpu_512b) + $(error Invalid SIMD BACKEND='$(BACKEND)': only 'cpu_scalar' and 'cpu_128b' are supported on PowerPC for the moment) endif else ifeq ($(UNAME_M),arm64) # ARM on Apple silicon - ifeq ($(BACKEND),cppnone) # this internally undefines __ARM_NEON + ifeq ($(BACKEND),cpu_scalar) # this internally undefines __ARM_NEON override AVXFLAGS = -DMGONGPU_NOARMNEON - else ifeq ($(BACKEND),cppsse4) # __ARM_NEON is always defined on Apple silicon + else ifeq ($(BACKEND),cpu_128b) # __ARM_NEON is always defined on Apple silicon override AVXFLAGS = - else ifeq ($(BACKEND),cppavx2) - $(error Invalid SIMD BACKEND='$(BACKEND)': only 'cppnone' and 'cppsse4' are supported on ARM for the moment) - else ifeq ($(BACKEND),cpp512y) - $(error Invalid SIMD BACKEND='$(BACKEND)': only 'cppnone' and 'cppsse4' are supported on ARM for the moment) - else ifeq ($(BACKEND),cpp512z) - $(error Invalid SIMD BACKEND='$(BACKEND)': only 'cppnone' and 'cppsse4' are supported on ARM for the moment) + else ifeq ($(BACKEND),cpu_256b) + $(error Invalid SIMD BACKEND='$(BACKEND)': only 'cpu_scalar' and 'cpu_128b' are supported on ARM for the moment) + else ifeq ($(BACKEND),cpu_512b_y) + $(error Invalid SIMD BACKEND='$(BACKEND)': only 'cpu_scalar' and 'cpu_128b' are supported on ARM for the moment) + else ifeq ($(BACKEND),cpu_512b) + $(error Invalid SIMD BACKEND='$(BACKEND)': only 'cpu_scalar' and 'cpu_128b' are supported on ARM for the moment) endif else ifeq ($(UNAME_M),aarch64) # ARM on Linux - ifeq ($(BACKEND),cppnone) # +nosimd ensures __ARM_NEON is absent + ifeq ($(BACKEND),cpu_scalar) # +nosimd ensures __ARM_NEON is absent override AVXFLAGS = -march=armv8-a+nosimd - else ifeq ($(BACKEND),cppsse4) # +simd ensures __ARM_NEON is present (128 width Q/quadword registers) + else ifeq ($(BACKEND),cpu_128b) # +simd ensures __ARM_NEON is present (128 width Q/quadword registers) override AVXFLAGS = -march=armv8-a+simd - else ifeq ($(BACKEND),cppavx2) - $(error Invalid SIMD BACKEND='$(BACKEND)': only 'cppnone' and 'cppsse4' are supported on aarch64 for the moment) - else ifeq ($(BACKEND),cpp512y) - $(error Invalid SIMD BACKEND='$(BACKEND)': only 'cppnone' and 'cppsse4' are supported on aarch64 for the moment) - else ifeq ($(BACKEND),cpp512z) - $(error Invalid SIMD BACKEND='$(BACKEND)': only 'cppnone' and 'cppsse4' are supported on aarch64 for the moment) + else ifeq ($(BACKEND),cpu_256b) + $(error Invalid SIMD BACKEND='$(BACKEND)': only 'cpu_scalar' and 'cpu_128b' are supported on aarch64 for the moment) + else ifeq ($(BACKEND),cpu_512b_y) + $(error Invalid SIMD BACKEND='$(BACKEND)': only 'cpu_scalar' and 'cpu_128b' are supported on aarch64 for the moment) + else ifeq ($(BACKEND),cpu_512b) + $(error Invalid SIMD BACKEND='$(BACKEND)': only 'cpu_scalar' and 'cpu_128b' are supported on aarch64 for the moment) endif else ifneq ($(shell $(CXX) --version | grep ^nvc++),) # support nvc++ #531 - ifeq ($(BACKEND),cppnone) + ifeq ($(BACKEND),cpu_scalar) override AVXFLAGS = -mno-sse3 # no SIMD - else ifeq ($(BACKEND),cppsse4) + else ifeq ($(BACKEND),cpu_128b) override AVXFLAGS = -mno-avx # SSE4.2 with 128 width (xmm registers) - else ifeq ($(BACKEND),cppavx2) + else ifeq ($(BACKEND),cpu_256b) override AVXFLAGS = -march=haswell # AVX2 with 256 width (ymm registers) [DEFAULT for clang] - else ifeq ($(BACKEND),cpp512y) + else ifeq ($(BACKEND),cpu_512b_y) override AVXFLAGS = -march=skylake -mprefer-vector-width=256 # AVX512 with 256 width (ymm registers) [DEFAULT for gcc] - else ifeq ($(BACKEND),cpp512z) + else ifeq ($(BACKEND),cpu_512b) override AVXFLAGS = -march=skylake -DMGONGPU_PVW512 # AVX512 with 512 width (zmm registers) endif else - ifeq ($(BACKEND),cppnone) + ifeq ($(BACKEND),cpu_scalar) override AVXFLAGS = -march=x86-64 # no SIMD (see #588) - else ifeq ($(BACKEND),cppsse4) + else ifeq ($(BACKEND),cpu_128b) override AVXFLAGS = -march=nehalem # SSE4.2 with 128 width (xmm registers) - else ifeq ($(BACKEND),cppavx2) + else ifeq ($(BACKEND),cpu_256b) override AVXFLAGS = -march=haswell # AVX2 with 256 width (ymm registers) [DEFAULT for clang] - else ifeq ($(BACKEND),cpp512y) + else ifeq ($(BACKEND),cpu_512b_y) override AVXFLAGS = -march=skylake-avx512 -mprefer-vector-width=256 # AVX512 with 256 width (ymm registers) [DEFAULT for gcc] - else ifeq ($(BACKEND),cpp512z) + else ifeq ($(BACKEND),cpu_512b) override AVXFLAGS = -march=skylake-avx512 -DMGONGPU_PVW512 # AVX512 with 512 width (zmm registers) endif endif @@ -666,7 +668,8 @@ endif #=== Configure build directories and build lockfiles === # Build lockfile "full" tag (defines full specification of object-file builds that cannot be intermixed) -override TAG = $(patsubst cpp%%,%%,$(BACKEND))_$(FPTYPE)_inl$(HELINL)_hrd$(HRDCOD) +# NB: the 'cpu_' prefix of the CPU/SIMD backends is stripped (see DIRTAG above) +override TAG = $(patsubst cpu_%%,%%,$(BACKEND))_$(FPTYPE)_inl$(HELINL)_hrd$(HRDCOD) # Export TAG (so that there is no need to check/define it again in src/Makefile) export TAG @@ -797,23 +800,23 @@ bldhip: bldnone: @echo - $(MAKE) $(_BLDFLAGS) BACKEND=cppnone + $(MAKE) $(_BLDFLAGS) BACKEND=cpu_scalar bldsse4: @echo - $(MAKE) $(_BLDFLAGS) BACKEND=cppsse4 + $(MAKE) $(_BLDFLAGS) BACKEND=cpu_128b bldavx2: @echo - $(MAKE) $(_BLDFLAGS) BACKEND=cppavx2 + $(MAKE) $(_BLDFLAGS) BACKEND=cpu_256b bld512y: @echo - $(MAKE) $(_BLDFLAGS) BACKEND=cpp512y + $(MAKE) $(_BLDFLAGS) BACKEND=cpu_512b_y bld512z: @echo - $(MAKE) $(_BLDFLAGS) BACKEND=cpp512z + $(MAKE) $(_BLDFLAGS) BACKEND=cpu_512b ifeq ($(UNAME_P),ppc64le) bldavxs: bldnone bldsse4 @@ -862,7 +865,7 @@ cleanall: #------------------------------------------------------------------------------- -# Detect backend (to be used in case of 'cppauto' to give info to the user) +# Detect backend (to be used in case of 'cpu' to give info to the user) .PHONY: detect-backend detect-backend: @echo "Resolved backend has already been written to $(BACKEND_LOG) at parse time." diff --git a/madgraph/iolibs/template_files/madmatrix/madmatrix_src.mk b/madgraph/iolibs/template_files/madmatrix/madmatrix_src.mk index 8b13f7f4e1..59337f2ad9 100644 --- a/madgraph/iolibs/template_files/madmatrix/madmatrix_src.mk +++ b/madgraph/iolibs/template_files/madmatrix/madmatrix_src.mk @@ -86,7 +86,7 @@ $(BUILDDIR)/.build.$(TAG): #------------------------------------------------------------------------------- # Generic target and build rules: objects from C++ or CUDA/HIP compilation. -# Plain .o suffix — the BUILDDIR (e.g. build.cppavx2/) provides backend separation. +# Plain .o suffix — the BUILDDIR (e.g. build.cpu_256b/) provides backend separation. # Use USEBUILDDIR=1 to build for multiple backends simultaneously without cleaning. ifeq ($(GPUCC),) $(BUILDDIR)/%%.o : %%.cc *.h $(BUILDDIR)/.build.$(TAG) diff --git a/madgraph/iolibs/template_files/madmatrix/mgOnGpuConfig.h b/madgraph/iolibs/template_files/madmatrix/mgOnGpuConfig.h index 09c76a6bd5..3875d71bb4 100644 --- a/madgraph/iolibs/template_files/madmatrix/mgOnGpuConfig.h +++ b/madgraph/iolibs/template_files/madmatrix/mgOnGpuConfig.h @@ -202,7 +202,7 @@ namespace mgOnGpu using mgOnGpu::fptype; using mgOnGpu::fptype2; -// Undefine ARM_NEON (hack for cppnone on Apple silicon ARM) +// Undefine ARM_NEON (hack for cpu_scalar on Apple silicon ARM) #ifdef MGONGPU_NOARMNEON #undef __ARM_NEON #endif diff --git a/madgraph/iolibs/template_files/mg7/launch.py b/madgraph/iolibs/template_files/mg7/launch.py index 5900585a95..6dc112a62f 100644 --- a/madgraph/iolibs/template_files/mg7/launch.py +++ b/madgraph/iolibs/template_files/mg7/launch.py @@ -975,14 +975,14 @@ def __init__(self, process: MadgraphProcess, meta: dict, subproc_id: int): devices = [devices] for device in devices: subproc_dir = os.path.dirname(subproc_path) - # 'cppauto' resolve quick fix + # 'cpu' resolve quick fix resolved = device - if device == "cppauto": + if device == "cpu": out = subprocess.run( - ["make", "-n", "BACKEND=cppauto", "detect-backend"], + ["make", "-n", "BACKEND=cpu", "detect-backend"], cwd=subproc_path, capture_output=True, text=True, ).stdout - match = re.search(r"BACKEND=(\S+) \(was cppauto\)", out) + match = re.search(r"BACKEND=(\S+) \(was cpu\)", out) if match: resolved = match.group(1) api_path = api_path_format.format(device=resolved) diff --git a/madgraph/iolibs/template_files/mg7/run_card.toml b/madgraph/iolibs/template_files/mg7/run_card.toml index 28410c2626..4ca46a1c96 100644 --- a/madgraph/iolibs/template_files/mg7/run_card.toml +++ b/madgraph/iolibs/template_files/mg7/run_card.toml @@ -1,6 +1,6 @@ [run] run_name = %(run.run_name)s -# options: cuda, hip, cpp, cppnone, cppsse4, cppavx2, cpp512y, cpp512z, cppauto +# options: cuda, hip, cpp, cpu_scalar, cpu_128b, cpu_256b, cpu_512b_y, cpu_512b, cpu devices = %(run.devices)s # options: # -1 to choose automatically diff --git a/madgraph/various/banner.py b/madgraph/various/banner.py index 3f8620757d..5b372b4fa0 100755 --- a/madgraph/various/banner.py +++ b/madgraph/various/banner.py @@ -6453,8 +6453,8 @@ def default_setup(self): # ----------------------------- [run] -------------------------- self.add_toml_param('run', 'run_name', "run", gridpack=True) - self.add_toml_param('run', 'devices', ["cppnone"], typelist=str, gridpack=True, - comment="options: cuda, hip, cpp, cppnone, cppsse4, cppavx2, cpp512y, cpp512z, cppauto") + self.add_toml_param('run', 'devices', ["cpu_scalar"], typelist=str, gridpack=True, + comment="options: cuda, hip, cpp, cpu_scalar, cpu_128b, cpu_256b, cpu_512b_y, cpu_512b, cpu") self.add_toml_param('run', 'simd_vector_size', -1, comment="-1 chooses automatically; on x86: 1, 4, 8; on Apple silicon: 1, 2") self.add_toml_param('run', 'cpu_thread_pool_size', -1, gridpack=True, diff --git a/madgraph/various/process_checks.py b/madgraph/various/process_checks.py index 00dd7eb6ce..87a5566ea7 100755 --- a/madgraph/various/process_checks.py +++ b/madgraph/various/process_checks.py @@ -4432,7 +4432,7 @@ def _parse_sa_output(text, target_pdgs=None): if p_dirs_mg7: check_dir_mg7 = pjoin(sa_dir_mg7, 'SubProcesses', p_dirs_mg7[0]) - backends = ["cppnone", "cppsse4", "cppavx2", "cpp512z", "cuda", "hip"] + backends = ["cpu_scalar", "cpu_128b", "cpu_256b", "cpu_512b", "cuda", "hip"] for backend in backends: with open(os.devnull, 'w') as devnull: ret = subprocess.call(f'make clean && make BACKEND={backend} USEBUILDDIR=1', shell=True, cwd=check_dir_mg7, diff --git a/madmatrix/MadtRex/makefiles/cudacpp_driver.mk b/madmatrix/MadtRex/makefiles/cudacpp_driver.mk index f01fc2c780..16b5eb3534 100644 --- a/madmatrix/MadtRex/makefiles/cudacpp_driver.mk +++ b/madmatrix/MadtRex/makefiles/cudacpp_driver.mk @@ -55,32 +55,32 @@ endif #------------------------------------------------------------------------------- -#=== Redefine BACKEND if the current value is 'cppauto' +#=== Redefine BACKEND if the current value is 'cpu' -# Set the default BACKEND choice corresponding to 'cppauto' (the 'best' C++ vectorization available) -ifeq ($(BACKEND),cppauto) +# Set the default BACKEND choice corresponding to 'cpu' (the 'best' C++ vectorization available) +ifeq ($(BACKEND),cpu) ifeq ($(UNAME_P),ppc64le) - override BACKEND = cppsse4 + override BACKEND = cpu_128b else ifneq (,$(filter $(UNAME_M),arm64 aarch64)) - override BACKEND = cppsse4 + override BACKEND = cpu_128b else ifeq ($(wildcard /proc/cpuinfo),) - override BACKEND = cppnone + override BACKEND = cpu_scalar ###$(warning Using BACKEND='$(BACKEND)' because host SIMD features cannot be read from /proc/cpuinfo) else ifeq ($(shell grep -m1 -c avx512vl /proc/cpuinfo)$(shell $(CXX) --version | grep ^clang),1) - override BACKEND = cpp512y + override BACKEND = cpu_512b_y else ifeq ($(shell grep -m1 -c avx2 /proc/cpuinfo),1) - override BACKEND = cppavx2 + override BACKEND = cpu_256b ###ifneq ($(shell grep -m1 -c avx512vl /proc/cpuinfo),1) ### $(warning Using BACKEND='$(BACKEND)' because host does not support avx512vl) ###else ### $(warning Using BACKEND='$(BACKEND)' because this is faster than avx512vl for clang) ###endif else ifeq ($(shell grep -m1 -c sse4_2 /proc/cpuinfo),1) - override BACKEND = cppsse4 + override BACKEND = cpu_128b else - override BACKEND = cppnone + override BACKEND = cpu_scalar endif - $(info BACKEND=$(BACKEND) (was cppauto)) + $(info BACKEND=$(BACKEND) (was cpu)) else $(info BACKEND='$(BACKEND)') endif @@ -254,9 +254,9 @@ else override GPUFLAGS= # Sanity check, this should never happen: if GPUCC is empty, then this is a C++ build, i.e. BACKEND is neither cuda nor hip. - # In practice, in the following, "ifeq ($(GPUCC),)" is equivalent to "ifneq ($(findstring cpp,$(BACKEND)),)". + # In practice, in the following, "ifeq ($(GPUCC),)" is equivalent to "ifneq ($(findstring cpu,$(BACKEND)),)". # Conversely, note that GPUFLAGS is non-empty also for C++ builds, but it is never used in that case. - ifeq ($(findstring cpp,$(BACKEND)),) + ifeq ($(findstring cpu,$(BACKEND)),) $(error INTERNAL ERROR! Unknown backend BACKEND='$(BACKEND)': supported backends are $(foreach backend,$(SUPPORTED_BACKENDS),'$(backend)')) endif @@ -325,13 +325,13 @@ export CXXNAMESUFFIX # PowerPC-specific CXX compiler flags (being reviewed) ifeq ($(UNAME_P),ppc64le) - CXXFLAGS+= -mcpu=power9 -mtune=power9 # gains ~2-3%% both for cppnone and cppsse4 - # Throughput references without the extra flags below: cppnone=1.41-1.42E6, cppsse4=2.15-2.19E6 + CXXFLAGS+= -mcpu=power9 -mtune=power9 # gains ~2-3%% both for cpu_scalar and cpu_128b + # Throughput references without the extra flags below: cpu_scalar=1.41-1.42E6, cpu_128b=2.15-2.19E6 ###CXXFLAGS+= -DNO_WARN_X86_INTRINSICS # no change ###CXXFLAGS+= -fpeel-loops # no change - ###CXXFLAGS+= -funroll-loops # gains ~1%% for cppnone, loses ~1%% for cppsse4 + ###CXXFLAGS+= -funroll-loops # gains ~1%% for cpu_scalar, loses ~1%% for cpu_128b ###CXXFLAGS+= -ftree-vectorize # no change - ###CXXFLAGS+= -flto # would increase to cppnone=4.08-4.12E6, cppsse4=4.99-5.03E6! + ###CXXFLAGS+= -flto # would increase to cpu_scalar=4.08-4.12E6, cpu_128b=4.99-5.03E6! else ###CXXFLAGS+= -flto # also on Intel this would increase throughputs by a factor 2 to 4... ######CXXFLAGS+= -fno-semantic-interposition # no benefit (neither alone, nor combined with -flto) @@ -430,66 +430,66 @@ endif $(info OMPFLAGS=$(OMPFLAGS)) CXXFLAGS += $(OMPFLAGS) -# Set the build flags appropriate to each BACKEND choice (example: "make BACKEND=cppnone") +# Set the build flags appropriate to each BACKEND choice (example: "make BACKEND=cpu_scalar") # [NB MGONGPU_PVW512 is needed because "-mprefer-vector-width=256" is not exposed in a macro] # [Use 'g++ -E -dM - < /dev/null' to check which #define's are enabled] # [See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=96476] ifeq ($(UNAME_P),ppc64le) - ifeq ($(BACKEND),cppsse4) + ifeq ($(BACKEND),cpu_128b) override AVXFLAGS = -D__SSE4_2__ # Power9 VSX with 128 width (VSR registers) - else ifeq ($(BACKEND),cppavx2) - $(error Invalid SIMD BACKEND='$(BACKEND)': only 'cppnone' and 'cppsse4' are supported on PowerPC for the moment) - else ifeq ($(BACKEND),cpp512y) - $(error Invalid SIMD BACKEND='$(BACKEND)': only 'cppnone' and 'cppsse4' are supported on PowerPC for the moment) - else ifeq ($(BACKEND),cpp512z) - $(error Invalid SIMD BACKEND='$(BACKEND)': only 'cppnone' and 'cppsse4' are supported on PowerPC for the moment) + else ifeq ($(BACKEND),cpu_256b) + $(error Invalid SIMD BACKEND='$(BACKEND)': only 'cpu_scalar' and 'cpu_128b' are supported on PowerPC for the moment) + else ifeq ($(BACKEND),cpu_512b_y) + $(error Invalid SIMD BACKEND='$(BACKEND)': only 'cpu_scalar' and 'cpu_128b' are supported on PowerPC for the moment) + else ifeq ($(BACKEND),cpu_512b) + $(error Invalid SIMD BACKEND='$(BACKEND)': only 'cpu_scalar' and 'cpu_128b' are supported on PowerPC for the moment) endif else ifeq ($(UNAME_M),arm64) # ARM on Apple silicon - ifeq ($(BACKEND),cppnone) # this internally undefines __ARM_NEON + ifeq ($(BACKEND),cpu_scalar) # this internally undefines __ARM_NEON override AVXFLAGS = -DMGONGPU_NOARMNEON - else ifeq ($(BACKEND),cppsse4) # __ARM_NEON is always defined on Apple silicon + else ifeq ($(BACKEND),cpu_128b) # __ARM_NEON is always defined on Apple silicon override AVXFLAGS = - else ifeq ($(BACKEND),cppavx2) - $(error Invalid SIMD BACKEND='$(BACKEND)': only 'cppnone' and 'cppsse4' are supported on ARM for the moment) - else ifeq ($(BACKEND),cpp512y) - $(error Invalid SIMD BACKEND='$(BACKEND)': only 'cppnone' and 'cppsse4' are supported on ARM for the moment) - else ifeq ($(BACKEND),cpp512z) - $(error Invalid SIMD BACKEND='$(BACKEND)': only 'cppnone' and 'cppsse4' are supported on ARM for the moment) + else ifeq ($(BACKEND),cpu_256b) + $(error Invalid SIMD BACKEND='$(BACKEND)': only 'cpu_scalar' and 'cpu_128b' are supported on ARM for the moment) + else ifeq ($(BACKEND),cpu_512b_y) + $(error Invalid SIMD BACKEND='$(BACKEND)': only 'cpu_scalar' and 'cpu_128b' are supported on ARM for the moment) + else ifeq ($(BACKEND),cpu_512b) + $(error Invalid SIMD BACKEND='$(BACKEND)': only 'cpu_scalar' and 'cpu_128b' are supported on ARM for the moment) endif else ifeq ($(UNAME_M),aarch64) # ARM on Linux - ifeq ($(BACKEND),cppnone) # +nosimd ensures __ARM_NEON is absent + ifeq ($(BACKEND),cpu_scalar) # +nosimd ensures __ARM_NEON is absent override AVXFLAGS = -march=armv8-a+nosimd - else ifeq ($(BACKEND),cppsse4) # +simd ensures __ARM_NEON is present (128 width Q/quadword registers) + else ifeq ($(BACKEND),cpu_128b) # +simd ensures __ARM_NEON is present (128 width Q/quadword registers) override AVXFLAGS = -march=armv8-a+simd - else ifeq ($(BACKEND),cppavx2) - $(error Invalid SIMD BACKEND='$(BACKEND)': only 'cppnone' and 'cppsse4' are supported on aarch64 for the moment) - else ifeq ($(BACKEND),cpp512y) - $(error Invalid SIMD BACKEND='$(BACKEND)': only 'cppnone' and 'cppsse4' are supported on aarch64 for the moment) - else ifeq ($(BACKEND),cpp512z) - $(error Invalid SIMD BACKEND='$(BACKEND)': only 'cppnone' and 'cppsse4' are supported on aarch64 for the moment) + else ifeq ($(BACKEND),cpu_256b) + $(error Invalid SIMD BACKEND='$(BACKEND)': only 'cpu_scalar' and 'cpu_128b' are supported on aarch64 for the moment) + else ifeq ($(BACKEND),cpu_512b_y) + $(error Invalid SIMD BACKEND='$(BACKEND)': only 'cpu_scalar' and 'cpu_128b' are supported on aarch64 for the moment) + else ifeq ($(BACKEND),cpu_512b) + $(error Invalid SIMD BACKEND='$(BACKEND)': only 'cpu_scalar' and 'cpu_128b' are supported on aarch64 for the moment) endif else ifneq ($(shell $(CXX) --version | grep ^nvc++),) # support nvc++ #531 - ifeq ($(BACKEND),cppnone) + ifeq ($(BACKEND),cpu_scalar) override AVXFLAGS = -mno-sse3 # no SIMD - else ifeq ($(BACKEND),cppsse4) + else ifeq ($(BACKEND),cpu_128b) override AVXFLAGS = -mno-avx # SSE4.2 with 128 width (xmm registers) - else ifeq ($(BACKEND),cppavx2) + else ifeq ($(BACKEND),cpu_256b) override AVXFLAGS = -march=haswell # AVX2 with 256 width (ymm registers) [DEFAULT for clang] - else ifeq ($(BACKEND),cpp512y) + else ifeq ($(BACKEND),cpu_512b_y) override AVXFLAGS = -march=skylake -mprefer-vector-width=256 # AVX512 with 256 width (ymm registers) [DEFAULT for gcc] - else ifeq ($(BACKEND),cpp512z) + else ifeq ($(BACKEND),cpu_512b) override AVXFLAGS = -march=skylake -DMGONGPU_PVW512 # AVX512 with 512 width (zmm registers) endif else - ifeq ($(BACKEND),cppnone) + ifeq ($(BACKEND),cpu_scalar) override AVXFLAGS = -march=x86-64 # no SIMD (see #588) - else ifeq ($(BACKEND),cppsse4) + else ifeq ($(BACKEND),cpu_128b) override AVXFLAGS = -march=nehalem # SSE4.2 with 128 width (xmm registers) - else ifeq ($(BACKEND),cppavx2) + else ifeq ($(BACKEND),cpu_256b) override AVXFLAGS = -march=haswell # AVX2 with 256 width (ymm registers) [DEFAULT for clang] - else ifeq ($(BACKEND),cpp512y) + else ifeq ($(BACKEND),cpu_512b_y) override AVXFLAGS = -march=skylake-avx512 -mprefer-vector-width=256 # AVX512 with 256 width (ymm registers) [DEFAULT for gcc] - else ifeq ($(BACKEND),cpp512z) + else ifeq ($(BACKEND),cpu_512b) override AVXFLAGS = -march=skylake-avx512 -DMGONGPU_PVW512 # AVX512 with 512 width (zmm registers) endif endif @@ -571,7 +571,7 @@ GPUFLAGS += $(XCOMPILERFLAG) -fPIC # Build lockfile "full" tag (defines full specification of build options that cannot be intermixed) # (Rationale: avoid mixing of builds with different random number generators) -override TAG = $(patsubst cpp%%,%%,$(BACKEND))_$(FPTYPE)_inl$(HELINL)_hrd$(HRDCOD)_$(HASCURAND)_$(HASHIPRAND) +override TAG = $(patsubst cpu_%%,%%,$(BACKEND))_$(FPTYPE)_inl$(HELINL)_hrd$(HRDCOD)_$(HASCURAND)_$(HASHIPRAND) # Export TAG (so that there is no need to check/define it again in cudacpp_src.mk) export TAG @@ -724,23 +724,23 @@ bldhip: bldnone: @echo - $(MAKE) USEBUILDDIR=1 BACKEND=cppnone -f $(CUDACPP_MAKEFILE) + $(MAKE) USEBUILDDIR=1 BACKEND=cpu_scalar -f $(CUDACPP_MAKEFILE) bldsse4: @echo - $(MAKE) USEBUILDDIR=1 BACKEND=cppsse4 -f $(CUDACPP_MAKEFILE) + $(MAKE) USEBUILDDIR=1 BACKEND=cpu_128b -f $(CUDACPP_MAKEFILE) bldavx2: @echo - $(MAKE) USEBUILDDIR=1 BACKEND=cppavx2 -f $(CUDACPP_MAKEFILE) + $(MAKE) USEBUILDDIR=1 BACKEND=cpu_256b -f $(CUDACPP_MAKEFILE) bld512y: @echo - $(MAKE) USEBUILDDIR=1 BACKEND=cpp512y -f $(CUDACPP_MAKEFILE) + $(MAKE) USEBUILDDIR=1 BACKEND=cpu_512b_y -f $(CUDACPP_MAKEFILE) bld512z: @echo - $(MAKE) USEBUILDDIR=1 BACKEND=cpp512z -f $(CUDACPP_MAKEFILE) + $(MAKE) USEBUILDDIR=1 BACKEND=cpu_512b -f $(CUDACPP_MAKEFILE) ifeq ($(UNAME_P),ppc64le) ###bldavxs: $(INCDIR)/fbridge.inc bldnone bldsse4 diff --git a/madmatrix/MadtRex/makefiles/cudacpp_runner.mk b/madmatrix/MadtRex/makefiles/cudacpp_runner.mk index fed66e030f..c4b7449534 100644 --- a/madmatrix/MadtRex/makefiles/cudacpp_runner.mk +++ b/madmatrix/MadtRex/makefiles/cudacpp_runner.mk @@ -56,32 +56,32 @@ endif #------------------------------------------------------------------------------- -#=== Redefine BACKEND if the current value is 'cppauto' +#=== Redefine BACKEND if the current value is 'cpu' -# Set the default BACKEND choice corresponding to 'cppauto' (the 'best' C++ vectorization available) -ifeq ($(BACKEND),cppauto) +# Set the default BACKEND choice corresponding to 'cpu' (the 'best' C++ vectorization available) +ifeq ($(BACKEND),cpu) ifeq ($(UNAME_P),ppc64le) - override BACKEND = cppsse4 + override BACKEND = cpu_128b else ifneq (,$(filter $(UNAME_M),arm64 aarch64)) - override BACKEND = cppsse4 + override BACKEND = cpu_128b else ifeq ($(wildcard /proc/cpuinfo),) - override BACKEND = cppnone + override BACKEND = cpu_scalar ###$(warning Using BACKEND='$(BACKEND)' because host SIMD features cannot be read from /proc/cpuinfo) else ifeq ($(shell grep -m1 -c avx512vl /proc/cpuinfo)$(shell $(CXX) --version | grep ^clang),1) - override BACKEND = cpp512y + override BACKEND = cpu_512b_y else ifeq ($(shell grep -m1 -c avx2 /proc/cpuinfo),1) - override BACKEND = cppavx2 + override BACKEND = cpu_256b ###ifneq ($(shell grep -m1 -c avx512vl /proc/cpuinfo),1) ### $(warning Using BACKEND='$(BACKEND)' because host does not support avx512vl) ###else ### $(warning Using BACKEND='$(BACKEND)' because this is faster than avx512vl for clang) ###endif else ifeq ($(shell grep -m1 -c sse4_2 /proc/cpuinfo),1) - override BACKEND = cppsse4 + override BACKEND = cpu_128b else - override BACKEND = cppnone + override BACKEND = cpu_scalar endif - $(info BACKEND=$(BACKEND) (was cppauto)) + $(info BACKEND=$(BACKEND) (was cpu)) else $(info BACKEND='$(BACKEND)') endif @@ -175,8 +175,8 @@ endif # PowerPC-specific CXX compiler flags (being reviewed) ifeq ($(UNAME_P),ppc64le) - CXXFLAGS+= -mcpu=power9 -mtune=power9 # gains ~2-3%% both for cppnone and cppsse4 - # Throughput references without the extra flags below: cppnone=1.41-1.42E6, cppsse4=2.15-2.19E6 + CXXFLAGS+= -mcpu=power9 -mtune=power9 # gains ~2-3%% both for cpu_scalar and cpu_128b + # Throughput references without the extra flags below: cpu_scalar=1.41-1.42E6, cpu_128b=2.15-2.19E6 else ###CXXFLAGS+= -flto # also on Intel this would increase throughputs by a factor 2 to 4... ######CXXFLAGS+= -fno-semantic-interposition # no benefit (neither alone, nor combined with -flto) @@ -275,66 +275,66 @@ endif $(info OMPFLAGS=$(OMPFLAGS)) CXXFLAGS += $(OMPFLAGS) -# Set the build flags appropriate to each BACKEND choice (example: "make BACKEND=cppnone") +# Set the build flags appropriate to each BACKEND choice (example: "make BACKEND=cpu_scalar") # [NB MGONGPU_PVW512 is needed because "-mprefer-vector-width=256" is not exposed in a macro] # [Use 'g++ -E -dM - < /dev/null' to check which #define's are enabled] # [See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=96476] ifeq ($(UNAME_P),ppc64le) - ifeq ($(BACKEND),cppsse4) + ifeq ($(BACKEND),cpu_128b) override AVXFLAGS = -D__SSE4_2__ # Power9 VSX with 128 width (VSR registers) - else ifeq ($(BACKEND),cppavx2) - $(error Invalid SIMD BACKEND='$(BACKEND)': only 'cppnone' and 'cppsse4' are supported on PowerPC for the moment) - else ifeq ($(BACKEND),cpp512y) - $(error Invalid SIMD BACKEND='$(BACKEND)': only 'cppnone' and 'cppsse4' are supported on PowerPC for the moment) - else ifeq ($(BACKEND),cpp512z) - $(error Invalid SIMD BACKEND='$(BACKEND)': only 'cppnone' and 'cppsse4' are supported on PowerPC for the moment) + else ifeq ($(BACKEND),cpu_256b) + $(error Invalid SIMD BACKEND='$(BACKEND)': only 'cpu_scalar' and 'cpu_128b' are supported on PowerPC for the moment) + else ifeq ($(BACKEND),cpu_512b_y) + $(error Invalid SIMD BACKEND='$(BACKEND)': only 'cpu_scalar' and 'cpu_128b' are supported on PowerPC for the moment) + else ifeq ($(BACKEND),cpu_512b) + $(error Invalid SIMD BACKEND='$(BACKEND)': only 'cpu_scalar' and 'cpu_128b' are supported on PowerPC for the moment) endif else ifeq ($(UNAME_M),arm64) # ARM on Apple silicon - ifeq ($(BACKEND),cppnone) # this internally undefines __ARM_NEON + ifeq ($(BACKEND),cpu_scalar) # this internally undefines __ARM_NEON override AVXFLAGS = -DMGONGPU_NOARMNEON - else ifeq ($(BACKEND),cppsse4) # __ARM_NEON is always defined on Apple silicon + else ifeq ($(BACKEND),cpu_128b) # __ARM_NEON is always defined on Apple silicon override AVXFLAGS = - else ifeq ($(BACKEND),cppavx2) - $(error Invalid SIMD BACKEND='$(BACKEND)': only 'cppnone' and 'cppsse4' are supported on ARM for the moment) - else ifeq ($(BACKEND),cpp512y) - $(error Invalid SIMD BACKEND='$(BACKEND)': only 'cppnone' and 'cppsse4' are supported on ARM for the moment) - else ifeq ($(BACKEND),cpp512z) - $(error Invalid SIMD BACKEND='$(BACKEND)': only 'cppnone' and 'cppsse4' are supported on ARM for the moment) + else ifeq ($(BACKEND),cpu_256b) + $(error Invalid SIMD BACKEND='$(BACKEND)': only 'cpu_scalar' and 'cpu_128b' are supported on ARM for the moment) + else ifeq ($(BACKEND),cpu_512b_y) + $(error Invalid SIMD BACKEND='$(BACKEND)': only 'cpu_scalar' and 'cpu_128b' are supported on ARM for the moment) + else ifeq ($(BACKEND),cpu_512b) + $(error Invalid SIMD BACKEND='$(BACKEND)': only 'cpu_scalar' and 'cpu_128b' are supported on ARM for the moment) endif else ifeq ($(UNAME_M),aarch64) # ARM on Linux - ifeq ($(BACKEND),cppnone) # +nosimd ensures __ARM_NEON is absent + ifeq ($(BACKEND),cpu_scalar) # +nosimd ensures __ARM_NEON is absent override AVXFLAGS = -march=armv8-a+nosimd - else ifeq ($(BACKEND),cppsse4) # +simd ensures __ARM_NEON is present (128 width Q/quadword registers) + else ifeq ($(BACKEND),cpu_128b) # +simd ensures __ARM_NEON is present (128 width Q/quadword registers) override AVXFLAGS = -march=armv8-a+simd - else ifeq ($(BACKEND),cppavx2) - $(error Invalid SIMD BACKEND='$(BACKEND)': only 'cppnone' and 'cppsse4' are supported on aarch64 for the moment) - else ifeq ($(BACKEND),cpp512y) - $(error Invalid SIMD BACKEND='$(BACKEND)': only 'cppnone' and 'cppsse4' are supported on aarch64 for the moment) - else ifeq ($(BACKEND),cpp512z) - $(error Invalid SIMD BACKEND='$(BACKEND)': only 'cppnone' and 'cppsse4' are supported on aarch64 for the moment) + else ifeq ($(BACKEND),cpu_256b) + $(error Invalid SIMD BACKEND='$(BACKEND)': only 'cpu_scalar' and 'cpu_128b' are supported on aarch64 for the moment) + else ifeq ($(BACKEND),cpu_512b_y) + $(error Invalid SIMD BACKEND='$(BACKEND)': only 'cpu_scalar' and 'cpu_128b' are supported on aarch64 for the moment) + else ifeq ($(BACKEND),cpu_512b) + $(error Invalid SIMD BACKEND='$(BACKEND)': only 'cpu_scalar' and 'cpu_128b' are supported on aarch64 for the moment) endif else ifneq ($(shell $(CXX) --version | grep ^nvc++),) # support nvc++ #531 - ifeq ($(BACKEND),cppnone) + ifeq ($(BACKEND),cpu_scalar) override AVXFLAGS = -mno-sse3 # no SIMD - else ifeq ($(BACKEND),cppsse4) + else ifeq ($(BACKEND),cpu_128b) override AVXFLAGS = -mno-avx # SSE4.2 with 128 width (xmm registers) - else ifeq ($(BACKEND),cppavx2) + else ifeq ($(BACKEND),cpu_256b) override AVXFLAGS = -march=haswell # AVX2 with 256 width (ymm registers) [DEFAULT for clang] - else ifeq ($(BACKEND),cpp512y) + else ifeq ($(BACKEND),cpu_512b_y) override AVXFLAGS = -march=skylake -mprefer-vector-width=256 # AVX512 with 256 width (ymm registers) [DEFAULT for gcc] - else ifeq ($(BACKEND),cpp512z) + else ifeq ($(BACKEND),cpu_512b) override AVXFLAGS = -march=skylake -DMGONGPU_PVW512 # AVX512 with 512 width (zmm registers) endif else - ifeq ($(BACKEND),cppnone) + ifeq ($(BACKEND),cpu_scalar) override AVXFLAGS = -march=x86-64 # no SIMD (see #588) - else ifeq ($(BACKEND),cppsse4) + else ifeq ($(BACKEND),cpu_128b) override AVXFLAGS = -march=nehalem # SSE4.2 with 128 width (xmm registers) - else ifeq ($(BACKEND),cppavx2) + else ifeq ($(BACKEND),cpu_256b) override AVXFLAGS = -march=haswell # AVX2 with 256 width (ymm registers) [DEFAULT for clang] - else ifeq ($(BACKEND),cpp512y) + else ifeq ($(BACKEND),cpu_512b_y) override AVXFLAGS = -march=skylake-avx512 -mprefer-vector-width=256 # AVX512 with 256 width (ymm registers) [DEFAULT for gcc] - else ifeq ($(BACKEND),cpp512z) + else ifeq ($(BACKEND),cpu_512b) override AVXFLAGS = -march=skylake-avx512 -DMGONGPU_PVW512 # AVX512 with 512 width (zmm registers) endif endif @@ -416,7 +416,7 @@ GPUFLAGS += $(XCOMPILERFLAG) -fPIC # Build lockfile "full" tag (defines full specification of build options that cannot be intermixed) # (Rationale: avoid mixing of builds with different random number generators) -override TAG = $(patsubst cpp%%,%%,$(BACKEND))_$(FPTYPE)_inl$(HELINL)_hrd$(HRDCOD)_$(HASCURAND)_$(HASHIPRAND) +override TAG = $(patsubst cpu_%%,%%,$(BACKEND))_$(FPTYPE)_inl$(HELINL)_hrd$(HRDCOD)_$(HASCURAND)_$(HASHIPRAND) # Export TAG (so that there is no need to check/define it again in cudacpp_src.mk) export TAG @@ -741,23 +741,23 @@ bldhip: bldnone: @echo - $(MAKE) USEBUILDDIR=1 BACKEND=cppnone -f $(CUDACPP_MAKEFILE) + $(MAKE) USEBUILDDIR=1 BACKEND=cpu_scalar -f $(CUDACPP_MAKEFILE) bldsse4: @echo - $(MAKE) USEBUILDDIR=1 BACKEND=cppsse4 -f $(CUDACPP_MAKEFILE) + $(MAKE) USEBUILDDIR=1 BACKEND=cpu_128b -f $(CUDACPP_MAKEFILE) bldavx2: @echo - $(MAKE) USEBUILDDIR=1 BACKEND=cppavx2 -f $(CUDACPP_MAKEFILE) + $(MAKE) USEBUILDDIR=1 BACKEND=cpu_256b -f $(CUDACPP_MAKEFILE) bld512y: @echo - $(MAKE) USEBUILDDIR=1 BACKEND=cpp512y -f $(CUDACPP_MAKEFILE) + $(MAKE) USEBUILDDIR=1 BACKEND=cpu_512b_y -f $(CUDACPP_MAKEFILE) bld512z: @echo - $(MAKE) USEBUILDDIR=1 BACKEND=cpp512z -f $(CUDACPP_MAKEFILE) + $(MAKE) USEBUILDDIR=1 BACKEND=cpu_512b -f $(CUDACPP_MAKEFILE) ifeq ($(UNAME_P),ppc64le) ###bldavxs: $(INCDIR)/fbridge.inc bldnone bldsse4 diff --git a/madmatrix/launch_plugin.py b/madmatrix/launch_plugin.py index 9554922472..2416a9a1d4 100644 --- a/madmatrix/launch_plugin.py +++ b/madmatrix/launch_plugin.py @@ -36,24 +36,24 @@ def compile(self, *args, **opts): common_run_interface.CommonRunCmd.update_make_opts_full(path, {'override FPTYPE': self.run_card['floating_type'] }) misc.sprint('FPTYPE checked') - cudacpp_supported_backends = [ 'fortran', 'cuda', 'hip', 'cpp', 'cppnone', 'cppsse4', 'cppavx2', 'cpp512y', 'cpp512z', 'cppauto' ] + cudacpp_supported_backends = [ 'fortran', 'cuda', 'hip', 'cpp', 'cpu_scalar', 'cpu_128b', 'cpu_256b', 'cpu_512b_y', 'cpu_512b', 'cpu' ] if args and args[0][0] == 'madevent' and hasattr(self, 'run_card'): cudacpp_backend = self.run_card['cudacpp_backend'].lower() # the default value is defined in launch_plugin.py - if cudacpp_backend in ['cpp', 'cppauto']: + if cudacpp_backend in ['cpp', 'cpu']: backend_log = pjoin(opts["cwd"], ".resolved-backend") # try to remove old file if present try: os.remove(backend_log) except FileNotFoundError: pass - misc.compile(["-f", "cudacpp.mk", f"BACKEND=cppauto", f"BACKEND_LOG={backend_log}", "detect-backend"], **opts) + misc.compile(["-f", "cudacpp.mk", f"BACKEND=cpu", f"BACKEND_LOG={backend_log}", "detect-backend"], **opts) try: with open(backend_log, "r") as f: resolved_backend = f.read().strip() logger.info(f"Backend '{cudacpp_backend}' resolved as '{resolved_backend}'") cudacpp_backend = resolved_backend except FileNotFoundError: - raise RuntimeError("Could not resolve cudacpp_backend=cppauto|cpp; ensure Makefile detection runs properly.") + raise RuntimeError("Could not resolve cudacpp_backend=cpu|cpp; ensure Makefile detection runs properly.") logger.info(f"Building madevent in madevent_interface.py with '{cudacpp_backend}' matrix elements") if cudacpp_backend in cudacpp_supported_backends : args[0][0] = 'madevent_' + cudacpp_backend + '_link' @@ -68,7 +68,7 @@ def compile(self, *args, **opts): """#*********************************************************************** # SIMD/GPU configuration for the CUDACPP plugin #************************************************************************ - %(cudacpp_backend)s = cudacpp_backend ! CUDACPP backend: fortran, cuda, hip, cpp, cppnone, cppsse4, cppavx2, cpp512y, cpp512z, cppauto + %(cudacpp_backend)s = cudacpp_backend ! CUDACPP backend: fortran, cuda, hip, cpp, cpu_scalar, cpu_128b, cpu_256b, cpu_512b_y, cpu_512b, cpu """ template_off = '' @@ -105,7 +105,7 @@ def default_setup(self): allowed=['m','d','f'], comment='floating point precision: f (single), d (double), m (mixed: double for amplitudes, single for colors)' ) - cudacpp_supported_backends = [ 'fortran', 'cuda', 'hip', 'cpp', 'cppnone', 'cppsse4', 'cppavx2', 'cpp512y', 'cpp512z', 'cppauto' ] + cudacpp_supported_backends = [ 'fortran', 'cuda', 'hip', 'cpp', 'cpu_scalar', 'cpu_128b', 'cpu_256b', 'cpu_512b_y', 'cpu_512b', 'cpu' ] self.add_param('cudacpp_backend', 'cpp', include=False, hidden=False, allowed=cudacpp_supported_backends) self['vector_size'] = 16 # already setup in default class (just change value) diff --git a/madmatrix/trex.py b/madmatrix/trex.py index 9809fd01cf..b1d6817e4b 100644 --- a/madmatrix/trex.py +++ b/madmatrix/trex.py @@ -312,7 +312,7 @@ def __init__(self, *args, **kwargs): self.param_card = None self.reweight_card = [] self.reweight_names = [] - self.backend = 'cppauto' # default backend for tRex reweighting + self.backend = 'cpu' # default backend for tRex reweighting self.fptype = 'm' # default floating point type for tRex reweighting self.nb_threads = 1 if self.mother: @@ -465,7 +465,7 @@ def do_change(self,line): if len(args) < 2: return super().do_change(line) if args[0].lower() in ['cudacpp_backend', 'backend']: - cudacpp_supported_backends = [ 'cuda', 'hip', 'cpp', 'cppnone', 'cppsse4', 'cppavx2', 'cpp512y', 'cpp512z', 'cppauto' ] + cudacpp_supported_backends = [ 'cuda', 'hip', 'cpp', 'cpu_scalar', 'cpu_128b', 'cpu_256b', 'cpu_512b_y', 'cpu_512b', 'cpu' ] if args[1].lower() in cudacpp_supported_backends: self.backend = args[1].lower() logger.info("Setting tRex reweighting backend to '%s'" % self.backend) diff --git a/tests/acceptance_tests/test_cmd.py b/tests/acceptance_tests/test_cmd.py index 4dc23e282f..370915b4df 100755 --- a/tests/acceptance_tests/test_cmd.py +++ b/tests/acceptance_tests/test_cmd.py @@ -429,7 +429,7 @@ def test_output_mg7_directory(self): (madmatrix/cudacpp) directory for e+ e- > e+ e- -- the top-level layout (src/, SubProcesses/, lib/, Cards/, bin/), the mg7 cards and launcher, and that the generated subprocess compiles into the expected shared - libraries (scalar cppnone backend). + libraries (cpu_scalar backend). """ if os.path.isdir(self.out_dir): shutil.rmtree(self.out_dir) @@ -3486,7 +3486,7 @@ def test_mg7_ufo_aloha(self): header: the same FFV* helicity-amplitude functions are emitted as inline ``ALOHAOBJ`` C++ routines. This mirrors test_madevent_ufo_aloha but for the mg7 backend: it checks the routines are generated, the parameters / - process sources are present, and that the subprocess compiles (cppnone + process sources are present, and that the subprocess compiles (cpu_scalar backend) into the expected shared libraries. """ diff --git a/tests/acceptance_tests/test_cmd_madevent.py b/tests/acceptance_tests/test_cmd_madevent.py index dda209a8d9..340300f343 100755 --- a/tests/acceptance_tests/test_cmd_madevent.py +++ b/tests/acceptance_tests/test_cmd_madevent.py @@ -1649,7 +1649,7 @@ def test_e_p_collision_mg7(self): generate_events run. The mg7 (madmatrix/cudacpp) integrator is far too slow for a CI cross-section run on this process, so this equivalent validates the mg7 *output* path instead: that `output mg7` generates the - e- p > e- j subprocess directories and that they compile (scalar cppnone + e- p > e- j subprocess directories and that they compile (cpu_scalar backend) into the expected shared libraries. The cross-section comparison against the madevent reference remains a TODO pending a faster mg7 integrator. diff --git a/tests/input_files/mg7_run_card.toml b/tests/input_files/mg7_run_card.toml index 8a2db1fb34..8a78db6242 100644 --- a/tests/input_files/mg7_run_card.toml +++ b/tests/input_files/mg7_run_card.toml @@ -1,7 +1,7 @@ [run] run_name = "run" -# options: cuda, hip, cpp, cppnone, cppsse4, cppavx2, cpp512y, cpp512z, cppauto -devices = ["cppnone"] +# options: cuda, hip, cpp, cpu_scalar, cpu_128b, cpu_256b, cpu_512b_y, cpu_512b, cpu +devices = ["cpu_scalar"] # options: # -1 to choose automatically # on x86: 1, 4, 8 From a3611c523b9de246ce755dbf0352ef1155e74e3d Mon Sep 17 00:00:00 2001 From: Olivier Mattelaer Date: Fri, 14 Aug 2026 22:06:52 +0200 Subject: [PATCH 21/26] remove the bare cpp madmatrix backend token The previous rename kept `cpp` as a Python-only alias resolving to `cpu`. It was never a makefile backend (`make BACKEND=cpp` has always failed), so it only ever existed as a pseudo-value in the Python layer, and silently translating it is exactly the confusion the rename was meant to remove. Drop it everywhere it appeared as a backend value: - the `devices` option comment in the mg7 run_card.toml template, in RunCardMG7.default_setup and in the tests/input_files copy - the `cudacpp_supported_backends` lists and the run_card comment line in madmatrix/launch_plugin.py, now a single CUDACPP_SUPPORTED_BACKENDS constant - the tRex `do_change` backend list in madmatrix/trex.py The default value of madevent's `cudacpp_backend` was `cpp`; it becomes `cpu`, which is a real makefile backend with the same auto-detecting meaning. Existing process directories still carry `cudacpp_backend = cpp` in their run card, and the generic ConfigFile `allowed=` handling reacts to an out-of-range value by warning and silently keeping the previous value -- i.e. such a run would quietly build on a different backend. CPPRunCard now overrides __setitem__ for this one parameter and raises InvalidRunCard instead, naming the supported backends and the old->new mapping. C++ exporter names, the `cpp` compiler key, and cudacpp*/CPPProcess/.cpp identifiers are unrelated to the backend value and are left untouched, as is madmatrix/contrib (vendored upstream harnesses targeting an external MG5aMC checkout). Co-Authored-By: Claude Opus 5 --- .../iolibs/template_files/mg7/run_card.toml | 2 +- madgraph/various/banner.py | 2 +- madmatrix/launch_plugin.py | 41 +++++++++++++++---- madmatrix/trex.py | 2 +- tests/input_files/mg7_run_card.toml | 2 +- 5 files changed, 37 insertions(+), 12 deletions(-) diff --git a/madgraph/iolibs/template_files/mg7/run_card.toml b/madgraph/iolibs/template_files/mg7/run_card.toml index 4ca46a1c96..29bfed6a85 100644 --- a/madgraph/iolibs/template_files/mg7/run_card.toml +++ b/madgraph/iolibs/template_files/mg7/run_card.toml @@ -1,6 +1,6 @@ [run] run_name = %(run.run_name)s -# options: cuda, hip, cpp, cpu_scalar, cpu_128b, cpu_256b, cpu_512b_y, cpu_512b, cpu +# options: cuda, hip, cpu_scalar, cpu_128b, cpu_256b, cpu_512b_y, cpu_512b, cpu devices = %(run.devices)s # options: # -1 to choose automatically diff --git a/madgraph/various/banner.py b/madgraph/various/banner.py index 5b372b4fa0..e9dc844643 100755 --- a/madgraph/various/banner.py +++ b/madgraph/various/banner.py @@ -6454,7 +6454,7 @@ def default_setup(self): # ----------------------------- [run] -------------------------- self.add_toml_param('run', 'run_name', "run", gridpack=True) self.add_toml_param('run', 'devices', ["cpu_scalar"], typelist=str, gridpack=True, - comment="options: cuda, hip, cpp, cpu_scalar, cpu_128b, cpu_256b, cpu_512b_y, cpu_512b, cpu") + comment="options: cuda, hip, cpu_scalar, cpu_128b, cpu_256b, cpu_512b_y, cpu_512b, cpu") self.add_toml_param('run', 'simd_vector_size', -1, comment="-1 chooses automatically; on x86: 1, 4, 8; on Apple silicon: 1, 2") self.add_toml_param('run', 'cpu_thread_pool_size', -1, gridpack=True, diff --git a/madmatrix/launch_plugin.py b/madmatrix/launch_plugin.py index 2416a9a1d4..5cd31b9862 100644 --- a/madmatrix/launch_plugin.py +++ b/madmatrix/launch_plugin.py @@ -25,6 +25,11 @@ import madgraph.various.banner as banner_mod import madgraph.interface.common_run_interface as common_run_interface +# The backends accepted by the 'cudacpp_backend' run_card entry. 'cpu' is the +# auto-detecting CPU backend (the makefile resolves it to the widest SIMD flavour +# available on the host); the cpu_* entries pin one explicit SIMD width. +CUDACPP_SUPPORTED_BACKENDS = [ 'fortran', 'cuda', 'hip', 'cpu_scalar', 'cpu_128b', 'cpu_256b', 'cpu_512b_y', 'cpu_512b', 'cpu' ] + class CPPMEInterface(madevent_interface.MadEventCmdShell): def compile(self, *args, **opts): """ """ @@ -36,10 +41,10 @@ def compile(self, *args, **opts): common_run_interface.CommonRunCmd.update_make_opts_full(path, {'override FPTYPE': self.run_card['floating_type'] }) misc.sprint('FPTYPE checked') - cudacpp_supported_backends = [ 'fortran', 'cuda', 'hip', 'cpp', 'cpu_scalar', 'cpu_128b', 'cpu_256b', 'cpu_512b_y', 'cpu_512b', 'cpu' ] - if args and args[0][0] == 'madevent' and hasattr(self, 'run_card'): + cudacpp_supported_backends = CUDACPP_SUPPORTED_BACKENDS + if args and args[0][0] == 'madevent' and hasattr(self, 'run_card'): cudacpp_backend = self.run_card['cudacpp_backend'].lower() # the default value is defined in launch_plugin.py - if cudacpp_backend in ['cpp', 'cpu']: + if cudacpp_backend == 'cpu': backend_log = pjoin(opts["cwd"], ".resolved-backend") # try to remove old file if present try: @@ -53,7 +58,7 @@ def compile(self, *args, **opts): logger.info(f"Backend '{cudacpp_backend}' resolved as '{resolved_backend}'") cudacpp_backend = resolved_backend except FileNotFoundError: - raise RuntimeError("Could not resolve cudacpp_backend=cpu|cpp; ensure Makefile detection runs properly.") + raise RuntimeError("Could not resolve cudacpp_backend=cpu; ensure Makefile detection runs properly.") logger.info(f"Building madevent in madevent_interface.py with '{cudacpp_backend}' matrix elements") if cudacpp_backend in cudacpp_supported_backends : args[0][0] = 'madevent_' + cudacpp_backend + '_link' @@ -68,7 +73,7 @@ def compile(self, *args, **opts): """#*********************************************************************** # SIMD/GPU configuration for the CUDACPP plugin #************************************************************************ - %(cudacpp_backend)s = cudacpp_backend ! CUDACPP backend: fortran, cuda, hip, cpp, cpu_scalar, cpu_128b, cpu_256b, cpu_512b_y, cpu_512b, cpu + %(cudacpp_backend)s = cudacpp_backend ! CUDACPP backend: fortran, cuda, hip, cpu_scalar, cpu_128b, cpu_256b, cpu_512b_y, cpu_512b, cpu """ template_off = '' @@ -77,6 +82,27 @@ def compile(self, *args, **opts): class CPPRunCard(banner_mod.RunCardLO): blocks = banner_mod.RunCardLO.blocks + [plugin_block] + def __setitem__(self, name, value, *args, **opts): + """Refuse an unsupported cudacpp_backend instead of silently falling back. + + The generic ConfigFile machinery reacts to a value outside an 'allowed' + list by logging a warning and keeping the previous value. For + cudacpp_backend that would mean building and running on a backend the + user never asked for, so make it a hard error here. This matters in + particular for run cards written before the backend renaming, which + still carry the removed 'cpp' value. + """ + if isinstance(name, str) and name.strip().lower() == 'cudacpp_backend': + allowed = getattr(self, 'allowed_value', {}).get('cudacpp_backend', []) + if allowed and str(value).strip().lower() not in [str(v).lower() for v in allowed]: + raise banner_mod.InvalidRunCard( + "Invalid cudacpp_backend='%s': supported backends are [ '%s' ]. " \ + % (str(value).strip(), "', '".join(str(v) for v in allowed)) + + "The CPU/SIMD backends were renamed: use 'cpu' (auto-detected SIMD width) " \ + "instead of 'cpp', and 'cpu_scalar'/'cpu_128b'/'cpu_256b'/'cpu_512b_y'/'cpu_512b' " \ + "instead of 'cppnone'/'cppsse4'/'cppavx2'/'cpp512y'/'cpp512z'.") + return super().__setitem__(name, value, *args, **opts) + def reset_simd(self, old_value, new_value, name): if not hasattr(self, 'path'): raise Exception('INTERNAL ERROR! CPPRunCard instance has no attribute path') # now ok after fixing #790 @@ -105,9 +131,8 @@ def default_setup(self): allowed=['m','d','f'], comment='floating point precision: f (single), d (double), m (mixed: double for amplitudes, single for colors)' ) - cudacpp_supported_backends = [ 'fortran', 'cuda', 'hip', 'cpp', 'cpu_scalar', 'cpu_128b', 'cpu_256b', 'cpu_512b_y', 'cpu_512b', 'cpu' ] - self.add_param('cudacpp_backend', 'cpp', include=False, hidden=False, - allowed=cudacpp_supported_backends) + self.add_param('cudacpp_backend', 'cpu', include=False, hidden=False, + allowed=CUDACPP_SUPPORTED_BACKENDS) self['vector_size'] = 16 # already setup in default class (just change value) self['aloha_flag'] = '--fast-math' self['matrix_flag'] = '-O3' diff --git a/madmatrix/trex.py b/madmatrix/trex.py index b1d6817e4b..6bccecf1ee 100644 --- a/madmatrix/trex.py +++ b/madmatrix/trex.py @@ -465,7 +465,7 @@ def do_change(self,line): if len(args) < 2: return super().do_change(line) if args[0].lower() in ['cudacpp_backend', 'backend']: - cudacpp_supported_backends = [ 'cuda', 'hip', 'cpp', 'cpu_scalar', 'cpu_128b', 'cpu_256b', 'cpu_512b_y', 'cpu_512b', 'cpu' ] + cudacpp_supported_backends = [ 'cuda', 'hip', 'cpu_scalar', 'cpu_128b', 'cpu_256b', 'cpu_512b_y', 'cpu_512b', 'cpu' ] if args[1].lower() in cudacpp_supported_backends: self.backend = args[1].lower() logger.info("Setting tRex reweighting backend to '%s'" % self.backend) diff --git a/tests/input_files/mg7_run_card.toml b/tests/input_files/mg7_run_card.toml index 8a78db6242..eb57edf4ab 100644 --- a/tests/input_files/mg7_run_card.toml +++ b/tests/input_files/mg7_run_card.toml @@ -1,6 +1,6 @@ [run] run_name = "run" -# options: cuda, hip, cpp, cpu_scalar, cpu_128b, cpu_256b, cpu_512b_y, cpu_512b, cpu +# options: cuda, hip, cpu_scalar, cpu_128b, cpu_256b, cpu_512b_y, cpu_512b, cpu devices = ["cpu_scalar"] # options: # -1 to choose automatically From e7bb1bc9e37d0fd3003e0f80f2819d924b3fca12 Mon Sep 17 00:00:00 2001 From: Olivier Mattelaer Date: Sun, 16 Aug 2026 23:40:08 +0200 Subject: [PATCH 22/26] drop the debug sprints from the madmatrix and madevent output path misc.sprint tags its output with [file at line N], so the statements that actually add noise were collected by running the operations rather than by grepping: output madevent / standalone / mg7 / standalone_fortran, widened to a decay chain, a merged-flavor process and MSSM. The firing set was stable at fifteen sites, all in the output phase. Fourteen are removed here. None sits before a raise, and none is in the 3.7.2 branch: madmatrix/output.py and madmatrix/model_handling.py do not exist there, 3.7.2 has no sprint at all in export_cpp.py, and the two in its helas_objects.py are both commented out. What goes is the "Entering ProcessExporterMadMatrix.*" traces and their type(...) dumps, an argname trace, two config-map dumps, the "Exporting model to C++ standalone format" line, raw coups_dep and type_param_dict dumps, and a merged-particle PDG trace. The fifteenth, misc.py sprint(error) in the desktop-notification helper, is kept: it is in 3.7.2 as well, and it is the only trace of an exception that is otherwise swallowed by the except/pass around it. Launch was already quiet -- bin/generate_events for both madevent and mg7 produced events with no sprint output at all -- so nothing there needed touching. Each removed line was checked to be a complete statement and not the sole body of its block. After the change the same four outputs still build and only the misc.py sprint remains. Co-Authored-By: Claude Opus 5 --- madgraph/core/helas_objects.py | 1 - madgraph/iolibs/export_cpp.py | 3 --- madmatrix/model_handling.py | 3 --- madmatrix/output.py | 7 ------- 4 files changed, 14 deletions(-) diff --git a/madgraph/core/helas_objects.py b/madgraph/core/helas_objects.py index 93a5c43945..62df651630 100755 --- a/madgraph/core/helas_objects.py +++ b/madgraph/core/helas_objects.py @@ -1680,7 +1680,6 @@ def get_coupling_for_flavor(self, model, tag_name='flavortag'): return None pdg_out = self.get('pdg_code') - misc.sprint(pdg_out, self[tag_name], [p.get_pdg_code() for p in vertex.get('particles')]) if abs(pdg_out) in model.get('merged_particles'): pdg_vertex = [p.get_pdg_code() for p in vertex.get('particles')] index_merge, merge_pdg = [(i,pdg) for i, pdg in enumerate(pdg_vertex) if abs(pdg) in model.get('merged_particles')][0] diff --git a/madgraph/iolibs/export_cpp.py b/madgraph/iolibs/export_cpp.py index d464d0cc62..9de3034268 100755 --- a/madgraph/iolibs/export_cpp.py +++ b/madgraph/iolibs/export_cpp.py @@ -101,7 +101,6 @@ class UFOModelConverterCPP(object): def __init__(self, model, output_path, wanted_lorentz = [], wanted_couplings = [], replace_dict={}): """ initialization of the objects """ - misc.sprint('Exporting model to C++ standalone format') self.model = model self.model_name = ProcessExporterCPP.get_model_name(model['name']) @@ -216,7 +215,6 @@ def prepare_couplings(self, wanted_couplings = []): # Handle flavor couplings # strategy picke one of the actual coupling and check if this is a running one or not flavor_couplings = [c for c in wanted_couplings if isinstance(c, base_objects.FLV_Coupling)] - misc.sprint(self.coups_dep) deps = [c.name for c in self.coups_dep.values()] for one_flv in flavor_couplings: one_coupling = one_flv.get_one_coupling() @@ -348,7 +346,6 @@ def write_parameters(self, params): # For each parameter type, write out the definition string # type parameters; - misc.sprint(type_param_dict) res_strings = [] for key in type_param_dict: res_strings.append("%s %s;" % (self.type_dict[key], diff --git a/madmatrix/model_handling.py b/madmatrix/model_handling.py index 5805c7d4e7..574c627efa 100644 --- a/madmatrix/model_handling.py +++ b/madmatrix/model_handling.py @@ -168,7 +168,6 @@ def get_header_txt(self, name=None, couplings=None,mode='', combined=False): else: type = self.type2def[format] + ' ' + self.type2def['aloha_ref'] list_arg = '' - misc.sprint(argname,self.tag) if argname.startswith('COUP'): type = self.type2def['double'] # AV from cxtype_sv to fptype array (running alphas #373) if 'M' in self.tag: @@ -2185,8 +2184,6 @@ def edit_coloramps(self): iconfig_to_diag[iconfig] = config[0] diag_to_iconfig[config[0]] = iconfig - misc.sprint(iconfig_to_diag) - misc.sprint(diag_to_iconfig) # Note that if the last diagram is/are not mapped to a channel nb_diag # will be smaller than the true number of diagram. This is fine for color diff --git a/madmatrix/output.py b/madmatrix/output.py index 1820b10e96..7efb431e65 100644 --- a/madmatrix/output.py +++ b/madmatrix/output.py @@ -144,7 +144,6 @@ 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 - misc.sprint('Entering ProcessExporterMadMatrix.__init__ (initialise the exporter)') args[1]["me_lib_format"] = pjoin("lib", "libmadmatrix_{process_id}_{{device}}.so") super().__init__(*args, **kwargs) # Honor the output command's --mask=True|False (flavor-mask @@ -161,7 +160,6 @@ def _parse_flavor_mask_option(self): # AV - overload the default version: create CMake directory, do not create lib directory def copy_template(self, model): - misc.sprint('Entering ProcessExporterMadMatrix.copy_template (initialise the directory)') super().copy_template(model) # Rename Makefile to makefile if self.template_src_make: @@ -171,11 +169,6 @@ def copy_template(self, model): # AV - add debug printouts (in addition to the default one from OM's tutorial) def generate_subprocess_directory(self, matrix_element, cpp_helas_call_writer, proc_number=None): - misc.sprint('Entering ProcessExporterMadMatrix.generate_subprocess_directory (create the directory)') - misc.sprint(' type(matrix_element)=%s'%type(matrix_element)) # e.g. madgraph.core.helas_objects.HelasMatrixElement - misc.sprint(' type(cpp_helas_call_writer)=%s'%type(cpp_helas_call_writer)) # e.g. madgraph.iolibs.helas_call_writers.GPUFOHelasCallWriter - misc.sprint(' type(proc_number)=%s me=%s'%(type(proc_number) if proc_number is not None else None, proc_number)) # e.g. int - misc.sprint("need to link", self.to_link_in_P) # Propagate the --mask toggle to the helas call writer that emits the # guarded wavefunction/amplitude calls. if cpp_helas_call_writer is not None: From 0bee7d70325dda6f4fdc95cdc42a7c776d8cbbe8 Mon Sep 17 00:00:00 2001 From: Olivier Mattelaer Date: Mon, 17 Aug 2026 00:38:10 +0200 Subject: [PATCH 23/26] only warn about a lorentz redefinition that is not an argument reordering When the same lorentz name is defined twice, import_ufo compares the two structure strings and warns when they differ. The comparison sits right after a block that renumbers the indices of the vertex, and that renumbering can reorder the arguments of a symmetric function: importing the sm model reports Metric(3,2) against Metric(2,3) and Metric(4,2) against Metric(2,4), which are the same object written two ways. The warning therefore fired on every check run while telling the user nothing. Add models/lorentz_symmetric_structures.txt, listing the structure functions whose arguments commute, and canonicalize both sides by sorting the arguments of those functions before comparing. The file stores the rule rather than the individual pairs: the indices come out of the renumbering, so Metric(1,3), Metric(-1,2) and so on are all equally possible, and one line covers them all. The canonicalisation is deliberately narrow. It rewrites only calls whose arguments are all plain (possibly negative) integers, so a nested expression is never touched, and it applies only to the functions named in the file, which carries a warning that listing a non-symmetric one would silence a real redefinition. Kept as misc.sprint rather than promoted to a logger warning, so the line stays identical to the one in 3.7.2; what changes is only how often it has something to say. The new test class covers both directions, since suppressing the noise is only half of it: the Metric reorderings compare equal, including with summed (negative) indices which have to sort numerically rather than as text, while a different index, a reordered Gamma or ProjP, and a mixed case where only the symmetric half matches are all still reported. Co-Authored-By: Claude Opus 5 --- models/import_ufo.py | 52 ++++++++++++++++++++- models/lorentz_symmetric_structures.txt | 19 ++++++++ tests/unit_tests/various/test_import_ufo.py | 40 ++++++++++++++++ 3 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 models/lorentz_symmetric_structures.txt diff --git a/models/import_ufo.py b/models/import_ufo.py index a20cce8097..ec6ab0b8b2 100755 --- a/models/import_ufo.py +++ b/models/import_ufo.py @@ -59,6 +59,52 @@ # Suffixes to employ for the various poles of CTparameters pole_dict = {-2:'2EPS',-1:'1EPS',0:'FIN'} +# Cache for lorentz_symmetric_structures.txt (read at most once per session). +_symmetric_lorentz_structures = None + +def get_symmetric_lorentz_structures(): + """Return the lorentz structure functions whose arguments commute, as + listed in models/lorentz_symmetric_structures.txt.""" + + global _symmetric_lorentz_structures + if _symmetric_lorentz_structures is None: + names = [] + path = pjoin(root_path, 'lorentz_symmetric_structures.txt') + try: + with open(path) as fsock: + for line in fsock: + line = line.split('#', 1)[0].strip() + if line: + names.append(line) + except IOError: + logger.debug('%s not found: no lorentz structure is canonicalised', + path) + _symmetric_lorentz_structures = names + return _symmetric_lorentz_structures + +def canonicalize_lorentz_structure(structure): + """Sort the arguments of the symmetric functions appearing in *structure*. + + Renumbering the indices of a vertex can reorder the arguments of a + symmetric function, so that Metric(3,2) and Metric(2,3) -- the same object + -- come out as different strings. Sorting the arguments of the functions + known to be symmetric makes the two spellings compare equal, so that only a + real disagreement between two definitions is reported. + + Only calls whose arguments are all plain (possibly negative) integer + indices are touched, so a nested expression is never rewritten.""" + + if not isinstance(structure, str): + return structure + for name in get_symmetric_lorentz_structures(): + pattern = re.compile(r'\b%s\(\s*(-?\d+(?:\s*,\s*-?\d+)*)\s*\)' + % re.escape(name)) + def sort_args(matchobj, name=name): + args = [a.strip() for a in matchobj.group(1).split(',')] + return '%s(%s)' % (name, ','.join(sorted(args, key=int))) + structure = pattern.sub(sort_args, structure) + return structure + class UFOImportError(MadGraph5Error): """ a error class for wrong import of UFO model""" @@ -1378,7 +1424,11 @@ def get_symmetric_lorentz(self, old_lorentz, substitution, change_number=False): new_lor = self.add_lorentz(new_name, new_spins, new_expr, formfact=new_formfact) except AssertionError: prev_def = [l for l in self.model['lorentz'] if l.name==new_name][0] - if prev_def.structure != new_expr: + # compare canonicalised forms: the index renumbering above can + # reorder the arguments of a symmetric function, and that alone + # is not a redefinition (see canonicalize_lorentz_structure) + if canonicalize_lorentz_structure(prev_def.structure) != \ + canonicalize_lorentz_structure(new_expr): misc.sprint("WARNING, two different definition for one lorentz name", prev_def.structure, new_expr) new_lor = prev_def return new_lor diff --git a/models/lorentz_symmetric_structures.txt b/models/lorentz_symmetric_structures.txt new file mode 100644 index 0000000000..355c9c3bbf --- /dev/null +++ b/models/lorentz_symmetric_structures.txt @@ -0,0 +1,19 @@ +# Lorentz structure functions whose arguments commute. +# +# When the same lorentz name is defined twice, import_ufo compares the two +# structure strings and warns if they differ. Some differences are not real +# redefinitions: renumbering the indices of a vertex can reorder the arguments +# of a symmetric function, so Metric(3,2) and Metric(2,3) come out as different +# strings while denoting the same object. +# +# Every function listed here has its arguments sorted before that comparison, +# so a difference in argument order alone is silent and only a genuine +# disagreement is reported. +# +# One function name per line; blank lines and #-comments are ignored. Only add +# a function whose arguments really do commute -- listing a non-symmetric one +# would silence a real redefinition. Sorting applies solely to calls whose +# arguments are all plain (possibly negative) integer indices. + +# g^{mu nu} is symmetric by definition. +Metric diff --git a/tests/unit_tests/various/test_import_ufo.py b/tests/unit_tests/various/test_import_ufo.py index 373e8f2124..78eaceb6cc 100755 --- a/tests/unit_tests/various/test_import_ufo.py +++ b/tests/unit_tests/various/test_import_ufo.py @@ -1668,3 +1668,43 @@ def test_restrict_from_a_param_card(self): found += 1 self.assertEqual(found, 1) + + +class TestLorentzStructureCanonicalisation(unittest.TestCase): + """Sorting the arguments of the symmetric lorentz structures. + + Renumbering the indices of a vertex can reorder the arguments of a + symmetric function, so that the same object is written Metric(3,2) in one + definition and Metric(2,3) in another. import_ufo compares the two + structures when a lorentz name is defined twice and warns when they + disagree; without canonicalisation that warning fires on every such + renumbering and hides the real disagreements among the noise. + """ + + def test_symmetric_functions_are_loaded(self): + """Metric is declared symmetric in lorentz_symmetric_structures.txt.""" + self.assertIn('Metric', import_ufo.get_symmetric_lorentz_structures()) + + def test_argument_order_of_a_symmetric_function_is_ignored(self): + """The two spellings of one Metric compare equal.""" + canon = import_ufo.canonicalize_lorentz_structure + # the two cases actually met when importing the sm model + self.assertEqual(canon('Metric(3,2)'), canon('Metric(2,3)')) + self.assertEqual(canon('Metric(4,2)'), canon('Metric(2,4)')) + # summed indices are negative, and must sort numerically (not as text) + self.assertEqual(canon('Metric(-1,2)'), canon('Metric(2,-1)')) + # and inside a larger expression + self.assertEqual(canon('Metric(1,2)*Gamma(3,4,5)'), + canon('Metric(2,1)*Gamma(3,4,5)')) + + def test_real_differences_are_still_reported(self): + """Canonicalisation must not silence a genuine redefinition.""" + canon = import_ufo.canonicalize_lorentz_structure + # different indices, not a reordering + self.assertNotEqual(canon('Metric(1,2)'), canon('Metric(1,3)')) + # Gamma and ProjP are NOT symmetric: reordering them stays a difference + self.assertNotEqual(canon('Gamma(1,2,3)'), canon('Gamma(3,2,1)')) + self.assertNotEqual(canon('ProjP(1,2)'), canon('ProjP(2,1)')) + # a symmetric part that matches does not excuse an asymmetric part + self.assertNotEqual(canon('Metric(1,2)*ProjM(3,4)'), + canon('Metric(2,1)*ProjM(4,3)')) From ba9b4030ee76eb12a160038a380b882c810accf6 Mon Sep 17 00:00:00 2001 From: Olivier Mattelaer Date: Mon, 17 Aug 2026 07:38:21 +0200 Subject: [PATCH 24/26] carry the lorentz argument symmetry on the structure itself Replace models/lorentz_symmetric_structures.txt with an is_symmetric attribute on the aloha objects: False on FactoryLorentz, True on Metric. canonicalize_lorentz_structure now scans the expression for calls and asks the structure whether its arguments commute, instead of consulting a list kept in a separate file. The object already knew: Metric.get_unique_name sorts its two indices, which is exactly the statement that g^{mu nu} is symmetric, only expressed inside a method. The attribute states it once, next to that method, and any other consumer can now read it rather than rediscover it. An unknown name answers False, so a structure aloha does not define is never taken for symmetric. Behaviour is unchanged: the two spellings of one Metric still compare equal, including with summed (negative) indices, while a different index, a reordered Gamma or ProjP, and a mixed case where only the symmetric half matches are all still reported. The aloha parallel suite has 9 pre-existing errors (NameError: TMP0, from tests that eval generated expression strings). Checked against a detached worktree at the previous commit: 124 tests and the same 9 error names on both sides, so they are untouched by this change. Co-Authored-By: Claude Opus 5 --- aloha/aloha_lib.py | 9 ++++- aloha/aloha_object.py | 7 +++- models/import_ufo.py | 45 ++++++++------------- models/lorentz_symmetric_structures.txt | 19 --------- tests/unit_tests/various/test_import_ufo.py | 14 +++++-- 5 files changed, 40 insertions(+), 54 deletions(-) delete mode 100644 models/lorentz_symmetric_structures.txt diff --git a/aloha/aloha_lib.py b/aloha/aloha_lib.py index cc6b7ba4fd..1fe72eba99 100755 --- a/aloha/aloha_lib.py +++ b/aloha/aloha_lib.py @@ -1061,11 +1061,16 @@ def __str__(self): return '%s' % self.name class FactoryLorentz(FactoryVar): - """ A symbolic Object for All Helas object. All Helas Object Should + """ A symbolic Object for All Helas object. All Helas Object Should derivated from this class""" - + mult_class = MultLorentz # The class for the multiplication object_class = LorentzObject # Define How to create the basic object. + # True when the arguments of the structure commute, so that writing them in + # any order denotes the same object (Metric(1,2) == Metric(2,1)). Consumers + # use it to tell a mere reordering apart from a real difference; see + # models.import_ufo.canonicalize_lorentz_structure. + is_symmetric = False def __new__(cls, *args): name = cls.get_unique_name(*args) diff --git a/aloha/aloha_object.py b/aloha/aloha_object.py index bed31d991d..dba10eedd5 100755 --- a/aloha/aloha_object.py +++ b/aloha/aloha_object.py @@ -1013,9 +1013,12 @@ def create_representation(self): self.lorentz_ind,self.spin_ind) class Metric(aloha_lib.FactoryLorentz): - + object_class = L_Metric - + # g^{mu nu} is symmetric by definition -- which is why get_unique_name + # below sorts its two indices. + is_symmetric = True + @classmethod def get_unique_name(cls,l1,l2): diff --git a/models/import_ufo.py b/models/import_ufo.py index ec6ab0b8b2..d1964136fc 100755 --- a/models/import_ufo.py +++ b/models/import_ufo.py @@ -59,28 +59,15 @@ # Suffixes to employ for the various poles of CTparameters pole_dict = {-2:'2EPS',-1:'1EPS',0:'FIN'} -# Cache for lorentz_symmetric_structures.txt (read at most once per session). -_symmetric_lorentz_structures = None +def is_symmetric_lorentz_structure(name): + """True when the lorentz structure *name* has commuting arguments. -def get_symmetric_lorentz_structures(): - """Return the lorentz structure functions whose arguments commute, as - listed in models/lorentz_symmetric_structures.txt.""" + The answer belongs to the structure itself: the aloha objects carry an + is_symmetric attribute, False on FactoryLorentz and set to True by the + structures whose arguments may be written in any order (Metric).""" - global _symmetric_lorentz_structures - if _symmetric_lorentz_structures is None: - names = [] - path = pjoin(root_path, 'lorentz_symmetric_structures.txt') - try: - with open(path) as fsock: - for line in fsock: - line = line.split('#', 1)[0].strip() - if line: - names.append(line) - except IOError: - logger.debug('%s not found: no lorentz structure is canonicalised', - path) - _symmetric_lorentz_structures = names - return _symmetric_lorentz_structures + structure = getattr(aloha_object, name, None) + return bool(getattr(structure, 'is_symmetric', False)) def canonicalize_lorentz_structure(structure): """Sort the arguments of the symmetric functions appearing in *structure*. @@ -96,14 +83,16 @@ def canonicalize_lorentz_structure(structure): if not isinstance(structure, str): return structure - for name in get_symmetric_lorentz_structures(): - pattern = re.compile(r'\b%s\(\s*(-?\d+(?:\s*,\s*-?\d+)*)\s*\)' - % re.escape(name)) - def sort_args(matchobj, name=name): - args = [a.strip() for a in matchobj.group(1).split(',')] - return '%s(%s)' % (name, ','.join(sorted(args, key=int))) - structure = pattern.sub(sort_args, structure) - return structure + + def sort_args(matchobj): + name = matchobj.group(1) + if not is_symmetric_lorentz_structure(name): + return matchobj.group(0) + args = [a.strip() for a in matchobj.group(2).split(',')] + return '%s(%s)' % (name, ','.join(sorted(args, key=int))) + + return re.sub(r'\b([A-Za-z_]\w*)\(\s*(-?\d+(?:\s*,\s*-?\d+)*)\s*\)', + sort_args, structure) class UFOImportError(MadGraph5Error): """ a error class for wrong import of UFO model""" diff --git a/models/lorentz_symmetric_structures.txt b/models/lorentz_symmetric_structures.txt deleted file mode 100644 index 355c9c3bbf..0000000000 --- a/models/lorentz_symmetric_structures.txt +++ /dev/null @@ -1,19 +0,0 @@ -# Lorentz structure functions whose arguments commute. -# -# When the same lorentz name is defined twice, import_ufo compares the two -# structure strings and warns if they differ. Some differences are not real -# redefinitions: renumbering the indices of a vertex can reorder the arguments -# of a symmetric function, so Metric(3,2) and Metric(2,3) come out as different -# strings while denoting the same object. -# -# Every function listed here has its arguments sorted before that comparison, -# so a difference in argument order alone is silent and only a genuine -# disagreement is reported. -# -# One function name per line; blank lines and #-comments are ignored. Only add -# a function whose arguments really do commute -- listing a non-symmetric one -# would silence a real redefinition. Sorting applies solely to calls whose -# arguments are all plain (possibly negative) integer indices. - -# g^{mu nu} is symmetric by definition. -Metric diff --git a/tests/unit_tests/various/test_import_ufo.py b/tests/unit_tests/various/test_import_ufo.py index 78eaceb6cc..33d7cf2abd 100755 --- a/tests/unit_tests/various/test_import_ufo.py +++ b/tests/unit_tests/various/test_import_ufo.py @@ -1681,9 +1681,17 @@ class TestLorentzStructureCanonicalisation(unittest.TestCase): renumbering and hides the real disagreements among the noise. """ - def test_symmetric_functions_are_loaded(self): - """Metric is declared symmetric in lorentz_symmetric_structures.txt.""" - self.assertIn('Metric', import_ufo.get_symmetric_lorentz_structures()) + def test_symmetry_is_carried_by_the_structure(self): + """is_symmetric lives on the aloha object, and defaults to False.""" + import aloha.aloha_object as aloha_object + import aloha.aloha_lib as aloha_lib + self.assertFalse(aloha_lib.FactoryLorentz.is_symmetric) + self.assertTrue(aloha_object.Metric.is_symmetric) + self.assertFalse(aloha_object.Gamma.is_symmetric) + self.assertTrue(import_ufo.is_symmetric_lorentz_structure('Metric')) + self.assertFalse(import_ufo.is_symmetric_lorentz_structure('Gamma')) + # an unknown name must not be taken for a symmetric structure + self.assertFalse(import_ufo.is_symmetric_lorentz_structure('NotAThing')) def test_argument_order_of_a_symmetric_function_is_ignored(self): """The two spellings of one Metric compare equal.""" From 7c6720664d5620a7fb4446b2a5ddc1829af87976 Mon Sep 17 00:00:00 2001 From: Olivier Mattelaer Date: Mon, 17 Aug 2026 10:39:20 +0200 Subject: [PATCH 25/26] require python 3.12 Raise the minimum interpreter from 3.7 to 3.12 in every gate that guards an entry point: bin/madgraph and bin/create_release.py, the MadSpin launcher, the LO and NLO Template launchers (madevent, generate_events, aMCatNLO, calculate_xsect, shower, the gridpack runner), and the two in-process checks in madevent_interface and amcatnlo_run_interface. INSTALL is updated to match. madmatrix carried its own, looser gate (minpython = (3,8), from the version that made {} preserve insertion order). It is raised to the same 3.12 so that the plugin can never be the reason a supported interpreter is refused, and its comment now points at bin/madgraph rather than restating a rule of its own. bin/madgraph also warned separately when version_info[1] < 10 that support for 3.9 and below had ended. That is unreachable once the hard gate is 3.12, and it was testing only the minor number, so it would have fired on a hypothetical 4.0. Removed. Left alone: madspace/pyproject.toml keeps requires-python = ">=3.11". It is a separately packaged library and the two constraints do not conflict, MadGraph simply being the stricter of the pair. The template launchers ship into generated process directories, so new outputs carry the new gate; directories generated earlier keep the one they were written with. Checked on a fresh output that bin/madevent has it. Co-Authored-By: Claude Opus 5 --- INSTALL | 2 +- MadSpin/madspin | 4 ++-- Template/LO/bin/generate_events | 4 ++-- Template/LO/bin/internal/Gridpack/gridrun | 4 ++-- Template/LO/bin/madevent | 4 ++-- Template/NLO/bin/aMCatNLO | 4 ++-- Template/NLO/bin/calculate_xsect | 4 ++-- Template/NLO/bin/generate_events | 4 ++-- Template/NLO/bin/shower | 4 ++-- bin/create_release.py | 4 ++-- bin/madgraph | 6 ++---- madgraph/interface/amcatnlo_run_interface.py | 4 ++-- madgraph/interface/madevent_interface.py | 4 ++-- madmatrix/__init__.py | 7 ++++--- 14 files changed, 29 insertions(+), 30 deletions(-) diff --git a/INSTALL b/INSTALL index aa76051e1f..d6192fe3fe 100644 --- a/INSTALL +++ b/INSTALL @@ -24,7 +24,7 @@ Dependencies : * MadGraph5_aMC@NLO * -- Python 3.7 (or higher) +- Python 3.12 (or higher) * MadEvent * Package for the LO cross-section computation and generation of events diff --git a/MadSpin/madspin b/MadSpin/madspin index 088fca86b0..0c04f25c9d 100755 --- a/MadSpin/madspin +++ b/MadSpin/madspin @@ -19,8 +19,8 @@ and call immediately the command line interface scripts""" from __future__ import absolute_import import sys -if sys.version_info < (3, 7): - sys.exit('MadSpin works only with python 3.7 (or later).\n\ +if sys.version_info < (3, 12): + sys.exit('MadSpin works only with python 3.12 (or later).\n\ Please upgrade your version of python.') import os diff --git a/Template/LO/bin/generate_events b/Template/LO/bin/generate_events index 5577cc66a0..93189287ed 100755 --- a/Template/LO/bin/generate_events +++ b/Template/LO/bin/generate_events @@ -27,8 +27,8 @@ import time root_path = os.path.split(os.path.dirname(os.path.realpath( __file__ )))[0] pjoin = os.path.join -if sys.version_info < (3, 7): - sys.exit('MadEvent works with python 3.7 or higher.\n\ +if sys.version_info < (3, 12): + sys.exit('MadEvent works with python 3.12 or higher.\n\ Please upgrade your version of python.') try: diff --git a/Template/LO/bin/internal/Gridpack/gridrun b/Template/LO/bin/internal/Gridpack/gridrun index 01d4ab53f5..dfff0766c4 100755 --- a/Template/LO/bin/internal/Gridpack/gridrun +++ b/Template/LO/bin/internal/Gridpack/gridrun @@ -20,8 +20,8 @@ and call immediately the command line interface scripts""" from __future__ import absolute_import import sys -if sys.version_info < (3, 7): - sys.exit('MadGraph/MadEvent 5 works only with python 3.7 or later.\n\ +if sys.version_info < (3, 12): + sys.exit('MadGraph/MadEvent 5 works only with python 3.12 or later.\n\ Please upgrate your version of python.') try: diff --git a/Template/LO/bin/madevent b/Template/LO/bin/madevent index 6d5d59c8c2..fd4ffd6a75 100755 --- a/Template/LO/bin/madevent +++ b/Template/LO/bin/madevent @@ -19,8 +19,8 @@ and call immediately the command line interface scripts""" import sys -if sys.version_info < (3, 7): - sys.exit('MadGraph/MadEvent 5 works only with python 3.7 or later.\n\ +if sys.version_info < (3, 12): + sys.exit('MadGraph/MadEvent 5 works only with python 3.12 or later.\n\ Please upgrate your version of python.') try: diff --git a/Template/NLO/bin/aMCatNLO b/Template/NLO/bin/aMCatNLO index 3ba4da5765..2b7cefaedd 100755 --- a/Template/NLO/bin/aMCatNLO +++ b/Template/NLO/bin/aMCatNLO @@ -19,8 +19,8 @@ and call immediately the command line interface scripts""" import sys -if sys.version_info < (3, 7): - sys.exit('MadGraph/aMCatNLO 5 works only with python 3.7 or later .\n\ +if sys.version_info < (3, 12): + sys.exit('MadGraph/aMCatNLO 5 works only with python 3.12 or later .\n\ Please upgrate your version of python.') try: diff --git a/Template/NLO/bin/calculate_xsect b/Template/NLO/bin/calculate_xsect index 04188b0110..cdf30c2294 100755 --- a/Template/NLO/bin/calculate_xsect +++ b/Template/NLO/bin/calculate_xsect @@ -28,8 +28,8 @@ pjoin = os.path.join sys.path.append(pjoin(root_path,'bin','internal')) import amcatnlo_run_interface as run -if sys.version_info < (3, 7): - sys.exit('MadEvent works with python 3.7 or higher.\n\ +if sys.version_info < (3, 12): + sys.exit('MadEvent works with python 3.12 or higher.\n\ Please upgrade your version of python.') try: diff --git a/Template/NLO/bin/generate_events b/Template/NLO/bin/generate_events index ecf13bc537..262938fd0a 100755 --- a/Template/NLO/bin/generate_events +++ b/Template/NLO/bin/generate_events @@ -28,8 +28,8 @@ pjoin = os.path.join sys.path.append(pjoin(root_path,'bin','internal')) import amcatnlo_run_interface as run -if sys.version_info < (3, 7): - sys.exit('MadEvent works with python 3.7 or higher.\n\ +if sys.version_info < (3, 12): + sys.exit('MadEvent works with python 3.12 or higher.\n\ Please upgrade your version of python.') try: diff --git a/Template/NLO/bin/shower b/Template/NLO/bin/shower index c77f56ac0c..8d2d564da7 100755 --- a/Template/NLO/bin/shower +++ b/Template/NLO/bin/shower @@ -28,8 +28,8 @@ pjoin = os.path.join sys.path.append(pjoin(root_path,'bin','internal')) import amcatnlo_run_interface as run -if sys.version_info < (3, 7): - sys.exit('MadEvent works with python 3.7 and higher.\n\ +if sys.version_info < (3, 12): + sys.exit('MadEvent works with python 3.12 and higher.\n\ Please upgrade your version of python.') try: diff --git a/bin/create_release.py b/bin/create_release.py index 401857a77c..88278f1b41 100755 --- a/bin/create_release.py +++ b/bin/create_release.py @@ -131,8 +131,8 @@ def get_first_contributions(repo_path): import sys -if sys.version_info < (3, 7): - sys.exit('MadGraph5_aMC@NLO works only with python 3.7 or later.\n\ +if sys.version_info < (3, 12): + sys.exit('MadGraph5_aMC@NLO works only with python 3.12 or later.\n\ Please upgrate your version of python.') import glob diff --git a/bin/madgraph b/bin/madgraph index 017be5079e..a9b3777b85 100755 --- a/bin/madgraph +++ b/bin/madgraph @@ -24,11 +24,9 @@ and call immediately the command line interface scripts""" import sys import logging import logging.config -if sys.version_info < (3, 7): - sys.exit('MadGraph5_aMC@NLO works with python 3.7 or higher.\n\ +if sys.version_info < (3, 12): + sys.exit('MadGraph5_aMC@NLO works with python 3.12 or higher.\n\ Please upgrade your version of python.') -if sys.version_info[1] < 10: - logging.warning("\033[91mSupport for Python3.9 (and below) has been dropped since end of 2025. Please consider update your version of Python. Continue at your own risk \033[0m") import os diff --git a/madgraph/interface/amcatnlo_run_interface.py b/madgraph/interface/amcatnlo_run_interface.py index ab2688abbd..cdea2099e9 100755 --- a/madgraph/interface/amcatnlo_run_interface.py +++ b/madgraph/interface/amcatnlo_run_interface.py @@ -6094,8 +6094,8 @@ class aMCatNLOCmdShell(aMCatNLOCmd, cmd.CmdShell): # This can ONLY run a single command !! import sys - if sys.version_info < (3, 7): - sys.exit('MadGraph5_aMc@NLO works only with python 3.7 and later.\n'+\ + if sys.version_info < (3, 12): + sys.exit('MadGraph5_aMc@NLO works only with python 3.12 and later.\n'+\ 'Please upgrade your version of python or specify a compatible version.') import os diff --git a/madgraph/interface/madevent_interface.py b/madgraph/interface/madevent_interface.py index b28e345532..a2c88efb22 100755 --- a/madgraph/interface/madevent_interface.py +++ b/madgraph/interface/madevent_interface.py @@ -7933,8 +7933,8 @@ def wait_monitoring(Idle, Running, Done): # Launch the interface without any check if one code is already running. # This can ONLY run a single command !! import sys - if sys.version_info < (3, 7): - sys.exit('MadGraph/MadEvent 5 works only with python 3.7 or later).\n'+\ + if sys.version_info < (3, 12): + sys.exit('MadGraph/MadEvent 5 works only with python 3.12 or later).\n'+\ 'Please upgrate your version of python.') import os diff --git a/madmatrix/__init__.py b/madmatrix/__init__.py index 425d69d10f..0eab42beec 100644 --- a/madmatrix/__init__.py +++ b/madmatrix/__init__.py @@ -6,10 +6,11 @@ PLUGIN_NAME = __name__ # PLUGIN_NAME can be one of PLUGIN/CUDACPP_OUTPUT or MG5aMC_PLUGIN/CUDACPP_OUTPUT -# AV - Require Python >= 3.8 to ensure that {} dictionaries preserve the order of item insertion -# (note: python3.7 would probably be enough but this plugin has only been tested using python3.8) +# Require the same Python as MadGraph itself (see bin/madgraph), so that the +# plugin never becomes the reason a supported interpreter is refused. It used +# to ask only for >= 3.8, the version that made {} preserve insertion order. import sys -minpython = (3,8) +minpython = (3,12) if sys.version_info < minpython : print('ERROR! Cannot load plugin %s: Python >= %s.%s is required' % (PLUGIN_NAME, minpython[0], minpython[1] )) From ea68c2956e4a6ee144c024dadfdf33debda22afe Mon Sep 17 00:00:00 2001 From: Olivier Mattelaer Date: Tue, 18 Aug 2026 09:17:39 +0200 Subject: [PATCH 26/26] default the mg7 devices to the auto-detected cpu backend The mg7 run_card shipped devices = ["cpu_scalar"], so a default run used the scalar code path and left every SIMD backend unused unless the user knew to ask for one. Default to "cpu" instead, which resolves to the widest vectorisation the host supports. This works because launch.py already resolves it: a devices entry becomes a library file name (lib/libmadmatrix__.so) and no ..._cpu.so is ever built, so the loader runs "make -n BACKEND=cpu detect-backend" and uses the resolved name for the path, while the compile itself is driven with BACKEND=cpu. Without that step defaulting to "cpu" would have looked right and then failed to find its library. The options comment now lists cpu first and says it auto-detects, in the param and in the run_card template: the name alone does not convey that it picks a SIMD width, and it is what a user gets without asking. Only this one place sets the default. The other cpu_scalar mentions are tests that pass an explicit backend, the check-language backend loop and make targets, none of which should follow it. Checked on a real run: the generated card carries devices = ["cpu"], the run resolves to cpu_128b on this host and builds libmadmatrix_P1_epem_mupmum_cpu_128b.so. Forcing cpu_scalar in the same directory gives 0.1042841 +- 0.0006047 against 0.1041746 +- 0.0006001 for the auto backend, a 0.13 sigma difference, with both libraries present so the switch is real. Co-Authored-By: Claude Opus 5 --- madgraph/iolibs/template_files/mg7/run_card.toml | 2 +- madgraph/various/banner.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/madgraph/iolibs/template_files/mg7/run_card.toml b/madgraph/iolibs/template_files/mg7/run_card.toml index 29bfed6a85..1d74c326aa 100644 --- a/madgraph/iolibs/template_files/mg7/run_card.toml +++ b/madgraph/iolibs/template_files/mg7/run_card.toml @@ -1,6 +1,6 @@ [run] run_name = %(run.run_name)s -# options: cuda, hip, cpu_scalar, cpu_128b, cpu_256b, cpu_512b_y, cpu_512b, cpu +# options: cpu (auto-detected SIMD width), cpu_scalar, cpu_128b, cpu_256b, cpu_512b_y, cpu_512b, cuda, hip devices = %(run.devices)s # options: # -1 to choose automatically diff --git a/madgraph/various/banner.py b/madgraph/various/banner.py index e9dc844643..78bd523480 100755 --- a/madgraph/various/banner.py +++ b/madgraph/various/banner.py @@ -6453,8 +6453,8 @@ def default_setup(self): # ----------------------------- [run] -------------------------- self.add_toml_param('run', 'run_name', "run", gridpack=True) - self.add_toml_param('run', 'devices', ["cpu_scalar"], typelist=str, gridpack=True, - comment="options: cuda, hip, cpu_scalar, cpu_128b, cpu_256b, cpu_512b_y, cpu_512b, cpu") + self.add_toml_param('run', 'devices', ["cpu"], typelist=str, gridpack=True, + comment="options: cpu (auto-detected SIMD width), cpu_scalar, cpu_128b, cpu_256b, cpu_512b_y, cpu_512b, cuda, hip") self.add_toml_param('run', 'simd_vector_size', -1, comment="-1 chooses automatically; on x86: 1, 4, 8; on Apple silicon: 1, 2") self.add_toml_param('run', 'cpu_thread_pool_size', -1, gridpack=True,