diff --git a/.gitignore b/.gitignore index c8a04fc..0c8f8e0 100644 --- a/.gitignore +++ b/.gitignore @@ -137,3 +137,10 @@ dmypy.json # MadGraph5 py.py drell-yan_output/ +MatrixElements/ + +# ROOT +*.root + +# Failure +core diff --git a/bluewaters/README.md b/bluewaters/README.md index 35110a9..6084b1e 100644 --- a/bluewaters/README.md +++ b/bluewaters/README.md @@ -36,6 +36,12 @@ bash run_delphes.sh drell-yan bash run_preprocessing.sh drell-yan ``` +* To then finally run MoMEMta for the hypothesis described with the [MoMEMta-MaGMEE](https://github.com/MoMEMta/MoMEMta-MaGMEE) MadGraph5 plugin run + +```console +bash run_momemta.sh drell-yan +``` + ## Interactive Session If you need to run an interactive session (which will be slower) you can first allocate resources on the `shifter` queue from Torque with `qsub` diff --git a/bluewaters/drell-yan/momemta.pbs b/bluewaters/drell-yan/momemta.pbs new file mode 100644 index 0000000..4f57263 --- /dev/null +++ b/bluewaters/drell-yan/momemta.pbs @@ -0,0 +1,56 @@ +#!/bin/bash + +# Set the number of processing elements (PEs) or cores +# Set the number of PEs per node +#PBS -l nodes=1:ppn=8:xk + +# Set the wallclock time +#PBS -l walltime=48:00:00 + +# Use shifter queue +#PBS -l gres=shifter + +# Set the PBS_JOBNAME +#PBS -N momemta + +# Set the job stdout and stderr +#PBS -e "${PBS_JOBNAME}.${PBS_JOBID}.err" +#PBS -o "${PBS_JOBNAME}.${PBS_JOBID}.out" + +# Set email notification on termination or abort +#PBS -m ea +#PBS -M matthew.feickert@cern.ch + +# Set allocation to charge +#PBS -A bbdz + +# Ensure shifter enabled +module load shifter + +PHYSICS_PROCESS="drell-yan" +OUTPUT_BASE_PATH="/mnt/c/scratch/sciteam/${USER}/${PHYSICS_PROCESS}/${PBS_JOBNAME}" +OUTPUT_PATH="${OUTPUT_BASE_PATH}/${PBS_JOBID}" +mkdir -p "${OUTPUT_PATH}" + +# $HOME is /u/sciteam/${USER} +SHIFTER_IMAGE="neubauergroup/bluewaters-momemta:1.0.1" +shifterimg pull "${SHIFTER_IMAGE}" + +INPUT_PATH="/mnt/c/scratch/sciteam/${USER}/${PHYSICS_PROCESS}/preprocessing/12498179.bw/preprocessing_output_10e4.root" +OUTPUT_FILE="${OUTPUT_PATH}/momemta_weights.root" + +aprun \ + --bypass-app-transfer \ + --pes-per-node 1 \ + --cpu-binding none \ + -- shifter \ + --clearenv \ + --image="${SHIFTER_IMAGE}" \ + --volume="${OUTPUT_BASE_PATH}":/root/data \ + --volume=/mnt/a/"${HOME}":/mnt/a/"${HOME}" \ + --workdir=/root/data \ + -- /bin/bash -c 'source scl_source enable devtoolset-8 && \ + export PATH="/usr/local/venv/bin:${PATH}" && \ + printf "\n# printenv:\n" && printenv && printf "\n\n" && \ + cd /mnt/a/'"${HOME}"'/MadGraph5-simulation-configs/momemta/'"${PHYSICS_PROCESS}"' && \ + bash run_momemta.sh '"${INPUT_PATH}"' '"${OUTPUT_FILE}" diff --git a/bluewaters/run_momemta.sh b/bluewaters/run_momemta.sh new file mode 100644 index 0000000..3428312 --- /dev/null +++ b/bluewaters/run_momemta.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +PROCESS_DIRECTORY="${1:-drell-yan}" +qsub "${PROCESS_DIRECTORY}/momemta.pbs" diff --git a/configs/momemta/drell-yan.mg5 b/configs/momemta/drell-yan.mg5 new file mode 100644 index 0000000..66dbde8 --- /dev/null +++ b/configs/momemta/drell-yan.mg5 @@ -0,0 +1,2 @@ +generate p p > l+ l- +output MoMEMta pp_drell_yan diff --git a/momemta/drell-yan/CMakeLists.txt b/momemta/drell-yan/CMakeLists.txt new file mode 100644 index 0000000..87eb665 --- /dev/null +++ b/momemta/drell-yan/CMakeLists.txt @@ -0,0 +1,39 @@ +cmake_minimum_required(VERSION 3.7...3.20) + +if(${CMAKE_VERSION} VERSION_LESS 3.12) + cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) +endif() + +project(Drell-Yan-Examples) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -pedantic -Wextra") + +# Require c++11 *at least*, use default compiler standard if possible +if (CMAKE_CXX_STANDARD_COMPUTED_DEFAULT STRLESS "11" OR + CMAKE_CXX_STANDARD_COMPUTED_DEFAULT STREQUAL "98") + set(CMAKE_CXX_STANDARD 11) + set(CMAKE_CXX_STANDARD_REQUIRED ON) +endif() + +# Stick to the standard +set(CMAKE_CXX_EXTENSIONS OFF) + +# Find dependencices + +# CMake will automagically also link to MoMEMta's dependencies, ie LHAPDF and ROOT +find_package(MoMEMta CONFIG REQUIRED) + +# But MoMEMta doesn't use TreePlayer: we have to add it ourselves +find_library(ROOT_TREEPLAYER_LIBRARY TreePlayer HINTS ${ROOT_LIBRARY_DIR} REQUIRED) + +# Figure out what do do here and how to simplify things. Above is bolierplate and below is the code + +add_executable(drell-yan_example "drell-yan_example.cxx") + +target_link_libraries(drell-yan_example momemta::momemta) +# FIXME: The TTbar example uses TreePlayer so copy this here FOR NOW +target_link_libraries(drell-yan_example ${ROOT_TREEPLAYER_LIBRARY}) + +set_target_properties(drell-yan_example + PROPERTIES + OUTPUT_NAME "drell-yan_example") diff --git a/momemta/drell-yan/drell-yan_example.cxx b/momemta/drell-yan/drell-yan_example.cxx new file mode 100644 index 0000000..658b7f2 --- /dev/null +++ b/momemta/drell-yan/drell-yan_example.cxx @@ -0,0 +1,172 @@ +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +using LorentzVectorM = ROOT::Math::LorentzVector>; + +/* + * Example executable file loading an input sample of events, + * computing weights using MoMEMta in the Drell-Yan hypothesis, + * and saving these weights along with a copy of the event content in an output file. + */ + +void normalizeInput(LorentzVector& p4) { + if (p4.M() > 0) + return; + + // Increase the energy until M is positive + p4.SetE(p4.P()); + while (p4.M2() < 0) { + double delta = p4.E() * 1e-5; + p4.SetE(p4.E() + delta); + }; +} + +int main(int argc, char** argv) { + + std::string inputPath; // required input + std::string outputPath; // required input + std::string configPath {"drell-yan_example.lua"}; // default value + std::string chainName {"event_selection/hftree"}; // default value + std::vector unusedCLIArguments; + + for (int idx = 1; idx < argc; ++idx) { + // --input + if (std::string(argv[idx]) == "--input") { + if (idx + 1 < argc) { // Make sure not at the end of argv + inputPath = argv[++idx]; // value is argv entry after flag + } else { + std::cerr << "--input option requires one argument." << std::endl; + return 1; + } + } + // --output + else if (std::string(argv[idx]) == "--output") { + if (idx + 1 < argc) { + outputPath = argv[++idx]; + } else { + std::cerr << "--output option requires one argument." << std::endl; + return 1; + } + } + // --chain + else if (std::string(argv[idx]) == "--chain") { + if (idx + 1 < argc) { + chainName = argv[++idx]; + } + } + // --luaconfig + else if (std::string(argv[idx]) == "--luaconfig") { + if (idx + 1 < argc) { + configPath = argv[++idx]; + } + } + else { + unusedCLIArguments.push_back(argv[idx]); + } + } + + using std::swap; + + // Load events from input file, retrieve reconstructed particles and MET + TChain chain(chainName.c_str()); + // Path needs to be findable inside of Docker container + chain.Add(inputPath.c_str()); + TTreeReader myReader(&chain); + + // TODO: serialize the 4-momentum into the TTree over just using branches + // TTreeReaderValue lep_plus_p4M(myReader, "lep1_p4"); + // TTreeReaderValue lep_minus_p4M(myReader, "lep2_p4"); + + TTreeReaderValue leading_lep_PID(myReader, "lep1_PID"); + + TTreeReaderValue lep_plus_Px(myReader, "lep1_Px"); + TTreeReaderValue lep_plus_Py(myReader, "lep1_Py"); + TTreeReaderValue lep_plus_Pz(myReader, "lep1_Pz"); + TTreeReaderValue lep_plus_E(myReader, "lep1_E"); + + TTreeReaderValue lep_minus_Px(myReader, "lep2_Px"); + TTreeReaderValue lep_minus_Py(myReader, "lep2_Py"); + TTreeReaderValue lep_minus_Pz(myReader, "lep2_Pz"); + TTreeReaderValue lep_minus_E(myReader, "lep2_E"); + + // Define output TTree, which will contain the weights we're computing (including uncertainty and computation time) + std::unique_ptr out_tree = std::make_unique("momemta", "momemta"); + double weight_DY, weight_DY_err, weight_DY_time; + out_tree->Branch("weight_DY", &weight_DY); + out_tree->Branch("weight_DY_err", &weight_DY_err); + out_tree->Branch("weight_DY_time", &weight_DY_time); + + // Prepare MoMEMta to compute the weights + + // logging::set_level(logging::level::debug); + logging::set_level(logging::level::error); + + // Construct the ConfigurationReader from the Lua file + ConfigurationReader configuration(configPath); + + // Instantiate MoMEMta using a **frozen** configuration + MoMEMta weight(configuration.freeze()); + + int counter = 0; + while (myReader.Next()) { + /* + * Prepare the LorentzVectors passed to MoMEMta: + * In the input file they are written in the PtEtaPhiM basis, + * while MoMEMta expects PxPyPzE, so we have to perform this change of basis: + * + * We define here Particles, allowing MoMEMta to correctly map the inputs to the configuration file. + * The string identifier used here must be the same as used to declare the inputs in the config file + */ + // momemta::Particle lep_plus("lepton1", LorentzVector { lep_plus_p4M->Px(), lep_plus_p4M->Py(), lep_plus_p4M->Pz(), lep_plus_p4M->E() }); + // momemta::Particle lep_minus("lepton2", LorentzVector { lep_minus_p4M->Px(), lep_minus_p4M->Py(), lep_minus_p4M->Pz(), lep_minus_p4M->E() }); + momemta::Particle lep_plus("lepton1", LorentzVector { *lep_plus_Px, *lep_plus_Py, *lep_plus_Pz, *lep_plus_E }); + momemta::Particle lep_minus("lepton2", LorentzVector { *lep_minus_Px, *lep_minus_Py, *lep_minus_Pz, *lep_minus_E }); + + // Due to numerical instability, the mass can sometimes be negative. If it's the case, change the energy in order to be mass-positive + normalizeInput(lep_plus.p4); + normalizeInput(lep_minus.p4); + + // Ensure the leptons are given in the correct order w.r.t their charge + if (*leading_lep_PID < 0) + swap(lep_plus, lep_minus); + + auto start_time = std::chrono::system_clock::now(); + // Compute the weights! + std::vector> weights = weight.computeWeights({lep_minus, lep_plus}); + auto end_time = std::chrono::system_clock::now(); + + // Retrieve the weight and uncertainty + weight_DY = weights.back().first; + weight_DY_err = weights.back().second; + weight_DY_time = std::chrono::duration_cast(end_time - start_time).count(); + + LOG(debug) << "Event " << myReader.GetCurrentEntry() << " result: " << weight_DY << " +- " << weight_DY_err; + LOG(info) << "Weight computed in " << weight_DY_time << "ms"; + + out_tree->Fill(); + + ++counter; + if (counter % 1000 == 0) + std::cout << "calculated weights for " << counter << " events\n"; + + } + std::cout << "calculated weights for " << counter << " events\n"; + + // Save output to TTree + out_tree->SaveAs(outputPath.c_str()); + + return 0; +} diff --git a/momemta/drell-yan/drell-yan_example.lua b/momemta/drell-yan/drell-yan_example.lua new file mode 100644 index 0000000..773151a --- /dev/null +++ b/momemta/drell-yan/drell-yan_example.lua @@ -0,0 +1,108 @@ +-- c.f. https://github.com/MoMEMta/Tutorials/blob/master/Paper_configs/MELA_ZZ_bkg.lua for example copied form from + +-- Load the library containing the matrix element +-- FIXME: Requires manual configuration +load_modules('MatrixElements/pp_drell_yan/build/libme_pp_drell_yan.so') + +-- Declare inputs required by this configuration file. Since it's Drell-Yan, we expect 2 inputs (output paticles) +-- the two leptons +-- P4 for each particle are passed when calling the C++ `computeWeights` function +local lepton1 = declare_input("lepton1") +local lepton2 = declare_input("lepton2") + +-- Global parameters used by several modules +-- Changing these has NO impact on the value of the parameters used by the matrix element! +parameters = { + energy = 13000., + Z_mass = 91.1876, + Z_width = 2.49, + + -- You can export a graphviz representation of the computation graph using the + -- `export_graph_as` parameter + -- Use the `dot` command to convert the graph into a PDF + -- dot -Tpdf drell-yan_computing_graph.dot -o drell-yan_computing_graph.pdf + export_graph_as = "drell-yan_computing_graph.dot" +} + +-- Configuration of Cuba +cuba = { + relative_accuracy = 0.05, + verbosity = 3 +} + +-- The transfer functions take as input the particles passed in the computeWeights() function, +-- and each add a dimension of integration +GaussianTransferFunctionOnEnergy.tf_p1 = { + -- add_dimension() generates an input tag allowing the retrieve a new phase-space point component, + -- and it notifies MoMEMta that a new integration dimension is requested + ps_point = add_dimension(), + -- We use the directly the inputs declared above. The `reco_p4` attribute returns the correct input tag + reco_particle = lepton1.reco_p4, + sigma = 0.10, + sigma_range = 3., +} + +-- We can assign to each input a `gen` p4. By default, the gen p4 is the same as the reco one, which is useful when +-- no transfer function is applied. Here however, we applied a transfer function to the `lepton1` input, meaning that the +-- output of the `tf_p1` module correspond now to the `gen` p4 of `lepton1`. To reflect that, we explicitly set the gen p4 +-- to be the output of the `tf_p1` module +lepton1.set_gen_p4("tf_p1::output"); + +GaussianTransferFunctionOnEnergy.tf_p2 = { + ps_point = add_dimension(), + reco_particle = lepton2.reco_p4, + sigma = 0.10, + sigma_range = 3., +} +lepton2.set_gen_p4("tf_p2::output") + +-- Compute the phase space density for observed particles not concerned by the change of variable : here lepton on which we evaluate the TF +-- The TF jacobian just account for dp/dE to go from |p| to E, not the phase space volume, the other blocks give the whole product of jacobian and phase space volume to go from one param to another +StandardPhaseSpace.phaseSpaceOut = { + particles = {'tf_p1::output', 'tf_p2::output'} +} + +BuildInitialState.boost = { + do_transverse_boost = true, + particles = {lepton1.reco_p4, lepton2.reco_p4} +} + +jacobians = {'phaseSpaceOut::phase_space'} + +genParticles = { + lepton1.reco_p4, + lepton2.reco_p4, +} + +MatrixElement.drellyan = { + pdf = 'CT10nlo', + pdf_scale = parameter('Z_mass'), + + matrix_element = 'pp_drell_yan_sm_P1_Sigma_sm_uux_epem', + matrix_element_parameters = { + card = 'MatrixElements/pp_drell_yan/Cards/param_card.dat' + }, + + initialState = 'boost::partons', + + particles = { + inputs = genParticles, + ids = { + { + pdg_id = -11, + me_index = 1, + }, + + { + pdg_id = 11, + me_index = 2, + } + } + }, + + jacobians = jacobians +} + + +-- Define quantity to be returned to MoMEMta +integrand("drellyan::output") diff --git a/momemta/drell-yan/run_momemta.sh b/momemta/drell-yan/run_momemta.sh new file mode 100644 index 0000000..8a0c31b --- /dev/null +++ b/momemta/drell-yan/run_momemta.sh @@ -0,0 +1,53 @@ +#!/bin/bash + +# Run this inside of the directory for the hypothesis + +# Ensure lhadpdf set exists +lhapdf get CT10nlo + +if [[ -d MatrixElements ]];then + rm -rf MatrixElements +fi +mkdir MatrixElements +pushd MatrixElements +# Generate the matrix Element with MadGraph5 +mg5_aMC ../../../configs/momemta/drell-yan.mg5 +rm py.py +popd + +# Build Matrix Element +# c.f. https://github.com/MoMEMta/MoMEMta-MaGMEE#usage +# Matrix Element namespace name defined in ../../../configs/momemta/drell-yan.mg5 +cmake \ + -DCMAKE_INSTALL_PREFIX=/usr/local/venv \ + -S MatrixElements/pp_drell_yan \ + -B MatrixElements/pp_drell_yan/build +cmake MatrixElements/pp_drell_yan/build -L +cmake --build MatrixElements/pp_drell_yan/build \ + --clean-first \ + --parallel $(($(nproc) - 1)) + +# Example level build +if [ -d build ];then + rm -rf build +fi +# Cleanup if any failed runs +if [ -f core ]; then + rm core +fi +cmake \ + -DCMAKE_INSTALL_PREFIX=/usr/local/venv \ + -S . \ + -B build +cmake build -L +cmake --build build \ + --clean-first \ + --parallel $(($(nproc) - 1)) + +INPUT_PATH="${1:-/home/feickert/workarea/MadGraph5-simulation-configs/preprocessing/preprocessing_output.root}" +OUTPUT_PATH="${2:-momemta_weights.root}" + +# Current configuration in drell_yan.cxx requires running from top level of example dir +time ./build/drell-yan_example \ + --input "${INPUT_PATH}" \ + --output "${OUTPUT_PATH}" diff --git a/preprocessing/scripts/HistCollections.py b/preprocessing/scripts/HistCollections.py index 0912884..d521098 100644 --- a/preprocessing/scripts/HistCollections.py +++ b/preprocessing/scripts/HistCollections.py @@ -272,6 +272,7 @@ def __init__(self, tag, topdir, detaillevel=99): self.add_branch("nLep", "i") # self.add_branch("lep1_p4", "TLorentzVector") + self.add_branch("lep1_PID", "i") self.add_branch("lep1_Pt", "f") self.add_branch("lep1_Eta", "f") self.add_branch("lep1_Phi", "f") @@ -284,6 +285,7 @@ def __init__(self, tag, topdir, detaillevel=99): self.add_branch("lep1_E", "f") # self.add_branch("lep2_p4", "TLorentzVector") + self.add_branch("lep2_PID", "i") self.add_branch("lep2_Pt", "f") self.add_branch("lep2_Eta", "f") self.add_branch("lep2_Phi", "f") @@ -345,10 +347,10 @@ def fill(self, event, weight=0): # Do some characterizations # event.sorted_leptons are pT sorted in descending order leading_lepton = ( - event.sorted_leptons[0].P4() if len(event.sorted_leptons) > 0 else None + event.sorted_leptons[0] if len(event.sorted_leptons) > 0 else None ) subleading_lepton = ( - event.sorted_leptons[1].P4() if len(event.sorted_leptons) > 1 else None + event.sorted_leptons[1] if len(event.sorted_leptons) > 1 else None ) muons_momentum = ROOT.TLorentzVector() @@ -467,47 +469,57 @@ def fill(self, event, weight=0): # Leptons if leading_lepton: + leading_lepton_p4 = leading_lepton.P4() # self.branches["lep1_p4"][0] = leading_lepton - self.branches["lep1_Pt"][0] = leading_lepton.Pt() - self.branches["lep1_Eta"][0] = leading_lepton.Eta() - self.branches["lep1_Phi"][0] = leading_lepton.Phi() - self.branches["lep1_M"][0] = leading_lepton.M() + self.branches["lep1_PID"][0] = leading_lepton.Particle.GetObject().PID + self.branches["lep1_Pt"][0] = leading_lepton_p4.Pt() + self.branches["lep1_Eta"][0] = leading_lepton_p4.Eta() + self.branches["lep1_Phi"][0] = leading_lepton_p4.Phi() + self.branches["lep1_M"][0] = leading_lepton_p4.M() # Temporary hack to get 4-momentum components out - self.branches["lep1_Px"][0] = leading_lepton.Px() - self.branches["lep1_Py"][0] = leading_lepton.Py() - self.branches["lep1_Pz"][0] = leading_lepton.Pz() - self.branches["lep1_E"][0] = leading_lepton.E() + self.branches["lep1_Px"][0] = leading_lepton_p4.Px() + self.branches["lep1_Py"][0] = leading_lepton_p4.Py() + self.branches["lep1_Pz"][0] = leading_lepton_p4.Pz() + self.branches["lep1_E"][0] = leading_lepton_p4.E() else: - self.branches["lep1_Pt"][0] = default_fill - self.branches["lep1_Eta"][0] = default_fill - self.branches["lep1_Phi"][0] = default_fill - self.branches["lep1_M"][0] = default_fill - # Temporary hack to get 4-momentum components out - self.branches["lep1_Px"][0] = default_fill - self.branches["lep1_Py"][0] = default_fill - self.branches["lep1_Pz"][0] = default_fill - self.branches["lep1_E"][0] = default_fill + for branch_name in [ + "lep1_PID", + "lep1_Pt", + "lep1_Eta", + "lep1_Phi", + "lep1_M", + "lep1_Px", + "lep1_Py", + "lep1_Pz", + "lep1_E", + ]: + self.branches[branch_name][0] = default_fill if subleading_lepton: + subleading_lepton_p4 = subleading_lepton.P4() # self.branches["lep1_p4"][0] = subleading_lepton - self.branches["lep2_Pt"][0] = subleading_lepton.Pt() - self.branches["lep2_Eta"][0] = subleading_lepton.Eta() - self.branches["lep2_Phi"][0] = subleading_lepton.Phi() - self.branches["lep2_M"][0] = subleading_lepton.M() + self.branches["lep2_PID"][0] = subleading_lepton.Particle.GetObject().PID + self.branches["lep2_Pt"][0] = subleading_lepton_p4.Pt() + self.branches["lep2_Eta"][0] = subleading_lepton_p4.Eta() + self.branches["lep2_Phi"][0] = subleading_lepton_p4.Phi() + self.branches["lep2_M"][0] = subleading_lepton_p4.M() # Temporary hack to get 4-momentum components out - self.branches["lep2_Px"][0] = subleading_lepton.Px() - self.branches["lep2_Py"][0] = subleading_lepton.Py() - self.branches["lep2_Pz"][0] = subleading_lepton.Pz() - self.branches["lep2_E"][0] = subleading_lepton.E() + self.branches["lep2_Px"][0] = subleading_lepton_p4.Px() + self.branches["lep2_Py"][0] = subleading_lepton_p4.Py() + self.branches["lep2_Pz"][0] = subleading_lepton_p4.Pz() + self.branches["lep2_E"][0] = subleading_lepton_p4.E() else: - self.branches["lep2_Pt"][0] = default_fill - self.branches["lep2_Eta"][0] = default_fill - self.branches["lep2_Phi"][0] = default_fill - self.branches["lep2_M"][0] = default_fill - # Temporary hack to get 4-momentum components out - self.branches["lep2_Px"][0] = default_fill - self.branches["lep2_Py"][0] = default_fill - self.branches["lep2_Pz"][0] = default_fill - self.branches["lep2_E"][0] = default_fill + for branch_name in [ + "lep2_PID", + "lep2_Pt", + "lep2_Eta", + "lep2_Phi", + "lep2_M", + "lep2_Px", + "lep2_Py", + "lep2_Pz", + "lep2_E", + ]: + self.branches[branch_name][0] = default_fill self.tree.Fill() diff --git a/run_docker.sh b/run_docker.sh index 1d08bb4..9303ef5 100644 --- a/run_docker.sh +++ b/run_docker.sh @@ -1,9 +1,9 @@ #!/bin/bash -# image_name="neubauergroup/momemta-python-centos" -# image_tag="latest" -image_name="scailfin/delphes-python-centos" -image_tag="3.5.0" +image_name="neubauergroup/momemta-python-centos" +image_tag="latest" +# image_name="scailfin/delphes-python-centos" +# image_tag="3.5.0" image="${image_name}:${image_tag}" docker pull "${image}"