#!/usr/bin/env bash set -euo pipefail ROOT_DIR="$PWD" if [ -d "$ROOT_DIR/MG5_aMC_v2_9_27" ]; then echo "[skip] MG5_aMC_v2_9_27 already exists -> skipping download" else wget https://launchpad.net/mg5amcnlo/lts/2.9.x/+download/MG5_aMC_v2.9.27.tar.gz tar -axvf MG5_aMC_v2.9.27.tar.gz rm MG5_aMC_v2.9.27.tar.gz fi cd "$ROOT_DIR/MG5_aMC_v2_9_27/models" if [ -d "SMEFTsim_topU3l_MwScheme_UFO" ]; then echo "[skip] SMEFTsim_topU3l_MwScheme_UFO already exists -> skipping clone" else rm -rf SMEFTsim git clone git@github.com:SMEFTsim/SMEFTsim.git mv SMEFTsim/UFO_models/SMEFTsim_topU3l_MwScheme_UFO . rm -rf SMEFTsim fi cd SMEFTsim_topU3l_MwScheme_UFO/ BASE="restrict_SMlimit_massless.dat" COEFF="cHWB" # The trick: this must be a generic, non-special value -- NOT exactly 0 or 1. # MG5 hardcodes/prunes a parameter restricted to 0 or 1 (dropping its Block # SMEFT entry from param_card.dat), but leaves any other value -- like this # one -- external/settable. create.sh depends on that: it reuses this same # restriction card for PROC_cHWB_NP1, whose reweight_card.dat needs cHWB to # still be a settable parameter. VALUE="9.999999e-01" OUT="restrict_${COEFF}_massless.dat" if [ -f "$OUT" ]; then echo "[skip] $OUT already exists" else sed -E "s/^(.*)0\.000000e\+00( # ${COEFF}[[:space:]]*)\$/\1${VALUE}\2/" \ "$BASE" > "$OUT" fi # --------------------------------------------------------------------------- # Portability fix: MG5's f2py toolchain. Local only -- no downloads, no # execution of remote code. Safe to rerun. # # MG5's "f2py_compiler_py3" config option can only ever hold a single # executable token: do_set() in madgraph/interface/common_run_interface.py # tokenizes the config line on whitespace and keeps only args[1], and # misc.which() can't resolve a value containing spaces either way -- so a # multi-word command like "python3 -m numpy.f2py" can never be set directly. # On macOS specifically, Homebrew's f2py/f2py3/f2py3.10/f2py3.11 binaries are # commonly stale symlinks into an old numpy Cellar path whose shebang points # at a python3.x Homebrew has since removed -> compile fails with "f2py: No # such file or directory". # # Fix: generate a single-token wrapper script around a python3 this script # has actually verified has a working numpy.f2py, and point MG5's config at # that wrapper instead of the real (multi-word) command. MG5_DIR="$ROOT_DIR/MG5_aMC_v2_9_27" echo "==> Looking for a working python3 + numpy.f2py ..." PYTHON3="" for CAND in python3 /usr/bin/python3 /opt/homebrew/bin/python3 /usr/local/bin/python3 \ python3.13 python3.12 python3.11 python3.10 python3.9; do command -v "$CAND" >/dev/null 2>&1 || continue RESOLVED="$(command -v "$CAND")" if "$RESOLVED" -c "import numpy.f2py" >/dev/null 2>&1; then PYTHON3="$RESOLVED" break fi done if [ -z "$PYTHON3" ]; then echo "ERROR: no python3 on PATH has a working numpy.f2py module." >&2 echo " fix with, e.g.: python3 -m pip install --user numpy" >&2 exit 1 fi NUMPY_VER="$("$PYTHON3" -c 'import numpy; print(numpy.__version__)')" echo " using $PYTHON3 (numpy $NUMPY_VER)" WRAPPER="$MG5_DIR/f2py_wrapper.sh" cat > "$WRAPPER" < Wrote $WRAPPER" echo "==> Checking compilers ..." HAVE_FORTRAN=1 check_tool() { local tool="$1" hint="$2" if command -v "$tool" >/dev/null 2>&1; then echo " OK $tool -> $(command -v "$tool")" else echo " MISSING $tool -- install with: $hint" if [ "$tool" = "gfortran" ]; then HAVE_FORTRAN=0; fi fi } case "$(uname -s)" in Darwin) check_tool gfortran "brew install gcc"; check_tool clang "xcode-select --install" ;; Linux) check_tool gfortran "sudo apt-get install gfortran"; check_tool g++ "sudo apt-get install g++" ;; *) check_tool gfortran "install a Fortran compiler"; check_tool g++ "install a C++ compiler" ;; esac if [ "$HAVE_FORTRAN" = "1" ]; then echo "==> Smoke-testing f2py wrapper ..." TMPD="$(mktemp -d)" trap 'rm -rf "$TMPD"' EXIT cat > "$TMPD/hello.f" <<'FORTRAN_EOF' subroutine hello(x, y) real*8 x, y Cf2py intent(in) x Cf2py intent(out) y y = x * 2.0d0 end FORTRAN_EOF if ( cd "$TMPD" && "$WRAPPER" -c -m hello hello.f ) >"$TMPD/f2py.log" 2>&1; then echo " OK: wrapper compiles Fortran extensions." else echo "ERROR: f2py smoke test failed:" >&2 cat "$TMPD/f2py.log" >&2 exit 1 fi else echo "==> Skipping f2py smoke test (no gfortran) -- install it, then rerun setup.sh." fi # Generic "set key = value in this mg5_configuration.txt" helper -- strips # any existing (commented-out or live) line for that key and appends the new # value, so reruns are idempotent no matter what state the file was in. # Shared below by both the f2py_compiler_py3 patch and the lhapdf patch. patch_config() { local cfg="$1" key="$2" value="$3" [ -f "$cfg" ] || return 0 if grep -qF "$key = $value" "$cfg" 2>/dev/null; then echo "[skip] $cfg already points $key at $value"; return 0 fi local tmp="$cfg.tmp.$$" grep -v -E "^#?[[:space:]]*${key}[[:space:]]*=" "$cfg" > "$tmp" printf '%s = %s\n' "$key" "$value" >> "$tmp" mv "$tmp" "$cfg" echo "==> Patched $cfg ($key)" } # Only the master template + user-level override -- deliberately NOT touching # any already-generated PROC_*/ output, since those may belong to a run of # create.sh that's still in flight. patch_config "$MG5_DIR/input/mg5_configuration.txt" "f2py_compiler_py3" "$WRAPPER" patch_config "$HOME/.mg5/mg5_configuration.txt" "f2py_compiler_py3" "$WRAPPER" echo "==> f2py toolchain fix complete." # --------------------------------------------------------------------------- # LHAPDF6 install, via MG5's own "install lhapdf6" command. Local compile, # one network fetch of source -- reproducible, idempotent, safe to rerun. # # Why via MG5's installer and not e.g. Homebrew: the systematics module # (QCD scale + PDF uncertainty reweighting, madgraph/various/systematics.py) # does `import lhapdf` *inside whatever python3 process is already running # MG5* -- madgraph/various/misc.py's import_python_lhapdf() does the import # in-process, not via a subprocess (verified by reading it). A compiled # lhapdf python extension is ABI-locked to the exact python3 minor version # it was built against. Installing LHAPDF6 via Homebrew would build it # against Homebrew's own python (currently python@3.10 here) while MG5 runs # under whatever `env python3` resolves to (the system python3 on this # machine) -- a cross-interpreter mismatch that would leave `import lhapdf` # failing even with a perfectly good install. MG5's own "install lhapdf6" # instead builds LHAPDF6's python bindings against the exact python3 # already running MG5 at install time, sidestepping the ABI problem # entirely -- and it's the officially-supported path (madgraph_interface.py # even offers it interactively when e.g. installing pythia8's lhapdf # dependency). Since create.sh invokes MG5's bin/* scripts the same way # (`env python3`), the interpreter at install time and at run time matches. # # This does need network access once, to fetch LHAPDF6's source and MG5's # HEPToolsInstaller manifest from MG5's own package servers, then compiles # LHAPDF6 from source (C++, a few minutes) -- there's no vendored/offline # tarball for it under MG5_aMC_v2_9_27/vendor/ (unlike CutTools/StdHEP/etc, # which do ship offline copies). LHAPDF_CONFIG="$MG5_DIR/HEPTools/lhapdf6_py3/bin/lhapdf-config" if [ -x "$LHAPDF_CONFIG" ]; then echo "[skip] $LHAPDF_CONFIG already exists -> skipping LHAPDF6 install" else echo "==> Installing LHAPDF6 via MG5's own installer (compiles from source, can take several minutes) ..." INSTALL_CMD="$(mktemp "${TMPDIR:-/tmp}/install_lhapdf6.XXXXXX")" printf 'install lhapdf6\n' > "$INSTALL_CMD" # LHAPDF's own ./configure auto-detects where to install its python # bindings by asking the running python3's sysconfig for its "platlib" # scheme dir. On this machine python3 resolves to Xcode's bundled # framework python (sys.executable lands under # /Applications/Xcode.app/.../Python3.framework/...), whose sysconfig # reports the *system*, root-owned /Library/Python/3.X/site-packages -- # so an unprivileged `make install` fails there with "Permission # denied" partway through (the C++ library/lhapdf-config/PDF-set # machinery all install fine; only this last step needs root, verified # by hitting exactly this failure on a first attempt here). # LHAPDF's configure.ac explicitly supports overriding this via the # LHAPDF_PYTHONPATH env variable (its own notice says so: "override # with LHAPDF_PYTHONPATH env variable") -- point it at a directory # *inside* our own install prefix instead. This isn't just a # workaround: it's exactly the layout MG5's own madgraph/various/ # misc.py import_python_lhapdf() already knows how to find on its own # (it scans /*/site-packages), so no further # wiring is needed for MG5 to pick it up at runtime. Scoped to a # subshell so it doesn't leak into the rest of this script. PYVER="$(python3 -c 'import sys; print("%d.%d" % sys.version_info[:2])')" LHAPDF_INSTALL_DIR="$MG5_DIR/HEPTools/lhapdf6_py3" ( export LHAPDF_PYTHONPATH="$LHAPDF_INSTALL_DIR/lib/python$PYVER/site-packages" "$MG5_DIR/bin/mg5_aMC" "$INSTALL_CMD" ) rm -f "$INSTALL_CMD" if [ ! -x "$LHAPDF_CONFIG" ]; then echo "ERROR: LHAPDF6 install finished but $LHAPDF_CONFIG is missing." >&2 echo " Check the mg5_aMC output above for the actual failure." >&2 exit 1 fi fi echo " using $("$LHAPDF_CONFIG" --version 2>&1) at $LHAPDF_CONFIG" # Belt-and-braces: MG5's own "install lhapdf6" already persists this path # under the *lhapdf_py3* key (via its internal `save options ... lhapdf_py3` # call), but madevent's `systematics` command (do_systematics(), in # madgraph/interface/common_run_interface.py) reads the *lhapdf* key -- no # _py3 suffix -- which is only ever set in-memory during the install session # itself and never written back to mg5_configuration.txt. Patch it in # ourselves, same mechanism as f2py_compiler_py3 above, so a *fresh* MG5 # process (like the ones create.sh launches later for event generation and # systematics) resolves it too, not just the one running this install. patch_config "$MG5_DIR/input/mg5_configuration.txt" "lhapdf" "$LHAPDF_CONFIG" patch_config "$HOME/.mg5/mg5_configuration.txt" "lhapdf" "$LHAPDF_CONFIG" echo "==> LHAPDF6 install complete."