diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 87b7185..0a65191 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,296 +1,296 @@ # --------------------------------- # - General Setup - # --------------------------------- stages: - build - test - FOG:build - FOG:test - clean_code - publish variables: # build directories HEJ_BUILD_DIR: tmp_HEJ/HEJ_build HEJ_INSTALL_DIR: tmp_HEJ/HEJ_installed FOG_BUILD_DIR: tmp_HEJ/FOG_build FOG_INSTALL_DIR: ${HEJ_INSTALL_DIR} # docker images DOCKER_BASIC: hejdock/hepenv DOCKER_HEPMC3: hejdock/hepmc3env DOCKER_QCDLOOP: hejdock/qcdloopenv DOCKER_RIVET: hejdock/rivetenv # ----------- Macros ----------- after_script: - date .tags_template: tags: &tags_def - docker # save complete history of failed tests .save_failure: artifacts: &artifacts_failed when: on_failure untracked: true expire_in: 3d # --------------------------------- # - Script Templates - # --------------------------------- # ----------- Build ----------- .HEJ_build: tags: *tags_def stage: build before_script: - date - source /cvmfs/pheno.egi.eu/HEJ/HEJ_env.sh || exit 1 # prepare build - t_HEJ_DIR=${PWD} - t_HEJ_INSTALL_DIR=${PWD}/${HEJ_INSTALL_DIR} - t_HEJ_BUILD_DIR=${PWD}/${HEJ_BUILD_DIR} - mkdir -p ${t_HEJ_BUILD_DIR} - cd ${t_HEJ_BUILD_DIR} - cmake ${t_HEJ_DIR} -DCMAKE_BUILD_TYPE=DEBUG -DCMAKE_INSTALL_PREFIX=${t_HEJ_INSTALL_DIR} script: - make -j $(nproc --ignore=1) - make install artifacts: # save build and installed folder name: build expire_in: 1d paths: - ${HEJ_INSTALL_DIR} - ${HEJ_BUILD_DIR} # ----------- Test ----------- .HEJ_test: tags: *tags_def stage: test before_script: - date - source /cvmfs/pheno.egi.eu/HEJ/HEJ_env.sh || exit 1 # load HEJ - t_HEJ_DIR=${PWD} - t_HEJ_INSTALL_DIR=${PWD}/${HEJ_INSTALL_DIR} - export LD_LIBRARY_PATH=${t_HEJ_INSTALL_DIR}/lib:${LD_LIBRARY_PATH} - export PATH=${t_HEJ_INSTALL_DIR}/bin:${PATH} - cd ${HEJ_BUILD_DIR} - cmake ${t_HEJ_DIR} # rerun cmake to create all test configure files script: - ctest --output-on-failure artifacts: *artifacts_failed ## ----------- FOG build ----------- .FOG_build: tags: *tags_def stage: FOG:build before_script: - date - source /cvmfs/pheno.egi.eu/HEJ/HEJ_env.sh || exit 1 # load HEJ - t_HEJ_INSTALL_DIR=${PWD}/${HEJ_INSTALL_DIR} - export LD_LIBRARY_PATH=${t_HEJ_INSTALL_DIR}/lib:${LD_LIBRARY_PATH} - export PATH=${t_HEJ_INSTALL_DIR}/bin:${PATH} # prepare build - t_FOG_DIR=${PWD}/FixedOrderGen - t_FOG_INSTALL_DIR=${PWD}/${FOG_INSTALL_DIR} - t_FOG_BUILD_DIR=${PWD}/${FOG_BUILD_DIR} - mkdir -p ${t_FOG_BUILD_DIR} - cd ${t_FOG_BUILD_DIR} - cmake ${t_FOG_DIR} -DCMAKE_BUILD_TYPE=DEBUG -DCMAKE_INSTALL_PREFIX=${t_FOG_INSTALL_DIR} script: - make -j $(nproc --ignore=1) - make install artifacts: # save build and installed folder name: FOG_build expire_in: 1d paths: - ${HEJ_INSTALL_DIR} - ${FOG_INSTALL_DIR} - ${FOG_BUILD_DIR} ## ----------- FOG test ----------- .FOG_test: tags: *tags_def stage: FOG:test before_script: - date - source /cvmfs/pheno.egi.eu/HEJ/HEJ_env.sh || exit 1 # load HEJ - t_FOG_DIR=${PWD}/FixedOrderGen - t_HEJ_INSTALL_DIR=${PWD}/${HEJ_INSTALL_DIR} - t_FOG_INSTALL_DIR=${PWD}/${FOG_INSTALL_DIR} - export LD_LIBRARY_PATH=${t_HEJ_INSTALL_DIR}/lib:${LD_LIBRARY_PATH} - export PATH=${t_HEJ_INSTALL_DIR}/bin:${t_FOG_INSTALL_DIR}/bin:${PATH} - t_FOG_BUILD_DIR=${PWD}/${FOG_BUILD_DIR} - cd ${t_FOG_BUILD_DIR} - cmake ${t_FOG_DIR} # rerun cmake to create all test configure files script: - - make test + - ctest --output-on-failure artifacts: *artifacts_failed # --------------------------------- # - Build & Test - # --------------------------------- # ----------- basic ----------- build:basic: image: ${DOCKER_BASIC} extends: .HEJ_build test:basic: image: ${DOCKER_BASIC} extends: .HEJ_test dependencies: - build:basic FOG:build:basic: image: ${DOCKER_BASIC} extends: .FOG_build dependencies: - build:basic FOG:test:basic: image: ${DOCKER_BASIC} extends: .FOG_test dependencies: - FOG:build:basic # ----------- HepMC 3 ----------- build:hepmc3: image: ${DOCKER_HEPMC3} extends: .HEJ_build test:hepmc3: image: ${DOCKER_HEPMC3} extends: .HEJ_test dependencies: - build:hepmc3 # ----------- QCDloop ----------- build:qcdloop: image: ${DOCKER_QCDLOOP} extends: .HEJ_build test:qcdloop: image: ${DOCKER_QCDLOOP} extends: .HEJ_test dependencies: - build:qcdloop # ----------- rivet ----------- build:rivet: image: ${DOCKER_RIVET} extends: .HEJ_build test:rivet: image: ${DOCKER_RIVET} extends: .HEJ_test dependencies: - build:rivet script: - ctest --output-on-failure - bash -c '[ -f tst.yoda ]' && echo "found rivet output" - rivet-cmphistos tst.yoda - bash -c '[ -f MC_XS_XS.dat ]' && echo "yoda not empty" # --------------------------------- # - Clean Code - # --------------------------------- No_tabs: stage: clean_code tags: *tags_def image: hejdock/git dependencies: [] script: - date - check_tabs # ----------- No gcc warnings ----------- .Warning_build: extends: .HEJ_build stage: clean_code dependencies: [] script: - cd ${t_HEJ_DIR} # suppress warnings from side packages - sed -i 's/include_directories(${LHAPDF/include_directories(SYSTEM ${LHAPDF/g' CMakeLists.txt - sed -i 's/include_directories(${fastjet/include_directories(SYSTEM ${fastjet/g' CMakeLists.txt - sed -i 's/include_directories(${Boost/include_directories(SYSTEM ${Boost/g' CMakeLists.txt - cd ${t_HEJ_BUILD_DIR} - cmake ${t_HEJ_DIR} -DCMAKE_CXX_FLAGS="-Werror" - make -j $(nproc --ignore=1) artifacts: # don't save anything .Warning_FOG: extends: .FOG_build stage: clean_code script: - cd ${t_FOG_DIR} # suppress warnings from side packages - sed -i 's/include_directories(${LHAPDF/include_directories(SYSTEM ${LHAPDF/g' CMakeLists.txt - sed -i 's/include_directories(${fastjet/include_directories(SYSTEM ${fastjet/g' CMakeLists.txt - sed -i 's/include_directories(${Boost/include_directories(SYSTEM ${Boost/g' CMakeLists.txt - cd ${t_FOG_BUILD_DIR} - cmake ${t_FOG_DIR} -DCMAKE_CXX_FLAGS="-Werror" - make -j $(nproc --ignore=1) artifacts: # don't save anything No_Warning:basic: image: ${DOCKER_BASIC} extends: .Warning_build No_Warning:basic:FOG: image: ${DOCKER_BASIC} extends: .Warning_FOG dependencies: - build:basic # --------------------------------- # - Publish - # --------------------------------- Publish_version: stage: publish tags: *tags_def image: hejdock/git dependencies: [] only: - /^v\d+\.\d+$/ except: - tags when: on_success before_script: - mkdir -p .ssh/ && echo "${SSH_KEY}" > .ssh/id_rsa && chmod 0600 .ssh/id_rsa - rm -rf ~/.ssh/id_rsa; mkdir -p ~/.ssh/ - ln -s $PWD/.ssh/id_rsa ~/.ssh/id_rsa && chmod 0600 ~/.ssh/id_rsa - ssh -T ${PUBLIC_GIT_PREFIX} -o "StrictHostKeyChecking no" || echo "added ssh" script: - git remote add public ${PUBLIC_GIT_PREFIX}${PUBLIC_GIT_POSTFIX} - git checkout $CI_COMMIT_REF_NAME - git branch - git pull - git push public - git push public --tags after_script: - rm -f /root/.ssh/id_rsa && rm -fr .ssh - git remote rm public diff --git a/CMakeLists.txt b/CMakeLists.txt index c48e69f..903f689 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,280 +1,292 @@ cmake_minimum_required(VERSION 3.1 FATAL_ERROR) set(CMAKE_LEGACY_CYGWIN_WIN32 0) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) -project("HEJ" VERSION 2.0.4 LANGUAGES C CXX) +project("HEJ" VERSION 2.0.5 LANGUAGES C CXX) # Set a default build type if none was specified set(default_build_type "RelWithDebInfo") if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) message(STATUS "Setting build type to '${default_build_type}' as none was specified.") set(CMAKE_BUILD_TYPE "${default_build_type}" CACHE STRING "Choose the type of build." FORCE) # Set the possible values of build type for cmake-gui set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "MinSizeRel" "RelWithDebInfo") endif() ## Flags for the compiler. No warning allowed. if (CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra") elseif (MSVC) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4 /WX /EHsc") endif() set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD 14) ## Create Version set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/Modules/") # Get the latest abbreviated commit hash of the working branch execute_process( COMMAND git rev-parse HEAD WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} OUTPUT_VARIABLE PROJECT_GIT_REVISION OUTPUT_STRIP_TRAILING_WHITESPACE ) # Get the current working branch execute_process( COMMAND git rev-parse --abbrev-ref HEAD WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} OUTPUT_VARIABLE PROJECT_GIT_BRANCH OUTPUT_STRIP_TRAILING_WHITESPACE ) ## target directories for install set(INSTALL_INCLUDE_DIR_BASE include) set(INSTALL_INCLUDE_DIR ${INSTALL_INCLUDE_DIR_BASE}/HEJ) set(INSTALL_BIN_DIR bin) set(INSTALL_LIB_DIR lib) set(INSTALL_CONFIG_DIR lib/cmake/HEJ) ## Template files configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/cmake/Templates/Version.hh.in ${PROJECT_BINARY_DIR}/include/HEJ/Version.hh @ONLY ) configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/cmake/Templates/HEJ-config.cc.in ${PROJECT_BINARY_DIR}/src/bin/HEJ-config.cc @ONLY ) # Generate CMake config file include(CMakePackageConfigHelpers) configure_package_config_file( cmake/Templates/hej-config.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/${INSTALL_CONFIG_DIR}/hej-config.cmake INSTALL_DESTINATION ${INSTALL_CONFIG_DIR} PATH_VARS INSTALL_INCLUDE_DIR_BASE INSTALL_LIB_DIR ) write_basic_package_version_file( ${CMAKE_CURRENT_BINARY_DIR}/${INSTALL_CONFIG_DIR}/hej-config-version.cmake COMPATIBILITY SameMajorVersion ) install( FILES ${CMAKE_CURRENT_BINARY_DIR}/${INSTALL_CONFIG_DIR}/hej-config.cmake ${CMAKE_CURRENT_BINARY_DIR}/${INSTALL_CONFIG_DIR}/hej-config-version.cmake DESTINATION ${INSTALL_CONFIG_DIR}) ## Add directories and find dependences include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include ${PROJECT_BINARY_DIR}/include) find_package(fastjet REQUIRED) include_directories(${fastjet_INCLUDE_DIRS}) find_package(CLHEP 2.3 REQUIRED) include_directories(${CLHEP_INCLUDE_DIRS}) find_package(LHAPDF REQUIRED) include_directories(${LHAPDF_INCLUDE_DIRS}) ## Amend unintuitive behaviour of FindBoost.cmake ## Priority of BOOST_ROOT over BOOSTROOT matches FindBoost.cmake ## at least for cmake 3.12 if(DEFINED BOOST_ROOT) message("BOOST_ROOT set - only looking for Boost in ${BOOST_ROOT}") set(Boost_NO_BOOST_CMAKE ON) elseif(DEFINED BOOSTROOT) message("BOOSTROOT set - only looking for Boost in ${BOOSTROOT}") set(Boost_NO_BOOST_CMAKE ON) endif() find_package(Boost REQUIRED COMPONENTS iostreams) include_directories(${Boost_INCLUDE_DIRS}) find_package(yaml-cpp) # requiring yaml does not work with fedora include_directories(${YAML_CPP_INCLUDE_DIR}) if(${EXCLUDE_HepMC}) message(STATUS "Skipping HepMC") # avoid "unused variable" warning if EXCLUDE_rivet is set by user set(EXCLUDE_rivet TRUE) else() find_package(HepMC 2) endif() if(${HepMC_FOUND}) set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DHEJ_BUILD_WITH_HepMC_VERSION=${HepMC_VERSION_MAJOR}" ) include_directories(${HepMC_INCLUDE_DIRS}) if(${EXCLUDE_rivet}) message(STATUS "Skipping rivet") else() find_package(rivet) endif() if(${rivet_FOUND}) include_directories(${rivet_INCLUDE_DIRS}) set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DHEJ_BUILD_WITH_RIVET" ) endif() endif() if(${EXCLUDE_QCDloop}) message(STATUS "Skipping QCDloop") else() find_package(QCDloop 2) endif() if(${QCDloop_FOUND}) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DHEJ_BUILD_WITH_QCDLOOP") include_directories(SYSTEM ${QCDloop_INCLUDE_DIRS}) endif() add_subdirectory(src) ## define executable add_executable(HEJ src/bin/HEJ.cc) ## link libraries target_link_libraries(HEJ hejlib) add_executable(HEJ-config src/bin/HEJ-config.cc) file(GLOB hej_headers ${CMAKE_CURRENT_SOURCE_DIR}/include/HEJ/*.hh ${PROJECT_BINARY_DIR}/include/HEJ/*.hh) file(GLOB lhef_headers ${CMAKE_CURRENT_SOURCE_DIR}/include/LHEF/*.h) install(FILES ${hej_headers} DESTINATION ${INSTALL_INCLUDE_DIR}) install(FILES ${lhef_headers} DESTINATION include/LHEF/) install(TARGETS HEJ HEJ-config DESTINATION ${INSTALL_BIN_DIR}) ## tests enable_testing() set(tst_dir "${CMAKE_CURRENT_SOURCE_DIR}/t") add_executable(test_classify ${tst_dir}/test_classify.cc) add_executable(test_psp ${tst_dir}/test_psp.cc) add_executable(test_ME_generic ${tst_dir}/test_ME_generic.cc) add_executable(check_res ${tst_dir}/check_res.cc) add_executable(check_lhe ${tst_dir}/check_lhe.cc) add_library(scales SHARED ${tst_dir}/scales.cc) add_executable(test_scale_import ${tst_dir}/test_scale_import) add_executable(test_descriptions ${tst_dir}/test_descriptions) add_executable(test_scale_arithmetics ${tst_dir}/test_scale_arithmetics) +add_executable(test_parameters ${tst_dir}/test_parameters) +add_executable(test_colours ${tst_dir}/test_colours) target_link_libraries(test_classify hejlib) target_link_libraries(test_psp hejlib) target_link_libraries(test_ME_generic hejlib) target_link_libraries(check_res hejlib) target_link_libraries(check_lhe hejlib) target_link_libraries(test_scale_import hejlib) target_link_libraries(test_descriptions hejlib) target_link_libraries(test_scale_arithmetics hejlib) +target_link_libraries(test_parameters hejlib) +target_link_libraries(test_colours hejlib) ## add tests add_test( NAME t_classify COMMAND test_classify ${tst_dir}/classify.lhe.gz ) add_test( NAME t_psp COMMAND test_psp ${tst_dir}/psp_gen.lhe.gz ) set(tst_ME_data_dir "${tst_dir}/ME_data") add_test( NAME t_ME_j COMMAND test_ME_generic ${tst_ME_data_dir}/config_mtinf.yml ${tst_ME_data_dir}/ME_jets_tree.dat ${tst_ME_data_dir}/PSP_jets.lhe.gz ) add_test( NAME t_ME_h COMMAND test_ME_generic ${tst_ME_data_dir}/config_mtinf.yml ${tst_ME_data_dir}/ME_h_mtinf_tree.dat ${tst_ME_data_dir}/PSP_h.lhe.gz ) # add_test( # NAME t_ME_w # COMMAND test_ME_generic ${tst_ME_data_dir}/config_w_ME.yml ${tst_ME_data_dir}/ME_w_tree.dat ${tst_ME_data_dir}/PSP_w.lhe.gz # ) add_test( NAME t_ME_w_FKL COMMAND test_ME_generic ${tst_ME_data_dir}/config_w_ME.yml ${tst_ME_data_dir}/ME_w_FKL_tree.dat ${tst_ME_data_dir}/PSP_w_FKL.lhe.gz ) add_test( NAME t_ME_w_FKL_virt COMMAND test_ME_generic ${tst_ME_data_dir}/config_w_ME.yml ${tst_ME_data_dir}/ME_w_FKL_virt.dat ${tst_ME_data_dir}/PSP_w_FKL.lhe.gz ) if(${QCDloop_FOUND}) add_test( NAME t_ME_h_mt COMMAND test_ME_generic ${tst_ME_data_dir}/config_mt.yml ${tst_ME_data_dir}/ME_h_mt_tree.dat ${tst_ME_data_dir}/PSP_h.lhe.gz ) add_test( NAME t_ME_h_mtmb COMMAND test_ME_generic ${tst_ME_data_dir}/config_mtmb.yml ${tst_ME_data_dir}/ME_h_mtmb_tree.dat ${tst_ME_data_dir}/PSP_h.lhe.gz ) endif() add_test( NAME t_2j COMMAND check_res ${tst_dir}/2j.lhe.gz 3.49391e+07 419684 ) add_test( NAME t_3j COMMAND check_res ${tst_dir}/3j.lhe.gz 2.37902e+06 25746.6 ) add_test( NAME t_4j COMMAND check_res ${tst_dir}/4j.lhe.gz 603713 72822.6 ) add_test( NAME t_h_3j COMMAND check_res ${tst_dir}/h_3j.lhe.gz 0.821622 0.0220334 ) add_test( NAME t_h_3j_uno COMMAND check_res ${tst_dir}/h_3j_uno.lhe.gz 0.0261968 0.000341549 uno ) if(${HepMC_FOUND}) file(READ "${tst_dir}/jet_config.yml" config) file(WRITE "${tst_dir}/jet_config_withHepMC.yml" "${config} - tst.hepmc") if(${rivet_FOUND}) file(READ "${tst_dir}/jet_config_withHepMC.yml" config) file(WRITE "${tst_dir}/jet_config_withRivet.yml" "${config}\n\nanalysis:\n rivet: MC_XS\n output: tst") add_test( NAME t_main COMMAND HEJ ${tst_dir}/jet_config_withRivet.yml ${tst_dir}/2j.lhe.gz ) else() add_test( NAME t_main COMMAND HEJ ${tst_dir}/jet_config_withHepMC.yml ${tst_dir}/2j.lhe.gz ) endif() if(${HepMC_VERSION_MAJOR} GREATER 2) add_executable(check_hepmc ${tst_dir}/check_hepmc.cc) target_link_libraries(check_hepmc hejlib) add_test( NAME t_hepmc COMMAND check_hepmc tst.hepmc ) endif() else() add_test( NAME t_main COMMAND HEJ ${tst_dir}/jet_config.yml ${tst_dir}/2j.lhe.gz ) endif() add_test( NAME t_lhe COMMAND check_lhe tst.lhe ) add_test( NAME t_scale_import COMMAND test_scale_import ${tst_dir}/jet_config_with_import.yml ) add_test( NAME t_descriptions COMMAND test_descriptions ) add_test( NAME t_scale_arithmetics COMMAND test_scale_arithmetics ${tst_dir}/jet_config.yml ${tst_dir}/2j.lhe.gz ) +add_test( + NAME test_parameters + COMMAND test_parameters + ) +add_test( + NAME t_colour_flow + COMMAND test_colours + ) diff --git a/Changes-API.md b/Changes-API.md new file mode 100644 index 0000000..8df426b --- /dev/null +++ b/Changes-API.md @@ -0,0 +1,56 @@ +# Changelog for HEJ API + +This log lists only changes on the HEJ API. These are primarily code changes +relevant for calling HEJ as an API. This file should only be read as an addition +to `Changes.md`, where the main features are documented. + +## Version 2.X + +### 2.X.0 + +* Made `MatrixElement.tree_kin(...)` and `MatrixElement.tree_param(...)` public +* New class `CrossSectionAccumulator` to keep track of Cross Section of the + different subproccess +* New template struct `Parameters` similar to old `Weights` + - `Weights` are now an alias for `Parameters<double>`. Calling `Weights` did + not change + - `Weights.hh` was replaced by `Parameters.hh`. The old `Weights.hh` header + will be removed in HEJ Version 2.3.0 +* Function to multiplication and division of `EventParameters.weight` by double + - This can be combined with `Parameters`, e.g. + `Parameters<EventParameters>*Weights`, see also `Events.parameters()` + - Moved `EventParameters` to `Parameters.hh` header +* Restructured `Event` class + - `Event` can now only be build from a (new) `Event::EventData` class + - Removed default constructor for `Event` + - `Event::EventData` replaces the old `UnclusteredEvent` struct. + - `UnclusteredEvent` is now deprecated, and will be removed in HEJ Version + 2.3.0 + - Removed `Event.unclustered()` function + - Added new member function `Events.parameters()`, to directly access + (underlying) `Parameters<EventParameters>` +* Added optional Colour charges to particles (`Particle.colour`) + - Colour connection in the HEJ limit can be generated via + `Event.generate_colours` (automatically done in the resummation) + +## Version 2.0 + +### 2.0.5 + +* no further changes to API + +### 2.0.4 + +* Fixed wrong path of `HEJ_INCLUDE_DIR` in `hej-config.cmake` + +### 2.0.3 + +* no further changes to API + +### 2.0.2 + +* no further changes to API + +### 2.0.1 + +* no further changes to API diff --git a/Changes.md b/Changes.md index 7f93376..f1d36fb 100644 --- a/Changes.md +++ b/Changes.md @@ -1,35 +1,43 @@ -# Version 2.0 +# Changelog -## 2.X.0 +This is the log for changes to the HEJ program. Further changes to the HEJ API +are documented in `Changes-API.md`. If you are using HEJ as a library, please +also read the changes there. + +## Version 2.X + +### 2.X.0 -* Made `MatrixElement::tree_kin` and `MatrixElement::tree_param` public * Allow multiplication and division of multiple scale functions e.g. `H_T/2*m_j1j2` +* Print cross sections at end of run * Follow HepMC convention for particle Status codes: incoming = 11, decaying = 2, outgoing = 1 (unchanged) -* New class `CrossSectionAccumulator` to keep track of Cross Section of the - different subproccess +* Partons now have a Colour charge + - Colours are read from and written to LHE files + - For reweighted events the colours are created according to leading colour in + the FKL limit * Allow changing the regulator lambda in input (`regulator parameter`, only for advanced users) -## 2.0.4 +## 2.0.5 + +* Fixed event classification for input not ordered in rapidity + +### 2.0.4 * Fixed wrong path of `HEJ_INCLUDE_DIR` in `hej-config.cmake` -* Correctly include rivet headers -* New `EXCLUDE_package` variable in `cmake` to not interface to specific - packages -* Consistent search and include for side packages in `cmake` -## 2.0.3 +### 2.0.3 * Fixed parsing of (numerical factor) * (base scale) in configuration * Don't change scale names, but sanitise Rivet output file names instead -## 2.0.2 +### 2.0.2 * Changed scale names to `"_over_"` and `"_times_"` for proper file names (was `"/"` and `"*"` before) -## 2.0.1 +### 2.0.1 * Fixed name of fixed-order generator in error message. diff --git a/FixedOrderGen/CMakeLists.txt b/FixedOrderGen/CMakeLists.txt index bc4e530..ef16662 100644 --- a/FixedOrderGen/CMakeLists.txt +++ b/FixedOrderGen/CMakeLists.txt @@ -1,113 +1,113 @@ cmake_minimum_required(VERSION 3.1 FATAL_ERROR) set(CMAKE_LEGACY_CYGWIN_WIN32 0) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) -project("HEJ Fixed Order Generation" VERSION 2.0.4 LANGUAGES C CXX) +project("HEJ Fixed Order Generation" VERSION 2.0.5 LANGUAGES C CXX) # Set a default build type if none was specified set(default_build_type "RelWithDebInfo") if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) message(STATUS "Setting build type to '${default_build_type}' as none was specified.") set(CMAKE_BUILD_TYPE "${default_build_type}" CACHE STRING "Choose the type of build." FORCE) # Set the possible values of build type for cmake-gui set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "MinSizeRel" "RelWithDebInfo") endif() ## Flags for the compiler. No warning allowed. if (CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra") elseif (MSVC) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4 /WX /EHsc") endif() set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD 14) ## Create Version set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/Modules/") # Get the latest abbreviated commit hash of the working branch execute_process( COMMAND git rev-parse HEAD WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} OUTPUT_VARIABLE PROJECT_GIT_REVISION OUTPUT_STRIP_TRAILING_WHITESPACE ) # Get the current working branch execute_process( COMMAND git rev-parse --abbrev-ref HEAD WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} OUTPUT_VARIABLE PROJECT_GIT_BRANCH OUTPUT_STRIP_TRAILING_WHITESPACE ) CONFIGURE_FILE( ${CMAKE_CURRENT_SOURCE_DIR}/cmake/Templates/Version.hh.in ${PROJECT_BINARY_DIR}/include/Version.hh @ONLY ) ## Add directories and find dependences include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include ${PROJECT_BINARY_DIR}/include) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/../cmake/Modules/") find_package(HEJ 2 REQUIRED) include_directories(${HEJ_INCLUDE_DIR}) find_package(fastjet REQUIRED) include_directories(${fastjet_INCLUDE_DIRS}) find_package(CLHEP 2.3 REQUIRED) include_directories(${CLHEP_INCLUDE_DIRS}) find_package(LHAPDF REQUIRED) include_directories(${LHAPDF_INCLUDE_DIRS}) ## Amend unintuitive behaviour of FindBoost.cmake ## Priority of BOOST_ROOT over BOOSTROOT matches FindBoost.cmake ## at least for cmake 3.12 if(DEFINED BOOST_ROOT) message("BOOST_ROOT set - only looking for Boost in ${BOOST_ROOT}") set(Boost_NO_BOOST_CMAKE ON) elseif(DEFINED BOOSTROOT) message("BOOSTROOT set - only looking for Boost in ${BOOSTROOT}") set(Boost_NO_BOOST_CMAKE ON) endif() find_package(Boost REQUIRED COMPONENTS iostreams) include_directories(${Boost_INCLUDE_DIRS}) find_package(yaml-cpp) # requiring yaml does not work with fedora include_directories(${YAML_CPP_INCLUDE_DIR}) ## define executable file(GLOB HEJFOG_source ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cc) list(REMOVE_ITEM HEJFOG_source ${CMAKE_CURRENT_SOURCE_DIR}/src/main.cc) add_library(hejfog STATIC ${HEJFOG_source}) add_executable(HEJFOG ${CMAKE_CURRENT_SOURCE_DIR}/src/main.cc) ## link libraries set(libraries ${CMAKE_DL_LIBS} ${LHAPDF_LIBRARIES} ${CLHEP_LIBRARIES} ${fastjet_LIBRARIES} ${Boost_LIBRARIES} ${YAML_CPP_LIBRARIES} ${HEJ_LIBRARIES}) target_link_libraries(hejfog ${libraries}) target_link_libraries(HEJFOG hejfog) install(TARGETS HEJFOG DESTINATION bin) ## tests enable_testing() set(tst_dir "${CMAKE_CURRENT_SOURCE_DIR}/t") foreach(tst h_2j h_3j h_5j h_3j_uno1 h_3j_uno2 h_2j_decay 2j 4j W_2j_classify W_nj_classify) add_executable(test_${tst} ${tst_dir}/${tst}.cc) target_link_libraries(test_${tst} hejfog) add_test(NAME t_${tst} COMMAND test_${tst} WORKING_DIRECTORY ${tst_dir}) endforeach() add_test( NAME t_main_2j COMMAND HEJFOG ${tst_dir}/config_2j.yml ) add_test( NAME t_main_h2j COMMAND HEJFOG ${tst_dir}/config_h_2j.yml ) add_test( NAME t_main_h2j_decay COMMAND HEJFOG ${tst_dir}/config_h_2j_decay.yml ) diff --git a/FixedOrderGen/configFO.yml b/FixedOrderGen/configFO.yml index 926b1c8..3cce48a 100644 --- a/FixedOrderGen/configFO.yml +++ b/FixedOrderGen/configFO.yml @@ -1,76 +1,79 @@ -# number of generated events -events: 200000 +## number of generated events +events: 200 jets: min pt: 20 peak pt: 30 algorithm: antikt R: 0.4 max rapidity: 5 beam: energy: 6500 particles: [p, p] -pdf: 13100 +pdf: 230000 -process: p p => W+ 2j -# fraction of events with two extremal emissions in one direction -# that contain an subleading emission e.g. unordered emission +process: p p => h 4j + +## fraction of events with two extremal emissions in one direction +## that contain an subleading emission e.g. unordered emission subleading fraction: 0.00 -# Allow different subleading configurations -# By default all implemented processes are allowed + +## Allow different subleading configurations +## By default all implemented processes are allowed. # -#subleading channels: [unordered, qqx] -#subleading channels: [qqx] -#subleading channels: [unordered] +# subleading channels: +# - unordered +# - qqx -scales: 91.188 +scales: max jet pperp event output: - - HEJFO.hepmc + - HEJFO.lhe +# - HEJFO_events.hepmc particle properties: Higgs: mass: 125 width: 0.004165 decays: {into: [photon, photon], branching ratio: 0.0023568762400521404} Wp: mass: 80.385 width: 2.085 decays: {into: [e+, nu_e], branching ratio: 1} Wm: mass: 80.385 width: 2.085 decays: {into: [e, nu_e_bar], branching ratio: 1} random generator: name: mixmax - seed: 90 +# seed: 1 -# unweighting parameters -# remove to obtain weighted events +## unweighting parameters +## remove to obtain weighted events # unweight: -# sample size: 0 # should be similar to "events:", but not more than ~10000 +# sample size: 200 # should be similar to "events:", but not more than ~10000 # max deviation: 0 -# to use a rivet analysis +## to use a rivet analysis # # analysis: # rivet: MC_XS # rivet analysis name # output: HEJ # name of the yoda files, ".yoda" and scale suffix will be added # -# to use a custom analysis +## to use a custom analysis # # analysis: # plugin: /path/to/libmyanalysis.so # my analysis parameter: some value -# parameters for Higgs-gluon couplings -# this requires compilation with qcdloop +## parameters for Higgs-gluon couplings +## this requires compilation with qcdloop # # Higgs coupling: # use impact factors: false # mt: 174 # include bottom: true # mb: 4.7 diff --git a/FixedOrderGen/include/EventGenerator.hh b/FixedOrderGen/include/EventGenerator.hh index a039144..e2064c7 100644 --- a/FixedOrderGen/include/EventGenerator.hh +++ b/FixedOrderGen/include/EventGenerator.hh @@ -1,56 +1,57 @@ #pragma once -#include "HEJ/PDF.hh" #include "HEJ/MatrixElement.hh" +#include "HEJ/optional.hh" +#include "HEJ/PDF.hh" #include "HEJ/RNG.hh" +#include "Beam.hh" #include "JetParameters.hh" +#include "ParticleProperties.hh" #include "Process.hh" -#include "Beam.hh" #include "Status.hh" -#include "ParticleProperties.hh" namespace HEJ{ class Event; class HiggsCouplingSettings; class ScaleGenerator; } //! Namespace for HEJ Fixed Order Generator namespace HEJFOG{ class EventGenerator{ public: EventGenerator( Process process, Beam beam, HEJ::ScaleGenerator scale_gen, JetParameters jets, int pdf_id, double subl_change, unsigned int subl_channels, ParticlesPropMap particles_properties, HEJ::HiggsCouplingSettings Higgs_coupling, HEJ::RNG & ran ); - HEJ::Event gen_event(); + HEJ::optional<HEJ::Event> gen_event(); Status status() const { return status_; } private: HEJ::PDF pdf_; HEJ::MatrixElement ME_; HEJ::ScaleGenerator scale_gen_; Process process_; JetParameters jets_; Beam beam_; Status status_; double subl_change_; unsigned int subl_channels_; ParticlesPropMap particles_properties_; std::reference_wrapper<HEJ::RNG> ran_; }; } diff --git a/FixedOrderGen/include/PhaseSpacePoint.hh b/FixedOrderGen/include/PhaseSpacePoint.hh index 164c7ae..d2234b4 100644 --- a/FixedOrderGen/include/PhaseSpacePoint.hh +++ b/FixedOrderGen/include/PhaseSpacePoint.hh @@ -1,216 +1,216 @@ /** \file PhaseSpacePoint.hh * \brief Contains the PhaseSpacePoint Class */ #pragma once #include <bitset> #include <vector> #include "HEJ/Event.hh" #include "HEJ/Particle.hh" #include "HEJ/PDF.hh" #include "HEJ/PDG_codes.hh" #include "HEJ/RNG.hh" #include "JetParameters.hh" #include "ParticleProperties.hh" #include "Status.hh" namespace HEJFOG{ class Process; using HEJ::Particle; //! A point in resummation phase space class PhaseSpacePoint{ public: //! Default PhaseSpacePoint Constructor PhaseSpacePoint() = default; //! PhaseSpacePoint Constructor /** * @param proc The process to generate * @param jet_properties Jet defintion & cuts * @param pdf The pdf set (used for sampling) * @param E_beam Energie of the beam * @param subl_chance Chance to turn a potentially unordered * emission into an actual one * @param subl_channels Possible subleading channels. * see HEJFOG::Subleading * @param particle_properties Properties of producted boson * * Initially, only FKL phase space points are generated. subl_chance gives * the change of turning one emissions into a subleading configuration, * i.e. either unordered or central quark/anti-quark pair. Unordered * emissions require that the most extremal emission in any direction is * a quark or anti-quark and the next emission is a gluon. Quark/anti-quark * pairs are only generated for W processes. At most one subleading * emission will be generated in this way. */ PhaseSpacePoint( Process const & proc, JetParameters const & jet_properties, HEJ::PDF & pdf, double E_beam, double subl_chance, unsigned int subl_channels, ParticlesPropMap const & particles_properties, HEJ::RNG & ran ); //! Get Weight Function /** * @returns Weight of Event */ double weight() const{ return weight_; } Status status() const{ return status_; } //! Get Incoming Function /** * @returns Incoming Particles */ std::array<Particle, 2> const & incoming() const{ return incoming_; } //! Get Outgoing Function /** * @returns Outgoing Particles */ std::vector<Particle> const & outgoing() const{ return outgoing_; } std::unordered_map<size_t, std::vector<Particle>> const & decays() const{ return decays_; } private: /** * @internal * @brief Generate LO parton momentum * * @param count Number of partons to generate * @param is_pure_jets If true ensures momentum conservation in x and y * @param jet_param Jet properties to fulfil * @param max_pt max allowed pt for a parton (typically E_CMS) * @param ran Random Number Generator * * @returns Momentum of partons * * Ensures that each parton is in its own jet. * Generation is independent of parton flavour. Output is sorted in rapidity. */ std::vector<fastjet::PseudoJet> gen_LO_partons( int count, bool is_pure_jets, JetParameters const & jet_param, double max_pt, HEJ::RNG & ran ); Particle gen_boson( HEJ::ParticleID bosonid, double mass, double width, HEJ::RNG & ran ); template<class ParticleMomenta> fastjet::PseudoJet gen_last_momentum( ParticleMomenta const & other_momenta, double mass_square, double y ) const; bool jets_ok( std::vector<fastjet::PseudoJet> const & Born_jets, std::vector<fastjet::PseudoJet> const & partons ) const; /** * @internal * @brief Generate incoming partons according to the PDF * * @param uf Scale used in the PDF */ void reconstruct_incoming( Process const & proc, unsigned int subl_channels, HEJ::PDF & pdf, double E_beam, double uf, HEJ::RNG & ran ); /** * @internal * @brief Returns list of all allowed initial states partons */ std::array<std::bitset<11>,2> filter_partons( Process const & proc, unsigned int const subl_channels, HEJ::RNG & ran ); HEJ::ParticleID generate_incoming_id( size_t beam_idx, double x, double uf, HEJ::PDF & pdf, std::bitset<11> allowed_partons, HEJ::RNG & ran ); bool momentum_conserved(double ep) const; HEJ::Particle const & most_backward_FKL( std::vector<HEJ::Particle> const & partons ) const; HEJ::Particle const & most_forward_FKL( std::vector<HEJ::Particle> const & partons ) const; HEJ::Particle & most_backward_FKL(std::vector<HEJ::Particle> & partons) const; HEJ::Particle & most_forward_FKL(std::vector<HEJ::Particle> & partons) const; bool extremal_FKL_ok( std::vector<fastjet::PseudoJet> const & partons ) const; double random_normal(double stddev, HEJ::RNG & ran); /** * @internal * @brief Turns a FKL configuration into a subleading one * * @param chance Change to switch to subleading configuration * @param channels Allowed channels for subleading process * @param proc Process to decide which subleading * configurations are allowed * * With a chance of "chance" the FKL configuration is either turned into * a unordered configuration or, for A/W/Z bosons, a configuration with * a central quark/anti-quark pair. */ void maybe_turn_to_subl(double chance, unsigned int channels, Process const & proc, HEJ::RNG & ran); void turn_to_uno(bool can_be_uno_backward, bool can_be_uno_forward, HEJ::RNG & ran); void turn_to_qqx(bool allow_strange, HEJ::RNG & ran); std::vector<Particle> decay_boson( HEJ::Particle const & parent, std::vector<Decay> const & decays, HEJ::RNG & ran ); /// @brief setup outgoing partons to ensure correct coupling to boson void couple_boson(HEJ::ParticleID boson, HEJ::RNG & ran); Decay select_decay_channel( std::vector<Decay> const & decays, HEJ::RNG & ran ); double gen_hard_pt( int np, double ptmin, double ptmax, double y, HEJ::RNG & ran ); double gen_soft_pt(int np, double ptmax, HEJ::RNG & ran); double gen_parton_pt( int count, JetParameters const & jet_param, double ptmax, double y, HEJ::RNG & ran ); double weight_; Status status_; std::array<Particle, 2> incoming_; std::vector<Particle> outgoing_; //! Particle decays in the format {outgoing index, decay products} std::unordered_map<size_t, std::vector<Particle>> decays_; }; - HEJ::UnclusteredEvent to_UnclusteredEvent(PhaseSpacePoint const & psp); + HEJ::Event::EventData to_EventData(PhaseSpacePoint const & psp); } diff --git a/FixedOrderGen/src/EventGenerator.cc b/FixedOrderGen/src/EventGenerator.cc index eb99958..c97839a 100644 --- a/FixedOrderGen/src/EventGenerator.cc +++ b/FixedOrderGen/src/EventGenerator.cc @@ -1,80 +1,80 @@ #include "EventGenerator.hh" #include "Process.hh" #include "Beam.hh" #include "JetParameters.hh" #include "PhaseSpacePoint.hh" #include "HEJ/Event.hh" #include "HEJ/config.hh" namespace HEJFOG{ EventGenerator::EventGenerator( Process process, Beam beam, HEJ::ScaleGenerator scale_gen, JetParameters jets, int pdf_id, double subl_change, unsigned int subl_channels, ParticlesPropMap particles_properties, HEJ::HiggsCouplingSettings Higgs_coupling, HEJ::RNG & ran ): pdf_{pdf_id, beam.particles[0], beam.particles[1]}, ME_{ [this](double mu){ return pdf_.Halphas(mu); }, HEJ::MatrixElementConfig{ false, std::move(Higgs_coupling) } }, scale_gen_{std::move(scale_gen)}, process_{std::move(process)}, jets_{std::move(jets)}, beam_{std::move(beam)}, subl_change_{subl_change}, subl_channels_{subl_channels}, particles_properties_{std::move(particles_properties)}, ran_{ran} { } - HEJ::Event EventGenerator::gen_event(){ + HEJ::optional<HEJ::Event> EventGenerator::gen_event(){ HEJFOG::PhaseSpacePoint psp{ process_, jets_, pdf_, beam_.energy, subl_change_, subl_channels_, particles_properties_, ran_ }; status_ = psp.status(); if(status_ != good) return {}; HEJ::Event ev = scale_gen_( HEJ::Event{ - to_UnclusteredEvent(std::move(psp)), - jets_.def, jets_.min_pt + to_EventData( std::move(psp) ).cluster( jets_.def, jets_.min_pt) } ); + ev.generate_colours(ran_); + const double shat = HEJ::shat(ev); const double xa = (ev.incoming()[0].E()-ev.incoming()[0].pz())/(2.*beam_.energy); const double xb = (ev.incoming()[1].E()+ev.incoming()[1].pz())/(2.*beam_.energy); - // evaluate matrix element on this point - const auto ME_weights = ME_.tree(ev); - ev.central().weight *= ME_weights.central/(shat*shat); + // evaluate matrix element + ev.parameters() *= ME_.tree(ev)/(shat*shat); + // and PDFs ev.central().weight *= pdf_.pdfpt(0,xa,ev.central().muf, ev.incoming()[0].type); ev.central().weight *= pdf_.pdfpt(0,xb,ev.central().muf, ev.incoming()[1].type); for(size_t i = 0; i < ev.variations().size(); ++i){ auto & var = ev.variations(i); - var.weight *= ME_weights.variations[i]/(shat*shat); var.weight *= pdf_.pdfpt(0,xa,var.muf, ev.incoming()[0].type); var.weight *= pdf_.pdfpt(0,xb,var.muf, ev.incoming()[1].type); } return ev; } } diff --git a/FixedOrderGen/src/PhaseSpacePoint.cc b/FixedOrderGen/src/PhaseSpacePoint.cc index 8969c40..4543185 100644 --- a/FixedOrderGen/src/PhaseSpacePoint.cc +++ b/FixedOrderGen/src/PhaseSpacePoint.cc @@ -1,665 +1,654 @@ #include "PhaseSpacePoint.hh" -#include <random> #include <algorithm> #include "HEJ/Constants.hh" -#include <CLHEP/Random/Randomize.h> -#include <CLHEP/Random/RanluxEngine.h> - #include "HEJ/exceptions.hh" #include "HEJ/kinematics.hh" #include "HEJ/Particle.hh" #include "HEJ/utility.hh" #include "Process.hh" #include "Subleading.hh" - using namespace HEJ; namespace HEJFOG{ static_assert( std::numeric_limits<double>::has_quiet_NaN, "no quiet NaN for double" ); constexpr double NaN = std::numeric_limits<double>::quiet_NaN(); - HEJ::UnclusteredEvent to_UnclusteredEvent(PhaseSpacePoint const & psp){ - HEJ::UnclusteredEvent result; + + HEJ::Event::EventData to_EventData(PhaseSpacePoint const & psp){ + HEJ::Event::EventData result; result.incoming = psp.incoming(); - std::sort( - begin(result.incoming), end(result.incoming), - [](Particle o1, Particle o2){return o1.p.pz()<o2.p.pz();} - ); assert(result.incoming.size() == 2); - result.outgoing = psp.outgoing(); + result.outgoing=psp.outgoing(); + // technically Event::EventData doesn't have to be sorted, + // but PhaseSpacePoint should be anyway assert( std::is_sorted( begin(result.outgoing), end(result.outgoing), HEJ::rapidity_less{} ) ); assert(result.outgoing.size() >= 2); - result.decays = psp.decays(); - result.central.mur = NaN; - result.central.muf = NaN; - result.central.weight = psp.weight(); + result.decays=psp.decays(); + result.parameters.central= {NaN, NaN, psp.weight() }; return result; } namespace{ bool can_swap_to_uno( HEJ::Particle const & p1, HEJ::Particle const & p2 ){ return is_parton(p1) && p1.type != pid::gluon && p2.type == pid::gluon; } size_t count_gluons(std::vector<Particle>::const_iterator first, std::vector<Particle>::const_iterator last){ return std::count_if(first, last, [](Particle const & p) {return p.type == pid::gluon;}); } /** assumes FKL configurations between first and last, * else there can be a quark in a non-extreme position * e.g. uno configuration gqg would pass */ bool can_change_to_qqx( std::vector<Particle>::const_iterator first, std::vector<Particle>::const_iterator last){ return 1 < count_gluons(first,last); } bool is_AWZ_proccess(Process const & proc){ return proc.boson && is_AWZ_boson(*proc.boson); } bool is_up_type(Particle const & part){ return HEJ::is_anyquark(part) && !(abs(part.type)%2); } bool is_down_type(Particle const & part){ return HEJ::is_anyquark(part) && abs(part.type)%2; } - /// true iff parton can couple to a W - bool can_couple_to_W(Particle const & part, int const sign_W){ + bool can_couple_to_W(Particle const & part, pid::ParticleID const W_id){ + const int W_charge = W_id>0?1:-1; return abs(part.type)<5 - && ( (sign_W*part.type > 0 && is_up_type(part)) - || (sign_W*part.type < 0 && is_down_type(part)) ); + && ( (W_charge*part.type > 0 && is_up_type(part)) + || (W_charge*part.type < 0 && is_down_type(part)) ); } } void PhaseSpacePoint::maybe_turn_to_subl( double chance, unsigned int const channels, Process const & proc, HEJ::RNG & ran ){ if(proc.njets <= 2) return; assert(outgoing_.size() >= 2); // decide what kind of subleading process is allowed bool allow_uno = false; bool allow_strange = true; const size_t nout = outgoing_.size(); const bool can_be_uno_backward = (channels&Subleading::uno) && can_swap_to_uno(outgoing_[0], outgoing_[1]); const bool can_be_uno_forward = (channels&Subleading::uno) && can_swap_to_uno(outgoing_[nout-1], outgoing_[nout-2]); allow_uno = can_be_uno_backward || can_be_uno_forward; bool allow_qqx = false; if(is_AWZ_proccess(proc)) { allow_qqx = (channels&Subleading::qqx) && can_change_to_qqx(outgoing_.cbegin(), outgoing_.cend()); - const int sign_W = *proc.boson>0?1:-1; if(std::none_of(outgoing_.cbegin(), outgoing_.cend(), - [sign_W](Particle const & p){ return can_couple_to_W(p, sign_W);})) { + [&proc](Particle const & p){ return can_couple_to_W(p, *proc.boson);})) { // enforce qqx if A/W/Z can't couple somewhere else assert(allow_qqx); allow_uno = false; chance = 1.; // strange not allowed for W if(abs(*proc.boson)== pid::Wp) allow_strange = false; } } if(!allow_uno && !allow_qqx) return; if(ran.flat() < chance){ weight_ /= chance; if(allow_uno && !allow_qqx){ turn_to_uno(can_be_uno_backward, can_be_uno_forward, ran); } else if (!allow_uno && allow_qqx) { turn_to_qqx(allow_strange, ran); } else { assert( allow_uno && allow_qqx); if(ran.flat() < 0.5) turn_to_uno(can_be_uno_backward, can_be_uno_forward, ran); else turn_to_qqx(allow_strange, ran); weight_ *= 2.; } } else weight_ /= 1 - chance; } void PhaseSpacePoint::turn_to_uno( const bool can_be_uno_backward, const bool can_be_uno_forward, HEJ::RNG & ran ){ if(!can_be_uno_backward && !can_be_uno_forward) return; const size_t nout = outgoing_.size(); if(can_be_uno_backward && can_be_uno_forward){ if(ran.flat() < 0.5){ std::swap(outgoing_[0].type, outgoing_[1].type); } else { std::swap(outgoing_[nout-1].type, outgoing_[nout-2].type); } weight_ *= 2.; } else if(can_be_uno_backward){ std::swap(outgoing_[0].type, outgoing_[1].type); } else { assert(can_be_uno_forward); std::swap(outgoing_[nout-1].type, outgoing_[nout-2].type); } } - void PhaseSpacePoint::turn_to_qqx(const bool allow_stange, HEJ::RNG & ran){ + void PhaseSpacePoint::turn_to_qqx(const bool allow_strange, HEJ::RNG & ran){ /// find first and last gluon in FKL chain auto first = std::find_if(outgoing_.begin(), outgoing_.end(), [](Particle const & p){return p.type == pid::gluon;}); std::vector<Particle*> FKL_gluons; - for(auto p = first; p<outgoing_.end(); ++p){ - if((*p).type == pid::gluon) FKL_gluons.push_back(&*p); - else if(is_quark(*p) || is_antiquark(*p)) break; + for(auto p = first; p!=outgoing_.end(); ++p){ + if(p->type == pid::gluon) FKL_gluons.push_back(&*p); + else if(is_anyquark(*p)) break; } const size_t ng = FKL_gluons.size(); if(ng < 2) throw std::logic_error("not enough gluons to create qqx"); // select flavour of quark const double r1 = 2.*ran.flat()-1.; - const double max_flavour = allow_stange?n_f:n_f-1; + const double max_flavour = allow_strange?n_f:n_f-1; weight_ *= max_flavour*2; - int flavour = pid::down; - for (double sum = 1./max_flavour; sum < std::abs(r1); sum += 1./max_flavour) - ++flavour; + int flavour = pid::down + std::floor(std::abs(r1)*max_flavour); flavour*=r1<0.?-1:1; // select gluon for switch const size_t idx = floor((ng-1) * ran.flat()); weight_ *= (ng-1); FKL_gluons[idx]->type = ParticleID(flavour); FKL_gluons[idx+1]->type = ParticleID(-flavour); } template<class ParticleMomenta> fastjet::PseudoJet PhaseSpacePoint::gen_last_momentum( ParticleMomenta const & other_momenta, const double mass_square, const double y ) const { std::array<double,2> pt{0.,0.}; for (auto const & p: other_momenta) { pt[0]-= p.px(); pt[1]-= p.py(); } const double mperp = sqrt(pt[0]*pt[0]+pt[1]*pt[1]+mass_square); const double pz=mperp*sinh(y); const double E=mperp*cosh(y); return {pt[0], pt[1], pz, E}; } PhaseSpacePoint::PhaseSpacePoint( Process const & proc, JetParameters const & jet_param, HEJ::PDF & pdf, double E_beam, double const subl_chance, unsigned int const subl_channels, ParticlesPropMap const & particles_properties, HEJ::RNG & ran ) { assert(proc.njets >= 2); if(proc.boson && particles_properties.find(*(proc.boson)) == particles_properties.end()) throw HEJ::missing_option("Boson " +std::to_string(*(proc.boson))+" can't be generated: missing properties"); status_ = good; weight_ = 1; const int nout = proc.njets + (proc.boson?1:0); outgoing_.reserve(nout); // generate parton momenta const bool is_pure_jets = !proc.boson; auto partons = gen_LO_partons( proc.njets, is_pure_jets, jet_param, E_beam, ran ); // pre fill flavour with gluons for(auto&& p_out: partons) { - outgoing_.emplace_back(Particle{pid::gluon, std::move(p_out)}); + outgoing_.emplace_back(Particle{pid::gluon, std::move(p_out), {}}); } if(status_ != good) return; // create boson if(proc.boson){ const auto & boson_prop = particles_properties.at(*proc.boson); auto boson(gen_boson(*proc.boson, boson_prop.mass, boson_prop.width, ran)); const auto pos = std::upper_bound( begin(outgoing_),end(outgoing_),boson,rapidity_less{} ); outgoing_.insert(pos, std::move(boson)); if(! boson_prop.decays.empty()){ const size_t boson_idx = std::distance(begin(outgoing_), pos); decays_.emplace( boson_idx, decay_boson(outgoing_[boson_idx], boson_prop.decays, ran) ); } } // normalisation of momentum-conserving delta function weight_ *= pow(2*M_PI, 4); - reconstruct_incoming(proc, subl_channels, pdf,E_beam,jet_param.min_pt, ran); + /** @TODO + * uf (jet_param.min_pt) doesn't correspond to our final scale choice. + * The HEJ scale generators currently expect a full event as input, + * so fixing this is not completely trivial + */ + reconstruct_incoming(proc, subl_channels, pdf, E_beam, jet_param.min_pt, ran); if(status_ != good) return; // set outgoing states most_backward_FKL(outgoing_).type = incoming_[0].type; most_forward_FKL(outgoing_).type = incoming_[1].type; maybe_turn_to_subl(subl_chance, subl_channels, proc, ran); if(proc.boson) couple_boson(*proc.boson, ran); } double PhaseSpacePoint::gen_hard_pt( int np , double ptmin, double ptmax, double y, HEJ::RNG & ran ) { // heuristic parameters for pt sampling const double ptpar = ptmin + np/5.; const double arg_small_y = atan((ptmax - ptmin)/ptpar); const double y_cut = 3.; const double r1 = ran.flat(); if(y < y_cut){ const double pt = ptmin + ptpar*tan(r1*arg_small_y); const double temp = cos(r1*arg_small_y); weight_ *= pt*ptpar*arg_small_y/(temp*temp); return pt; } const double ptpar2 = ptpar/(1 + 5*(y-y_cut)); const double temp = 1. - std::exp((ptmin-ptmax)/ptpar2); const double pt = ptmin - ptpar2*std::log(1-r1*temp); weight_ *= pt*ptpar2*temp/(1-r1*temp); return pt; } double PhaseSpacePoint::gen_soft_pt(int np, double max_pt, HEJ::RNG & ran) { constexpr double ptpar = 4.; const double r = ran.flat(); const double pt = max_pt + ptpar/np*std::log(r); weight_ *= pt*ptpar/(np*r); return pt; } double PhaseSpacePoint::gen_parton_pt( int count, JetParameters const & jet_param, double max_pt, double y, HEJ::RNG & ran ) { constexpr double p_small_pt = 0.02; if(! jet_param.peak_pt) { return gen_hard_pt(count, jet_param.min_pt, max_pt, y, ran); } const double r = ran.flat(); if(r > p_small_pt) { weight_ /= 1. - p_small_pt; return gen_hard_pt(count, *jet_param.peak_pt, max_pt, y, ran); } weight_ /= p_small_pt; const double pt = gen_soft_pt(count, *jet_param.peak_pt, ran); if(pt < jet_param.min_pt) { weight_=0.0; status_ = not_enough_jets; return jet_param.min_pt; } return pt; } std::vector<fastjet::PseudoJet> PhaseSpacePoint::gen_LO_partons( int np, bool is_pure_jets, JetParameters const & jet_param, double max_pt, HEJ::RNG & ran ){ if (np<2) throw std::invalid_argument{"Not enough partons in gen_LO_partons"}; weight_ /= pow(16.*pow(M_PI,3),np); weight_ /= std::tgamma(np+1); //remove rapidity ordering std::vector<fastjet::PseudoJet> partons; partons.reserve(np); for(int i = 0; i < np; ++i){ const double y = -jet_param.max_y + 2*jet_param.max_y*ran.flat(); weight_ *= 2*jet_param.max_y; const bool is_last_parton = i+1 == np; if(is_pure_jets && is_last_parton) { constexpr double parton_mass_sq = 0.; partons.emplace_back(gen_last_momentum(partons, parton_mass_sq, y)); break; } const double phi = 2*M_PI*ran.flat(); weight_ *= 2.0*M_PI; const double pt = gen_parton_pt(np, jet_param, max_pt, y, ran); if(weight_ == 0.0) return {}; partons.emplace_back(fastjet::PtYPhiM(pt, y, phi)); assert(jet_param.min_pt <= partons[i].pt()); assert(partons[i].pt() <= max_pt+1e-5); } // Need to check that at LO, the number of jets = number of partons; fastjet::ClusterSequence cs(partons, jet_param.def); auto cluster_jets=cs.inclusive_jets(jet_param.min_pt); if (cluster_jets.size()!=unsigned(np)){ weight_=0.0; status_ = not_enough_jets; return {}; } std::sort(begin(partons), end(partons), rapidity_less{}); return partons; } Particle PhaseSpacePoint::gen_boson( HEJ::ParticleID bosonid, double mass, double width, HEJ::RNG & ran ){ // Usual phase space measure weight_ /= 16.*pow(M_PI, 3); // Generate a y Gaussian distributed around 0 /// @TODO: magic number only for Higgs /// @TODO better sampling for W const double y = random_normal(1.6, ran); const double r1 = ran.flat(); const double sH = mass*( mass + width*tan(M_PI/2.*r1 + (r1-1.)*atan(mass/width)) ); auto p = gen_last_momentum(outgoing_, sH, y); - return Particle{bosonid, std::move(p)}; + return Particle{bosonid, std::move(p), {}}; } Particle const & PhaseSpacePoint::most_backward_FKL( std::vector<Particle> const & partons ) const{ if(!HEJ::is_parton(partons[0])) return partons[1]; return partons[0]; } Particle const & PhaseSpacePoint::most_forward_FKL( std::vector<Particle> const & partons ) const{ const size_t last_idx = partons.size() - 1; if(!HEJ::is_parton(partons[last_idx])) return partons[last_idx-1]; return partons[last_idx]; } Particle & PhaseSpacePoint::most_backward_FKL( std::vector<Particle> & partons ) const{ if(!HEJ::is_parton(partons[0])) return partons[1]; return partons[0]; } Particle & PhaseSpacePoint::most_forward_FKL( std::vector<Particle> & partons ) const{ const size_t last_idx = partons.size() - 1; if(!HEJ::is_parton(partons[last_idx])) return partons[last_idx-1]; return partons[last_idx]; } namespace { /// partons are ordered: even = anti, 0 = gluon ParticleID index_to_pid(size_t i){ if(!i) return pid::gluon; return static_cast<ParticleID>(i%2?(i+1)/2:-i/2); } /// partons are ordered: even = anti, 0 = gluon size_t pid_to_index(ParticleID id){ if(id==pid::gluon) return 0; return id>0?id*2-1:abs(id)*2; } std::bitset<11> init_allowed(ParticleID const id){ if(abs(id) == pid::proton) return ~0; std::bitset<11> out = 0; if(is_parton(id)) out[pid_to_index(id)] = 1; return out; } /// decides which "index" (see index_to_pid) are allowed for process std::bitset<11> allowed_quarks(ParticleID const boson){ std::bitset<11> allowed = ~0; if(abs(boson) == pid::Wp){ // special case W: // Wp: anti-down or up-type quark, no b/t -> 0001100110(1) = 205 // Wm: down or anti-up-type quark, no b/t -> 0010011001(1) = 307 allowed = boson>0?205:307; } return allowed; } } /** * checks which partons are allowed as initial state: * 1. only allow what is given in the Runcard (p -> all) * 2. A/W/Z require something to couple to * a) no qqx => no incoming gluon * b) 2j => no incoming gluon * c) 3j => can couple OR is gluon => 2 gluons become qqx later */ std::array<std::bitset<11>,2> PhaseSpacePoint::filter_partons( Process const & proc, unsigned int const subl_channels, HEJ::RNG & ran ){ std::array<std::bitset<11>,2> allowed_partons{ init_allowed(proc.incoming[0]), init_allowed(proc.incoming[1]) }; bool const allow_qqx = subl_channels&Subleading::qqx; // special case A/W/Z if(is_AWZ_proccess(proc) && ((proc.njets < 4) || !allow_qqx)){ // all possible incoming states auto allowed(allowed_quarks(*proc.boson)); if(proc.njets == 2 || !allow_qqx) allowed[0]=0; // possible states per leg std::array<std::bitset<11>,2> const maybe_partons{ allowed_partons[0]&allowed, allowed_partons[1]&allowed}; if(maybe_partons[0].any() && maybe_partons[1].any()){ // two options to get allowed initial state => choose one at random const size_t idx = ran.flat() < 0.5; allowed_partons[idx] = maybe_partons[idx]; // else choose the possible } else if(maybe_partons[0].any()) { allowed_partons[0] = maybe_partons[0]; } else if(maybe_partons[1].any()) { allowed_partons[1] = maybe_partons[1]; } else{ throw std::invalid_argument{"Incoming state not allowed."}; } } return allowed_partons; } void PhaseSpacePoint::reconstruct_incoming( Process const & proc, unsigned int const subl_channels, HEJ::PDF & pdf, double E_beam, double uf, HEJ::RNG & ran ){ std::tie(incoming_[0].p, incoming_[1].p) = incoming_momenta(outgoing_); // calculate xa, xb const double sqrts=2*E_beam; const double xa=(incoming_[0].p.e()-incoming_[0].p.pz())/sqrts; const double xb=(incoming_[1].p.e()+incoming_[1].p.pz())/sqrts; // abort if phase space point is outside of collider energy reach if (xa>1. || xb>1.){ weight_=0; status_ = too_much_energy; return; } - // pick pdfs - /** @TODO - * ufa, ufb don't correspond to our final scale choice. - * The HEJ scale generators currently expect a full event as input, - * so fixing this is not completely trivial - */ auto const & ids = proc.incoming; std::array<std::bitset<11>,2> allowed_partons( filter_partons(proc, subl_channels, ran)); for(size_t i = 0; i < 2; ++i){ if(ids[i] == pid::proton || ids[i] == pid::p_bar){ + // pick ids according to pdfs incoming_[i].type = generate_incoming_id(i, i?xb:xa, uf, pdf, allowed_partons[i], ran); } else { assert(allowed_partons[i][pid_to_index(ids[i])]); incoming_[i].type = ids[i]; } } assert(momentum_conserved(1e-7)); } HEJ::ParticleID PhaseSpacePoint::generate_incoming_id( size_t const beam_idx, double const x, double const uf, HEJ::PDF & pdf, std::bitset<11> allowed_partons, HEJ::RNG & ran ){ std::array<double,11> pdf_wt; pdf_wt[0] = allowed_partons[0]?fabs(pdf.pdfpt(beam_idx,x,uf,pid::gluon)):0.; double pdftot = pdf_wt[0]; for(size_t i = 1; i < pdf_wt.size(); ++i){ pdf_wt[i] = allowed_partons[i]?4./9.*fabs(pdf.pdfpt(beam_idx,x,uf,index_to_pid(i))):0; pdftot += pdf_wt[i]; } const double r1 = pdftot * ran.flat(); double sum = 0; for(size_t i=0; i < pdf_wt.size(); ++i){ if (r1 < (sum+=pdf_wt[i])){ weight_*= pdftot/pdf_wt[i]; return index_to_pid(i); } } std::cerr << "Error in choosing incoming parton: "<<x<<" "<<uf<<" " <<sum<<" "<<pdftot<<" "<<r1<<std::endl; throw std::logic_error{"Failed to choose parton flavour"}; } void PhaseSpacePoint::couple_boson( HEJ::ParticleID const boson, HEJ::RNG & ran ){ if(abs(boson) != pid::Wp) return; // only matters for W /// @TODO this could be use to sanity check gamma and Z // find all possible quarks - const int sign_W = boson>0?1:-1; std::vector<Particle*> allowed_parts; for(auto & part: outgoing_){ // Wp -> up OR anti-down, Wm -> anti-up OR down, no bottom - if ( can_couple_to_W(part, sign_W) ) + if ( can_couple_to_W(part, boson) ) allowed_parts.push_back(&part); } if(allowed_parts.size() == 0){ throw std::logic_error{"Found no parton for coupling with boson"}; } // select one and flip it size_t idx = 0; if(allowed_parts.size() > 1){ /// @TODO more efficient sampling /// old code: probability[i] = exp(parton[i].y - W.y) idx = floor(ran.flat()*allowed_parts.size()); weight_ *= allowed_parts.size(); } + const int W_charge = boson>0?1:-1; allowed_parts[idx]->type = - static_cast<ParticleID>( allowed_parts[idx]->type - sign_W ); + static_cast<ParticleID>( allowed_parts[idx]->type - W_charge ); } double PhaseSpacePoint::random_normal( double stddev, HEJ::RNG & ran ){ const double r1 = ran.flat(); const double r2 = ran.flat(); const double lninvr1 = -log(r1); const double result = stddev*sqrt(2.*lninvr1)*cos(2.*M_PI*r2); weight_ *= exp(result*result/(2*stddev*stddev))*sqrt(2.*M_PI)*stddev; return result; } bool PhaseSpacePoint::momentum_conserved(double ep) const{ fastjet::PseudoJet diff; for(auto const & in: incoming()) diff += in.p; for(auto const & out: outgoing()) diff -= out.p; return nearby_ep(diff, fastjet::PseudoJet{}, ep); } Decay PhaseSpacePoint::select_decay_channel( std::vector<Decay> const & decays, HEJ::RNG & ran ){ double br_total = 0.; for(auto const & decay: decays) br_total += decay.branching_ratio; // adjust weight // this is given by (channel branching ratio)/(chance to pick channel) // where (chance to pick channel) = // (channel branching ratio)/(total branching ratio) weight_ *= br_total; const double r1 = br_total*ran.flat(); double br_sum = 0.; for(auto const & decay: decays){ br_sum += decay.branching_ratio; if(r1 < br_sum) return decay; } throw std::logic_error{"unreachable"}; } std::vector<Particle> PhaseSpacePoint::decay_boson( HEJ::Particle const & parent, std::vector<Decay> const & decays, HEJ::RNG & ran ){ const auto channel = select_decay_channel(decays, ran); if(channel.products.size() != 2){ throw HEJ::not_implemented{ "only decays into two particles are implemented" }; } std::vector<Particle> decay_products(channel.products.size()); for(size_t i = 0; i < channel.products.size(); ++i){ decay_products[i].type = channel.products[i]; } // choose polar and azimuth angle in parent rest frame const double E = parent.m()/2; const double theta = 2.*M_PI*ran.flat(); const double cos_phi = 2.*ran.flat()-1.; const double sin_phi = sqrt(1. - cos_phi*cos_phi); // Know 0 < phi < pi const double px = E*cos(theta)*sin_phi; const double py = E*sin(theta)*sin_phi; const double pz = E*cos_phi; decay_products[0].p.reset(px, py, pz, E); decay_products[1].p.reset(-px, -py, -pz, E); for(auto & particle: decay_products) particle.p.boost(parent.p); return decay_products; } } diff --git a/FixedOrderGen/src/Unweighter.cc b/FixedOrderGen/src/Unweighter.cc index 0ac4d74..7bb1695 100644 --- a/FixedOrderGen/src/Unweighter.cc +++ b/FixedOrderGen/src/Unweighter.cc @@ -1,34 +1,26 @@ #include "Unweighter.hh" #include <cassert> #include "HEJ/Event.hh" namespace HEJFOG { namespace detail { bool has_jet_softer_than(HEJ::Event const & ev, double pt) { assert(! ev.jets().empty()); const auto softest_jet = fastjet::sorted_by_pt(ev.jets()).back(); return softest_jet.pt() < pt; } } - namespace { - void normalise_weights(HEJ::Event & ev, double target) { - const double awt = std::abs(ev.central().weight); - ev.central().weight *= target/awt; - for(auto & var: ev.variations()) var.weight *= target/awt; - } - } - HEJ::optional<HEJ::Event> Unweighter::unweight(HEJ::Event ev) const { if(detail::has_jet_softer_than(ev, min_unweight_pt_)) return ev; const double awt = std::abs(ev.central().weight); if(ran_.get().flat() < awt/cut_) { - if(awt < cut_) normalise_weights(ev, cut_); + if(awt < cut_) ev.parameters() *= cut_/awt; return ev; } return {}; } } diff --git a/FixedOrderGen/src/main.cc b/FixedOrderGen/src/main.cc index 6d55edb..01b9b80 100644 --- a/FixedOrderGen/src/main.cc +++ b/FixedOrderGen/src/main.cc @@ -1,233 +1,228 @@ /** * Name: main.cc * Authors: Jeppe R. Andersen */ #include <algorithm> #include <chrono> #include <fstream> #include <iostream> #include <map> #include <memory> #include "yaml-cpp/yaml.h" #include "LHEF/LHEF.h" #include "HEJ/CombinedEventWriter.hh" #include "HEJ/CrossSectionAccumulator.hh" #include "HEJ/get_analysis.hh" #include "HEJ/LesHouchesWriter.hh" #include "HEJ/make_RNG.hh" #include "HEJ/ProgressBar.hh" #include "HEJ/stream.hh" #include "config.hh" #include "EventGenerator.hh" #include "PhaseSpacePoint.hh" #include "Unweighter.hh" #include "Version.hh" namespace{ constexpr auto banner = " __ ___ __ ______ __ __ \n" " / / / (_)___ _/ /_ / ____/___ ___ _________ ___ __ / /__ / /______ \n" " / /_/ / / __ `/ __ \\ / __/ / __ \\/ _ \\/ ___/ __ `/ / / / __ / / _ \\/ __/ ___/ \n" " / __ / / /_/ / / / / / /___/ / / / __/ / / /_/ / /_/ / / /_/ / __/ /_(__ ) \n" " /_/ /_/_/\\__, /_/ /_/ /_____/_/ /_/\\___/_/ \\__, /\\__, / \\____/\\___/\\__/____/ \n" " ____///__/ __ ____ ///__//____/ ______ __ \n" " / ____(_) _____ ____/ / / __ \\_________/ /__ _____ / ____/__ ____ ___ _________ _/ /_____ _____\n" " / /_ / / |/_/ _ \\/ __ / / / / / ___/ __ / _ \\/ ___/ / / __/ _ \\/ __ \\/ _ \\/ ___/ __ `/ __/ __ \\/ ___/\n" " / __/ / /> </ __/ /_/ / / /_/ / / / /_/ / __/ / / /_/ / __/ / / / __/ / / /_/ / /_/ /_/ / / \n" " /_/ /_/_/|_|\\___/\\__,_/ \\____/_/ \\__,_/\\___/_/ \\____/\\___/_/ /_/\\___/_/ \\__,_/\\__/\\____/_/ \n" ; constexpr double invGeV2_to_pb = 389379292.; constexpr long long max_warmup_events = 10000; } HEJFOG::Config load_config(char const * filename){ try{ return HEJFOG::load_config(filename); } catch(std::exception const & exc){ std::cerr << "Error: " << exc.what() << '\n'; std::exit(EXIT_FAILURE); } } std::unique_ptr<HEJ::Analysis> get_analysis( YAML::Node const & parameters ){ try{ return HEJ::get_analysis(parameters); } catch(std::exception const & exc){ std::cerr << "Failed to load analysis: " << exc.what() << '\n'; std::exit(EXIT_FAILURE); } } -void rescale_weights(HEJ::Event & ev, double factor) { - ev.central().weight *= factor; - for(auto & var: ev.variations()){ - var.weight *= factor; - } -} - int main(int argn, char** argv) { using namespace std::string_literals; if (argn < 2) { std::cerr << "\n# Usage:\n." << argv[0] << " config_file\n"; return EXIT_FAILURE; } std::cout << banner; std::cout << "Version " << HEJFOG::Version::String() << ", revision " << HEJFOG::Version::revision() << std::endl; fastjet::ClusterSequence::print_banner(); using clock = std::chrono::system_clock; const auto start_time = clock::now(); // read configuration auto config = load_config(argv[1]); std::unique_ptr<HEJ::Analysis> analysis = get_analysis( config.analysis_parameters ); assert(analysis != nullptr); auto ran = HEJ::make_RNG(config.rng.name, config.rng.seed); assert(ran != nullptr); HEJ::ScaleGenerator scale_gen{ config.scales.base, config.scales.factors, config.scales.max_ratio }; HEJFOG::EventGenerator generator{ config.process, config.beam, std::move(scale_gen), config.jets, config.pdf_id, config.subleading_fraction, config.subleading_channels, config.particles_properties, config.Higgs_coupling, *ran }; LHEF::HEPRUP heprup; heprup.IDBMUP=std::pair<long,long>(config.beam.particles[0], config.beam.particles[1]); heprup.EBMUP=std::make_pair(config.beam.energy, config.beam.energy); heprup.PDFGUP=std::make_pair(0,0); heprup.PDFSUP=std::make_pair(config.pdf_id,config.pdf_id); heprup.NPRUP=1; heprup.XSECUP=std::vector<double>(1.); heprup.XERRUP=std::vector<double>(1.); heprup.LPRUP=std::vector<int>{1}; heprup.generators.emplace_back(LHEF::XMLTag{}); heprup.generators.back().name = HEJFOG::Version::package_name(); heprup.generators.back().version = HEJFOG::Version::String(); HEJ::CombinedEventWriter writer{config.output, heprup}; HEJ::optional<HEJFOG::Unweighter> unweighter{}; std::map<HEJFOG::Status, int> status_counter; std::vector<HEJ::Event> events; int trials = 0; // warm-up phase to train unweighter if(config.unweight) { std::cout << "Calibrating unweighting ...\n"; const auto warmup_start = clock::now(); const size_t warmup_events = config.unweight->sample_size; HEJ::ProgressBar<size_t> warmup_progress{std::cout, warmup_events}; for(; events.size() < warmup_events; ++trials){ auto ev = generator.gen_event(); ++status_counter[generator.status()]; - if(generator.status() == HEJFOG::good && analysis->pass_cuts(ev, ev)) { - events.emplace_back(std::move(ev)); + assert( (generator.status() == HEJFOG::good) == bool(ev) ); + if(generator.status() == HEJFOG::good && analysis->pass_cuts(*ev, *ev)) { + events.emplace_back(std::move(*ev)); ++warmup_progress; } } std::cout << std::endl; unweighter = HEJFOG::Unweighter{ begin(events), end(events), config.unweight->max_dev, *ran, config.jets.peak_pt?(*config.jets.peak_pt):0. }; std::vector<HEJ::Event> unweighted_events; for(auto && ev: events) { auto unweighted = unweighter->unweight(std::move(ev)); if(unweighted) { unweighted_events.emplace_back(std::move(*unweighted)); } } events = std::move(unweighted_events); if(events.empty()) { std::cerr << "Failed to generate events. Please increase \"unweight: sample size\"" " or reduce \"unweight: max deviation\"\n"; return EXIT_FAILURE; } const auto warmup_end = clock::now(); const double completion = static_cast<double>(events.size())/config.events; const std::chrono::duration<double> remaining_time = (warmup_end- warmup_start)*(1./completion - 1); const auto finish = clock::to_time_t( std::chrono::time_point_cast<std::chrono::seconds>(warmup_end + remaining_time) ); std::cout << "Generated " << events.size() << "/" << config.events << " events (" << static_cast<int>(std::round(100*completion)) << "%)\n" << "Estimated remaining generation time: " << remaining_time.count() << " seconds (" << std::put_time(std::localtime(&finish), "%c") << ")\n\n"; } HEJ::ProgressBar<long long> progress{std::cout, config.events}; progress.increment(events.size()); events.reserve(config.events); for(; events.size() < static_cast<size_t>(config.events); ++trials){ auto ev = generator.gen_event(); ++status_counter[generator.status()]; - if(generator.status() == HEJFOG::good && analysis->pass_cuts(ev, ev)) { + assert( (generator.status() == HEJFOG::good) == bool(ev) ); + if(generator.status() == HEJFOG::good && analysis->pass_cuts(*ev, *ev)) { if(unweighter) { - auto unweighted = unweighter->unweight(std::move(ev)); + auto unweighted = unweighter->unweight(std::move(*ev)); if(! unweighted) continue; - ev = std::move(*unweighted); + ev = std::move(unweighted); } - events.emplace_back(std::move(ev)); + events.emplace_back(std::move(*ev)); ++progress; } } std::cout << std::endl; HEJ::CrossSectionAccumulator xs; for(auto & ev: events){ - rescale_weights(ev, invGeV2_to_pb/trials); + ev.parameters() *= invGeV2_to_pb/trials; analysis->fill(ev, ev); writer.write(ev); xs.fill(ev); } analysis->finalise(); const std::chrono::duration<double> run_time = (clock::now() - start_time); std::cout << "\nTask Runtime: " << run_time.count() << " seconds.\n\n"; std::cout << xs << '\n'; for(auto && entry: status_counter){ const double fraction = static_cast<double>(entry.second)/trials; const int percent = std::round(100*fraction); std::cout << "status " << std::left << std::setw(16) << (to_string(entry.first) + ":") << " ["; for(int i = 0; i < percent/2; ++i) std::cout << '#'; for(int i = percent/2; i < 50; ++i) std::cout << ' '; std::cout << "] " << percent << "%\n"; } } diff --git a/FixedOrderGen/t/2j.cc b/FixedOrderGen/t/2j.cc index 80bdff3..ea68d04 100644 --- a/FixedOrderGen/t/2j.cc +++ b/FixedOrderGen/t/2j.cc @@ -1,59 +1,60 @@ #ifdef NDEBUG #undef NDEBUG #endif #include <algorithm> #include <cmath> #include <cassert> #include <iostream> #include "config.hh" #include "EventGenerator.hh" #include "HEJ/Mixmax.hh" #include "HEJ/Event.hh" #include "HEJ/PDF.hh" #include "HEJ/MatrixElement.hh" using namespace HEJFOG; int main(){ constexpr double invGeV2_to_pb = 389379292.; constexpr double xs_ref = 86.42031848*1e6; //calculated with "combined" HEJ svn r3480 auto config = load_config("config_2j.yml"); HEJ::Mixmax ran{}; HEJFOG::EventGenerator generator{ config.process, config.beam, HEJ::ScaleGenerator{ config.scales.base, config.scales.factors, config.scales.max_ratio }, config.jets, config.pdf_id, config.subleading_fraction, config.subleading_channels, config.particles_properties, config.Higgs_coupling, ran }; double xs = 0., xs_err = 0.; for (int trials = 0; trials < config.events; ++trials){ auto ev = generator.gen_event(); if(generator.status() != good) continue; - ev.central().weight *= invGeV2_to_pb; - ev.central().weight /= config.events; + assert(ev); + ev->central().weight *= invGeV2_to_pb; + ev->central().weight /= config.events; - xs += ev.central().weight; - xs_err += ev.central().weight*ev.central().weight; + xs += ev->central().weight; + xs_err += ev->central().weight*ev->central().weight; } xs_err = std::sqrt(xs_err); std::cout << xs_ref << " ~ " << xs << " +- " << xs_err << '\n'; assert(std::abs(xs - xs_ref) < 3*xs_err); assert(xs_err < 0.01*xs); } diff --git a/FixedOrderGen/t/4j.cc b/FixedOrderGen/t/4j.cc index 5b4e1b4..a597242 100644 --- a/FixedOrderGen/t/4j.cc +++ b/FixedOrderGen/t/4j.cc @@ -1,60 +1,61 @@ #ifdef NDEBUG #undef NDEBUG #endif #include <algorithm> #include <cmath> #include <cassert> #include <iostream> #include "config.hh" #include "EventGenerator.hh" #include "HEJ/Mixmax.hh" #include "HEJ/Event.hh" #include "HEJ/PDF.hh" #include "HEJ/MatrixElement.hh" using namespace HEJFOG; int main(){ constexpr double invGeV2_to_pb = 389379292.; constexpr double xs_ref = 0.81063619*1e6; //calculated with "combined" HEJ svn r3480 auto config = load_config("config_2j.yml"); config.process.njets = 4; HEJ::Mixmax ran{}; HEJFOG::EventGenerator generator{ config.process, config.beam, HEJ::ScaleGenerator{ config.scales.base, config.scales.factors, config.scales.max_ratio }, config.jets, config.pdf_id, config.subleading_fraction, config.subleading_channels, config.particles_properties, config.Higgs_coupling, ran }; double xs = 0., xs_err = 0.; for (int trials = 0; trials < config.events; ++trials){ auto ev = generator.gen_event(); if(generator.status() != good) continue; - ev.central().weight *= invGeV2_to_pb; - ev.central().weight /= config.events; + assert(ev); + ev->central().weight *= invGeV2_to_pb; + ev->central().weight /= config.events; - xs += ev.central().weight; - xs_err += ev.central().weight*ev.central().weight; + xs += ev->central().weight; + xs_err += ev->central().weight*ev->central().weight; } xs_err = std::sqrt(xs_err); std::cout << xs_ref << " ~ " << xs << " +- " << xs_err << '\n'; assert(std::abs(xs - xs_ref) < 3*xs_err); assert(xs_err < 0.03*xs); } diff --git a/FixedOrderGen/t/W_2j_classify.cc b/FixedOrderGen/t/W_2j_classify.cc index 1381244..4cddbf0 100644 --- a/FixedOrderGen/t/W_2j_classify.cc +++ b/FixedOrderGen/t/W_2j_classify.cc @@ -1,146 +1,147 @@ +// check that the PSP generates only "valid" W + 2 jets events + #ifdef NDEBUG #undef NDEBUG #endif #include "JetParameters.hh" #include "ParticleProperties.hh" #include "PhaseSpacePoint.hh" #include "Process.hh" #include "Subleading.hh" #include "HEJ/Mixmax.hh" #include "HEJ/PDF.hh" #include "HEJ/utility.hh" using namespace HEJFOG; using namespace HEJ; - namespace { void print_psp(PhaseSpacePoint const & psp){ std::cerr << "Process:\n" << psp.incoming()[0].type << " + "<< psp.incoming()[1].type << " -> "; for(auto const & out: psp.outgoing()){ std::cerr << out.type << " "; } std::cerr << "\n"; } void bail_out(PhaseSpacePoint const & psp, std::string msg){ print_psp(psp); throw std::logic_error{msg}; } bool is_up_type(Particle const & part){ return HEJ::is_anyquark(part) && !(abs(part.type)%2); } bool is_down_type(Particle const & part){ return HEJ::is_anyquark(part) && abs(part.type)%2; } bool check_W2j(PhaseSpacePoint const & psp, ParticleID const W_type){ bool found_quark = false; bool found_anti = false; std::vector<Particle> out_partons; std::vector<Particle> Wp; for(auto const & p: psp.outgoing()){ if(p.type == W_type) Wp.push_back(p); else if(is_parton(p)) out_partons.push_back(p); else bail_out(psp, "Found particle with is not " +std::to_string(int(W_type))+" or parton"); } if(Wp.size() != 1 || out_partons.size() != 2){ bail_out(psp, "Found wrong number of outgoing partons"); } for(size_t j=0; j<2; ++j){ auto const & in = psp.incoming()[j]; auto const & out = out_partons[j]; if(is_quark(in) || is_antiquark(in)) { found_quark = true; if(in.type != out.type) { // switch in quark type -> Wp couples to it if(found_anti){ // already found qq for coupling to W bail_out(psp, "Found second up/down pair"); } else if(abs(in.type)>4 || abs(out.type)>4){ bail_out(psp, "Found bottom/top pair"); } found_anti = true; if( is_up_type(in)) { // "up" in if(W_type > 0){ // -> only allowed u -> Wp + d if(in.type < 0 || is_up_type(out) || out.type < 0) bail_out(psp, "u -/> Wp + d"); } else { // -> only allowed ux -> Wm + dx if(in.type > 0 || is_up_type(out) || out.type > 0) bail_out(psp, "ux -/> Wm + dx"); } } else { // "down" in if(W_type > 0){ // -> only allowed dx -> Wp + ux if(in.type > 0 || is_down_type(out) || out.type > 0) bail_out(psp, "dx -/> Wp + ux"); } else { // -> only allowed d -> Wm + u if(in.type < 0 || is_down_type(out) || out.type < 0) bail_out(psp, "d -/> Wm + u"); } } } } } if(!found_quark) { bail_out(psp, "Found no initial quarks"); } else if(!found_anti){ bail_out(psp, "Found no up/down pair"); } return true; } } int main(){ constexpr size_t n_psp_base = 1337; const JetParameters jet_para{ fastjet::JetDefinition(fastjet::JetAlgorithm::antikt_algorithm, 0.4), 30, 5, 30}; PDF pdf(11000, pid::proton, pid::proton); constexpr double E_cms = 13000.; constexpr double subl_change = 0.5; constexpr auto subl_channels = Subleading::all; const ParticlesPropMap boson_prop{ {pid::Wp, {91.1876, 2.085, {Decay{ {pid::e_bar, pid::nu_e}, 1.}} }}, {pid::Wm, {91.1876, 2.085, {Decay{ {pid::e, pid::nu_e_bar}, 1.}} }} }; HEJ::Mixmax ran{}; // Wp2j Process proc {{pid::proton,pid::proton}, 2, pid::Wp}; size_t n_psp = n_psp_base; for( size_t i = 0; i<n_psp; ++i){ const PhaseSpacePoint psp{proc,jet_para,pdf,E_cms, subl_change,subl_channels, boson_prop, ran}; if(psp.status()==good){ check_W2j(psp, *proc.boson); } else { // bad process -> try again ++n_psp; } } std::cout << "Wp+2j: Took " << n_psp << " to generate " << n_psp_base << " successfully PSP (" << 1.*n_psp/n_psp_base << " trials/PSP)" << std::endl; // Wm2j proc = Process{{pid::proton,pid::proton}, 2, pid::Wm}; n_psp = n_psp_base; for( size_t i = 0; i<n_psp; ++i){ const PhaseSpacePoint psp{proc,jet_para,pdf,E_cms, subl_change,subl_channels, boson_prop, ran}; if(psp.status()==good){ check_W2j(psp, *proc.boson); } else { // bad process -> try again ++n_psp; } } std::cout << "Wm+2j: Took " << n_psp << " to generate " << n_psp_base << " successfully PSP (" << 1.*n_psp/n_psp_base << " trials/PSP)" << std::endl; std::cout << "All processes passed." << std::endl; return EXIT_SUCCESS; } diff --git a/FixedOrderGen/t/W_nj_classify.cc b/FixedOrderGen/t/W_nj_classify.cc index 06ff18f..2da7126 100644 --- a/FixedOrderGen/t/W_nj_classify.cc +++ b/FixedOrderGen/t/W_nj_classify.cc @@ -1,177 +1,179 @@ +// check that the PSP generates the all W+jet subleading processes + #ifdef NDEBUG #undef NDEBUG #endif #include <algorithm> #include "JetParameters.hh" #include "ParticleProperties.hh" #include "PhaseSpacePoint.hh" #include "Process.hh" #include "Subleading.hh" #include "HEJ/Event.hh" #include "HEJ/Mixmax.hh" #include "HEJ/PDF.hh" #include "HEJ/utility.hh" using namespace HEJFOG; using namespace HEJ; namespace { void print_psp(PhaseSpacePoint const & psp){ std::cerr << "Process:\n" << psp.incoming()[0].type << " + "<< psp.incoming()[1].type << " -> "; for(auto const & out: psp.outgoing()){ std::cerr << out.type << " "; } std::cerr << "\n"; } void bail_out(PhaseSpacePoint const & psp, std::string msg){ print_psp(psp); throw std::logic_error{msg}; } } int main(){ constexpr size_t n_psp_base = 10375; const JetParameters jet_para{ fastjet::JetDefinition(fastjet::JetAlgorithm::antikt_algorithm, 0.4), 30, 5, 30}; PDF pdf(11000, pid::proton, pid::proton); constexpr double E_cms = 13000.; constexpr double subl_change = 0.8; const ParticlesPropMap boson_prop{ {pid::Wp, {91.1876, 2.085, {Decay{ {pid::e_bar, pid::nu_e}, 1.}} }}, {pid::Wm, {91.1876, 2.085, {Decay{ {pid::e, pid::nu_e_bar}, 1.}} }} }; HEJ::Mixmax ran{}; auto subl_channels = Subleading::all; std::vector<event_type::EventType> allowed_types{event_type::FKL, event_type::unob, event_type::unof, event_type::qqxexb, event_type::qqxexf}; std::cout << "Wp3j" << std::endl; // Wp3j Process proc {{pid::proton,pid::proton}, 3, pid::Wp}; size_t n_psp = n_psp_base; std::unordered_map<event_type::EventType, size_t> type_counter; for( size_t i = 0; i<n_psp; ++i){ const PhaseSpacePoint psp{proc,jet_para,pdf,E_cms, subl_change,subl_channels, boson_prop, ran}; if(psp.status()==good){ - const Event ev(to_UnclusteredEvent(psp), jet_para.def, jet_para.min_pt); + const Event ev{ to_EventData(psp).cluster(jet_para.def, jet_para.min_pt) }; ++type_counter[ev.type()]; if( std::find(allowed_types.cbegin(), allowed_types.cend(), ev.type()) == allowed_types.cend()) { bail_out(psp, "Found not allowed event of type " +std::string(event_type::names[ev.type()])); } } else { // bad process -> try again ++n_psp; } } std::cout << "Wp+3j: Took " << n_psp << " to generate " << n_psp_base << " successfully PSP (" << 1.*n_psp/n_psp_base << " trials/PSP)" << std::endl; std::cout << "States by classification:\n"; for(auto const & entry: type_counter){ const double fraction = static_cast<double>(entry.second)/n_psp_base; const int percent = std::round(100*fraction); std::cout << std::left << std::setw(25) << (event_type::names[entry.first] + std::string(":")) << entry.second << " (" << percent << "%)\n"; } for(auto const & t: allowed_types){ if(type_counter[t] < 0.05 * n_psp_base){ std::cerr << "Less than 5% of the events are of type " << event_type::names[t] << std::endl; return EXIT_FAILURE; } } // Wm3j - only uno proc = Process{{pid::proton,pid::proton}, 3, pid::Wm}; n_psp = n_psp_base; subl_channels = Subleading::uno; allowed_types = {event_type::FKL, event_type::unob, event_type::unof}; type_counter.clear(); for( size_t i = 0; i<n_psp; ++i){ const PhaseSpacePoint psp{proc,jet_para,pdf,E_cms, subl_change,subl_channels, boson_prop, ran}; if(psp.status()==good){ - const Event ev(to_UnclusteredEvent(psp), jet_para.def, jet_para.min_pt); + const Event ev{ to_EventData(psp).cluster(jet_para.def, jet_para.min_pt) }; ++type_counter[ev.type()]; if( std::find(allowed_types.cbegin(), allowed_types.cend(), ev.type()) == allowed_types.cend()) { bail_out(psp, "Found not allowed event of type " +std::string(event_type::names[ev.type()])); } } else { // bad process -> try again ++n_psp; } } std::cout << "Wm+3j (only uno): Took " << n_psp << " to generate " << n_psp_base << " successfully PSP (" << 1.*n_psp/n_psp_base << " trials/PSP)" << std::endl; std::cout << "States by classification:\n"; for(auto const & entry: type_counter){ const double fraction = static_cast<double>(entry.second)/n_psp_base; const int percent = std::round(100*fraction); std::cout << std::left << std::setw(25) << (event_type::names[entry.first] + std::string(":")) << entry.second << " (" << percent << "%)\n"; } for(auto const & t: allowed_types){ if(type_counter[t] < 0.05 * n_psp_base){ std::cerr << "Less than 5% of the events are of type " << event_type::names[t] << std::endl; return EXIT_FAILURE; } } // Wm4j proc = Process{{pid::proton,pid::proton}, 4, pid::Wm}; n_psp = n_psp_base; subl_channels = Subleading::all; allowed_types = {event_type::FKL, event_type::unob, event_type::unof, event_type::qqxexb, event_type::qqxexf, event_type::qqxmid}; type_counter.clear(); for( size_t i = 0; i<n_psp; ++i){ const PhaseSpacePoint psp{proc,jet_para,pdf,E_cms, subl_change,subl_channels, boson_prop, ran}; if(psp.status()==good){ - const Event ev(to_UnclusteredEvent(psp), jet_para.def, jet_para.min_pt); + const Event ev{ to_EventData(psp).cluster(jet_para.def, jet_para.min_pt)}; ++type_counter[ev.type()]; if( std::find(allowed_types.cbegin(), allowed_types.cend(), ev.type()) == allowed_types.cend()) { bail_out(psp, "Found not allowed event of type " +std::string(event_type::names[ev.type()])); } } else { // bad process -> try again ++n_psp; } } std::cout << "Wm+4j: Took " << n_psp << " to generate " << n_psp_base << " successfully PSP (" << 1.*n_psp/n_psp_base << " trials/PSP)" << std::endl; std::cout << "States by classification:\n"; for(auto const & entry: type_counter){ const double fraction = static_cast<double>(entry.second)/n_psp_base; const int percent = std::round(100*fraction); std::cout << std::left << std::setw(25) << (event_type::names[entry.first] + std::string(":")) << entry.second << " (" << percent << "%)\n"; } for(auto const & t: allowed_types){ if(type_counter[t] < 0.03 * n_psp_base){ std::cerr << "Less than 3% of the events are of type " << event_type::names[t] << std::endl; return EXIT_FAILURE; } } std::cout << "All processes passed." << std::endl; return EXIT_SUCCESS; } diff --git a/FixedOrderGen/t/h_2j.cc b/FixedOrderGen/t/h_2j.cc index 3e3135e..4ad315d 100644 --- a/FixedOrderGen/t/h_2j.cc +++ b/FixedOrderGen/t/h_2j.cc @@ -1,67 +1,68 @@ #ifdef NDEBUG #undef NDEBUG #endif #include <algorithm> #include <cmath> #include <cassert> #include <iostream> #include "config.hh" #include "EventGenerator.hh" #include "HEJ/Ranlux64.hh" #include "HEJ/Event.hh" #include "HEJ/PDF.hh" #include "HEJ/MatrixElement.hh" using namespace HEJFOG; int main(){ constexpr double invGeV2_to_pb = 389379292.; constexpr double xs_ref = 2.04928; // +- 0.00377252 //calculated with HEJ revision 9570e3809613272ac4b8bf3236279ba23cf64d20 auto config = load_config("config_h_2j.yml"); HEJ::Ranlux64 ran{}; HEJFOG::EventGenerator generator{ config.process, config.beam, HEJ::ScaleGenerator{ config.scales.base, config.scales.factors, config.scales.max_ratio }, config.jets, config.pdf_id, config.subleading_fraction, config.subleading_channels, config.particles_properties, config.Higgs_coupling, ran }; double xs = 0., xs_err = 0.; for (int trials = 0; trials < config.events; ++trials){ auto ev = generator.gen_event(); if(generator.status() != good) continue; - ev.central().weight *= invGeV2_to_pb; - ev.central().weight /= config.events; + assert(ev); + ev->central().weight *= invGeV2_to_pb; + ev->central().weight /= config.events; const auto the_Higgs = std::find_if( - begin(ev.outgoing()), end(ev.outgoing()), + begin(ev->outgoing()), end(ev->outgoing()), [](HEJ::Particle const & p){ return p.type == HEJ::ParticleID::h; } ); - assert(the_Higgs != end(ev.outgoing())); + assert(the_Higgs != end(ev->outgoing())); if(std::abs(the_Higgs->rapidity()) > 5.) continue; - xs += ev.central().weight; - xs_err += ev.central().weight*ev.central().weight; + xs += ev->central().weight; + xs_err += ev->central().weight*ev->central().weight; } xs_err = std::sqrt(xs_err); std::cout << xs_ref << " ~ " << xs << " +- " << xs_err << std::endl; assert(std::abs(xs - xs_ref) < 3*xs_err); assert(xs_err < 0.01*xs); } diff --git a/FixedOrderGen/t/h_2j_decay.cc b/FixedOrderGen/t/h_2j_decay.cc index 8454b7b..e404707 100644 --- a/FixedOrderGen/t/h_2j_decay.cc +++ b/FixedOrderGen/t/h_2j_decay.cc @@ -1,86 +1,87 @@ #ifdef NDEBUG #undef NDEBUG #endif #include <algorithm> #include <cmath> #include <cassert> #include <iostream> #include "config.hh" #include "EventGenerator.hh" #include "HEJ/Event.hh" #include "HEJ/MatrixElement.hh" #include "HEJ/Particle.hh" #include "HEJ/PDF.hh" #include "HEJ/Ranlux64.hh" #include "HEJ/utility.hh" using namespace HEJFOG; bool pass_dR_cut( std::vector<fastjet::PseudoJet> const & jets, std::vector<HEJ::Particle> const & photons ){ constexpr double delta_R_min = 0.7; for(auto const & jet: jets){ for(auto const & photon: photons){ if(jet.delta_R(photon.p) < delta_R_min) return false; } } return true; } int main(){ constexpr double invGeV2_to_pb = 389379292.; constexpr double xs_ref = 0.00429198; // +- 1.0488e-05 //calculated with HEJ revision 9570e3809613272ac4b8bf3236279ba23cf64d20 auto config = load_config("config_h_2j_decay.yml"); HEJ::Ranlux64 ran{}; HEJFOG::EventGenerator generator{ config.process, config.beam, HEJ::ScaleGenerator{ config.scales.base, config.scales.factors, config.scales.max_ratio }, config.jets, config.pdf_id, config.subleading_fraction, config.subleading_channels, config.particles_properties, config.Higgs_coupling, ran }; double xs = 0., xs_err = 0.; for (int trials = 0; trials < config.events; ++trials){ auto ev = generator.gen_event(); if(generator.status() != good) continue; - assert(ev.decays().size() == 1); - const auto decay = begin(ev.decays()); - assert(ev.outgoing().size() > decay->first); - const auto & the_Higgs = ev.outgoing()[decay->first]; + assert(ev); + assert(ev->decays().size() == 1); + const auto decay = begin(ev->decays()); + assert(ev->outgoing().size() > decay->first); + const auto & the_Higgs = ev->outgoing()[decay->first]; assert(the_Higgs.type == HEJ::pid::Higgs); assert(decay->second.size() == 2); auto const & gamma = decay->second; assert(gamma[0].type == HEJ::pid::photon); assert(gamma[1].type == HEJ::pid::photon); assert(HEJ::nearby_ep(gamma[0].p + gamma[1].p, the_Higgs.p, 1e-6)); - if(!pass_dR_cut(ev.jets(), gamma)) continue; - ev.central().weight *= invGeV2_to_pb; - ev.central().weight /= config.events; + if(!pass_dR_cut(ev->jets(), gamma)) continue; + ev->central().weight *= invGeV2_to_pb; + ev->central().weight /= config.events; - xs += ev.central().weight; - xs_err += ev.central().weight*ev.central().weight; + xs += ev->central().weight; + xs_err += ev->central().weight*ev->central().weight; } xs_err = std::sqrt(xs_err); std::cout << xs_ref << " ~ " << xs << " +- " << xs_err << std::endl; assert(std::abs(xs - xs_ref) < 3*xs_err); assert(xs_err < 0.012*xs); } diff --git a/FixedOrderGen/t/h_3j.cc b/FixedOrderGen/t/h_3j.cc index cad089a..bb2baff 100644 --- a/FixedOrderGen/t/h_3j.cc +++ b/FixedOrderGen/t/h_3j.cc @@ -1,68 +1,69 @@ #ifdef NDEBUG #undef NDEBUG #endif #include <algorithm> #include <cmath> #include <cassert> #include <iostream> #include "config.hh" #include "EventGenerator.hh" #include "HEJ/Ranlux64.hh" #include "HEJ/Event.hh" #include "HEJ/MatrixElement.hh" #include "HEJ/PDF.hh" using namespace HEJFOG; int main(){ constexpr double invGeV2_to_pb = 389379292.; constexpr double xs_ref = 1.07807; // +- 0.0071 //calculated with HEJ revision 93efdc851b02a907a6fcc63956387f9f4c1111c2 +1 auto config = load_config("config_h_2j.yml"); config.process.njets = 3; HEJ::Ranlux64 ran{}; HEJFOG::EventGenerator generator{ config.process, config.beam, HEJ::ScaleGenerator{ config.scales.base, config.scales.factors, config.scales.max_ratio }, config.jets, config.pdf_id, config.subleading_fraction, config.subleading_channels, config.particles_properties, config.Higgs_coupling, ran }; double xs = 0., xs_err = 0.; for (int trials = 0; trials < config.events; ++trials){ auto ev = generator.gen_event(); if(generator.status() != good) continue; - ev.central().weight *= invGeV2_to_pb; - ev.central().weight /= config.events; + assert(ev); + ev->central().weight *= invGeV2_to_pb; + ev->central().weight /= config.events; const auto the_Higgs = std::find_if( - begin(ev.outgoing()), end(ev.outgoing()), + begin(ev->outgoing()), end(ev->outgoing()), [](HEJ::Particle const & p){ return p.type == HEJ::ParticleID::h; } ); - assert(the_Higgs != end(ev.outgoing())); + assert(the_Higgs != end(ev->outgoing())); if(std::abs(the_Higgs->rapidity()) > 5.) continue; - xs += ev.central().weight; - xs_err += ev.central().weight*ev.central().weight; + xs += ev->central().weight; + xs_err += ev->central().weight*ev->central().weight; } xs_err = std::sqrt(xs_err); std::cout << xs_ref << " ~ " << xs << " +- " << xs_err << std::endl; assert(std::abs(xs - xs_ref) < 3*xs_err); assert(xs_err < 0.02*xs); } diff --git a/FixedOrderGen/t/h_3j_uno1.cc b/FixedOrderGen/t/h_3j_uno1.cc index 4c0f6f3..c100dcb 100644 --- a/FixedOrderGen/t/h_3j_uno1.cc +++ b/FixedOrderGen/t/h_3j_uno1.cc @@ -1,72 +1,73 @@ #ifdef NDEBUG #undef NDEBUG #endif // check that adding uno emissions doesn't change the FKL cross section #include <algorithm> #include <cassert> #include <cmath> #include <iostream> #include "config.hh" #include "EventGenerator.hh" #include "HEJ/Ranlux64.hh" #include "Subleading.hh" #include "HEJ/Event.hh" #include "HEJ/MatrixElement.hh" #include "HEJ/PDF.hh" using namespace HEJFOG; int main(){ constexpr double invGeV2_to_pb = 389379292.; constexpr double xs_ref = 0.0243548; // +- 0.000119862 //calculated with HEJ revision 9570e3809613272ac4b8bf3236279ba23cf64d20 auto config = load_config("config_h_2j.yml"); config.process.njets = 3; config.process.incoming = {HEJ::pid::u, HEJ::pid::u}; config.subleading_channels = HEJFOG::Subleading::uno; HEJ::Ranlux64 ran{}; HEJFOG::EventGenerator generator{ config.process, config.beam, HEJ::ScaleGenerator{ config.scales.base, config.scales.factors, config.scales.max_ratio }, config.jets, config.pdf_id, config.subleading_fraction, config.subleading_channels, config.particles_properties, config.Higgs_coupling, ran }; double xs = 0., xs_err = 0.; int uno_found = 0; for (int trials = 0; trials < config.events; ++trials){ auto ev = generator.gen_event(); if(generator.status() != good) continue; - if(ev.type() != HEJ::event_type::FKL){ + assert(ev); + if(ev->type() != HEJ::event_type::FKL){ ++uno_found; continue; } - ev.central().weight *= invGeV2_to_pb; - ev.central().weight /= config.events; + ev->central().weight *= invGeV2_to_pb; + ev->central().weight /= config.events; - xs += ev.central().weight; - xs_err += ev.central().weight*ev.central().weight; + xs += ev->central().weight; + xs_err += ev->central().weight*ev->central().weight; } xs_err = std::sqrt(xs_err); std::cout << xs_ref << " ~ " << xs << " +- " << xs_err << '\n'; std::cout << uno_found << " events with unordered emission" << std::endl; assert(uno_found > 0); assert(std::abs(xs - xs_ref) < 3*xs_err); assert(xs_err < 0.05*xs); } diff --git a/FixedOrderGen/t/h_3j_uno2.cc b/FixedOrderGen/t/h_3j_uno2.cc index 5c6c409..4dc4770 100644 --- a/FixedOrderGen/t/h_3j_uno2.cc +++ b/FixedOrderGen/t/h_3j_uno2.cc @@ -1,67 +1,68 @@ #ifdef NDEBUG #undef NDEBUG #endif // check uno cross section #include <algorithm> #include <cassert> #include <cmath> #include <iostream> #include "config.hh" #include "EventGenerator.hh" #include "HEJ/Ranlux64.hh" #include "Subleading.hh" #include "HEJ/Event.hh" #include "HEJ/MatrixElement.hh" #include "HEJ/PDF.hh" using namespace HEJFOG; int main(){ constexpr double invGeV2_to_pb = 389379292.; constexpr double xs_ref = 0.00347538; // +- 3.85875e-05 //calculated with HEJ revision 9570e3809613272ac4b8bf3236279ba23cf64d20 auto config = load_config("config_h_2j.yml"); config.process.njets = 3; config.process.incoming = {HEJ::pid::u, HEJ::pid::u}; config.subleading_fraction = 1.; config.subleading_channels = HEJFOG::Subleading::uno; HEJ::Ranlux64 ran{}; HEJFOG::EventGenerator generator{ config.process, config.beam, HEJ::ScaleGenerator{ config.scales.base, config.scales.factors, config.scales.max_ratio }, config.jets, config.pdf_id, config.subleading_fraction, config.subleading_channels, config.particles_properties, config.Higgs_coupling, ran }; double xs = 0., xs_err = 0.; for (int trials = 0; trials < config.events; ++trials){ auto ev = generator.gen_event(); - if(ev.type() == HEJ::event_type::FKL) continue; if(generator.status() != good) continue; - ev.central().weight *= invGeV2_to_pb; - ev.central().weight /= config.events; + assert(ev); + if(ev->type() == HEJ::event_type::FKL) continue; + ev->central().weight *= invGeV2_to_pb; + ev->central().weight /= config.events; - xs += ev.central().weight; - xs_err += ev.central().weight*ev.central().weight; + xs += ev->central().weight; + xs_err += ev->central().weight*ev->central().weight; } xs_err = std::sqrt(xs_err); std::cout << xs_ref << " ~ " << xs << " +- " << xs_err << std::endl; assert(std::abs(xs - xs_ref) < 3*xs_err); assert(xs_err < 0.05*xs); } diff --git a/FixedOrderGen/t/h_5j.cc b/FixedOrderGen/t/h_5j.cc index 6aa3d40..a1b60e8 100644 --- a/FixedOrderGen/t/h_5j.cc +++ b/FixedOrderGen/t/h_5j.cc @@ -1,63 +1,64 @@ #ifdef NDEBUG #undef NDEBUG #endif // This is a regression test // the reference cross section has not been checked against any other program #include <algorithm> #include <cassert> #include <cmath> #include <iostream> #include "config.hh" #include "EventGenerator.hh" #include "HEJ/Ranlux64.hh" #include "HEJ/Event.hh" #include "HEJ/MatrixElement.hh" #include "HEJ/PDF.hh" using namespace HEJFOG; int main(){ constexpr double invGeV2_to_pb = 389379292.; constexpr double xs_ref = 0.252273; // +- 0.00657742 //calculated with HEJ revision 9570e3809613272ac4b8bf3236279ba23cf64d20 auto config = load_config("config_h_2j.yml"); config.process.njets = 5; HEJ::Ranlux64 ran{}; HEJFOG::EventGenerator generator{ config.process, config.beam, HEJ::ScaleGenerator{ config.scales.base, config.scales.factors, config.scales.max_ratio }, config.jets, config.pdf_id, config.subleading_fraction, config.subleading_channels, config.particles_properties, config.Higgs_coupling, ran }; double xs = 0., xs_err = 0.; for (int trials = 0; trials < config.events; ++trials){ auto ev = generator.gen_event(); if(generator.status() != good) continue; - ev.central().weight *= invGeV2_to_pb; - ev.central().weight /= config.events; + assert(ev); + ev->central().weight *= invGeV2_to_pb; + ev->central().weight /= config.events; - xs += ev.central().weight; - xs_err += ev.central().weight*ev.central().weight; + xs += ev->central().weight; + xs_err += ev->central().weight*ev->central().weight; } xs_err = std::sqrt(xs_err); std::cout << xs_ref << " ~ " << xs << " +- " << xs_err << std::endl; assert(std::abs(xs - xs_ref) < 3*xs_err); assert(xs_err < 0.06*xs); } diff --git a/doc/developer_manual/biblio.bib b/doc/developer_manual/biblio.bib index cd82f36..a2bfb84 100644 --- a/doc/developer_manual/biblio.bib +++ b/doc/developer_manual/biblio.bib @@ -1,138 +1,173 @@ @Article{Andersen:2008gc, author = "Andersen, Jeppe R. and Del Duca, Vittorio and White, Chris D.", title = "{Higgs Boson Production in Association with Multiple Hard Jets}", journal = "JHEP", volume = "02", year = "2009", pages = "015", eprint = "0808.3696", archivePrefix = "arXiv", primaryClass = "hep-ph", doi = "10.1088/1126-6708/2009/02/015", SLACcitation = "%%CITATION = 0808.3696;%%" } @Article{Andersen:2008ue, author = "Andersen, Jeppe R. and White, Chris D.", title = "{A New Framework for Multijet Predictions and its application to Higgs Boson production at the LHC}", journal = "Phys. Rev.", volume = "D78", year = "2008", pages = "051501", eprint = "0802.2858", archivePrefix = "arXiv", primaryClass = "hep-ph", doi = "10.1103/PhysRevD.78.051501", SLACcitation = "%%CITATION = 0802.2858;%%" } @article{Alwall:2006yp, author = "Alwall, Johan and others", title = "{A Standard format for Les Houches event files}", booktitle = "{Monte Carlos for the LHC: A Workshop on the Tools for LHC Event Simulation (MC4LHC) Geneva, Switzerland, July 17-16, 2006}", journal = "Comput. Phys. Commun.", volume = "176", year = "2007", pages = "300-304", doi = "10.1016/j.cpc.2006.11.010", eprint = "hep-ph/0609017", archivePrefix = "arXiv", primaryClass = "hep-ph", reportNumber = "FERMILAB-PUB-06-337-T, CERN-LCGAPP-2006-03", SLACcitation = "%%CITATION = HEP-PH/0609017;%%" } @article{Andersen:2017kfc, author = "Andersen, Jeppe R. and Hapola, Tuomas and Maier, Andreas and Smillie, Jennifer M.", title = "{Higgs Boson Plus Dijets: Higher Order Corrections}", journal = "JHEP", volume = "09", year = "2017", pages = "065", doi = "10.1007/JHEP09(2017)065", eprint = "1706.01002", archivePrefix = "arXiv", primaryClass = "hep-ph", reportNumber = "EDINBURGH-2017-11, IPPP-17-33, DCPT-17-66, MCNET-17-9", SLACcitation = "%%CITATION = ARXIV:1706.01002;%%" } @article{Andersen:2018tnm, author = "Andersen, Jeppe R. and Hapola, Tuomas and Heil, Marian and Maier, Andreas and Smillie, Jennifer M.", title = "{Higgs-boson plus Dijets: Higher-Order Matching for High-Energy Predictions}", year = "2018", eprint = "1805.04446", archivePrefix = "arXiv", primaryClass = "hep-ph", reportNumber = "DCPT/18/66, IPPP/18/33, MCnet-18-10, DCPT-18-66, IPPP-18-33, MCNET-18-10", SLACcitation = "%%CITATION = ARXIV:1805.04446;%%" } @article{Andersen:2012gk, author = "Andersen, Jeppe R. and Hapola, Tuomas and Smillie, Jennifer M.", title = "{W Plus Multiple Jets at the LHC with High Energy Jets}", journal = "JHEP", volume = "09", year = "2012", pages = "047", doi = "10.1007/JHEP09(2012)047", eprint = "1206.6763", archivePrefix = "arXiv", primaryClass = "hep-ph", reportNumber = "EDINBURGH-2012-13, IPPP-12-45, DCPT-12-90, CP3-ORIGINS-2012-017, DIAS-2012-18, --DIAS-2012-18", SLACcitation = "%%CITATION = ARXIV:1206.6763;%%" } @article{Andersen:2016vkp, author = "Andersen, Jeppe R. and Medley, Jack J. and Smillie, Jennifer M.", title = "{$Z/\gamma^{∗}$ plus multiple hard jets in high energy collisions}", journal = "JHEP", volume = "05", year = "2016", pages = "136", doi = "10.1007/JHEP05(2016)136", eprint = "1603.05460", archivePrefix = "arXiv", primaryClass = "hep-ph", reportNumber = "EDINBURGH-2016-03, IPPP-16-19, DCPT-16-38, MCNET-16-08", SLACcitation = "%%CITATION = ARXIV:1603.05460;%%" } @article{DelDuca:2003ba, author = "Del Duca, V. and Kilgore, W. and Oleari, C. and Schmidt, C.R. and Zeppenfeld, D.", title = "{Kinematical limits on Higgs boson production via gluon fusion in association with jets}", journal = "Phys.Rev.", volume = "D67", pages = "073003", doi = "10.1103/PhysRevD.67.073003", year = "2003", eprint = "hep-ph/0301013", archivePrefix = "arXiv", primaryClass = "hep-ph", reportNumber = "DCPT-02-148, DFTT-20-2002, IPPP-02-74, MADPH-02-1276, MSUHEP-20620", SLACcitation = "%%CITATION = HEP-PH/0301013;%%", } @article{DelDuca:2001fn, author = "Del Duca, V. and Kilgore, W. and Oleari, C. and Schmidt, C. and Zeppenfeld, D.", title = "{Gluon fusion contributions to H + 2 jet production}", journal = "Nucl.Phys.", volume = "B616", pages = "367-399", doi = "10.1016/S0550-3213(01)00446-1", year = "2001", eprint = "hep-ph/0108030", archivePrefix = "arXiv", primaryClass = "hep-ph", reportNumber = "MADPH-01-1235, BNL-HET-01-28, MSUHEP-10709, DFTT-19-2001", SLACcitation = "%%CITATION = HEP-PH/0108030;%%", } +@article{Andersen:2011zd, + author = "Andersen, Jeppe R. and Lonnblad, Leif and Smillie, + Jennifer M.", + title = "{A Parton Shower for High Energy Jets}", + journal = "JHEP", + volume = "07", + year = "2011", + pages = "110", + doi = "10.1007/JHEP07(2011)110", + eprint = "1104.1316", + archivePrefix = "arXiv", + primaryClass = "hep-ph", + reportNumber = "CERN-PH-TH-2011-072, CP3-ORIGINS-2011-14, + EDINBURGH-2011-16, LU-TP-11-15, MCNET-11-12, LU-TP + --11-15", + SLACcitation = "%%CITATION = ARXIV:1104.1316;%%" +} +@article{Andersen:2017sht, + author = "Andersen, Jeppe R. and Brooks, Helen M. and Lönnblad, + Leif", + title = "{Merging High Energy with Soft and Collinear Logarithms + using HEJ and PYTHIA}", + journal = "JHEP", + volume = "09", + year = "2018", + pages = "074", + doi = "10.1007/JHEP09(2018)074", + eprint = "1712.00178", + archivePrefix = "arXiv", + primaryClass = "hep-ph", + reportNumber = "CPT/17/184, IPPP/17/92, LU-TP 17-38, MCnet-17-22, + CoEPP-MN-17-22, CPT-17-184, IPPP-17-92, LU-TP-17-38, + MCNET-17-22, COEPP-MN-17-22", + SLACcitation = "%%CITATION = ARXIV:1712.00178;%%" +} diff --git a/doc/developer_manual/developer_manual.tex b/doc/developer_manual/developer_manual.tex index e7c5692..6c83b9a 100644 --- a/doc/developer_manual/developer_manual.tex +++ b/doc/developer_manual/developer_manual.tex @@ -1,1438 +1,1537 @@ \documentclass[a4paper,11pt]{article} \usepackage{fourier} \usepackage[T1]{fontenc} \usepackage{microtype} \usepackage{geometry} \usepackage{enumitem} \setlist[description]{leftmargin=\parindent,labelindent=\parindent} \usepackage{amsmath} \usepackage{amssymb} \usepackage[utf8x]{inputenc} \usepackage{graphicx} \usepackage{xcolor} \usepackage{todonotes} \usepackage{listings} \usepackage{xspace} \usepackage{tikz} +\usepackage{subcaption} \usetikzlibrary{arrows.meta} \usetikzlibrary{shapes} \usetikzlibrary{calc} \usepackage[colorlinks,linkcolor={blue!50!black}]{hyperref} \graphicspath{{build/figures/}{figures/}} \emergencystretch \hsize \newcommand{\HEJ}{{\tt HEJ}\xspace} \newcommand{\HIGHEJ}{\emph{High Energy Jets}\xspace} \newcommand{\cmake}{\href{https://cmake.org/}{cmake}\xspace} \newcommand{\html}{\href{https://www.w3.org/html/}{html}\xspace} \newcommand{\YAML}{\href{http://yaml.org/}{YAML}\xspace} \newcommand{\QCDloop}{\href{https://github.com/scarrazza/qcdloop}{QCDloop}\xspace} \newcommand{\as}{\alpha_s} \DeclareRobustCommand{\mathgraphics}[1]{\vcenter{\hbox{\includegraphics{#1}}}} \def\spa#1.#2{\left\langle#1\,#2\right\rangle} \def\spb#1.#2{\left[#1\,#2\right]} \def\spaa#1.#2.#3{\langle\mskip-1mu{#1} | #2 | {#3}\mskip-1mu\rangle} \def\spbb#1.#2.#3{[\mskip-1mu{#1} | #2 | {#3}\mskip-1mu]} \def\spab#1.#2.#3{\langle\mskip-1mu{#1} | #2 | {#3}\mskip-1mu\rangle} \def\spba#1.#2.#3{\langle\mskip-1mu{#1}^+ | #2 | {#3}^+\mskip-1mu\rangle} \def\spav#1.#2.#3{\|\mskip-1mu{#1} | #2 | {#3}\mskip-1mu\|^2} \def\jc#1.#2.#3{j^{#1}_{#2#3}} \definecolor{darkgreen}{rgb}{0,0.4,0} \lstset{ % backgroundcolor=\color{lightgray}, % choose the background color; you must add \usepackage{color} or \usepackage{xcolor} basicstyle=\footnotesize\usefont{T1}{DejaVuSansMono-TLF}{m}{n}, % the size of the fonts that are used for the code breakatwhitespace=false, % sets if automatic breaks should only happen at whitespace breaklines=false, % sets automatic line breaking captionpos=t, % sets the caption-position to bottom commentstyle=\color{red}, % comment style deletekeywords={...}, % if you want to delete keywords from the given language escapeinside={\%*}{*)}, % if you want to add LaTeX within your code extendedchars=true, % lets you use non-ASCII characters; for 8-bits encodings only, does not work with UTF-8 frame=false, % adds a frame around the code keepspaces=true, % keeps spaces in text, useful for keeping indentation of code (possibly needs columns=flexible) keywordstyle=\color{blue}, % keyword style otherkeywords={}, % if you want to add more keywords to the set numbers=none, % where to put the line-numbers; possible values are (none, left, right) numbersep=5pt, % how far the line-numbers are from the code rulecolor=\color{black}, % if not set, the frame-color may be changed on line-breaks within not-black text (e.g. comments (green here)) showspaces=false, % show spaces everywhere adding particular underscores; it overrides 'showstringspaces' showstringspaces=false, % underline spaces within strings only showtabs=false, % show tabs within strings adding particular underscores stepnumber=2, % the step between two line-numbers. If it's 1, each line will be numbered stringstyle=\color{gray}, % string literal style tabsize=2, % sets default tabsize to 2 spaces title=\lstname, emph={}, emphstyle=\color{darkgreen} } \begin{document} \tikzstyle{mynode}=[rectangle split,rectangle split parts=2, draw,rectangle split part fill={lightgray, none}] \title{HEJ 2 developer manual} \author{} \maketitle \tableofcontents \newpage \section{Overview} \label{sec:overview} HEJ 2 is a C++ program and library implementing an algorithm to apply \HIGHEJ resummation~\cite{Andersen:2008ue,Andersen:2008gc} to pre-generated fixed-order events. This document is intended to give an overview over the concepts and structure of this implementation. \subsection{Project structure} \label{sec:project} HEJ 2 is developed under the \href{https://git-scm.com/}{git} version control system. The main repository is on the IPPP \href{https://gitlab.com/}{gitlab} server under \url{https://gitlab.dur.scotgrid.ac.uk/hej/hej}. To get a local copy, get an account on the gitlab server and use \begin{lstlisting}[language=sh,caption={}] git clone git@gitlab.dur.scotgrid.ac.uk:hej/hej.git \end{lstlisting} This should create a directory \texttt{hej} with the following contents: \begin{description} \item[doc:] Contains additional documentation, see section~\ref{sec:doc}. \item[include:] Contains the C++ header files. \item[src:] Contains the C++ source files. \item[t:] Contains the source code for the automated tests. \item[CMakeLists.txt:] Configuration file for the \cmake build system. See section~\ref{sec:cmake}. \item[cmake:] Auxiliary files for \cmake. This includes modules for finding installed software in \texttt{cmake/Modules} and templates for code generation during the build process in \texttt{cmake/Templates}. \item[config.yml:] Sample configuration file for running HEJ 2. \item[FixedOrderGen:] Contains the code for the fixed-order generator, see section~\ref{sec:HEJFOG}. \end{description} In the following all paths are given relative to the \texttt{hej} directory. \subsection{Documentation} \label{sec:doc} The \texttt{doc} directory contains user documentation in \texttt{doc/sphinx} and the configuration to generate source code documentation in \texttt{doc/doxygen}. The user documentation explains how to install and run HEJ 2. The format is \href{http://docutils.sourceforge.net/rst.html}{reStructuredText}, which is mostly human-readable. Other formats, like \html, can be generated with the \href{http://www.sphinx-doc.org/en/master/}{sphinx} generator with \begin{lstlisting}[language=sh,caption={}] make html \end{lstlisting} To document the source code we use \href{https://www.stack.nl/~dimitri/doxygen/}{doxygen}. To generate \html documentation, use the command \begin{lstlisting}[language=sh,caption={}] doxygen Doxyfile \end{lstlisting} in the \texttt{doc/doxygen} directory. \subsection{Build system} \label{sec:cmake} For the most part, HEJ 2 is a library providing classes and functions that can be used to add resummation to fixed-order events. In addition, there is a relatively small executable program leveraging this library to read in events from an input file and produce resummation events. Both the library and the program are built and installed with the help of \cmake. Debug information can be turned on by using \begin{lstlisting}[language=sh,caption={}] cmake base/directory -DCMAKE_BUILD_TYPE=Debug make install \end{lstlisting} This facilitates the use of debuggers like \href{https://www.gnu.org/software/gdb/}{gdb}. The main \cmake configuration file is \texttt{CMakeLists.txt}. It defines the compiler flags, software prerequisites, header and source files used to build HEJ 2, and the automated tests. \texttt{cmake/Modules} contains module files that help with the detection of the software prerequisites and \texttt{cmake/Templates} template files for the automatic generation of header and source files. For example, this allows to only keep the version information in one central location (\texttt{CMakeLists.txt}) and automatically generate a header file from the template \texttt{Version.hh.in} to propagate this to the C++ code. \subsection{General coding guidelines} \label{sec:notes} The goal is to make the HEJ 2 code well-structured and readable. Here are a number of guidelines to this end. \begin{description} \item[Observe the boy scout rule.] Always leave the code cleaner than how you found it. Ugly hacks can be useful for testing, but shouldn't make their way into the main branch. \item[Ask if something is unclear.] Often there is a good reason why code is written the way it is. Sometimes that reason is only obvious to the original author (use \lstinline!git blame! to find them), in which case they should be poked to add a comment. Sometimes there is no good reason, but nobody has had the time to come up with something better, yet. In some places the code might just be bad. \item[Don't break tests.] There are a number of tests in the \texttt{t} directory, which can be run with \lstinline!make test!. Ideally, all tests should run successfully in each git revision. If your latest commit broke a test and you haven't pushed to the central repository yet, you can fix it with \lstinline!git commit --amend!. If an earlier local commit broke a test, you can use \lstinline!git rebase -i! if you feel confident. Additionally each \lstinline!git push! is also automatically tested via the GitLab CI (see appendix~\ref{sec:gitlabCI}). \item[Test your new code.] When you add some new functionality, also add an automated test. This can be useful even if you don't know the ``correct'' result because it prevents the code from changing its behaviour silently in the future. \href{http://www.valgrind.org/}{valgrind} is a very useful tool to detect potential memory leaks. \item[Stick to the coding style.] It is somewhat easier to read code that has a uniform coding and indentation style. We don't have a strict style, but it helps if your code looks similar to what is already there. \end{description} \section{Program flow} \label{sec:flow} A run of the HEJ 2 program has three stages: initialisation, event processing, and cleanup. The following sections outline these stages and their relations to the various classes and functions in the code. Unless denoted otherwise, all classes and functions are part of the \lstinline!HEJ! namespace. The code for the HEJ 2 program is in \texttt{src/bin/HEJ.cc}, all other code comprises the HEJ 2 library. Classes and free functions are usually implemented in header and source files with a corresponding name, i.e. the code for \lstinline!MyClass! can usually be found in \texttt{include/HEJ/MyClass.hh} and \texttt{src/MyClass.cc}. \subsection{Initialisation} \label{sec:init} The first step is to load and parse the \YAML configuration file. The entry point for this is the \lstinline!load_config! function and the related code can be found in \texttt{include/HEJ/YAMLreader.hh}, \texttt{include/HEJ/config.hh} and the corresponding \texttt{.cc} files in the \texttt{src} directory. The implementation is based on the \href{https://github.com/jbeder/yaml-cpp}{yaml-cpp} library. The \lstinline!load_config! function returns a \lstinline!Config! object containing all settings. To detect potential mistakes as early as possible, we throw an exception whenever one of the following errors occurs: \begin{itemize} \item There is an unknown option in the \YAML file. \item A setting is invalid, for example a string is given where a number would be expected. \item An option value is not set. \end{itemize} The third rule is sometimes relaxed for ``advanced'' settings with an obvious default, like for importing custom scales or analyses. The information stored in the \lstinline!Config! object is then used to initialise various objects required for the event processing stage described in section~\ref{sec:processing}. First, the \lstinline!get_analysis! function creates an object that inherits from the \lstinline!Analysis! interface.\footnote{In the context of C++ the proper technical expression is ``pure abstract class''.} Using an interface allows us to decide the concrete type of the analysis at run time instead of having to make a compile-time decision. Depending on the settings, \lstinline!get_analysis! creates either a user-defined analysis loaded from an external library (see the user documentation \url{https://hej.web.cern.ch/HEJ/doc/current/user/}) or the default \lstinline!EmptyAnalysis!, which does nothing. Together with a number of further objects, whose roles are described in section~\ref{sec:processing}, we also initialise the global random number generator. We again use an interface to defer deciding the concrete type until the program is actually run. Currently, we support the \href{https://mixmax.hepforge.org/}{MIXMAX} (\texttt{include/HEJ/Mixmax.hh}) and Ranlux64 (\texttt{include/HEJ/Ranlux64.hh}) random number generators, both are provided by \href{http://proj-clhep.web.cern.ch/}{CLHEP}. We also set up a \lstinline!LHEF::Reader! object (see \href{http://home.thep.lu.se/~leif/LHEF/}{\texttt{include/LHEF/LHEF.h}}) for reading events from a file in the Les Houches event file format~\cite{Alwall:2006yp}. A small wrapper around the \href{https://www.boost.org/doc/libs/1_67_0/libs/iostreams/doc/index.html}{boost iostreams} library allows us to also read event files compressed with \href{https://www.gnu.org/software/gzip/}{gzip}. The wrapper code is in \texttt{include/HEJ/stream.hh} and the \texttt{src/stream.cc}. \subsection{Event processing} \label{sec:processing} In the second stage events are continously read from the event file. After jet clustering, a number of corresponding resummation events are generated for each input event and fed into the analysis and a number of output files. The roles of various classes and functions are illustrated in the following flow chart: \begin{center} \begin{tikzpicture}[node distance=2cm and 5mm] \node (reader) [mynode] {\lstinline!LHEF::Reader::readEvent!\nodepart{second}{read event}}; \node - (cluster) [mynode,below=of reader] - {\lstinline!Event! constructor\nodepart{second}{cluster jets}}; + (data) [mynode,below=of reader] + {\lstinline!Event::EventData! constructor\nodepart{second}{convert to \HEJ object}}; + \node + (cluster) [mynode,below=of data] + {\lstinline!Event::EventData::cluster!\nodepart{second}{cluster jets \& + classify \lstinline!EventType!}}; \node (resum) [mynode,below=of cluster] {\lstinline!EventReweighter::reweight!\nodepart{second}{perform resummation}}; \node (cut) [mynode,below=of resum] {\lstinline!Analysis::pass_cuts!\nodepart{second}{apply cuts}}; \node (fill) [mynode,below left=of cut] {\lstinline!Analysis::fill!\nodepart{second}{analyse event}}; \node (write) [mynode,below right=of cut] {\lstinline!CombinedEventWriter::write!\nodepart{second}{write out event}}; \node (control) [below=of cut] {}; \draw[-{Latex[length=3mm, width=1.5mm]}] - (reader.south) -- node[left] {\lstinline!LHEF::HEPEUP!} (cluster.north); + (reader.south) -- node[left] {\lstinline!LHEF::HEPEUP!} (data.north); + \draw[-{Latex[length=3mm, width=1.5mm]}] + (data.south) -- node[left] {\lstinline!Event::EventData!} (cluster.north); \draw[-{Latex[length=3mm, width=1.5mm]}] (cluster.south) -- node[left] {\lstinline!Event!} (resum.north); \draw[-{Latex[length=3mm, width=1.5mm]}] (resum.south) -- (cut.north); \draw[-{Latex[length=3mm, width=1.5mm]}] ($(resum.south)+(7mm, 0cm)$) -- ($(cut.north)+(7mm, 0cm)$); \draw[-{Latex[length=3mm, width=1.5mm]}] ($(resum.south)-(7mm, 0cm)$) -- node[left] {\lstinline!Event!} ($(cut.north)-(7mm, 0cm)$); \draw[-{Latex[length=3mm, width=1.5mm]}] ($(cut.south)-(3mm,0mm)$) .. controls ($(control)-(3mm,0mm)$) ..node[left] {\lstinline!Event!} (fill.east); \draw[-{Latex[length=3mm, width=1.5mm]}] ($(cut.south)-(3mm,0mm)$) .. controls ($(control)-(3mm,0mm)$) .. (write.west); \draw[-{Latex[length=3mm, width=1.5mm]}] ($(cut.south)+(3mm,0mm)$) .. controls ($(control)+(3mm,0mm)$) .. (fill.east); \draw[-{Latex[length=3mm, width=1.5mm]}] ($(cut.south)+(3mm,0mm)$) .. controls ($(control)+(3mm,0mm)$) ..node[right] {\lstinline!Event!} (write.west); \end{tikzpicture} \end{center} -The resummation is performed by the \lstinline!EventReweighter! class, -which is described in more detail in section~\ref{sec:resum}. The -\lstinline!CombinedEventWriter! writes events to zero or more output -files. To this end, it contains a number of objects implementing the -\lstinline!EventWriter! interface. These event writers typically write -the events to a file in a given format. We currently have the -\lstinline!LesHouchesWriter! for event files in the Les Houches Event -File format and the \lstinline!HepMCWriter! for the + +\lstinline!EventData! is an intermediate container, its members are completely +accessible. In contrast after jet clustering and classification the phase space +inside \lstinline!Event! can not be changed any more +(\href{https://wikipedia.org/wiki/Builder_pattern}{Builder design pattern}). The +resummation is performed by the \lstinline!EventReweighter! class, which is +described in more detail in section~\ref{sec:resum}. The +\lstinline!CombinedEventWriter! writes events to zero or more output files. To +this end, it contains a number of objects implementing the +\lstinline!EventWriter! interface. These event writers typically write the +events to a file in a given format. We currently have the +\lstinline!LesHouchesWriter! for event files in the Les Houches Event File +format and the \lstinline!HepMCWriter! for the \href{https://hepmc.web.cern.ch/hepmc/}{HepMC} format (Version 2 and 3). \subsection{Resummation} \label{sec:resum} In the \lstinline!EventReweighter::reweight! member function, we first classify the input fixed-order event (FKL, unordered, non-HEJ, \dots) and decide according to the user settings whether to discard, keep, or resum the event. If we perform resummation for the given event, we generate a number of trial \lstinline!PhaseSpacePoint! objects. Phase space generation is discussed in more detail in section~\ref{sec:pspgen}. We then perform jet clustering according to the settings for the resummation jets on each \lstinline!PhaseSpacePoint!, update the factorisation and renormalisation scale in the resulting \lstinline!Event! and reweight it according to the ratio of pdf factors and \HEJ matrix elements between resummation and original fixed-order event: \begin{center} - \begin{tikzpicture}[node distance=2cm and 5mm] + \begin{tikzpicture}[node distance=1.5cm and 5mm] \node (in) {}; \node (treat) [diamond,draw,below=of in,minimum size=3.5cm, label={[anchor=west, inner sep=8pt]west:discard}, label={[anchor=east, inner sep=14pt]east:keep}, label={[anchor=south, inner sep=20pt]south:reweight} ] {}; \draw (treat.north west) -- (treat.south east); \draw (treat.north east) -- (treat.south west); \node (psp) [mynode,below=of treat] {\lstinline!PhaseSpacePoint! constructor}; \node (cluster) [mynode,below=of psp] - {\lstinline!Event! constructor\nodepart{second}{cluster jets}}; + {\lstinline!Event::EventData::cluster!\nodepart{second}{cluster jets}}; + \node + (colour) [mynode,below=of cluster] + {\lstinline!Event::generate_colours()!\nodepart{second}{generate particle colour}}; \node - (gen_scales) [mynode,below=of cluster] + (gen_scales) [mynode,below=of colour] {\lstinline!ScaleGenerator::operator()!\nodepart{second}{update scales}}; \node (rescale) [mynode,below=of gen_scales] {\lstinline!PDF::pdfpt!, \lstinline!MatrixElement!\nodepart{second}{reweight}}; \node (out) [below of=rescale] {}; + \draw[-{Latex[length=3mm, width=1.5mm]}] (in.south) -- node[left] {\lstinline!Event!} (treat.north); \draw[-{Latex[length=3mm, width=1.5mm]}] (treat.south) -- node[left] {\lstinline!Event!} (psp.north); + \draw[-{Latex[length=3mm, width=1.5mm]}] (psp.south) -- (cluster.north); \draw[-{Latex[length=3mm, width=1.5mm]}] ($(psp.south)+(7mm, 0cm)$) -- ($(cluster.north)+(7mm, 0cm)$); \draw[-{Latex[length=3mm, width=1.5mm]}] ($(psp.south)-(7mm, 0cm)$) -- node[left] {\lstinline!PhaseSpacePoint!} ($(cluster.north)-(7mm, 0cm)$); + \draw[-{Latex[length=3mm, width=1.5mm]}] - (cluster.south) -- (gen_scales.north); + (cluster.south) -- (colour.north); \draw[-{Latex[length=3mm, width=1.5mm]}] - ($(cluster.south)+(7mm, 0cm)$) -- ($(gen_scales.north)+(7mm, 0cm)$); + ($(cluster.south)+(7mm, 0cm)$) -- ($(colour.north)+(7mm, 0cm)$); \draw[-{Latex[length=3mm, width=1.5mm]}] ($(cluster.south)-(7mm, 0cm)$) -- node[left] + {\lstinline!Event!} ($(colour.north)-(7mm, 0cm)$); + + \draw[-{Latex[length=3mm, width=1.5mm]}] + (colour.south) -- (gen_scales.north); + \draw[-{Latex[length=3mm, width=1.5mm]}] + ($(colour.south)+(7mm, 0cm)$) -- ($(gen_scales.north)+(7mm, 0cm)$); + \draw[-{Latex[length=3mm, width=1.5mm]}] + ($(colour.south)-(7mm, 0cm)$) -- node[left] {\lstinline!Event!} ($(gen_scales.north)-(7mm, 0cm)$); + \draw[-{Latex[length=3mm, width=1.5mm]}] (gen_scales.south) -- (rescale.north); \draw[-{Latex[length=3mm, width=1.5mm]}] ($(gen_scales.south)+(7mm, 0cm)$) -- ($(rescale.north)+(7mm, 0cm)$); \draw[-{Latex[length=3mm, width=1.5mm]}] ($(gen_scales.south)-(7mm, 0cm)$) -- node[left] {\lstinline!Event!} ($(rescale.north)-(7mm, 0cm)$); + \draw[-{Latex[length=3mm, width=1.5mm]}] (rescale.south) -- (out.north); \draw[-{Latex[length=3mm, width=1.5mm]}] ($(rescale.south)+(7mm, 0cm)$) -- ($(out.north)+(7mm, 0cm)$); \draw[-{Latex[length=3mm, width=1.5mm]}] ($(rescale.south)-(7mm, 0cm)$) -- node[left] {\lstinline!Event!} ($(out.north)-(7mm, 0cm)$); + \node (helper) at ($(treat.east) + (15mm,0cm)$) {}; \draw[-{Latex[length=3mm, width=1.5mm]}] (treat.east) -- ($(treat.east) + (15mm,0cm)$) -- node[left] {\lstinline!Event!} (helper |- gen_scales.east) -- (gen_scales.east) ; \end{tikzpicture} \end{center} - \subsection{Phase space point generation} \label{sec:pspgen} The resummed and matched \HEJ cross section for pure jet production of FKL configurations is given by (cf. eq. (3) of~\cite{Andersen:2018tnm}) \begin{align} \label{eq:resumdijetFKLmatched2} % \begin{split} \sigma&_{2j}^\mathrm{resum, match}=\sum_{f_1, f_2}\ \sum_m \prod_{j=1}^m\left( \int_{p_{j\perp}^B=0}^{p_{j\perp}^B=\infty} \frac{\mathrm{d}^2\mathbf{p}_{j\perp}^B}{(2\pi)^3}\ \int \frac{\mathrm{d} y_j^B}{2} \right) \ (2\pi)^4\ \delta^{(2)}\!\!\left(\sum_{k=1}^{m} \mathbf{p}_{k\perp}^B\right)\nonumber\\ &\times\ x_a^B\ f_{a, f_1}(x_a^B, Q_a^B)\ x_b^B\ f_{b, f_2}(x_b^B, Q_b^B)\ \frac{\overline{\left|\mathcal{M}_\text{LO}^{f_1f_2\to f_1g\cdots gf_2}\big(\big\{p^B_j\big\}\big)\right|}^2}{(\hat {s}^B)^2}\nonumber\\ & \times (2\pi)^{-4+3m}\ 2^m \nonumber\\ &\times\ \sum_{n=2}^\infty\ \int_{p_{1\perp}=p_{\perp,\mathrm{min}} }^{p_{1\perp}=\infty} \frac{\mathrm{d}^2\mathbf{p}_{1\perp}}{(2\pi)^3}\ \int_{p_{n\perp}=p_{\perp,\mathrm{min}}}^{p_{n\perp}=\infty} \frac{\mathrm{d}^2\mathbf{p}_{n\perp}}{(2\pi)^3}\ \prod_{i=2}^{n-1}\int_{p_{i\perp}=\lambda}^{p_{i\perp}=\infty} \frac{\mathrm{d}^2\mathbf{p}_{i\perp}}{(2\pi)^3}\ (2\pi)^4\ \delta^{(2)}\!\!\left(\sum_{k=1}^n \mathbf{p}_{k\perp}\right )\\ &\times \ \mathbf{T}_y \prod_{i=1}^n \left(\int \frac{\mathrm{d} y_i}{2}\right)\ \mathcal{O}_{mj}^e\ \left(\prod_{l=1}^{m-1}\delta^{(2)}(\mathbf{p}_{\mathcal{J}_{l}\perp}^B - \mathbf{j}_{l\perp})\right)\ \left(\prod_{l=1}^m\delta(y^B_{\mathcal{J}_l}-y_{\mathcal{J}_l})\right) \ \mathcal{O}_{2j}(\{p_i\})\nonumber\\ &\times \frac{(\hat{s}^B)^2}{\hat{s}^2}\ \frac{x_a f_{a,f_1}(x_a, Q_a)\ x_b f_{b,f_2}(x_b, Q_b)}{x_a^B\ f_{a,f_1}(x_a^B, Q_a^B)\ x_b^B\ f_{b,f_2}(x_b^B, Q_b^B)}\ \frac{\overline{\left|\mathcal{M}_{\mathrm{HEJ}}^{f_1 f_2\to f_1 g\cdots gf_2}(\{ p_i\})\right|}^2}{\overline{\left|\mathcal{M}_\text{LO, HEJ}^{f_1f_2\to f_1g\cdots gf_2}\big(\big\{p^B_j\big\}\big)\right|}^{2}} \,.\nonumber % \end{split} \end{align} The first two lines correspond to the generation of the fixed-order input events with incoming partons $f_1, f_2$ and outgoing momenta $p_j^B$, where $\mathbf{p}_{j\perp}^B$ and $y_j^B$ denote the respective transverse momentum and rapidity. Note that, at leading order, these coincide with the fixed-order jet momenta $p_{\mathcal{J}_j}^B$. $f_{a,f_1}(x_a, Q_a),f_{b,f_2}(x_b, Q_b)$ are the pdf factors for the incoming partons with momentum fractions $x_a$ and $x_b$. The square of the partonic centre-of-mass energy is denoted by $\hat{s}^B$ and $\mathcal{M}_\text{LO}^{f_1f_2\to f_1g\cdots gf_2}$ is the leading-order matrix element. The third line is a factor accounting for the different multiplicities between fixed-order and resummation events. Lines four and five are the integration over the resummation phase space described in this section. $p_i$ are the momenta of the outgoing partons in resummation phase space. $\mathbf{T}_y$ denotes rapidity ordering and $\mathcal{O}_{mj}^e$ projects out the exclusive $m$-jet component. The relation between resummation and fixed-order momenta is fixed by the $\delta$ functions. The first sets each transverse fixed-order jet momentum to some function $\mathbf{j_{l\perp}}$ of the resummation momenta. The exact form is described in section~\ref{sec:ptj_res}. The second $\delta$ forces the rapidities of resummation and fixed-order jets to be the same. Finally, the last line is the reweighting of pdf and matrix element factors already shown in section~\ref{sec:resum}. There are two kinds of cut-off in the integration over the resummation partons. $\lambda$ is a technical cut-off connected to the cancellation of infrared divergencies between real and virtual corrections. Its numerical value is set in \texttt{include/HEJ/Constants.h}. $p_{\perp,\mathrm{min}}$ regulates and \emph{uncancelled} divergence in the extremal parton momenta. Its size is set by the user configuration \url{https://hej.web.cern.ch/HEJ/doc/current/user/HEJ.html#settings}. It is straightforward to generalise eq.~(\ref{eq:resumdijetFKLmatched2}) to unordered configurations and processes with additional colourless emissions, for example a Higgs or electroweak boson. In the latter case only the fixed-order integration and the matrix elements change. \subsubsection{Gluon Multiplicity} \label{sec:psp_ng} The first step in evaluating the resummation phase space in eq.~(\ref{eq:resumdijetFKLmatched2}) is to randomly pick terms in the sum over the number of emissions. This sampling of the gluon multiplicity is done in the \lstinline!PhaseSpacePoint::sample_ng! function in \texttt{src/PhaseSpacePoint.cc}. The typical number of extra emissions depends strongly on the rapidity span of the underlying fixed-order event. Let us, for example, consider a fixed-order FKL-type multi-jet configuration with rapidities $y_{j_f},\,y_{j_b}$ of the most forward and backward jets, respectively. By eq.~(\ref{eq:resumdijetFKLmatched2}), the jet multiplicity and the rapidity of each jet are conserved when adding resummation. This implies that additional hard radiation is restricted to rapidities $y$ within a region $y_{j_b} \lesssim y \lesssim y_{j_f}$. Within \HEJ, we require the most forward and most backward emissions to be hard \todo{specify how hard} in order to avoid divergences, so this constraint in fact applies to \emph{all} additional radiation. To simplify the remaining discussion, let us remove the FKL rapidity ordering \begin{equation} \label{eq:remove_y_order} \mathbf{T}_y \prod_{i=1}^n\int \frac{\mathrm{d}y_i}{2} = \frac{1}{n!}\prod_{i=1}^n\int \frac{\mathrm{d}y_i}{2}\,, \end{equation} where all rapidity integrals now cover a region which is approximately bounded by $y_{j_b}$ and $y_{j_f}$. Each of the $m$ jets has to contain at least one parton; selecting random emissions we can rewrite the phase space integrals as \begin{equation} \label{eq:select_jets} \frac{1}{n!}\prod_{i=1}^n\int [\mathrm{d}p_i] = \left(\prod_{i=1}^{m}\int [\mathrm{d}p_i]\ {\cal J}_i(p_i)\right) \frac{1}{n_g!}\prod_{i=m+1}^{m+n_g}\int [\mathrm{d}p_i] \end{equation} with jet selection functions \begin{equation} \label{eq:def_jet_selection} {\cal J}_i(p) = \begin{cases} 1 &p\text{ clustered into jet }i\\ 0 & \text{otherwise} \end{cases} \end{equation} and $n_g \equiv n - m$. Here and in the following we use the short-hand notation $[\mathrm{d}p_i]$ to denote the phase-space measure for parton $i$. As is evident from eq.~\eqref{eq:select_jets}, adding an extra emission $n_g+1$ introduces a suppression factor $\tfrac{1}{n_g+1}$. However, the additional phase space integral also results in an enhancement proportional to $\Delta y_{j_f j_b} = y_{j_f} - y_{j_b}$. This is a result of the rapidity-independence of the MRK limit of the integrand, consisting of the matrix elements divided by the flux factor. Indeed, we observe that the typical number of gluon emissions is to a good approximation proportional to the rapidity separation and the phase space integral is dominated by events with $n_g \approx \Delta y_{j_f j_b}$. For the actual phase space sampling, we assume a Poisson distribution and extract the mean number of gluon emissions in different rapidity bins and fit the results to a linear function in $\Delta y_{j_f j_b}$, finding a coefficient of $0.975$ for the inclusive production of a Higgs boson with two jets. Here are the observed and fitted average gluon multiplicities as a function of $\Delta y_{j_f j_b}$: \begin{center} \includegraphics[width=.75\textwidth]{ng_mean} \end{center} As shown for two rapidity slices the assumption of a Poisson distribution is also a good approximation: \begin{center} \includegraphics[width=.49\textwidth]{{ng_1.5}.pdf}\hfill \includegraphics[width=.49\textwidth]{{ng_5.5}.pdf} \end{center} \subsubsection{Number of Gluons inside Jets} \label{sec:psp_ng_jet} For each of the $n_g$ gluon emissions we can split the phase-space integral into a (disconnected) region inside the jets and a remainder: \begin{equation} \label{eq:psp_split} \int [\mathrm{d}p_i] = \int [\mathrm{d}p_i]\, \theta\bigg(\sum_{j=1}^{m}{\cal J}_j(p_i)\bigg) + \int [\mathrm{d}p_i]\, \bigg[1-\theta\bigg(\sum_{j=1}^{m}{\cal J}_j(p_i)\bigg)\bigg]\,. \end{equation} The next step is to decide how many of the gluons will form part of a jet. This is done in the \lstinline!PhaseSpacePoint::sample_ng_jets! function. We choose an importance sampling which is flat in the plane spanned by the azimuthal angle $\phi$ and the rapidity $y$. This is observed in BFKL and valid in the limit of Multi-Regge-Kinematics (MRK). Furthermore, we assume anti-$k_t$ jets, which cover an area of $\pi R^2$. In principle, the total accessible area in the $y$-$\phi$ plane is given by $2\pi \Delta y_{fb}$, where $\Delta y_{fb}\geq \Delta y_{j_f j_b}$ is the a priori unknown rapidity separation between the most forward and backward partons. In most cases the extremal jets consist of single partons, so that $\Delta y_{fb} = \Delta y_{j_f j_b}$. For the less common case of two partons forming a jet we observe a maximum distance of $R$ between the constituents and the jet centre. In rare cases jets have more than two constituents. Empirically, they are always within a distance of $\tfrac{5}{3}R$ to the centre of the jet, so $\Delta y_{fb} \leq \Delta y_{j_f j_b} + \tfrac{10}{3} R$. In practice, the extremal partons are required to carry a large fraction of the jet transverse momentum and will therefore be much closer to the jet axis. In summary, for sufficiently large rapidity separations we can use the approximation $\Delta y_{fb} \approx \Delta y_{j_f j_b}$. This scenario is depicted here: \begin{center} \includegraphics[width=0.5\linewidth]{ps_large_y} \end{center} If there is no overlap between jets, the probability $p_{\cal J, >}$ for an extra gluon to end up inside a jet is then given by \begin{equation} \label{eq:p_J_large} p_{\cal J, >} = \frac{(m - 1)\*R^2}{2\Delta y_{j_f j_b}}\,. \end{equation} For a very small rapidity separation, eq.~\eqref{eq:p_J_large} obviously overestimates the true probability. The maximum phase space covered by jets in the limit of a vanishing rapidity distance between all partons is $2mR \Delta y_{fb}$: \begin{center} \includegraphics[width=0.5\linewidth]{ps_small_y} \end{center} We therefore estimate the probability for a parton to end up inside a jet as \begin{equation} \label{eq:p_J} p_{\cal J} = \min\bigg(\frac{(m - 1)\*R^2}{2\Delta y_{j_f j_b}}, \frac{mR}{\pi}\bigg)\,. \end{equation} Here we compare this estimate with the actually observed fraction of additional emissions into jets as a function of the rapidity separation: \begin{center} \includegraphics[width=0.75\linewidth]{pJ} \end{center} \subsubsection{Gluons outside Jets} \label{sec:gluons_nonjet} Using our estimate for the probability of a gluon to be a jet constituent, we choose a number $n_{g,{\cal J}}$ of gluons inside jets, which also fixes the number $n_g - n_{g,{\cal J}}$ of gluons outside jets. As explained later on, we need to generate the momenta of the gluons outside jets first. This is done in \lstinline!PhaseSpacePoint::gen_non_jet!. The azimuthal angle $\phi$ is generated flat within $0\leq \phi \leq 2 \pi$. The allowed rapidity interval is set by the most forward and backward partons, which are necessarily inside jets. Since these parton rapidities are not known at this point, we also have to postpone the rapidity generation for the gluons outside jets. For the scalar transverse momentum $p_\perp = |\mathbf{p}_\perp|$ of a gluon outside jets we use the parametrisation \begin{equation} \label{eq:p_nonjet} p_\perp = \lambda + \tilde{p}_\perp\*\tan(\tau\*r)\,, \qquad \tau = \arctan\bigg(\frac{p_{\perp{\cal J}_\text{min}} - \lambda}{\tilde{p}_\perp}\bigg)\,. \end{equation} For $r \in [0,1)$, $p_\perp$ is always less than the minimum momentum $p_{\perp{\cal J}_\text{min}}$ required for a jet. $\tilde{p}_\perp$ is a free parameter, a good empirical value is $\tilde{p}_\perp = [1.3 + 0.2\*(n_g - n_{g,\cal J})]\,$GeV \subsubsection{Resummation jet momenta} \label{sec:ptj_res} On the one hand, each jet momentum is given by the sum of its constituent momenta. On the other hand, the resummation jet momenta are fixed by the constraints in line five of the master equation~\eqref{eq:resumdijetFKLmatched2}. We therefore have to calculate the resummation jet momenta from these constraints before generating the momenta of the gluons inside jets. This is done in \lstinline!PhaseSpacePoint::reshuffle! and in the free \lstinline!resummation_jet_momenta! function (declared in \texttt{resummation\_jet.hh}). The resummation jet momenta are determined by the $\delta$ functions in line five of eq.~(\ref{eq:resumdijetFKLmatched2}). The rapidities are fixed to the rapidities of the jets in the input fixed-order events, so that the FKL ordering is guaranteed to be preserved. In traditional \HEJ reshuffling the transverse momentum are given through \begin{equation} \label{eq:ptreassign_old} \mathbf{p}^B_{\mathcal{J}_{l\perp}} = \mathbf{j}_{l\perp} \equiv \mathbf{p}_{\mathcal{J}_{l}\perp} + \mathbf{q}_\perp \,\frac{|\mathbf{p}_{\mathcal{J}_{l}\perp}|}{P_\perp}, \end{equation} where $\mathbf{q}_\perp = \sum_{j=1}^n \mathbf{p}_{i\perp} \bigg[1-\theta\bigg(\sum_{j=1}^{m}{\cal J}_j(p_i)\bigg)\bigg] $ is the total transverse momentum of all partons \emph{outside} jets and $P_\perp = \sum_{j=1}^m |\mathbf{p}_{\mathcal{J}_{j}\perp}|$. Since the total transverse momentum of an event vanishes, we can also use $\mathbf{q}_\perp = - \sum_{j=1}^m \mathbf{p}_{\mathcal{J}_{j}\perp}$. Eq.~(\ref{eq:ptreassign}) is a non-linear system of equations in the resummation jet momenta $\mathbf{p}_{\mathcal{J}_{l}\perp}$. Hence we would have to solve \begin{equation} \label{eq:ptreassign_eq} \mathbf{p}_{\mathcal{J}_{l}\perp}=\mathbf{j}^B_{l\perp} \equiv\mathbf{j}_{l\perp}^{-1} \left(\mathbf{p}^B_{\mathcal{J}_{l\perp}}\right) \end{equation} numerically. Since solving such a system is computationally expensive, we instead change the reshuffling around to be linear in the resummation jet momenta. Hence~\eqref{eq:ptreassign_eq} gets replaces by \begin{equation} \label{eq:ptreassign} \mathbf{p}_{\mathcal{J}_{l\perp}} = \mathbf{j}^B_{l\perp} \equiv \mathbf{p}^B_{\mathcal{J}_{l}\perp} - \mathbf{q}_\perp \,\frac{|\mathbf{p}^B_{\mathcal{J}_{l}\perp}|}{P^B_\perp}, \end{equation} which is linear in the resummation momentum. Consequently the equivalent of~\eqref{eq:ptreassign_old} is non-linear in the Born momentum. However the exact form of~\eqref{eq:ptreassign_old} is not relevant for the resummation. Both methods have been tested for two and three jets with the \textsc{rivet} standard analysis \texttt{MC\_JETS}. They didn't show any differences even after $10^9$ events. The reshuffling relation~\eqref{eq:ptreassign} allows the transverse momenta $p^B_{\mathcal{J}_{l\perp}}$ of the fixed-order jets to be somewhat below the minimum transverse momentum of resummation jets. It is crucial that this difference does not become too large, as the fixed-order cross section diverges for vanishing transverse momenta. In the production of a Higgs boson with resummation jets above $30\,$GeV we observe that the contribution from fixed-order events with jets softer than about $20\,$GeV can be safely neglected. This is shown in the following plot of the differential cross section over the transverse momentum of the softest fixed-order jet: \begin{center} \includegraphics[width=.75\textwidth]{ptBMin} \end{center} Finally, we have to account for the fact that the reshuffling relation~\eqref{eq:ptreassign} is non-linear in the Born momenta. To arrive at the master formula~\eqref{eq:resumdijetFKLmatched2} for the cross section, we have introduced unity in the form of an integral over the Born momenta with $\delta$ functions in the integrand, that is \begin{equation} \label{eq:delta_intro} 1 = \int_{p_{j\perp}^B=0}^{p_{j\perp}^B=\infty} \mathrm{d}^2\mathbf{p}_{j\perp}^B\delta^{(2)}(\mathbf{p}_{\mathcal{J}_{j\perp}}^B - \mathbf{j}_{j\perp})\,. \end{equation} If the arguments of the $\delta$ functions are not linear in the Born momenta, we have to compensate with additional Jacobians as factors. Explicitly, for the reshuffling relation~\eqref{eq:ptreassign} we have \begin{equation} \label{eq:delta_rewrite} \prod_{l=1}^m \delta^{(2)}(\mathbf{p}_{\mathcal{J}_{l\perp}}^B - \mathbf{j}_{l\perp}) = \Delta \prod_{l=1}^m \delta^{(2)}(\mathbf{p}_{\mathcal{J}_{l\perp}} - \mathbf{j}_{l\perp}^B)\,, \end{equation} where $\mathbf{j}_{l\perp}^B$ is given by~\eqref{eq:ptreassign_eq} and only depends on the Born momenta. We have extended the product to run to $m$ instead of $m-1$ by eliminating the last $\delta$ function $\delta^{(2)}\!\!\left(\sum_{k=1}^n \mathbf{p}_{k\perp}\right )$. The Jacobian $\Delta$ is the determinant of a $2m \times 2m$ matrix with $l, l' = 1,\dots,m$ and $X, X' = x,y$. \begin{equation} \label{eq:jacobian} \Delta = \left|\frac{\partial\,\mathbf{j}^B_{l'\perp}}{\partial\, \mathbf{p}^B_{{\cal J}_l \perp}} \right| = \left| \delta_{l l'} \delta_{X X'} - \frac{q_X\, p^B_{{\cal J}_{l'}X'}}{\left|\mathbf{p}^B_{{\cal J}_{l'} \perp}\right| P^B_\perp}\left(\delta_{l l'} - \frac{\left|\mathbf{p}^B_{{\cal J}_l \perp}\right|}{P^B_\perp}\right)\right|\,. \end{equation} The determinant is calculated in \lstinline!resummation_jet_weight!, again coming from the \texttt{resummation\_jet.hh} header. Having to introduce this Jacobian is not a disadvantage specific to the new reshuffling. If we instead use the old reshuffling relation~\eqref{eq:ptreassign_old} we \emph{also} have to introduce a similar Jacobian since we actually want to integrate over the resummation phase space and need to transform the argument of the $\delta$ function to be linear in the resummation momenta for this. \subsubsection{Gluons inside Jets} \label{sec:gluons_jet} After the steps outlined in section~\ref{sec:psp_ng_jet}, we have a total number of $m + n_{g,{\cal J}}$ constituents. In \lstinline!PhaseSpacePoint::distribute_jet_partons! we distribute them randomly among the jets such that each jet has at least one constituent. We then generate their momenta in \lstinline!PhaseSpacePoint::split! using the \lstinline!Splitter! class. The phase space integral for a jet ${\cal J}$ is given by \begin{equation} \label{eq:ps_jetparton} \prod_{i\text{ in }{\cal J}} \bigg(\int \mathrm{d}\mathbf{p}_{i\perp}\ \int \mathrm{d} y_i \bigg)\delta^{(2)}\Big(\sum_{i\text{ in }{\cal J}} \mathbf{p}_{i\perp} - \mathbf{j}_{\perp}^B\Big)\delta(y_{\mathcal{J}}-y^B_{\mathcal{J}})\,. \end{equation} For jets with a single constituent, the parton momentum is obiously equal to the jet momentum. In the case of two constituents, we observe that the partons are always inside the jet cone with radius $R$ and often very close to the jet centre. The following plots show the typical relative distance $\Delta R/R$ for this scenario: \begin{center} \includegraphics[width=0.45\linewidth]{dR_2} \includegraphics[width=0.45\linewidth]{dR_2_small} \end{center} According to this preference for small values of $\Delta R$, we parametrise the $\Delta R$ integrals as \begin{equation} \label{eq:dR_sampling} \frac{\Delta R}{R} = \begin{cases} 0.25\,x_R & x_R < 0.4 \\ 1.5\,x_R - 0.5 & x_R \geq 0.4 \end{cases}\,. \end{equation} Next, we generate $\Theta_1 \equiv \Theta$ and use the constraint $\Theta_2 = \Theta \pm \pi$. The transverse momentum of the first parton is then given by \begin{equation} \label{eq:delta_constraints} p_{1\perp} = \frac{p_{\mathcal{J} y} - \tan(\phi_2) p_{\mathcal{J} x}}{\sin(\phi_1) - \tan(\phi_2)\cos(\phi_1)}\,. \end{equation} We get $p_{2\perp}$ by exchanging $1 \leftrightarrow 2$ in the indices. To obtain the Jacobian of the transformation, we start from the single jet phase space eq.~(\ref{eq:ps_jetparton}) with the rapidity delta function already rewritten to be linear in the rapidity of the last parton, i.e. \begin{equation} \label{eq:jet_2p} \prod_{i=1,2} \bigg(\int \mathrm{d}\mathbf{p}_{i\perp}\ \int \mathrm{d} y_i \bigg)\delta^{(2)}\Big(\mathbf{p}_{1\perp} + \mathbf{p}_{2\perp} - \mathbf{j}_{\perp}^B\Big)\delta(y_2- \dots)\,. \end{equation} The integral over the second parton momentum is now trivial; we can just replace the integral over $y_2$ with the equivalent constraint \begin{equation} \label{eq:R2} \int \mathrm{d}R_2 \ \delta\bigg(R_2 - \bigg[\phi_{\cal J} - \arctan \bigg(\frac{p_{{\cal J}y} - p_{1y}}{p_{{\cal J}x} - p_{1x}}\bigg)\bigg]/\cos \Theta\bigg) \,. \end{equation} In order to fix the integral over $p_{1\perp}$ instead, we rewrite this $\delta$ function. This introduces the Jacobian \begin{equation} \label{eq:jac_pt1} \bigg|\frac{\partial p_{1\perp}}{\partial R_2} \bigg| = \frac{\cos(\Theta)\mathbf{p}_{2\perp}^2}{p_{{\cal J}\perp}\sin(\phi_{\cal J}-\phi_1)}\,. \end{equation} The final form of the integral over the two parton momenta is then \begin{equation} \label{eq:ps_jet_2p} \int \mathrm{d}R_1\ R_1 \int \mathrm{d}R_2 \int \mathrm{d}x_\Theta\ 2\pi \int \mathrm{d}p_{1\perp}\ p_{1\perp} \int \mathrm{d}p_{2\perp} \ \bigg|\frac{\partial p_{1\perp}}{\partial R_2} \bigg|\delta(p_{1\perp} -\dots) \delta(p_{2\perp} - \dots)\,. \end{equation} As is evident from section~\ref{sec:psp_ng_jet}, jets with three or more constituents are rare and an efficient phase-space sampling is less important. For such jets, we exploit the observation that partons with a distance larger than $R_{\text{max}} = \tfrac{5}{3} R$ to the jet centre are never clustered into the jet. Assuming $N$ constituents, we generate all components for the first $N-1$ partons and fix the remaining parton with the $\delta$-functional. In order to end up inside the jet, we use the parametrisation \begin{align} \label{eq:ps_jet_param} \phi_i ={}& \phi_{\cal J} + \Delta \phi_i\,, & \Delta \phi_i ={}& \Delta R_i \cos(\Theta_i)\,, \\ y_i ={}& y_{\cal J} + \Delta y_i\,, & \Delta y_i ={}& \Delta R_i \sin(\Theta_i)\,, \end{align} and generate $\Theta_i$ and $\Delta R_i$ randomly with $\Delta R_i \leq R_{\text{max}}$ and the empiric value $R_{\text{max}} = 5\*R/3$. We can then write the phase space integral for a single parton as $(p_\perp = |\mathbf{p}_\perp|)$ \begin{equation} \label{eq:ps_jetparton_x} \int \mathrm{d}\mathbf{p}_{\perp}\ \int \mathrm{d} y \approx \int_{\Box} \mathrm{d}x_{\perp} \mathrm{d}x_{ R} \mathrm{d}x_{\theta}\ 2\*\pi\,\*R_{\text{max}}^2\,\*x_{R}\,\*p_{\perp}\,\*(p_{\perp,\text{max}} - p_{\perp,\text{min}}) \end{equation} with \begin{align} \label{eq:ps_jetparton_parameters} \Delta \phi ={}& R_{\text{max}}\*x_{R}\*\cos(2\*\pi\*x_\theta)\,,& \Delta y ={}& R_{\text{max}}\*x_{R}\*\sin(2\*\pi\*x_\theta)\,, \\ p_{\perp} ={}& (p_{\perp,\text{max}} - p_{\perp,\text{min}})\*x_\perp + p_{\perp,\text{min}}\,. \end{align} $p_{\perp,\text{max}}$ is determined from the requirement that the total contribution from the first $n-1$ partons --- i.e. the projection onto the jet $p_{\perp}$ axis --- must never exceed the jet $p_\perp$. This gives \todo{This bound is too high} \begin{equation} \label{eq:pt_max} p_{i\perp,\text{max}} = \frac{p_{{\cal J}\perp} - \sum_{j<i} p_{j\perp} \cos \Delta \phi_j}{\cos \Delta \phi_i}\,. \end{equation} The $x$ and $y$ components of the last parton follow immediately from the first $\delta$ function. The last rapidity is fixed by the condition that the jet rapidity is kept fixed by the reshuffling, i.e. \begin{equation} \label{eq:yJ_delta} y^B_{\cal J} = y_{\cal J} = \frac 1 2 \ln \frac{\sum_{i=1}^n E_i+ p_{iz}}{\sum_{i=1}^n E_i - p_{iz}}\,. \end{equation} With $E_n \pm p_{nz} = p_{n\perp}\exp(\pm y_n)$ this can be rewritten to \begin{equation} \label{eq:yn_quad_eq} \exp(2y_{\cal J}) = \frac{\sum_{i=1}^{n-1} E_i+ p_{iz}+p_{n\perp} \exp(y_n)}{\sum_{i=1}^{n-1} E_i - p_{iz}+p_{n\perp} \exp(-y_n)}\,, \end{equation} which is a quadratic equation in $\exp(y_n)$. The physical solution is \begin{align} \label{eq:yn} y_n ={}& \log\Big(-b + \sqrt{b^2 + \exp(2y_{\cal J})}\,\Big)\,,\\ b ={}& \bigg(\sum_{i=1}^{n-1} E_i + p_{iz} - \exp(2y_{\cal J}) \sum_{i=1}^{n-1} E_i - p_{iz}\bigg)/(2 p_{n\perp})\,. \end{align} \todo{what's wrong with the following?} To eliminate the remaining rapidity integral, we transform the $\delta$ function to be linear in the rapidity $y$ of the last parton. The corresponding Jacobian is \begin{equation} \label{eq:jacobian_y} \bigg|\frac{\partial y_{\cal J}}{\partial y_n}\bigg|^{-1} = 2 \bigg( \frac{E_n + p_{nz}}{E_{\cal J} + p_{{\cal J}z}} + \frac{E_n - p_{nz}}{E_{\cal J} - p_{{\cal J}z}}\bigg)^{-1}\,. \end{equation} Finally, we check that all designated constituents are actually clustered into the considered jet. \subsubsection{Final steps} \label{sec:final} Knowing the rapidity span covered by the extremal partons, we can now generate the rapdities for the partons outside jets. We perform jet clustering on all partons and check in \lstinline!PhaseSpacePoint::jets_ok! that all the following criteria are fulfilled: \begin{itemize} \item The number of resummation jets must match the number of fixed-order jets. \item No partons designated to be outside jets may end up inside jets. \item All other outgoing partons \emph{must} end up inside jets. \item The extremal (in rapidity) partons must be inside the extremal jets. If there is, for example, an unordered forward emission, the most forward parton must end up inside the most forward jet and the next parton must end up inside second jet. \item The rapidities of fixed-order and resummation jets must match. \end{itemize} After this, we adjust the phase-space normalisation according to the third line of eq.~(\ref{eq:resumdijetFKLmatched2}), determine the flavours of the outgoing partons, and adopt any additional colourless bosons from the fixed-order input event. Finally, we use momentum conservation to reconstruct the momenta of the incoming partons. +\subsection{Colour connection} +\label{sec:Colour} + +\begin{figure} + \input{src/ColourConnect.tex} + \caption{Left: Non-crossing colour flow dominating in the MRK limit. The + crossing of the colour line connecting to particle 2 can be resolved by + writing particle 2 on the left. Right: A colour flow with a (manifest) + colour-crossing. The crossing can only be resolved if one breaks the + rapidities order, e.g. switching particles 2 and 3. From~\cite{Andersen:2017sht}.} + \label{fig:Colour_crossing} +\end{figure} + +After the phase space for the resummation event is generated, we can construct +the colour for each particle. To generate the colour flow one has to call +\lstinline!Event::generate_colours! on any \HEJ configuration. For non-\HEJ +event we do not change the colour, and assume it is provided by the user (e.g. +through the LHE file input). The colour connection is done in the large $N_c$ +(infinite number of colour) limit with leading colour in +MRK~\cite{Andersen:2008ue, Andersen:2017sht}. The idea is to allow only +$t$-channel colour exchange, without any crossing colour lines. For example the +colour crossing in the colour connection on the left of +figure~\ref{fig:Colour_crossing} can be resolved by switching \textit{particle +2} to the left. + +We can write down the colour connections by following the colour flow from +\textit{gluon a} to \textit{gluon b} and back to \textit{gluon a}, e.g. +figure~\ref{fig:Colour_gleft} corresponds to $a123ba$. In such an expression any +valid, non-crossing colour flow will connect all external legs while respecting +the rapidity ordering. Thus configurations like the left of +figure~\ref{fig:Colour_crossing} are allowed ($a134b2a$), but the right of the +same figures breaks the rapidity ordering between 2 and 3 ($a1324ba$). Note that +connections between $b$ and $a$ are in inverse order, e.g. $ab321a$ corresponds to~\ref{fig:Colour_gright} ($a123ba$) just with colour and anti-colour swapped. + +\begin{figure} +\centering +\subcaptionbox{$a123ba$\label{fig:Colour_gright}}{ + \includegraphics[height=0.25\textwidth]{figures/colour_gright.jpg}} +\subcaptionbox{$a13b2a$\label{fig:Colour_gleft}}{ + \includegraphics[height=0.25\textwidth]{figures/colour_gleft.jpg}} +\subcaptionbox{$a\_123ba$\label{fig:Colour_qx}}{ + \includegraphics[height=0.25\textwidth]{figures/colour_qx.jpg}} +\subcaptionbox{$a\_23b1a$\label{fig:Colour_uno}}{ + \includegraphics[height=0.25\textwidth]{figures/colour_uno.jpg}} +\subcaptionbox{$a14b3\_2a$\label{fig:Colour_qqx}}{ + \includegraphics[height=0.25\textwidth]{figures/colour_centralqqx.jpg}} +\caption{Different colour non-crossing colour connections. Both incoming + particles are drawn at the top or bottom and the outgoing left or right. + The Feynman diagram is shown in black and the colour flow in blue.} + %TODO Maybe make these plots nicer (in Latex/asy) +\end{figure} + +If we replace two gluons with a quark, (anti-)quark pair we break one of the +colour connections. Still the basic concept from before holds, we just have to +treat the connection between two (anti-)quarks like an unmovable (anti-)colour. +We denote such a connection by a underscore (e.g. $1\_a$). For example the +equivalent of~\ref{fig:Colour_gright} ($a123ba$) with an incoming antiquark +is~\ref{fig:Colour_qx} ($a\_123ba$). As said this also holds for other +subleading configurations like unordered emission~\ref{fig:Colour_uno} or +central quark-antiquark pairs~\ref{fig:Colour_qqx} \footnote{Obviously this can +not be guaranteed for non-\HEJ configurations, e.g. $qQ\to Qq$ requires a +$u$-channel exchange.}. + +Some rapidity ordering can have multiple possible colour connections, +e.g.~\ref{fig:Colour_gright} and~\ref{fig:Colour_gleft}. This is always the case +if a gluon radiates off a gluon line. In that case we randomly connect the gluon +to either the colour or anti-colour. Thus in the generation we keep track +whether we are on a quark or gluon line, and act accordingly. + + + \subsection{The matrix element } \label{sec:ME} The derivation of the \HEJ matrix element is explained in some detail in~\cite{Andersen:2017kfc}, where also results for leading and subleading matrix elements for pure multijet production and production of a Higgs boson with at least two associated jets are listed. Matrix elements for $W$ and $Z/\gamma^*$ production together with jets are given in~\cite{Andersen:2012gk,Andersen:2016vkp}, but not yet included. The matrix elements are implemented in the \lstinline!MatrixElement! class. To discuss the structure, let us consider the squared matrix element for FKL multijet production with $n$ final-state partons: \begin{align} \label{eq:ME} \begin{split} \overline{\left|\mathcal{M}_\text{HEJ}^{f_1 f_2 \to f_1 g\cdots g f_2}\right|}^2 = \ &\frac {(4\pi\alpha_s)^n} {4\ (N_c^2-1)} \cdot\ \textcolor{blue}{\frac {K_{f_1}(p_1^-, p_a^-)} {t_1}\ \cdot\ \frac{K_{f_2}(p_n^+, p_b^+)}{t_{n-1}}\ \cdot\ \left\|S_{f_1 f_2\to f_1 f_2}\right\|^2}\\ & \cdot \prod_{i=1}^{n-2} \textcolor{gray}{\left( \frac{-C_A}{t_it_{i+1}}\ V^\mu(q_i,q_{i+1})V_\mu(q_i,q_{i+1}) \right)}\\ & \cdot \prod_{j=1}^{n-1} \textcolor{red}{\exp\left[\omega^0(q_{j\perp})(y_{j+1}-y_j)\right]}. \end{split} \end{align} The structure and momentum assignment of the unsquared matrix element is as illustrated here: \begin{center} \includegraphics{HEJ_amplitude} \end{center} The square of the complete matrix element as given in eq.~\eqref{eq:ME} is calculated by \lstinline!MatrixElement::operator()!. The \textcolor{red}{last line} of eq.~\eqref{eq:ME} constitutes the all-order virtual correction, implemented in \lstinline!MatrixElement::virtual_corrections!. $\omega^0$ is the \textit{regularised Regge trajectory} \begin{equation} \label{eq:omega_0} \omega^0(q_\perp) = - C_A \frac{\alpha_s}{\pi} \log \left(\frac{q_\perp^2}{\lambda^2}\right)\,, \end{equation} where $\lambda$ is the slicing parameter limiting the softness of real gluon emissions, cf. eq.~\eqref{eq:resumdijetFKLmatched2}. $\lambda$ can be changed at runtime by setting \lstinline!regulator parameter! in \lstinline!conifg.yml!. The remaining parts, which correspond to the square of the leading-order HEJ matrix element $\overline{\left|\mathcal{M}_\text{LO, HEJ}^{f_1f_2\to f_1g\cdots gf_2}\big(\big\{p^B_j\big\}\big)\right|}^{2}$, are computed in \lstinline!MatrixElement::tree!. We can further factor off the scale-dependent ``parametric'' part \lstinline!MatrixElement::tree_param! containing all factors of the strong coupling $4\pi\alpha_s$. Using this function saves some CPU time when adjusting the renormalisation scale, see section~\ref{sec:resum}. The remaining ``kinematic'' factors are calculated in \lstinline!MatrixElement::kin!. \subsubsection{Matrix elements for Higgs plus dijet} \label{sec:ME_h_jets} In the production of a Higgs boson together with jets the parametric parts and the virtual corrections only require minor changes in the respective functions. However, in the ``kinematic'' parts we have to distinguish between several cases, which is done in \lstinline!MatrixElement::tree_kin_Higgs!. The Higgs boson can be \emph{central}, i.e. inside the rapidity range spanned by the extremal partons (\lstinline!MatrixElement::tree_kin_Higgs_central!) or \emph{peripheral} and outside this range (\lstinline!MatrixElement::tree_kin_Higgs_first! or \lstinline!MatrixElement::tree_kin_Higgs_last!). In case there is also an unordered gluon emission the matrix element is already suppressed by one logarithm $\log(s/t)$. Therefore at NLL the Higgs boson can only be emitted centrally\footnote{In principle emitting a Higgs boson \textit{on the other side} of the unordered gluon is possible by contracting an unordered and external Higgs current. Obviously this would not cover all possible configurations, e.g. $qQ\to HgqQ$ requires contraction of the standard $Q\to Q$ current with an (unknown) $q\to Hgq$ one.}. If a Higgs boson with momentum $p_H$ is emitted centrally, after parton $j$ in rapidity, the matrix element reads \begin{equation} \label{eq:ME_h_jets_central} \begin{split} \overline{\left|\mathcal{M}_\text{HEJ}^{f_1 f_2 \to f_1 g\cdot H \cdot g f_2}\right|}^2 = \ &\frac {\alpha_s^2 (4\pi\alpha_s)^n} {4\ (N_c^2-1)} \cdot\ \textcolor{blue}{\frac {K_{f_1}(p_1^-, p_a^-)} {t_1}\ \cdot\ \frac{1}{t_j t_{j+1}} \cdot\ \frac{K_{f_2}(p_n^+, p_b^+)}{t_{n}}\ \cdot\ \left\|S_{f_1 f_2\to f_1 H f_2}\right\|^2}\\ & \cdot \prod_{\substack{i=1\\i \neq j}}^{n-1} \textcolor{gray}{\left( \frac{-C_A}{t_it_{i+1}}\ V^\mu(q_i,q_{i+1})V_\mu(q_i,q_{i+1}) \right)}\\ & \cdot \textcolor{red}{\prod_{i=1}^{n-1} \exp\left[\omega^0(q_{i\perp})\Delta y_i\right]} \end{split} \end{equation} with the momentum definitions \begin{center} \includegraphics{HEJ_central_Higgs_amplitude} \end{center} $q_i$ is the $i$th $t$-channel momentum and $\Delta y_i$ the rapidity gap between outgoing \emph{particles} (not partons) $i$ and $i+1$ in rapidity ordering. For \emph{peripheral} emission in the backward direction (\lstinline!MatrixElement::tree_kin_Higgs_first!) we first check whether the most backward parton is a gluon or an (anti-)quark. In the latter case the leading contribution to the matrix element arises through emission off the $t$-channel gluons and we can use the same formula eq.~(\ref{eq:ME_h_jets_central}) as for central emission. If the most backward parton is a gluon, the square of the matrix element can be written as \begin{equation} \label{eq:ME_h_jets_peripheral} \begin{split} \overline{\left|\mathcal{M}_\text{HEJ}^{g f_2 \to H g\cdot g f_2}\right|}^2 = \ &\frac {\alpha_s^2 (4\pi\alpha_s)^n} {\textcolor{blue}{4\ (N_c^2-1)}} \textcolor{blue}{\cdot\ K_{H}\ \frac{K_{f_2}(p_n^+, p_b^+)}{t_{n-1}}\ \cdot\ \left\|S_{g f_2\to H g f_2}\right\|^2}\\ & \cdot \prod_{\substack{i=1}}^{n-2} \textcolor{gray}{\left( \frac{-C_A}{t_it_{i+1}}\ V^\mu(q_i,q_{i+1})V_\mu(q_i,q_{i+1}) \right)}\\ & \cdot \textcolor{red}{\prod_{i=1}^{n-1} \exp\left[\omega^0(q_{i\perp}) (y_{i+1} - y_i)\right]} \end{split} \end{equation} with the momenta as follows: \begin{center} \includegraphics{HEJ_peripheral_Higgs_amplitude} \end{center} The \textcolor{blue}{blue part} is implemented in \lstinline!MatrixElement::MH2_forwardH!. All other building blocks are already available.\todo{Impact factors} The actual current contraction is calculated in \lstinline!MH2gq_outsideH! inside \lstinline!currents.cc!, which corresponds to $\tfrac{16 \pi^2}{t_1} \left\|S_{g f_2\to H g f_2}\right\|^2$.\todo{Fix this insane normalisation} The forward emission of a Higgs boson is completely analogous. We can use the same function \lstinline!MatrixElement::MH2_forwardH!, swapping $p_1 \leftrightarrow p_n,\,p_a \leftrightarrow p_b$. \subsubsection{FKL ladder and Lipatov vertices} \label{sec:FKL_ladder} The ``FKL ladder'' is the product \begin{equation} \label{eq:FKL_ladder} \prod_{i=1}^{n-2} \left( \frac{-C_A}{t_it_{i+1}}\ V^\mu(q_i,q_{i+1})V_\mu(q_i,q_{i+1}) \right) \end{equation} appearing in the square of the matrix element for $n$ parton production, cf. eq.~(\ref{eq:ME}), and implemented in \lstinline!MatrixElement::FKL_ladder_weight!. The Lipatov vertex contraction $V^\mu(q_i,q_{i+1})V_\mu(q_i,q_{i+1})$ is implemented \lstinline!C2Lipatovots!. It is given by \todo{equation} \todo{mention difference between the two versions of \lstinline!C2Lipatovots!, maybe even get rid of one}. \subsubsection{Currents} \label{sec:currents} The current factors $\frac{K_{f_1}K_{f_2}}{t_1 t_{n-1}}\left\|S_{f_1 f_2\to f_1 f_2}\right\|^2$ and their extensions for unordered and Higgs boson emissions are implemented in the \lstinline!jM2!$\dots$ functions of \texttt{src/currents.cc}. \todo{Only $\|S\|^2$ should be in currents} \footnote{The current implementation for Higgs production in \texttt{src/currents.cc} includes the $1/4$ factor inside $S$, opposing to~\eqref{eq:ME}. Thus the overall normalisation is unaffected.} The ``colour acceleration multiplier'' (CAM) $K_{f}$ for a parton $f\in\{g,q,\bar{q}\}$ is defined as \begin{align} \label{eq:K_g} K_g(p_1^-, p_a^-) ={}& \frac{1}{2}\left(\frac{p_1^-}{p_a^-} + \frac{p_a^-}{p_1^-}\right)\left(C_A - \frac{1}{C_A}\right)+\frac{1}{C_A}\\ \label{eq:K_q} K_q(p_1^-, p_a^-) ={}&K_{\bar{q}}(p_1^-, p_a^-) = C_F\,. \end{align} The Higgs current CAM used in eq.~(\ref{eq:ME_h_jets_peripheral}) is \begin{equation} \label{eq:K_H} K_H = C_A\,. \end{equation} The current contractions are given by\todo{check all this carefully!} \begin{align} \label{eq:S} \left\|S_{f_1 f_2\to f_1 f_2}\right\|^2 ={}& \sum_{\substack{\lambda_a = +,-\\\lambda_b = +,-}} \left|j^{\lambda_a}_\mu(p_1, p_a)\ j^{\lambda_b\,\mu}(p_n, p_b)\right|^2 = 2\sum_{\lambda = +,-} \left|j^{-}_\mu(p_1, p_a)\ j^{\lambda\,\mu}(p_n, p_b)\right|^2\,,\\ \left\|S_{f_1 f_2\to f_1 H f_2}\right\|^2 ={}& \sum_{\substack{\lambda_a = +,-\\\lambda_b = +,-}} \left|j^{\lambda_a}_\mu(p_1, p_a)V_H^{\mu\nu}(q_j, q_{j+1})\ j^{\lambda_b}_\nu(p_n, p_b)\right|^2\,,\\ \left\|S_{g f_2 \to H g f_2}\right\|^2 ={}& \sum_{ \substack{ \lambda_{a} = +,-\\ \lambda_{1} =+,-\\ \lambda_{b} = +,- }} \left|j^{\lambda_a\lambda_1}_{H\,\mu}(p_1, p_a, p_H)\ j^{\lambda_b\,\mu}(p_n, p_b)\right|^2\,. \end{align} The ``basic'' currents $j$ are independent of the parton flavour and read \begin{equation} \label{eq:j} j^\pm_\mu(p, q) = u^{\pm,\dagger}(p)\ \sigma^\pm_\mu\ u^{\pm}(q)\,, \end{equation} where $\sigma_\mu^\pm = (1, \pm \sigma_i)$ and $\sigma_i$ are the Pauli matrices \begin{equation} \label{eq:Pauli_matrices} \sigma_1 = \begin{pmatrix} 0 & 1\\ 1 & 0 \end{pmatrix} \,, \qquad \sigma_2 = \begin{pmatrix} 0 & -i\\ i & 0 \end{pmatrix} \,, \qquad \sigma_3 = \begin{pmatrix} 1 & 0\\ 0 & -1 \end{pmatrix} \,. \end{equation} The two-component chiral spinors are given by \begin{align} \label{eq:u_plus} u^+(p)={}& \left(\sqrt{p^+}, \sqrt{p^-} \hat{p}_\perp \right) \,,\\ \label{eq:u_minus} u^-(p)={}& \left(\sqrt{p^-} \hat{p}^*_\perp, -\sqrt{p^+}\right)\,, \end{align} with $p^\pm = E\pm p_z,\, \hat{p}_\perp = \tfrac{p_\perp}{|p_\perp|},\, p_\perp = p_x + i p_y$. The spinors for vanishing transverse momentum are obtained by replacing $\hat{p_\perp} \to -1$. Explicitly, the currents read \begin{align} \label{eq:j-_explicit} j^-_\mu(p, q) ={}& \begin{pmatrix} \sqrt{p^+\,q^+} + \sqrt{p^-\,q^-} \hat{p}_{\perp} \hat{q}_{\perp}^*\\ \sqrt{p^-\,q^+}\, \hat{p}_{\perp} + \sqrt{p^+\,q^-}\,\hat{q}_{\perp}^*\\ -i \sqrt{p^-\,q^+}\, \hat{p}_{\perp} + i \sqrt{p^+\,q^-}\, \hat{q}_{\perp}^*\\ \sqrt{p^+\,q^+} - \sqrt{p^-\,q^-}\, \hat{p}_{\perp}\, \hat{q}_{\perp}^* \end{pmatrix}\\ j^+_\mu(p, q) ={}&\big(j^-_\mu(p, q)\big)^* \end{align} If $q= p_{\text{in}}$ is the momentum of an incoming parton, we have $\hat{p}_{\text{in} \perp} = -1$ and either $p_{\text{in}}^+ = 0$ or $p_{\text{in}}^- = 0$. The current simplifies further:\todo{Helicities flipped w.r.t code} \begin{align} \label{eq:j_explicit} j^-_\mu(p_{\text{out}}, p_{\text{in}}) ={}& \begin{pmatrix} \sqrt{p_{\text{in}}^+\,p_{\text{out}}^+}\\ \sqrt{p_{\text{in}}^+\,p_{\text{out}}^-} \ \hat{p}_{\text{out}\,\perp}\\ -i\,j^-_1\\ j^-_0 \end{pmatrix} & p_{\text{in}\,z} > 0\,,\\ j^-_\mu(p_{\text{out}}, p_{\text{in}}) ={}& \begin{pmatrix} -\sqrt{p_{\text{in}}^-\,p_{\text{out}}^{-\phantom{+}}} \ \hat{p}_{\text{out}\,\perp}\\ - \sqrt{p_{\text{in}}^-\,p_{\text{out}}^+}\\ i\,j^-_1\\ -j^-_0 \end{pmatrix} & p_{\text{in}\,z} < 0\,. \end{align} \section{The fixed-order generator} \label{sec:HEJFOG} Even at leading order, standard fixed-order generators can only generate events with a limited number of final-state particles within reasonable CPU time. The purpose of the fixed-order generator is to supplement this with high-multiplicity input events according to the first two lines of eq.~\eqref{eq:resumdijetFKLmatched2} with the \HEJ approximation $\mathcal{M}_\text{LO, HEJ}^{f_1f_2\to f_1g\cdots gf_2}$ instead of the full fixed-order matrix element $\mathcal{M}_\text{LO}^{f_1f_2\to f_1g\cdots gf_2}$. Its usage is described in the user documentation \url{https://hej.web.cern.ch/HEJ/doc/current/user/HEJFOG.html}. \subsection{File structure} \label{sec:HEJFOG_structure} The code for the fixed-order generator is in the \texttt{FixedOrderGen} directory, which contains the following: \begin{description} \item[include:] Contains the C++ header files. \item[src:] Contains the C++ source files. \item[t:] Contains the source code for the automated tests. \item[CMakeLists.txt:] Configuration file for the \cmake build system. \item[configFO.yml:] Sample configuration file for the fixed-order generator. \end{description} The code is generally in the \lstinline!HEJFOG! namespace. Functions and classes \lstinline!MyClass! are usually declared in \texttt{include/MyClass.hh} and implemented in \texttt{src/MyClass.cc}. \subsection{Program flow} \label{sec:prog_flow} A single run of the fixed-order generator consists of three or four stages. First, we perform initialisation similar to HEJ 2, see section~\ref{sec:init}. Since there is a lot of overlap we frequently reuse classes and functions from HEJ 2, i.e. from the \lstinline!HEJ! namespace. The code for parsing the configuration file is in \texttt{include/config.hh} and implemented in \texttt{src/config.cc}. If partial unweighting is requested in the user settings \url{https://hej.web.cern.ch/HEJ/doc/current/user/HEJFOG.html#settings}, the initialisation is followed by a calibration phase. We use a \lstinline!EventGenerator! to produce a number of trial events. We use these to calibrate the \lstinline!Unweighter! in its constructor and produce a first batch of partially unweighted events. This also allows us to estimate our unweighting efficiency. In the next step, we continue to generate events and potentially unweight them. Once the user-defined target number of events is reached, we adjust their weights according to the number of required trials. As in HEJ 2 (see section~\ref{sec:processing}), we pass the final events to a \lstinline!HEJ::Analysis! and a \lstinline!HEJ::CombinedEventWriter!. \subsection{Event generation} \label{sec:evgen} Event generation is performed by the \lstinline!EventGenerator::gen_event! member function. We begin by generating a \lstinline!PhaseSpacePoint!. This is not to be confused with the resummation phase space points represented by \lstinline!HEJ::PhaseSpacePoint!! After jet clustering, we compute the leading-order matrix element (see section~\ref{sec:ME}) and pdf factors. The phase space point generation is performed in the \lstinline!PhaseSpacePoint! constructor. We first construct the user-defined number of $n_p$ partons (by default gluons) in \lstinline!PhaseSpacePoint::gen_LO_partons!. We use flat sampling in rapidity and azimuthal angle. For the scalar transverse momenta, we distinguish between two cases. By default, they are generated based on a random variable $x_{p_\perp}$ according to \begin{equation} \label{eq:pt_sampling} p_\perp = p_{\perp,\text{min}} + \begin{cases} p_{\perp,\text{par}} \tan\left( x_{p_\perp} \arctan\left( \frac{p_{\perp,\text{max}} - p_{\perp,\text{min}}}{p_{\perp,\text{par}}} \right) \right) & y < y_\text{cut} \\ - \tilde{p}_{\perp,\text{par}}\log\left(1 - x_{p_\perp}\left[1 - \exp\left(\frac{p_{\perp,\text{min}} - p_{\perp,\text{max}}}{\tilde{p}_{\perp,\text{par}}}\right)\right]\right) & y \geq y_\text{cut} \end{cases}\,, \end{equation} where $p_{\perp,\text{min}}$ is the minimum jet transverse momentum, $p_{\perp,\text{max}}$ is the maximum transverse parton momentum, tentatively set to the beam energy, and $y_\text{cut}$, $p_{\perp,\text{par}}$ and $\tilde{p}_{\perp,\text{par}}$ are generation parameters set to heuristically determined values of \begin{align} y_\text{cut}&=3,\\ p_{\perp,\text{par}}&=p_{\perp,\min}+\frac{n_p}{5}, \\ \tilde{p}_{\perp,\text{par}}&=\frac{p_{\perp,\text{par}}}{1 + 5(y-y_\text{cut})}. \end{align} The problem with this generation is that the transverse momenta peak at the minimum transverse momentum required for fixed-order jets. However, if we use the generated events as input for \HEJ resummation, events with such soft transverse momenta hardly contribute, see section~\ref{sec:ptj_res}. To generate efficient input for resummation, there is the user option \texttt{peak pt}, which specifies the dominant transverse momentum for resummation jets. If this option is set, most jets will be generated as above, but with $p_{\perp,\text{min}}$ set to the peak transverse momentum $p_{\perp, \text{peak}}$. In addition, there is a small chance of around $2\%$ to generate softer jets. The heuristic ansatz for the transverse momentum distribution in the ``soft'' region is \begin{equation} \label{FO_pt_soft} \frac{\partial \sigma}{\partial p_\perp} \propto e^{n_p\frac{p_\perp- p_{\perp, \text{peak}}}{\bar{p}_\perp}}\,, \end{equation} where $n_p$ is the number of partons and $\bar{p}_\perp \approx 4\,$GeV. To achieve this distribution, we use \begin{equation} \label{eq:FO_pt_soft_sampling} p_\perp = p_{\perp, \text{peak}} + \bar{p}_\perp \frac{\log x_{p_\perp}}{n_p} \end{equation} and discard the phase space point if the parton is too soft, i.e. below the threshold for fixed-order jets. After ensuring that all partons form separate jets, we generate any potential colourless emissions. We then determine the incoming momenta and flavours in \lstinline!PhaseSpacePoint::reconstruct_incoming! and adjust the outgoing flavours to ensure an FKL configuration. Finally, we may reassign outgoing flavours to generate suppressed (for example unordered) configurations. \subsection{Unweighting} \label{sec:unweight} Straightforward event generation tends to produce many events with small weights. Those events have a negligible contribution to the final observables, but can take up considerable storage space and CPU time in later processing stages. This problem can be addressed by unweighting. For naive unweighting, one would determine the maximum weight $w_\text{max}$ of all events, discard each event with weight $w$ with a probability $p=w/w_\text{max}$, and set the weights of all remaining events to $w_\text{max}$. The downside to this procedure is that it also eliminates a sizeable fraction of events with moderate weight, so that the statistical convergence deteriorates. To ameliorate this problem, we perform unweighting only for events with sufficiently small weights. This is done by the \lstinline!Unweighter! class. In the constructor we estimate the mean and width of the weight-weight distribution from a sample of events. We use these estimates to determine the maximum weight below which unweighting is performed. The actual unweighting is the done in the \lstinline!Unweighter::unweight! function. \input{currents} \appendix \section{Continuous Integration} \label{sec:gitlabCI} GitLab provides ways to directly test code via \textit{Continuous integrations}. The CI is controlled by \texttt{.gitlab-ci.yml}. For all options for the YAML file see \href{https://docs.gitlab.com/ee/ci/yaml/}{docs.gitlab.com/ee/ci/yaml/}. GitLab also provides a small tool to check that YAML syntax is correct under \lstinline!CI/CD > Pipelines > CI Lint! or \href{https://gitlab.dur.scotgrid.ac.uk/hej/HEJ/-/ci/lint}{gitlab.dur.scotgrid.ac.uk/hej/HEJ/-/ci/lint}. Currently the CI is configured to trigger a \textit{Pipeline} on each \lstinline!git push!. The corresponding \textit{GitLab runners} are configured under \lstinline!CI/CD Settings>Runners! in the GitLab UI. All runners use a \href{https://www.docker.com/}{docker} image as virtual environments\footnote{To use only Docker runners set the \lstinline!docker! tag in \texttt{.gitlab-ci.yml}.}. The specific docker images maintained separately. If you add a new dependences, please also provide a docker image for the CI. The goal to be able to test \HEJ with all possible configurations. Each pipeline contains multiple stages (see \lstinline!stages! in \texttt{.gitlab-ci.yml}) which are executed in order from top to bottom. Additionally each stage contains multiple jobs. For example the stage \textit{build} contains the jobs \lstinline!build:basic!, \lstinline!build:qcdloop!, \lstinline!build:rivet!, etc., which compile \HEJ for different environments and dependences, by using different in the Docker images. Jobs staring with an dot are ignored by the Runner, e.g. \lstinline!.HEJ_build! is only used as a template but never executed directly. Only after all jobs of the previous stage was executed without any error the next stage will start. To pass information between one stage and the next we use \lstinline!artifacts!. The runner will automatically load all artifacts form all \lstinline!dependencies! for each job\footnote{If no dependencies are defined \textit{all} artifacts from all previous jobs are downloaded. Thus please specify an empty dependence if you do not want to load any artifacts.}. For example the compiled \HEJ code from \lstinline!build:basic! gets loaded in \lstinline!test:basic! and \lstinline!FOG:build:basic!, without recompiling \HEJ again. Additionally artifacts can be downloaded from the GitLab web page, which could be handy for debugging. The actual commands are given in the \lstinline!before_script!, \lstinline!script! and \lstinline!after_script! \footnote{\lstinline!after_script! is always executed} sections, and are standard Linux shell commands (dependent on the docker image). Any failed command, i.e. returning not zero, stops the job and making the pipeline fail entirely. Most tests are just running \lstinline!make test! or are based on it. Thus, to emphasise it again, write tests for your code in \lstinline!cmake!. The CI is only intended to make automated testing in different environments easier. \bibliographystyle{JHEP} \bibliography{biblio} \end{document} diff --git a/doc/developer_manual/figures/ColourConnects_FKL.pdf b/doc/developer_manual/figures/ColourConnects_FKL.pdf new file mode 100755 index 0000000..f011d62 Binary files /dev/null and b/doc/developer_manual/figures/ColourConnects_FKL.pdf differ diff --git a/doc/developer_manual/figures/ColourConnects_crossed.pdf b/doc/developer_manual/figures/ColourConnects_crossed.pdf new file mode 100755 index 0000000..e161928 Binary files /dev/null and b/doc/developer_manual/figures/ColourConnects_crossed.pdf differ diff --git a/doc/developer_manual/figures/colour_centralqqx.jpg b/doc/developer_manual/figures/colour_centralqqx.jpg new file mode 100644 index 0000000..84ea16a Binary files /dev/null and b/doc/developer_manual/figures/colour_centralqqx.jpg differ diff --git a/doc/developer_manual/figures/colour_gleft.jpg b/doc/developer_manual/figures/colour_gleft.jpg new file mode 100644 index 0000000..9a24558 Binary files /dev/null and b/doc/developer_manual/figures/colour_gleft.jpg differ diff --git a/doc/developer_manual/figures/colour_gright.jpg b/doc/developer_manual/figures/colour_gright.jpg new file mode 100644 index 0000000..32e5179 Binary files /dev/null and b/doc/developer_manual/figures/colour_gright.jpg differ diff --git a/doc/developer_manual/figures/colour_qx.jpg b/doc/developer_manual/figures/colour_qx.jpg new file mode 100644 index 0000000..344bbc0 Binary files /dev/null and b/doc/developer_manual/figures/colour_qx.jpg differ diff --git a/doc/developer_manual/figures/colour_uno.jpg b/doc/developer_manual/figures/colour_uno.jpg new file mode 100644 index 0000000..362686c Binary files /dev/null and b/doc/developer_manual/figures/colour_uno.jpg differ diff --git a/doc/developer_manual/src/ColourConnect.tex b/doc/developer_manual/src/ColourConnect.tex new file mode 100755 index 0000000..0330165 --- /dev/null +++ b/doc/developer_manual/src/ColourConnect.tex @@ -0,0 +1,52 @@ +\hspace{2cm} +%% labels left +\begin{minipage}[b]{0.5cm} + \begin{flushright} + $a$ + + \vspace{3.3cm} + $b$ + + \vspace{0.3cm} + \end{flushright} +\end{minipage} +\includegraphics[height=4.5cm]{figures/ColourConnects_FKL.pdf} +\hspace{2.5cm} +\includegraphics[height=4.5cm]{figures/ColourConnects_crossed.pdf} +\hspace{-5.8cm} +%% labels middle +\begin{minipage}[b]{4cm} + $1$ \hspace{2cm} $a$ + + \vspace{0.9cm} + $2$ + + \vspace{0.8cm} + $3$ + + \vspace{0.7cm} + $4$ \hspace{2cm} $b$ + + \vspace{0.3cm} +\end{minipage} +\hspace{1.6cm} +%% labels right +\begin{minipage}[b]{0.5cm} + $1$ + + \vspace{0.8cm} + $2$ + + \vspace{0.8cm} + $3$ + + \vspace{0.8cm} + $4$ + + \vspace{0.3cm} +\end{minipage} + +%%% Local Variables: +%%% mode: latex +%%% TeX-master: "../developer_manual" +%%% End: diff --git a/doc/doxygen/biblio.bib b/doc/doxygen/biblio.bib index 5fce287..3e960be 100644 --- a/doc/doxygen/biblio.bib +++ b/doc/doxygen/biblio.bib @@ -1,56 +1,86 @@ @article{Andersen:2011hs, author = "Andersen, Jeppe R. and Smillie, Jennifer M.", title = "{Multiple Jets at the LHC with High Energy Jets}", journal = "JHEP", volume = "06", year = "2011", pages = "010", doi = "10.1007/JHEP06(2011)010", eprint = "1101.5394", archivePrefix = "arXiv", primaryClass = "hep-ph", reportNumber = "CP3-ORIGINS-2011-02, EDINBURGH-2011-03", SLACcitation = "%%CITATION = ARXIV:1101.5394;%%" } @article{James:1993np, author = "James, F.", title = "{RANLUX: A FORTRAN implementation of the high quality pseudorandom number generator of Luscher}", journal = "Comput. Phys. Commun.", volume = "79", year = "1994", pages = "111-114", doi = "10.1016/0010-4655(94)90233-X", note = "[Erratum: Comput. Phys. Commun.97,357(1996)]", reportNumber = "CERN-CN-93-13", SLACcitation = "%%CITATION = CPHCB,79,111;%%" } @article{Luscher:1993dy, author = "Luscher, Martin", title = "{A Portable high quality random number generator for lattice field theory simulations}", journal = "Comput. Phys. Commun.", volume = "79", year = "1994", pages = "100-110", doi = "10.1016/0010-4655(94)90232-1", eprint = "hep-lat/9309020", archivePrefix = "arXiv", primaryClass = "hep-lat", reportNumber = "DESY-93-133", SLACcitation = "%%CITATION = HEP-LAT/9309020;%%" } @article{Savvidy:2014ana, author = "Savvidy, Konstantin G.", title = "{The MIXMAX random number generator}", journal = "Comput. Phys. Commun.", volume = "196", year = "2015", pages = "161-165", doi = "10.1016/j.cpc.2015.06.003", eprint = "1403.5355", archivePrefix = "arXiv", primaryClass = "hep-lat", reportNumber = "NITS-PHY-2014, NITS-PHY-2014003", SLACcitation = "%%CITATION = ARXIV:1403.5355;%%" } +@inproceedings{Boos:2001cv, + author = "Boos, E. and others", + title = "{Generic user process interface for event generators}", + booktitle = "{Physics at TeV colliders. Proceedings, Euro Summer + School, Les Houches, France, May 21-June 1, 2001}", + url = "http://lss.fnal.gov/archive/preprint/fermilab-conf-01-496-t.shtml", + year = "2001", + eprint = "hep-ph/0109068", + archivePrefix = "arXiv", + primaryClass = "hep-ph", + reportNumber = "FERMILAB-CONF-01-496-T", + SLACcitation = "%%CITATION = HEP-PH/0109068;%%" +} +@article{Andersen:2011zd, + author = "Andersen, Jeppe R. and Lonnblad, Leif and Smillie, + Jennifer M.", + title = "{A Parton Shower for High Energy Jets}", + journal = "JHEP", + volume = "07", + year = "2011", + pages = "110", + doi = "10.1007/JHEP07(2011)110", + eprint = "1104.1316", + archivePrefix = "arXiv", + primaryClass = "hep-ph", + reportNumber = "CERN-PH-TH-2011-072, CP3-ORIGINS-2011-14, + EDINBURGH-2011-16, LU-TP-11-15, MCNET-11-12, LU-TP + --11-15", + SLACcitation = "%%CITATION = ARXIV:1104.1316;%%" +} diff --git a/doc/sphinx/conf.py b/doc/sphinx/conf.py index a9d958d..12fdfff 100644 --- a/doc/sphinx/conf.py +++ b/doc/sphinx/conf.py @@ -1,175 +1,175 @@ # -*- coding: utf-8 -*- # # HEJ 2 documentation build configuration file, created by # sphinx-quickstart on Fri Sep 15 16:13:57 2017. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # # import os # import sys # sys.path.insert(0, os.path.abspath('.')) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. # # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = ['sphinx.ext.mathjax', 'sphinx.ext.githubpages'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # # source_suffix = ['.rst', '.md'] source_suffix = '.rst' # The master toctree document. master_doc = 'index' # General information about the project. project = u'HEJ 2' copyright = u'2017, Jeppe Andersen, Tuomas Hapola, Marian Heil, Andreas Maier, Jennifer Smillie' author = u'Jeppe Andersen, Tuomas Hapola, Marian Heil, Andreas Maier, Jennifer Smillie' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = u'2.0' # The full version, including alpha/beta/rc tags. -release = u'2.0.4' +release = u'2.0.5' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. language = None # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This patterns also effect to html_static_path and html_extra_path exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] highlight_language = 'C++' # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = False # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'alabaster' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. html_theme_options = { 'body_text': '#000000', 'narrow_sidebar_fg': '#000000', 'sidebar_header': '#000000', 'sidebar_link': '#000000', 'sidebar_text': '#000000' } # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". # html_static_path = ['_static'] # Custom sidebar templates, must be a dictionary that maps document names # to template names. # # This is required for the alabaster theme # refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars html_sidebars = { '**': [ 'about.html', 'navigation.html', 'relations.html', # needs 'show_related': True theme option to display 'searchbox.html', 'donate.html', ] } # -- Options for HTMLHelp output ------------------------------------------ # Output file base name for HTML help builder. htmlhelp_basename = 'HEJ2doc' # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # # 'preamble': '', # Latex figure (float) alignment # # 'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ (master_doc, 'HEJ2.tex', u'HEJ 2 Documentation', u'Jeppe Andersen, Tuomas Hapola, Marian Heil, Andreas Maier, Jennifer Smillie', 'manual'), ] # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ (master_doc, 'hej2', u'HEJ 2 Documentation', [author], 1) ] # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ (master_doc, 'HEJ2', u'HEJ 2 Documentation', author, 'HEJ2', 'One line description of project.', 'Miscellaneous'), ] diff --git a/include/HEJ/Constants.hh b/include/HEJ/Constants.hh index aadd813..4568911 100644 --- a/include/HEJ/Constants.hh +++ b/include/HEJ/Constants.hh @@ -1,34 +1,39 @@ /** \file * \brief Header file defining all global constants used for HEJ * * \authors Jeppe Andersen, Tuomas Hapola, Marian Heil, Andreas Maier, Jennifer Smillie * \date 2019 * \copyright GPLv2 or later */ #pragma once namespace HEJ{ /// @name QCD parameters //@{ constexpr double N_C = 3.; //!< number of Colours constexpr double C_A = N_C; //!< \f$C_A\f$ constexpr double C_F = (N_C*N_C - 1.)/(2.*N_C); //!< \f$C_F\f$ constexpr double t_f = 0.5; //!< \f$t_f\f$ constexpr double n_f = 5.; //!< number light flavours constexpr double beta0 = 11./3.*C_A - 4./3.*t_f*n_f; //!< \f$\beta_0\f$ //@} /// @name QFT parameters //@{ constexpr double vev = 246.2196508; //!< Higgs vacuum expectation value in GeV constexpr double gw = 0.653233; constexpr double MW = 80.419; // The W mass in GeV/c^2 constexpr double GammaW = 2.0476; // the W width in GeV/c^2 //@} /// @name Generation Parameters //@{ //! Default scale for virtual correction, \f$\lambda\f$ cf. eq. (20) in \cite Andersen:2011hs constexpr double CLAMBDA = 0.2; constexpr double CMINPT = 0.2; //!< minimal \f$p_t\f$ of all partons //@} +/// @name Conventional Parameters +//@{ + //! Value of first colour for colour dressing, according to LHE convention \cite Boos:2001cv + constexpr int COLOUR_OFFSET = 501; +//@} } diff --git a/include/HEJ/Event.hh b/include/HEJ/Event.hh index 6b59620..71b5503 100644 --- a/include/HEJ/Event.hh +++ b/include/HEJ/Event.hh @@ -1,199 +1,275 @@ /** \file * \brief Declares the Event class and helpers * * \authors Jeppe Andersen, Tuomas Hapola, Marian Heil, Andreas Maier, Jennifer Smillie * \date 2019 * \copyright GPLv2 or later */ #pragma once #include <array> #include <memory> #include <string> #include <unordered_map> #include <vector> #include "HEJ/event_types.hh" +#include "HEJ/Parameters.hh" #include "HEJ/Particle.hh" +#include "HEJ/RNG.hh" #include "fastjet/ClusterSequence.hh" namespace LHEF{ class HEPEUP; class HEPRUP; } namespace fastjet{ class JetDefinition; } namespace HEJ{ + struct UnclusteredEvent; - struct ParameterDescription; - - //! Event parameters - struct EventParameters{ - double mur; /**< Value of the Renormalisation Scale */ - double muf; /**< Value of the Factorisation Scale */ - double weight; /**< Event Weight */ - //! Optional description - std::shared_ptr<ParameterDescription> description = nullptr; - }; - - //! Description of event parameters - struct ParameterDescription { - //! Name of central scale choice (e.g. "H_T/2") - std::string scale_name; - //! Actual renormalisation scale divided by central scale - double mur_factor; - //! Actual factorisation scale divided by central scale - double muf_factor; - - ParameterDescription() = default; - ParameterDescription( - std::string scale_name, double mur_factor, double muf_factor - ): - scale_name{scale_name}, mur_factor{mur_factor}, muf_factor{muf_factor} - {}; - }; - - //! An event before jet clustering - struct UnclusteredEvent{ - //! Default Constructor - UnclusteredEvent() = default; - //! Constructor from LesHouches event information - UnclusteredEvent(LHEF::HEPEUP const & hepeup); - - std::array<Particle, 2> incoming; /**< Incoming Particles */ - std::vector<Particle> outgoing; /**< Outgoing Particles */ - //! Particle decays in the format {outgoing index, decay products} - std::unordered_map<size_t, std::vector<Particle>> decays; - //! Central parameter (e.g. scale) choice - EventParameters central; - std::vector<EventParameters> variations; /**< For parameter variation */ - }; - - /** An event with clustered jets + /** @brief An event with clustered jets * * This is the main HEJ 2 event class. * It contains kinematic information including jet clustering, * parameter (e.g. scale) settings and the event weight. */ class Event{ public: - //! Default Event Constructor - Event() = default; + class EventData; + //! No default Constructor + Event() = delete; //! Event Constructor adding jet clustering to an unclustered event + //! @deprecated UnclusteredEvent will be replaced by EventData in HEJ 2.3.0 + [[deprecated("UnclusteredEvent will be replaced by EventData")]] Event( - UnclusteredEvent ev, - fastjet::JetDefinition const & jet_def, double min_jet_pt + UnclusteredEvent const & ev, + fastjet::JetDefinition const & jet_def, double min_jet_pt ); //! The jets formed by the outgoing partons std::vector<fastjet::PseudoJet> jets() const; - //! The corresponding event before jet clustering - UnclusteredEvent const & unclustered() const { - return ev_; - } - - //! Central parameter choice - EventParameters const & central() const{ - return ev_.central; - } - - //! Central parameter choice - EventParameters & central(){ - return ev_.central; - } - //! Incoming particles std::array<Particle, 2> const & incoming() const{ - return ev_.incoming; + return incoming_; } - //! Outgoing particles std::vector<Particle> const & outgoing() const{ - return ev_.outgoing; + return outgoing_; } - //! Particle decays /** * The key in the returned map corresponds to the index in the * vector returned by outgoing() */ std::unordered_map<size_t, std::vector<Particle>> const & decays() const{ - return ev_.decays; + return decays_; } - //! Parameter (scale) variations - std::vector<EventParameters> const & variations() const{ - return ev_.variations; + //! All chosen parameter, i.e. scale choices (const version) + Parameters<EventParameters> const & parameters() const{ + return parameters_; + } + //! All chosen parameter, i.e. scale choices + Parameters<EventParameters> & parameters(){ + return parameters_; + } + + //! Central parameter choice (const version) + EventParameters const & central() const{ + return parameters_.central; + } + //! Central parameter choice + EventParameters & central(){ + return parameters_.central; } + //! Parameter (scale) variations (const version) + std::vector<EventParameters> const & variations() const{ + return parameters_.variations; + } //! Parameter (scale) variations std::vector<EventParameters> & variations(){ - return ev_.variations; + return parameters_.variations; } - //! Parameter (scale) variation + //! Parameter (scale) variation (const version) /** * @param i Index of the requested variation */ EventParameters const & variations(size_t i) const{ - return ev_.variations[i]; + return parameters_.variations[i]; } - //! Parameter (scale) variation /** * @param i Index of the requested variation */ EventParameters & variations(size_t i){ - return ev_.variations[i]; + return parameters_.variations[i]; } //! Indices of the jets the outgoing partons belong to /** * @param jets Jets to be tested * @returns A vector containing, for each outgoing parton, * the index in the vector of jets the considered parton * belongs to. If the parton is not inside any of the * passed jets, the corresponding index is set to -1. */ std::vector<int> particle_jet_indices( std::vector<fastjet::PseudoJet> const & jets ) const{ return cs_.particle_jet_indices(jets); } //! Jet definition used for clustering fastjet::JetDefinition const & jet_def() const{ return cs_.jet_def(); } //! Minimum jet transverse momentum double min_jet_pt() const{ return min_jet_pt_; } //! Event type event_type::EventType type() const{ return type_; } + //! Give colours to each particle + /** + * @returns true if new colours are generated, i.e. same as is_HEJ() + * @details Colour ordering is done according to leading colour in the MRK + * limit, see \cite Andersen:2011zd. This only affects \ref + * is_HEJ() "HEJ" configurations, all other \ref event_type + * "EventTypes" will be ignored. + * @note This overwrites all previously set colours. + */ + bool generate_colours(HEJ::RNG &); + private: - UnclusteredEvent ev_; + //! \internal + //! @brief Construct Event explicitly from input. + /** This is only intended to be called from EventData. + * + * \warning The input is taken _as is_, sorting and classification has to be + * done externally, i.e. by EventData + */ + Event( + std::array<Particle, 2> && incoming, + std::vector<Particle> && outgoing, + std::unordered_map<size_t, std::vector<Particle>> && decays, + Parameters<EventParameters> && parameters, + fastjet::JetDefinition const & jet_def, + double const min_jet_pt + ): incoming_{std::move(incoming)}, + outgoing_{std::move(outgoing)}, + decays_{std::move(decays)}, + parameters_{std::move(parameters)}, + cs_{ to_PseudoJet( filter_partons(outgoing_) ), jet_def }, + min_jet_pt_{min_jet_pt} + {}; + + std::array<Particle, 2> incoming_; + std::vector<Particle> outgoing_; + std::unordered_map<size_t, std::vector<Particle>> decays_; + Parameters<EventParameters> parameters_; fastjet::ClusterSequence cs_; double min_jet_pt_; event_type::EventType type_; - }; + }; // end class Event + + //! Class to store general Event setup, i.e. Phase space and weights + class Event::EventData{ + public: + //! Default Constructor + EventData() = default; + //! Constructor from LesHouches event information + EventData(LHEF::HEPEUP const & hepeup); + //! Constructor with all values given + EventData( + std::array<Particle, 2> const & incoming_, + std::vector<Particle> const & outgoing_, + std::unordered_map<size_t, std::vector<Particle>> const & decays_, + Parameters<EventParameters> const & parameters_ + ): + incoming(incoming_), outgoing(outgoing_), + decays(decays_), parameters(parameters_) + {}; + //! Move Constructor with all values given + EventData( + std::array<Particle, 2> && incoming_, + std::vector<Particle> && outgoing_, + std::unordered_map<size_t, std::vector<Particle>> && decays_, + Parameters<EventParameters> && parameters_ + ): + incoming(std::move(incoming_)), outgoing(std::move(outgoing_)), + decays(std::move(decays_)), parameters(std::move(parameters_)) + {}; + + //! Generate an Event from the stored EventData. + /** + * @details Do jet clustering and classification. + * Use this to generate an Event. + * + * @note Calling this function destroys EventData + * + * @param jet_def Jet definition + * @param min_jet_pt minimal \f$p_T\f$ for each jet + * + * @returns Full clustered and classified event. + */ + Event cluster( + fastjet::JetDefinition const & jet_def, double const min_jet_pt); + + //! Alias for cluster() + Event operator()( + fastjet::JetDefinition const & jet_def, double const min_jet_pt){ + return cluster(jet_def, min_jet_pt); + }; + + //! Sort particles in rapidity + void sort(); + + std::array<Particle, 2> incoming; + std::vector<Particle> outgoing; + std::unordered_map<size_t, std::vector<Particle>> decays; + Parameters<EventParameters> parameters; + }; // end class EventData //! Square of the partonic centre-of-mass energy \f$\hat{s}\f$ double shat(Event const & ev); //! Convert an event to a LHEF::HEPEUP LHEF::HEPEUP to_HEPEUP(Event const & event, LHEF::HEPRUP *); + // put deprecated warning at the end, so don't get the warning inside Event.hh, + // additionally doxygen can not identify [[deprecated]] correctly + struct [[deprecated("UnclusteredEvent will be replaced by EventData")]] + UnclusteredEvent; + //! An event before jet clustering + //! @deprecated UnclusteredEvent will be replaced by EventData in HEJ 2.3.0 + struct UnclusteredEvent{ + //! Default Constructor + UnclusteredEvent() = default; + //! Constructor from LesHouches event information + UnclusteredEvent(LHEF::HEPEUP const & hepeup); + + std::array<Particle, 2> incoming; /**< Incoming Particles */ + std::vector<Particle> outgoing; /**< Outgoing Particles */ + //! Particle decays in the format {outgoing index, decay products} + std::unordered_map<size_t, std::vector<Particle>> decays; + //! Central parameter (e.g. scale) choice + EventParameters central; + std::vector<EventParameters> variations; /**< For parameter variation */ + }; + } diff --git a/include/HEJ/EventReweighter.hh b/include/HEJ/EventReweighter.hh index b6b1c2d..e139ce1 100644 --- a/include/HEJ/EventReweighter.hh +++ b/include/HEJ/EventReweighter.hh @@ -1,190 +1,190 @@ /** \file * \brief Declares the EventReweighter class * * EventReweighter is the main class used within HEJ 2. It reweights the * resummation events. * * \authors Jeppe Andersen, Tuomas Hapola, Marian Heil, Andreas Maier, Jennifer Smillie * \date 2019 * \copyright GPLv2 or later */ #pragma once #include <array> #include <functional> #include <utility> #include <vector> #include "HEJ/config.hh" #include "HEJ/event_types.hh" #include "HEJ/MatrixElement.hh" #include "HEJ/PDF.hh" #include "HEJ/PDG_codes.hh" #include "HEJ/RNG.hh" #include "HEJ/ScaleFunction.hh" +#include "HEJ/Parameters.hh" namespace LHEF { class HEPRUP; } namespace HEJ{ class Event; - class Weights; //! Beam parameters /** * Currently, only symmetric beams are supported, * so there is a single beam energy. */ struct Beam{ double E; /**< Beam energy */ std::array<ParticleID, 2> type; /**< Beam particles */ }; //! Main class for reweighting events in HEJ. class EventReweighter{ using EventType = event_type::EventType; public: EventReweighter( Beam beam, /**< Beam Energy */ int pdf_id, /**< PDF ID */ ScaleGenerator scale_gen, /**< Scale settings */ EventReweighterConfig conf, /**< Configuration parameters */ HEJ::RNG & ran /**< Random number generator */ ); EventReweighter( LHEF::HEPRUP const & heprup, /**< LHEF event header */ ScaleGenerator scale_gen, /**< Scale settings */ EventReweighterConfig conf, /**< Configuration parameters */ HEJ::RNG & ran /**< Random number generator */ ); //! Get the used pdf PDF const & pdf() const; //! Generate resummation events for a given fixed-order event /** * @param ev Fixed-order event corresponding * to the resummation events * @param num_events Number of trial resummation configurations. * @returns A vector of resummation events. * * The result vector depends on the type of the input event and the * treatment of different types as specified in the constructor: * * \ref reweight The result vector contains between * 0 and num_events resummation events. * * \ref keep If the input event passes the resummation jet cuts * the result vector contains one event. Otherwise it is empty. * * \ref discard The result vector is empty */ std::vector<Event> reweight( Event const & ev, int num_events ); private: template<typename... T> PDF const & pdf(T&& ...); /** \internal * \brief main generation/reweighting function: * generate phase space points and divide out Born factors */ std::vector<Event> gen_res_events( Event const & ev, int num_events ); std::vector<Event> rescale( Event const & Born_ev, std::vector<Event> events ) const; /** \internal * \brief Do the Jets pass the resummation Cuts? * * @param ev Event in Question * @returns 0 or 1 depending on if ev passes Jet Cuts */ bool jets_pass_resummation_cuts(Event const & ev) const; /** \internal * \brief pdf_factors Function * * @param ev Event in Question * @returns EventFactor due to PDFs * * Calculates the Central value and the variation due * to the PDF choice made. */ Weights pdf_factors(Event const & ev) const; /** \internal * \brief matrix_elements Function * * @param ev Event in question * @returns EventFactor due to MatrixElements * * Calculates the Central value and the variation due * to the Matrix Element. */ Weights matrix_elements(Event const & ev) const; /** \internal * \brief Scale-dependent part of fixed-order matrix element * * @param ev Event in question * @returns EventFactor scale variation due to FO-ME. * * This is only called to compute the scale variation for events where * we don't do resummation (e.g. non-FKL). * Since at tree level the scale dependence is just due to alpha_s, * it is enough to return the alpha_s(mur) factors in the matrix element. * The rest drops out in the ratio of (output event ME)/(input event ME), * so we never have to compute it. */ Weights fixed_order_scale_ME(Event const & ev) const; /** \internal * \brief Computes the tree level matrix element * * @param ev Event in Question * @returns HEJ approximation to Tree level Matrix Element * * This computes the HEJ approximation to the tree level FO * Matrix element which is used within the LO weighting process. */ double tree_matrix_element(Event const & ev) const; //! \internal General parameters EventReweighterConfig param_; //! \internal Beam energy double E_beam_; //! \internal PDF PDF pdf_; //! \internal Object to calculate the square of the matrix element MatrixElement MEt2_; //! \internal Object to calculate event renormalisation and factorisation scales ScaleGenerator scale_gen_; /** \internal random number generator * * \note We use a reference_wrapper so that EventReweighter objects can * still be copied (which would be impossible with a reference). */ std::reference_wrapper<HEJ::RNG> ran_; }; template<typename... T> PDF const & EventReweighter::pdf(T&&... t){ return pdf_ = PDF{std::forward<T>(t)...}; } } diff --git a/include/HEJ/MatrixElement.hh b/include/HEJ/MatrixElement.hh index 8df28b9..cfcd15f 100644 --- a/include/HEJ/MatrixElement.hh +++ b/include/HEJ/MatrixElement.hh @@ -1,191 +1,191 @@ /** \file * \brief Contains the MatrixElement Class * * \authors Jeppe Andersen, Tuomas Hapola, Marian Heil, Andreas Maier, Jennifer Smillie * \date 2019 * \copyright GPLv2 or later */ #pragma once #include <functional> #include <vector> #include "fastjet/PseudoJet.hh" #include "HEJ/PDG_codes.hh" -#include "HEJ/Weights.hh" +#include "HEJ/Parameters.hh" #include "HEJ/config.hh" namespace CLHEP { class HepLorentzVector; } namespace HEJ{ class Event; class Particle; //! Class to calculate the squares of matrix elements class MatrixElement{ public: /** \brief MatrixElement Constructor * @param alpha_s Function taking the renormalisation scale * and returning the strong coupling constant * @param conf General matrix element settings */ MatrixElement( std::function<double (double)> alpha_s, MatrixElementConfig conf ); /** * \brief squares of regulated HEJ matrix elements * @param event The event for which to calculate matrix elements * @returns The squares of HEJ matrix elements including virtual corrections * * This function returns one value for the central parameter choice * and one additional value for each entry in \ref Event.variations(). * See eq. (22) in \cite Andersen:2011hs for the definition of the squared * matrix element. * * \internal Relation to standard HEJ Met2: MatrixElement = Met2*shat^2/(pdfta*pdftb) */ Weights operator()(Event const & event) const; //! Squares of HEJ tree-level matrix elements /** * @param event The event for which to calculate matrix elements * @returns The squares of HEJ matrix elements without virtual corrections * * cf. eq. (22) in \cite Andersen:2011hs */ Weights tree(Event const & event) const; /** * \brief Virtual corrections to matrix element squares * @param event The event for which to calculate matrix elements * @returns The virtual corrections to the squares of the matrix elements * * The all order virtual corrections to LL in the MRK limit is * given by replacing 1/t in the scattering amplitude according to the * lipatov ansatz. * * cf. second-to-last line of eq. (22) in \cite Andersen:2011hs * note that indices are off by one, i.e. out[0].p corresponds to p_1 */ Weights virtual_corrections(Event const & event) const; /** * \brief Scale-dependent part of tree-level matrix element squares * @param event The event for which to calculate matrix elements * @returns The scale-dependent part of the squares of the * tree-level matrix elements * * The tree-level matrix elements factorises into a renormalisation-scale * dependent part, given by the strong coupling to some power, and a * scale-independent remainder. This function only returns the former parts * for the central scale choice and all \ref Event.variations(). * * @see tree, tree_kin */ Weights tree_param( Event const & event ) const; /** * \brief Kinematic part of tree-level matrix element squares * @param event The event for which to calculate matrix elements * @returns The kinematic part of the squares of the * tree-level matrix elements * * The tree-level matrix elements factorises into a renormalisation-scale * dependent part, given by the strong coupling to some power, and a * scale-independent remainder. This function only returns the latter part. * Since it does not depend on the parameter variations, only a single value * is returned. * * @see tree, tree_param */ double tree_kin(Event const & event) const; private: double tree_param( Event const & event, double mur ) const; double virtual_corrections_W( Event const & event, double mur, Particle const & WBoson ) const; double virtual_corrections( Event const & event, double mur ) const; //! \internal cf. last line of eq. (22) in \cite Andersen:2011hs double omega0( double alpha_s, double mur, fastjet::PseudoJet const & q_j ) const; double tree_kin_jets( Event const & ev ) const; double tree_kin_W( Event const & ev ) const; double tree_kin_Higgs( Event const & ev ) const; double tree_kin_Higgs_first( Event const & ev ) const; double tree_kin_Higgs_last( Event const & ev ) const; /** * \internal * \brief Higgs inbetween extremal partons. * * Note that in the case of unordered emission, the Higgs is *always* * treated as if in between the extremal (FKL) partons, even if its * rapidity is outside the extremal parton rapidities */ double tree_kin_Higgs_between( Event const & ev ) const; double tree_param_partons( double alpha_s, double mur, std::vector<Particle> const & partons ) const; std::vector<int> in_extremal_jet_indices( std::vector<fastjet::PseudoJet> const & partons ) const; std::vector<Particle> tag_extremal_jet_partons( Event const & ev ) const; double MH2_forwardH( CLHEP::HepLorentzVector p1out, CLHEP::HepLorentzVector p1in, pid::ParticleID type2, CLHEP::HepLorentzVector p2out, CLHEP::HepLorentzVector p2in, CLHEP::HepLorentzVector pH, double t1, double t2 ) const; std::function<double (double)> alpha_s_; MatrixElementConfig param_; }; } diff --git a/include/HEJ/PDG_codes.hh b/include/HEJ/PDG_codes.hh index 17c44a9..cf3ca3a 100644 --- a/include/HEJ/PDG_codes.hh +++ b/include/HEJ/PDG_codes.hh @@ -1,143 +1,143 @@ /** \file PDG_codes.hh * \brief Contains the Particle IDs of all relevant SM particles. * * Large enumeration included which has multiple entries for potential * alternative names of different particles. There are also functions * which can be used to determine if a particle is a parton or if * it is a non-gluon boson. * * \authors Jeppe Andersen, Tuomas Hapola, Marian Heil, Andreas Maier, Jennifer Smillie * \date 2019 * \copyright GPLv2 or later */ #pragma once #include <string> namespace HEJ { //! particle ids according to PDG namespace pid { //! The possible particle identities. We use PDG IDs as standard. enum ParticleID{ d = 1, /*!< Down Quark */ down = d, /*!< Down Quark */ u = 2, /*!< Up Quark */ up = u, /*!< Up Quark */ s = 3, /*!< Strange Quark */ strange = s, /*!< Strange Quark */ c = 4, /*!< Charm Quark */ charm = c, /*!< Charm Quark */ b = 5, /*!< Bottom Quark */ bottom = b, /*!< Bottom Quark */ t = 6, /*!< Top Quark */ top = t, /*!< Top Quark */ e = 11, /*!< Electron */ electron = e, /*!< Electron */ nu_e = 12, /*!< Electron Neutrino */ electron_neutrino = nu_e, /*!< Electron neutrino */ mu = 13, /*!< Muon */ muon = mu, /*!< Muon */ nu_mu = 14, /*!< Muon Neutrino */ muon_neutrino = nu_mu, /*!< Muon Neutrino */ tau = 15, /*!< Tau */ nu_tau = 16, /*!< Tau Neutrino */ tau_neutrino = nu_tau, /*!< Tau Neutrino */ d_bar = -d, /*!< Anti-Down Quark */ u_bar = -u, /*!< Anti-Up quark */ s_bar = -s, /*!< Anti-Strange Quark */ c_bar = -c, /*!< Anti-Charm Quark */ b_bar = -b, /*!< Anti-Bottom Quark */ t_bar = -t, /*!< Anti-Top Quark */ e_bar = -e, /*!< Positron */ positron = e_bar, /*!< Positron */ nu_e_bar = -nu_e, /*!< Anti-Electron Neutrino */ mu_bar = -mu, /*!< Anti-Muon */ nu_mu_bar = -nu_mu, /*!< Anti-Muon Neutrino */ tau_bar = -tau, /*!< Anti-Tau */ nu_tau_bar = -nu_tau, /*!< Anti-Tau Neutrino */ gluon = 21, /*!< Gluon */ g = gluon, /*!< Gluon */ photon = 22, /*!< Photon */ gamma = photon, /*!< Photon */ Z = 23, /*!< Z Boson */ Wp = 24, /*!< W- Boson */ Wm = -Wp, /*!< W+ Boson */ h = 25, /*!< Higgs Boson */ Higgs = h, /*!< Higgs Boson */ higgs = h, /*!< Higgs Boson */ p = 2212, /*!< Proton */ proton = p, /*!< Proton */ p_bar = -p, /*!< Anti-Proton */ }; } using ParticleID = pid::ParticleID; //! Convert a particle name to the corresponding PDG particle ID ParticleID to_ParticleID(std::string const & name); /** * \brief Function to determine if particle is a parton * @param p PDG ID of particle * @returns true if the particle is a parton, false otherwise */ inline constexpr bool is_parton(ParticleID p){ return p == pid::gluon || std::abs(p) <= pid::top; } /** - * \brief function to determine if the particle is a photon, W, Z, or Higgs boson - * @param id PDG ID of particle - * @returns true if the partice is a A,W,Z, or H, false otherwise - */ - inline - constexpr bool is_AWZH_boson(ParticleID id){ - return id == pid::Wm || (id >= pid::photon && id <= pid::Higgs); - } - - /** * \brief Function to determine if particle is a quark * @param id PDG ID of particle * @returns true if the particle is a qaurk, false otherwise */ inline constexpr bool is_quark(ParticleID id){ return (id >= pid::down && id <= pid::top); } /** * \brief Function to determine if particle is a antiquark * @param id PDG ID of particle * @returns true if the particle is an antiquark, false otherwise */ inline constexpr bool is_antiquark(ParticleID id){ return (id <= pid::d_bar && id >= pid::t_bar); } /** - * \brief Function to determine if particle is a any-quark + * \brief Function to determine if particle is a (anti-)quark * @param id PDG ID of particle * @returns true if the particle is a quark or antiquark, false otherwise */ inline constexpr bool is_anyquark(ParticleID id){ return (id && id >= pid::t_bar && id <= pid::t); } /** + * \brief function to determine if the particle is a photon, W, Z, or Higgs boson + * @param id PDG ID of particle + * @returns true if the partice is a A,W,Z, or H, false otherwise + */ + inline + constexpr bool is_AWZH_boson(ParticleID id){ + return id == pid::Wm || (id >= pid::photon && id <= pid::Higgs); + } + + /** * \brief function to determine if the particle is a photon, W or Z * @param id PDG ID of particle * @returns true if the partice is a A,W,Z, or H, false otherwise */ inline constexpr bool is_AWZ_boson(ParticleID id){ return id == pid::Wm || (id >= pid::photon && id <= pid::Wp); } } diff --git a/include/HEJ/Parameters.hh b/include/HEJ/Parameters.hh new file mode 100644 index 0000000..b9ed559 --- /dev/null +++ b/include/HEJ/Parameters.hh @@ -0,0 +1,155 @@ +/** \file + * \brief Containers for Parameter variations, e.g. different Weights + * + * \authors Jeppe Andersen, Tuomas Hapola, Marian Heil, Andreas Maier, Jennifer Smillie + * \date 2019 + * \copyright GPLv2 or later + */ +#pragma once + +#include <memory> +#include <vector> + +#include "HEJ/exceptions.hh" + +namespace HEJ{ + //! Collection of parameters, e.g. Weights, assigned to a single event + /** + * A number of member functions of the MatrixElement class return Parameters + * objects containing the squares of the matrix elements for the various + * scale choices. + */ + template<class T> + struct Parameters { + T central; + std::vector<T> variations; + + template<class T_ext> + Parameters<T>& operator*=(Parameters<T_ext> const & other); + Parameters<T>& operator*=(double factor); + template<class T_ext> + Parameters<T>& operator/=(Parameters<T_ext> const & other); + Parameters<T>& operator/=(double factor); + }; + + template<class T1, class T2> inline + Parameters<T1> operator*(Parameters<T1> a, Parameters<T2> const & b) { + a*=b; + return a; + } + template<class T> inline + Parameters<T> operator*(Parameters<T> a, double b) { + a*=b; + return a; + } + template<class T> inline + Parameters<T> operator*(double b, Parameters<T> a) { + a*=b; + return a; + } + template<class T1, class T2> inline + Parameters<T1> operator/(Parameters<T1> a, Parameters<T2> const & b) { + a/=b; + return a; + } + template<class T> inline + Parameters<T> operator/(Parameters<T> a, double b) { + a/=b; + return a; + } + + //! Alias for weight container, e.g. used by the MatrixElement + using Weights = Parameters<double>; + + //! Description of event parameters, see also EventParameters + struct ParameterDescription { + //! Name of central scale choice (e.g. "H_T/2") + std::string scale_name; + //! Actual renormalisation scale divided by central scale + double mur_factor; + //! Actual factorisation scale divided by central scale + double muf_factor; + + ParameterDescription() = default; + ParameterDescription( + std::string scale_name, double mur_factor, double muf_factor + ): + scale_name{scale_name}, mur_factor{mur_factor}, muf_factor{muf_factor} + {}; + }; + + //! Event parameters + struct EventParameters{ + double mur; /**< Value of the Renormalisation Scale */ + double muf; /**< Value of the Factorisation Scale */ + double weight; /**< Event Weight */ + //! Optional description + std::shared_ptr<ParameterDescription> description = nullptr; + + //! multiply weight by factor + EventParameters& operator*=(double factor){ + weight*=factor; + return *this; + }; + //! divide weight by factor + EventParameters& operator/=(double factor){ + weight/=factor; + return *this; + }; + }; + inline EventParameters operator*(EventParameters a, double b){ + a*=b; + return a; + } + inline EventParameters operator*(double b, EventParameters a){ + a*=b; + return a; + } + inline EventParameters operator/(EventParameters a, double b){ + a/=b; + return a; + } + + //! @{ + //! @internal Implementation of template functions + template<class T> + template<class T_ext> + Parameters<T>& Parameters<T>::operator*=(Parameters<T_ext> const & other) { + if(other.variations.size() != variations.size()) { + throw std::invalid_argument{"Wrong number of Parameters"}; + } + central *= other.central; + for(std::size_t i = 0; i < variations.size(); ++i) { + variations[i] *= other.variations[i]; + } + return *this; + }; + + template<class T> + Parameters<T>& Parameters<T>::operator*=(double factor) { + central *= factor; + for(auto & wt: variations) wt *= factor; + return *this; + }; + + template<class T> + template<class T_ext> + Parameters<T>& Parameters<T>::operator/=(Parameters<T_ext> const & other) { + if(other.variations.size() != variations.size()) { + throw std::invalid_argument{"Wrong number of Parameters"}; + } + central /= other.central; + for(std::size_t i = 0; i < variations.size(); ++i) { + variations[i] /= other.variations[i]; + } + return *this; + }; + + template<class T> + Parameters<T>& Parameters<T>::operator/=(double factor) { + central /= factor; + for(auto & wt: variations) wt /= factor; + return *this; + }; + //! @} +} diff --git a/include/HEJ/Particle.hh b/include/HEJ/Particle.hh index cde958e..c6f321c 100644 --- a/include/HEJ/Particle.hh +++ b/include/HEJ/Particle.hh @@ -1,141 +1,148 @@ /** * \file Particle.hh * \brief Contains the particle struct * * \authors Jeppe Andersen, Tuomas Hapola, Marian Heil, Andreas Maier, Jennifer Smillie * \date 2019 * \copyright GPLv2 or later */ #pragma once +#include <utility> + #include "fastjet/PseudoJet.hh" +#include "HEJ/optional.hh" #include "HEJ/PDG_codes.hh" namespace HEJ { + using Colour = std::pair<int,int>; + //! Class representing a particle struct Particle { //! particle type ParticleID type; //! particle momentum fastjet::PseudoJet p; + //! (optional) colour & anti-colour + optional<Colour> colour; //! get rapidity double rapidity() const{ return p.rapidity(); } //! get transverse momentum double perp() const{ return p.perp(); } //! get momentum in x direction double px() const{ return p.px(); } //! get momentum in y direction double py() const{ return p.py(); } //! get momentum in z direction double pz() const{ return p.pz(); } //! get energy double E() const{ return p.E(); } //! get mass double m() const{ return p.m(); } }; //! Functor to compare rapidities /** * This can be used whenever a rapidity comparison function is needed, * for example in many standard library functions. * * @see pz_less */ struct rapidity_less{ template<class FourVector> bool operator()(FourVector const & p1, FourVector const & p2){ return p1.rapidity() < p2.rapidity(); } }; //! Functor to compare momenta in z direction /** * This can be used whenever a pz comparison function is needed, * for example in many standard library functions. * * @see rapidity_less */ struct pz_less{ template<class FourVector> bool operator()(FourVector const & p1, FourVector const & p2){ return p1.pz() < p2.pz(); } }; //! Convert a vector of Particles to a vector of particle momenta inline std::vector<fastjet::PseudoJet> to_PseudoJet( std::vector<Particle> const & v ){ std::vector<fastjet::PseudoJet> result; for(auto && sp: v) result.emplace_back(sp.p); return result; } //! Check if a particle is a parton, i.e. quark, antiquark, or gluon inline bool is_parton(Particle const & p){ return is_parton(p.type); } //! Check if a particle is a quark inline bool is_quark(Particle const & p){ return is_quark(p.type); } //! Check if a particle is an anti-quark inline bool is_antiquark(Particle const & p){ return is_antiquark(p.type); } - //! Check if a particle is a quark or any-quark + //! Check if a particle is a quark or anit-quark inline bool is_anyquark(Particle const & p){ return is_anyquark(p.type); } //! Check if a particle is a photon, W or Z boson inline bool is_AWZ_boson(Particle const & particle){ return is_AWZ_boson(particle.type); } //! Check if a particle is a photon, W, Z, or Higgs boson inline bool is_AWZH_boson(Particle const & particle){ return is_AWZH_boson(particle.type); } //! Extract all partons from a vector of particles inline std::vector<Particle> filter_partons( std::vector<Particle> const & v ){ std::vector<Particle> result; result.reserve(v.size()); std::copy_if( begin(v), end(v), std::back_inserter(result), [](Particle const & p){ return is_parton(p); } ); return result; } } diff --git a/include/HEJ/PhaseSpacePoint.hh b/include/HEJ/PhaseSpacePoint.hh index 6bfce05..6189932 100644 --- a/include/HEJ/PhaseSpacePoint.hh +++ b/include/HEJ/PhaseSpacePoint.hh @@ -1,155 +1,155 @@ /** \file * \brief Contains the PhaseSpacePoint Class * * \authors Jeppe Andersen, Tuomas Hapola, Marian Heil, Andreas Maier, Jennifer Smillie * \date 2019 * \copyright GPLv2 or later */ #pragma once #include <array> #include <functional> #include <unordered_map> #include <vector> #include "HEJ/config.hh" #include "HEJ/Particle.hh" #include "HEJ/RNG.hh" namespace HEJ{ class Event; //! A point in resummation phase space class PhaseSpacePoint{ public: //! Default PhaseSpacePoint Constructor PhaseSpacePoint() = default; //! PhaseSpacePoint Constructor /** * @param ev Clustered Jet Event * @param conf Configuration parameters * @param ran Random number generator */ PhaseSpacePoint( Event const & ev, PhaseSpacePointConfig conf, - HEJ::RNG & ran + RNG & ran ); //! Get phase space point weight double weight() const{ return weight_; } //! Access incoming particles std::array<Particle, 2> const & incoming() const{ return incoming_; } //! Access outgoing particles std::vector<Particle> const & outgoing() const{ return outgoing_; } //! Particle decays /** * The key in the returned map corresponds to the index in the * vector returned by outgoing() */ std::unordered_map<size_t, std::vector<Particle>> const & decays() const{ return decays_; } static constexpr int ng_max = 1000; //< maximum number of extra gluons private: std::vector<fastjet::PseudoJet> cluster_jets( std::vector<fastjet::PseudoJet> const & partons ) const; bool pass_resummation_cuts( std::vector<fastjet::PseudoJet> const & jets ) const; bool pass_extremal_cuts( fastjet::PseudoJet const & ext_parton, fastjet::PseudoJet const & jet ) const; int sample_ng(std::vector<fastjet::PseudoJet> const & Born_jets); int sample_ng_jets(int ng, std::vector<fastjet::PseudoJet> const & Born_jets); double probability_in_jet( std::vector<fastjet::PseudoJet> const & Born_jets ) const; std::vector<fastjet::PseudoJet> gen_non_jet( int ng_non_jet, double ptmin, double ptmax ); void rescale_rapidities( std::vector<fastjet::PseudoJet> & partons, double ymin, double ymax ); std::vector<fastjet::PseudoJet> reshuffle( std::vector<fastjet::PseudoJet> const & Born_jets, fastjet::PseudoJet const & q ); bool jets_ok( std::vector<fastjet::PseudoJet> const & Born_jets, std::vector<fastjet::PseudoJet> const & partons ) const; void reconstruct_incoming(std::array<Particle, 2> const & Born_incoming); double phase_space_normalisation( int num_Born_jets, int num_res_partons ) const; std::vector<fastjet::PseudoJet> split( std::vector<fastjet::PseudoJet> const & jets, int ng_jets ); std::vector<int> distribute_jet_partons( int ng_jets, std::vector<fastjet::PseudoJet> const & jets ); std::vector<fastjet::PseudoJet> split( std::vector<fastjet::PseudoJet> const & jets, std::vector<int> const & np_in_jet ); bool split_preserved_jets( std::vector<fastjet::PseudoJet> const & jets, std::vector<fastjet::PseudoJet> const & jet_partons ) const; template<class Particle> Particle const & most_backward_FKL( std::vector<Particle> const & partons ) const; template<class Particle> Particle const & most_forward_FKL( std::vector<Particle> const & partons ) const; template<class Particle> Particle & most_backward_FKL(std::vector<Particle> & partons) const; template<class Particle> Particle & most_forward_FKL(std::vector<Particle> & partons) const; bool extremal_ok( std::vector<fastjet::PseudoJet> const & partons ) const; void label_qqx(Event const & event); void copy_AWZH_boson_from(Event const & event); bool momentum_conserved() const; bool unob_, unof_, qqxb_, qqxf_, qqxmid_; double weight_; PhaseSpacePointConfig param_; std::array<Particle, 2> incoming_; std::vector<Particle> outgoing_; //! \internal Particle decays in the format {outgoing index, decay products} std::unordered_map<size_t, std::vector<Particle>> decays_; std::reference_wrapper<HEJ::RNG> ran_; }; } diff --git a/include/HEJ/Tensor.hh b/include/HEJ/Tensor.hh index 5df0041..b02ae30 100644 --- a/include/HEJ/Tensor.hh +++ b/include/HEJ/Tensor.hh @@ -1,201 +1,201 @@ /** \file * \brief Tensor Template Class declaration. * * This file contains the declaration of the Tensor Template class. This * is used to calculate some of the more complex currents within the * W+Jets implementation particularly. */ #pragma once #include <array> ///@TODO remove function implementation from header +///@TODO put in some namespace template <unsigned int N, unsigned int D> - class Tensor - { - public: +class Tensor{ +public: //Constructor - Tensor(void); + Tensor(); Tensor(COM x); //Destructor - virtual ~Tensor(void); + virtual ~Tensor(); - int rank(void){ + int rank(){ return N; } - int dim(void){ + int dim(){ return D; } int len(){ return size; } COM at(int i){ return components[i]; } COM at(int i, int j) { return components[D*i +j]; } COM at(int i, int j, int k) { return components[D*(D*i + j)+ k]; } COM at(int i,int j, int k,int l) { return components[D*(D*(D*i +j) + k) + l]; } COM at(int i,int j, int k,int l, int m){ return components[D*(D*(D*(D*i + j) + k) + l) + m]; } - bool isSet(void){ + bool isSet(){ if(components.size()==0) return false; else return true; } void Fill(COM x){ components=x; } //Set component indexed by i,j,k,l,m void Set(int i,COM x){ components[i] = x; } void Set(int i, int j, COM x) { components[D*i +j] = x; } void Set(int i, int j, int k, COM x) { components[D*(D*i + j)+ k] = x; } void Set(int i,int j, int k,int l,COM x) { components[D*(D*(D*i +j) + k) + l] = x; } void Set(int i,int j, int k,int l, int m, COM x){ components[D*(D*(D*(D*i + j) + k) + l) + m] = x; } Tensor<N,D> operator*(const double x){ Tensor<N,D> newT; newT.components=components*COM(x,0); return newT; } Tensor<N,D> operator*(const COM x){ Tensor<N,D> newT; newT.components=components*x; return newT; } Tensor<N,D> operator/(const double x){ Tensor<N,D> newT; newT.components=components/COM(x,0); return newT; } Tensor<N,D> operator/(const COM x){ Tensor<N,D> newT; newT.components=components/x; return newT; } Tensor<N,D> operator+(const Tensor<N,D> T2){ Tensor<N,D> newT; newT.components=components+T2.components; return newT; } Tensor<N,D> operator-(const Tensor<N,D> T2){ Tensor<N,D> newT; newT.components=components-T2.components; return newT; } void operator+=(const Tensor<N,D> T2){ components = components+T2.components; } void operator-=(const Tensor<N,D> T2){ components=components-T2.components; } Tensor<N+1,D> rightprod(const Tensor<1,D> T2){ Tensor<N+1,D> newT; for(int i=0; i<size;i++){ for(unsigned int j=0;j<D;j++){ - newT.components[i*D+j]=components[i]*T2.components[j]; + newT.components[i*D+j]=components[i]*T2.components[j]; } } return newT; } Tensor<N+1,D> leftprod(const Tensor<1,D> T2){ Tensor<N+1,D> newT; for(unsigned int j=0;j<D;j++){ for(int i=0; i<size;i++){ - newT.components[j*size+i]=components[i]*T2.components[j]; + newT.components[j*size+i]=components[i]*T2.components[j]; } } return newT; } //T^(mu1...mk..mN)T2_(muk) contract kth index, where k member of [1,N] Tensor<N-1,D> contract(const Tensor<1,D> T2, int k){ Tensor<N-1,D> newT; for(int j=0; j<newT.len(); j++){ COM temp; int itemp = pow(D,(N-k)); for (unsigned int i=0; i<D; i++){ - int index = D*itemp*floor(j/itemp) + itemp*i +j%(itemp); - temp+=components[index]*T2.components[i]*sign(i); + int index = D*itemp*floor(j/itemp) + itemp*i +j%(itemp); + temp+=components[index]*T2.components[i]*sign(i); } newT.components[j]=temp; } return newT; } - std::valarray<COM> components; - private: +private: int size; COM sign(unsigned int i){ if(i==0) return 1.; else return -1.; } - }; +}; -template <unsigned int N, unsigned int D> Tensor<N,D>::Tensor(void) +template <unsigned int N, unsigned int D> Tensor<N,D>::Tensor() { size = pow(D,N); components.resize(size); } template <unsigned int N, unsigned int D> Tensor<N,D>::Tensor(COM x) { size = pow(D,N); components.resize(size, x); } -template <unsigned int N, unsigned int D> Tensor<N,D>::~Tensor(void) {} - +template <unsigned int N, unsigned int D> Tensor<N,D>::~Tensor() {} // Tensor Functions: // Tensor<1,4> Sigma(int i, int j, bool hel); -// Tensor<2,4> Metric(void); +// Tensor<2,4> Metric(); // int tensor2listindex(std::array<int,5> indexlist); // int tensor2listindex(std::array<int,3> indexlist); // void perms41(int same4, int diff, std::vector<std::array<int,5>> * perms); // void perms32(int same3, int diff, std::vector<std::array<int,5>> * perms); // void perms311(int same3, int diff1, int diff2, std::vector<std::array<int,5>> * perms); // void perms221(int same2a, int same2b, int diff, std::vector<std::array<int,5>> * perms); // void perms2111(int same2, int diff1,int diff2,int diff3, std::vector<std::array<int,5>> * perms); // void perms21(int same, int diff, std::vector<std::array<int,3>> * perms); // void perms111(int diff1, int diff2, int diff3, std::vector<std::array<int,3>> * perms); - -Tensor<2,4> Metric(void); -Tensor<1,4> TCurrent(CLHEP::HepLorentzVector p1, bool h1,CLHEP::HepLorentzVector p2, bool h2); -Tensor<3,4> T3Current(CLHEP::HepLorentzVector p1, bool h1,CLHEP::HepLorentzVector p2, bool h2); -Tensor<5,4> T5Current(CLHEP::HepLorentzVector p1, bool h1,CLHEP::HepLorentzVector p2, bool h2); +Tensor<2,4> Metric(); +Tensor<1,4> TCurrent(CLHEP::HepLorentzVector p1, bool h1, + CLHEP::HepLorentzVector p2, bool h2); +Tensor<3,4> T3Current(CLHEP::HepLorentzVector p1, bool h1, + CLHEP::HepLorentzVector p2, bool h2); +Tensor<5,4> T5Current(CLHEP::HepLorentzVector p1, bool h1, + CLHEP::HepLorentzVector p2, bool h2); Tensor<1,4> Construct1Tensor(CCurrent j); Tensor<1,4> Construct1Tensor(CLHEP::HepLorentzVector p); Tensor<1,4> eps(CLHEP::HepLorentzVector k, CLHEP::HepLorentzVector ref, bool pol); -bool init_sigma_index(void); +bool init_sigma_index(); diff --git a/include/HEJ/Weights.hh b/include/HEJ/Weights.hh index fd34e92..1c1016f 100644 --- a/include/HEJ/Weights.hh +++ b/include/HEJ/Weights.hh @@ -1,49 +1,13 @@ /** \file - * \brief Container for event weights + * \brief Legacy Header for Weights + * \note This Header was moved to "HEJ/Parameters.hh" + * \TODO remove in HEJ 2.3.0 * * \authors Jeppe Andersen, Tuomas Hapola, Marian Heil, Andreas Maier, Jennifer Smillie * \date 2019 * \copyright GPLv2 or later */ +#warning "HEJ/Weights.hh is deprecated use HEJ/Parameters.hh instead" #pragma once -#include <vector> - -namespace HEJ{ - //! Collection of weights assigned to a single event - /** - * A number of member functions of the MatrixElement class return Weights - * objects containing the squares of the matrix elements for the various - * scale choices. - */ - struct Weights { - double central; - std::vector<double> variations; - - Weights& operator*=(Weights const & other); - Weights& operator*=(double factor); - Weights& operator/=(Weights const & other); - Weights& operator/=(double factor); - }; - - inline - Weights operator*(Weights a, Weights const & b) { - return a*=b; - } - inline - Weights operator*(Weights a, double b) { - return a*=b; - } - inline - Weights operator*(double b, Weights a) { - return a*=b; - } - inline - Weights operator/(Weights a, Weights const & b) { - return a/=b; - } - inline - Weights operator/(Weights a, double b) { - return a/=b; - } -} +#include "HEJ/Parameters.hh" diff --git a/include/HEJ/event_types.hh b/include/HEJ/event_types.hh index d2e6097..ac85fd6 100644 --- a/include/HEJ/event_types.hh +++ b/include/HEJ/event_types.hh @@ -1,80 +1,82 @@ /** \file * \brief Define different types of events. * * \authors Jeppe Andersen, Tuomas Hapola, Marian Heil, Andreas Maier, Jennifer Smillie * \date 2019 * \copyright GPLv2 or later */ #pragma once #include "HEJ/utility.hh" namespace HEJ{ //! Namespace for event types namespace event_type{ //! Possible event types enum EventType: size_t{ FKL, /**< FKL-type event */ unordered_backward, /**< event with unordered backward emission */ unordered_forward, /**< event with unordered forward emission */ extremal_qqxb, /**< event with a backward extremal qqbar */ extremal_qqxf, /**< event with a forward extremal qqbar */ central_qqx, /**< event with a central qqbar */ nonHEJ, /**< event configuration not covered by HEJ */ no_2_jets, /**< event with less than two jets */ bad_final_state, /**< event with an unsupported final state */ unob = unordered_backward, unof = unordered_forward, qqxexb = extremal_qqxb, qqxexf = extremal_qqxf, qqxmid = central_qqx, first_type = FKL, last_type = bad_final_state }; //! Event type names /** * For example, names[FKL] is the string "FKL" */ static constexpr auto names = make_array( "FKL", "unordered backward", "unordered forward", "extremal qqbar backward", "extremal qqbar forward", "central qqbar", "nonHEJ", "no 2 jets", "bad final state" ); + //! Returns True for a HEJ \ref event_type::EventType "EventType" inline bool is_HEJ(EventType type) { switch(type) { case FKL: case unordered_backward: case unordered_forward: case extremal_qqxb: case extremal_qqxf: case central_qqx: return true; default: return false; } } + //! Returns True for an unordered \ref event_type::EventType "EventType" inline bool is_uno(EventType type) { return type == unordered_backward || type == unordered_forward; } inline bool is_qqx(EventType type) { return type == extremal_qqxb || type == extremal_qqxf || type == central_qqx; } } } diff --git a/include/HEJ/utility.hh b/include/HEJ/utility.hh index 398875d..9d2d53e 100644 --- a/include/HEJ/utility.hh +++ b/include/HEJ/utility.hh @@ -1,114 +1,104 @@ /** * \file * \brief Contains various utilities * * \authors Jeppe Andersen, Tuomas Hapola, Marian Heil, Andreas Maier, Jennifer Smillie * \date 2019 * \copyright GPLv2 or later */ #pragma once #include <memory> #include <boost/core/demangle.hpp> #include "fastjet/PseudoJet.hh" namespace HEJ{ //! Create a std::unique_ptr to a T object /** * For non-array types this works like std::make_unique, * which is not available under C++11 */ template<class T, class... Args> std::unique_ptr<T> make_unique(Args&&... a){ return std::unique_ptr<T>{new T{std::forward<Args>(a)...}}; } //! Create an array containing the passed arguments template<typename T, typename... U> constexpr std::array<T, 1 + sizeof...(U)> make_array(T t, U&&... rest){ return {{t, std::forward<U>(rest)...}}; } inline std::string join( std::string const & /* delim */ ){ return ""; } inline std::string join( std::string const & /* delim */, std::string const & str ){ return str; } //! Join strings with a delimiter /** * @param delim Delimiter to be put between consecutive strings * @param first First string * @param second Second string * @param rest Remaining strings */ template<typename... Strings> std::string join( std::string const & delim, std::string const & first, std::string const & second, Strings&&... rest ){ return join(delim, first + delim + second, std::forward<Strings>(rest)...); } //! Return the name of the argument's type template<typename T> std::string type_string(T&&){ return boost::core::demangle(typeid(T).name()); } //! Eliminate compiler warnings for unused variables template<typename... T> constexpr void ignore(T&&...) {} //! Check whether two doubles are closer than ep > 0 to each other inline bool nearby_ep(double a, double b, double ep){ assert(ep > 0); return std::abs(a-b) < ep; } //! Check whether all components of two PseudoJets are closer than ep to each other inline bool nearby_ep( fastjet::PseudoJet const & pa, fastjet::PseudoJet const & pb, double ep ){ assert(ep > 0); for(size_t i = 0; i < 4; ++i){ if(!nearby_ep(pa[i], pb[i], ep)) return false; } return true; } inline bool nearby( fastjet::PseudoJet const & pa, fastjet::PseudoJet const & pb, double const norm = 1. ){ return nearby_ep(pa, pb, 1e-7*norm); } - - //! Check whether two rapidities are close - inline - bool nearby_rap( - double rapa, double rapb, double rap - ){ - assert(rap > 0); - if(!nearby_ep(rapa, rapb, rap)) return false; - return true; - } } diff --git a/src/Event.cc b/src/Event.cc index 6f04831..66ec30f 100644 --- a/src/Event.cc +++ b/src/Event.cc @@ -1,692 +1,795 @@ /** * \authors Jeppe Andersen, Tuomas Hapola, Marian Heil, Andreas Maier, Jennifer Smillie * \date 2019 * \copyright GPLv2 or later */ #include "HEJ/Event.hh" #include <algorithm> #include <assert.h> #include <numeric> #include <utility> #include "LHEF/LHEF.h" #include "fastjet/JetDefinition.hh" +#include "HEJ/Constants.hh" #include "HEJ/exceptions.hh" #include "HEJ/PDG_codes.hh" namespace HEJ{ namespace{ constexpr int status_in = -1; constexpr int status_decayed = 2; constexpr int status_out = 1; /// @name helper functions to determine event type //@{ /** * \brief check if final state valid for HEJ * * check if there is at most one photon, W, H, Z in the final state * and all the rest are quarks or gluons */ bool final_state_ok(std::vector<Particle> const & outgoing){ bool has_AWZH_boson = false; for(auto const & out: outgoing){ if(is_AWZH_boson(out.type)){ if(has_AWZH_boson) return false; has_AWZH_boson = true; } else if(! is_parton(out.type)) return false; } return true; } template<class Iterator> Iterator remove_AWZH(Iterator begin, Iterator end){ return std::remove_if( begin, end, [](Particle const & p){return is_AWZH_boson(p);} ); } template<class Iterator> bool valid_outgoing(Iterator begin, Iterator end){ return std::distance(begin, end) >= 2 && std::is_sorted(begin, end, rapidity_less{}) && std::count_if( begin, end, [](Particle const & s){return is_AWZH_boson(s);} ) < 2; } /** * \brief function which determines if type change is consistent with W emission. * @param in incoming Particle * @param out outgoing Particle * * Ensures that change type of quark line is possible by a flavour changing * W emission. */ bool is_W_Current(ParticleID in, ParticleID out){ if((in==1 && out==2)||(in==2 && out==1)){ return true; } else if((in==-1 && out==-2)||(in==-2 && out==-1)){ return true; } else if((in==3 && out==4)||(in==4 && out==3)){ return true; } else if((in==-3 && out==-4)||(in==-4 && out==-3)){ return true; } else{ return false; } } /** * \brief checks if particle type remains same from incoming to outgoing * @param in incoming Particle * @param out outgoing Particle */ bool is_Pure_Current(ParticleID in, ParticleID out){ if(abs(in)<=6 || in==21) return (in==out); else return false; } // @note that this changes the outgoing range! template<class ConstIterator, class Iterator> bool is_FKL( ConstIterator begin_incoming, ConstIterator end_incoming, Iterator begin_outgoing, Iterator end_outgoing ){ assert(std::distance(begin_incoming, end_incoming) == 2); assert(std::distance(begin_outgoing, end_outgoing) >= 2); // One photon, W, H, Z in the final state is allowed. // Remove it for remaining tests, end_outgoing = remove_AWZH(begin_outgoing, end_outgoing); if(std::all_of( begin_outgoing + 1, end_outgoing - 1, [](Particle const & p){ return p.type == pid::gluon; }) ){ // Test if this is a standard FKL configuration. if (is_Pure_Current(begin_incoming->type, begin_outgoing->type) && is_Pure_Current((end_incoming-1)->type, (end_outgoing-1)->type)){ return true; } } return false; } template<class ConstIterator, class Iterator> bool is_W_FKL( ConstIterator begin_incoming, ConstIterator end_incoming, Iterator begin_outgoing, Iterator end_outgoing ){ assert(std::distance(begin_incoming, end_incoming) == 2); assert(std::distance(begin_outgoing, end_outgoing) >= 2); // One photon, W, H, Z in the final state is allowed. // Remove it for remaining tests, end_outgoing = remove_AWZH(begin_outgoing, end_outgoing); if(std::all_of( begin_outgoing + 1, end_outgoing - 1, [](Particle const & p){ return p.type == pid::gluon; }) ){ // Test if this is a standard FKL configuration. if(is_W_Current(begin_incoming->type, begin_outgoing->type) && is_Pure_Current((end_incoming-1)->type, (end_outgoing-1)->type)){ return true; } else if(is_Pure_Current(begin_incoming->type, begin_outgoing->type) && is_W_Current((end_incoming-1)->type, (end_outgoing-1)->type)){ return true; } } return false; } bool is_FKL( std::array<Particle, 2> const & incoming, std::vector<Particle> outgoing ){ assert(std::is_sorted(begin(incoming), end(incoming), pz_less{})); assert(valid_outgoing(begin(outgoing), end(outgoing))); const auto WEmit = std::find_if( begin(outgoing), end(outgoing), [](Particle const & s){ return abs(s.type) == pid::Wp; } ); - if (abs(WEmit->type) == pid::Wp){ + if (WEmit != end(outgoing) && abs(WEmit->type) == pid::Wp){ return is_W_FKL( begin(incoming), end(incoming), begin(outgoing), end(outgoing) ); } else{ return is_FKL( begin(incoming), end(incoming), begin(outgoing), end(outgoing) ); } } bool has_2_jets(Event const & event){ return event.jets().size() >= 2; } /** * \brief Checks whether event is unordered backwards * @param ev Event * @returns Is Event Unordered Backwards * * - Checks there is more than 3 constuents in the final state * - Checks there is more than 3 jets * - Checks the most backwards parton is a gluon * - Checks the most forwards jet is not a gluon * - Checks the rest of the event is FKL * - Checks the second most backwards is not a different boson * - Checks the unordered gluon actually forms a jet */ bool is_unordered_backward(Event const & ev){ auto const & in = ev.incoming(); auto const & out = ev.outgoing(); assert(std::is_sorted(begin(in), end(in), pz_less{})); assert(valid_outgoing(begin(out), end(out))); if(out.size() < 3) return false; if(ev.jets().size() < 3) return false; if(in.front().type == pid::gluon) return false; if(out.front().type != pid::gluon) return false; // When skipping the unordered emission // the remainder should be a regular FKL event, // except that the (new) first outgoing particle must not be a A,W,Z,H. const auto FKL_begin = next(begin(out)); if(is_AWZH_boson(*FKL_begin)) return false; if(!is_FKL(in, {FKL_begin, end(out)})) return false; // check that the unordered gluon forms an extra jet const auto jets = sorted_by_rapidity(ev.jets()); const auto indices = ev.particle_jet_indices({jets.front()}); return indices[0] >= 0 && indices[1] == -1; } /** * \brief Checks for a forward unordered gluon emission * @param ev Event * @returns Is the event a forward unordered emission * * \see is_unordered_backward */ bool is_unordered_forward(Event const & ev){ auto const & in = ev.incoming(); auto const & out = ev.outgoing(); assert(std::is_sorted(begin(in), end(in), pz_less{})); assert(valid_outgoing(begin(out), end(out))); if(out.size() < 3) return false; if(ev.jets().size() < 3) return false; if(in.back().type == pid::gluon) return false; if(out.back().type != pid::gluon) return false; // When skipping the unordered emission // the remainder should be a regular FKL event, // except that the (new) last outgoing particle must not be a A,W,Z,H. const auto FKL_end = prev(end(out)); if(is_AWZH_boson(*prev(FKL_end))) return false; if(!is_FKL(in, {begin(out), FKL_end})) return false; // check that the unordered gluon forms an extra jet const auto jets = sorted_by_rapidity(ev.jets()); const auto indices = ev.particle_jet_indices({jets.back()}); return indices.back() >= 0 && indices[indices.size()-2] == -1; } /** * \brief Checks for a forward extremal qqx * @param ev Event * @returns Is the event a forward extremal qqx event * * Checks there is 3 or more than 3 constituents in the final state * Checks there is 3 or more than 3 jets * Checks most forwards incoming is gluon * Checks most extremal particle is not a Higgs (either direction) * Checks the second most forwards particle is not Higgs boson * Checks the most forwards parton is a either quark or anti-quark. * Checks the second most forwards parton is anti-quark or quark. * Checks the qqbar pair form 2 separate jets. */ bool is_Ex_qqxf(Event const & ev){ auto const & in = ev.incoming(); auto const & out = ev.outgoing(); assert(std::is_sorted(begin(in), end(in), pz_less{})); assert(valid_outgoing(begin(out), end(out))); int fkl_end=2; if(out.size() < 3) return false; if(ev.jets().size() < 3) return false; if(in.back().type != pid::gluon) return false; if(out.back().type == pid::Higgs || out.front().type == pid::Higgs || out.rbegin()[1].type == pid::Higgs) return false; // if extremal AWZ if(is_AWZ_boson(out.back())){ // if extremal AWZ fkl_end++; if (is_quark(out.rbegin()[1])){ //if second quark if (!(is_antiquark(out.rbegin()[2]))) return false;// third must be anti-quark } else if (is_antiquark(out.rbegin()[1])){ //if second anti-quark if (!(is_quark(out.rbegin()[2]))) return false;// third must be quark } else return false; } else if (is_quark(out.rbegin()[0])){ //if extremal quark if(is_AWZ_boson(out.rbegin()[1])){ // if second AWZ fkl_end++; if (!(is_antiquark(out.rbegin()[2]))) return false;// third must be anti-quark } else if (!(is_antiquark(out.rbegin()[1]))) return false;// second must be anti-quark } else if (is_antiquark(out.rbegin()[0])){ //if extremal anti-quark if(is_AWZ_boson(out.rbegin()[1])){ // if second AWZ fkl_end++; if (!(is_quark(out.rbegin()[2]))) return false;// third must be quark } else if (!(is_quark(out.rbegin()[1]))) return false;// second must be quark } else return false; // When skipping the qqbar // New last outgoing particle must not be a Higgs if (out.rbegin()[fkl_end].type == pid::Higgs) return false; const auto jets = fastjet::sorted_by_rapidity(ev.jets()); const auto indices = ev.particle_jet_indices({jets}); // Ensure qqbar pair are in separate jets if(indices[indices.size()-2] != indices[indices.size()-1]-1) return false; // Opposite current should be logical to process if (is_AWZ_boson(out.front().type)){ return (is_Pure_Current(in.front().type, out[1].type) || is_W_Current(in.front().type,out[1].type)); } else return (is_Pure_Current(in.front().type, out[0].type) || is_W_Current(in.front().type,out[0].type)); } /** * \brief Checks for a backward extremal qqx * @param ev Event * @returns Is the event a backward extremal qqx event * * Checks there is 3 or more than 3 constituents in the final state * Checks there is 3 or more than 3 jets * Checks most backwards incoming is gluon * Checks most extremal particle is not a Higgs (either direction) y * Checks the second most backwards particle is not Higgs boson y * Checks the most backwards parton is a either quark or anti-quark. y * Checks the second most backwards parton is anti-quark or quark. y * Checks the qqbar pair form 2 separate jets. */ bool is_Ex_qqxb(Event const & ev){ auto const & in = ev.incoming(); auto const & out = ev.outgoing(); assert(std::is_sorted(begin(in), end(in), pz_less{})); assert(valid_outgoing(begin(out), end(out))); int fkl_start=2; if(out.size() < 3) return false; if(ev.jets().size() < 3) return false; if(in.front().type != pid::gluon) return false; if(out.back().type == pid::Higgs || out.front().type == pid::Higgs || out[1].type == pid::Higgs) return false; if(is_AWZ_boson(out.front())){ // if extremal AWZ fkl_start++; if (is_quark(out[1])){ //if second quark if (!(is_antiquark(out[2]))) return false;// third must be anti-quark } else if (is_antiquark(out[1])){ //if second anti-quark if (!(is_quark(out[2]))) return false;// third must be quark } else return false; } else if (is_quark(out[0])){ // if extremal quark if(is_AWZ_boson(out[1])){ // if second AWZ fkl_start++; if (!(is_antiquark(out[2]))) return false;// third must be anti-quark } else if (!(is_antiquark(out[1]))) return false;// second must be anti-quark } else if (is_antiquark(out[0])){ //if extremal anti-quark if(is_AWZ_boson(out[1])){ // if second AWZ fkl_start++; if (!(is_quark(out[2]))) return false;// third must be quark } else if (!(is_quark(out[1]))) return false;// second must be quark } else return false; // When skipping the qqbar // New last outgoing particle must not be a Higgs. if (out[fkl_start].type == pid::Higgs) return false; const auto jets = fastjet::sorted_by_rapidity(ev.jets()); const auto indices = ev.particle_jet_indices({jets}); // Ensure qqbar pair form separate jets. if(indices[0] != indices[1]-1) return false; // Other current should be logical to process if (is_AWZ_boson(out.back())){ return (is_Pure_Current(in.back().type, out.rbegin()[1].type) || is_W_Current(in.back().type,out.rbegin()[1].type)); } else return (is_Pure_Current(in.back().type, out.rbegin()[0].type) || is_W_Current(in.back().type, out.rbegin()[0].type)); } /** * \brief Checks for a central qqx * @param ev Event * @returns Is the event a central extremal qqx event * * Checks there is 4 or more than 4 constuents in the final state * Checks there is 4 or more than 4 jets * Checks most extremal particle is not a Higgs (either direction) y * Checks for a central quark in the outgoing states * Checks for adjacent anti-quark parton. (allowing for AWZ boson emission between) * Checks external currents are logically sound. */ bool is_Mid_qqx(Event const & ev){ auto const & in = ev.incoming(); auto const & out = ev.outgoing(); assert(std::is_sorted(begin(in), end(in), pz_less{})); assert(valid_outgoing(begin(out), end(out))); if(out.size() < 4) return false; if(ev.jets().size() < 4) return false; if(out.back().type == pid::Higgs || out.front().type == pid::Higgs) return false; size_t start_FKL=0; size_t end_FKL=0; if (is_AWZ_boson(out.back())){ end_FKL++; } if (is_AWZ_boson(out.front())){ start_FKL++; } if ((is_Pure_Current(in.back().type,out.rbegin()[end_FKL].type) && is_Pure_Current(in.front().type,out[start_FKL].type))){ //nothing to do } else if (is_W_Current(in.back().type,out.rbegin()[end_FKL].type) && is_Pure_Current(in.front().type,out[start_FKL].type)){ //nothing to do } else if (!(is_Pure_Current(in.back().type,out.rbegin()[end_FKL].type) && is_W_Current(in.front().type,out[start_FKL].type))){ return false; } const auto jets = fastjet::sorted_by_rapidity(ev.jets()); const auto indices = ev.particle_jet_indices({jets}); auto const out_partons = filter_partons(out); for (size_t i = 1; i<out_partons.size()-2; i++){ if ((is_quark(out_partons[i]) && (is_antiquark(out_partons[i+1]))) || (is_antiquark(out_partons[i]) && (is_quark(out_partons[i+1])))){ return (indices[i+1] == indices[i]+1 && indices[i] != -1); } } return false; } using event_type::EventType; EventType classify(Event const & ev){ if(! final_state_ok(ev.outgoing())) return EventType::bad_final_state; if(! has_2_jets(ev)) return EventType::no_2_jets; if(is_FKL(ev.incoming(), ev.outgoing())) return EventType::FKL; if(is_unordered_backward(ev)) return EventType::unordered_backward; if(is_unordered_forward(ev)) return EventType::unordered_forward; if(is_Ex_qqxb(ev)) return EventType::extremal_qqxb; if(is_Ex_qqxf(ev)) return EventType::extremal_qqxf; if(is_Mid_qqx(ev)) return EventType::central_qqx; return EventType::nonHEJ; } //@} Particle extract_particle(LHEF::HEPEUP const & hepeup, int i){ - return Particle{ - static_cast<ParticleID>(hepeup.IDUP[i]), - fastjet::PseudoJet{ - hepeup.PUP[i][0], hepeup.PUP[i][1], - hepeup.PUP[i][2], hepeup.PUP[i][3] - } + const ParticleID id = static_cast<ParticleID>(hepeup.IDUP[i]); + const fastjet::PseudoJet momentum{ + hepeup.PUP[i][0], hepeup.PUP[i][1], + hepeup.PUP[i][2], hepeup.PUP[i][3] }; + if(is_parton(id)) + return Particle{ id, std::move(momentum), hepeup.ICOLUP[i] }; + return Particle{ id, std::move(momentum), {} }; } bool is_decay_product(std::pair<int, int> const & mothers){ if(mothers.first == 0) return false; return mothers.second == 0 || mothers.first == mothers.second; } } // namespace anonymous - UnclusteredEvent::UnclusteredEvent(LHEF::HEPEUP const & hepeup): - central(EventParameters{ + Event::EventData::EventData(LHEF::HEPEUP const & hepeup){ + parameters.central = EventParameters{ hepeup.scales.mur, hepeup.scales.muf, hepeup.weight() - }) - { + }; size_t in_idx = 0; for (int i = 0; i < hepeup.NUP; ++i) { // skip decay products // we will add them later on, but we have to ensure that // the decayed particle is added before if(is_decay_product(hepeup.MOTHUP[i])) continue; auto particle = extract_particle(hepeup, i); // needed to identify mother particles for decay products particle.p.set_user_index(i+1); if(hepeup.ISTUP[i] == status_in){ if(in_idx > incoming.size()) { throw std::invalid_argument{ "Event has too many incoming particles" }; } incoming[in_idx++] = std::move(particle); } else outgoing.emplace_back(std::move(particle)); } // add decay products for (int i = 0; i < hepeup.NUP; ++i) { if(!is_decay_product(hepeup.MOTHUP[i])) continue; const int mother_id = hepeup.MOTHUP[i].first; const auto mother = std::find_if( begin(outgoing), end(outgoing), [mother_id](Particle const & particle){ return particle.p.user_index() == mother_id; } ); if(mother == end(outgoing)){ throw std::invalid_argument{"invalid decay product parent"}; } const int mother_idx = std::distance(begin(outgoing), mother); assert(mother_idx >= 0); decays[mother_idx].emplace_back(extract_particle(hepeup, i)); } } Event::Event( - UnclusteredEvent ev, - fastjet::JetDefinition const & jet_def, double min_jet_pt + UnclusteredEvent const & ev, + fastjet::JetDefinition const & jet_def, double const min_jet_pt ): - ev_{std::move(ev)}, - cs_{to_PseudoJet(filter_partons(ev_.outgoing)), jet_def}, - min_jet_pt_{min_jet_pt} - { + Event( Event::EventData{ + ev.incoming, ev.outgoing, ev.decays, + Parameters<EventParameters>{ev.central, ev.variations} + }.cluster(jet_def, min_jet_pt) ) + {} + + //! @TODO remove in HEJ 2.3.0 + UnclusteredEvent::UnclusteredEvent(LHEF::HEPEUP const & hepeup){ + Event::EventData const evData{hepeup}; + incoming = evData.incoming; + outgoing = evData.outgoing; + decays = evData.decays; + central = evData.parameters.central; + variations = evData.parameters.variations; + } + + void Event::EventData::sort(){ // sort particles std::sort( - begin(ev_.incoming), end(ev_.incoming), + begin(incoming), end(incoming), [](Particle o1, Particle o2){return o1.p.pz()<o2.p.pz();} ); - auto old_outgoing = std::move(ev_.outgoing); + auto old_outgoing = std::move(outgoing); std::vector<size_t> idx(old_outgoing.size()); std::iota(idx.begin(), idx.end(), 0); std::sort(idx.begin(), idx.end(), [&old_outgoing](size_t i, size_t j){ return old_outgoing[i].rapidity() < old_outgoing[j].rapidity(); }); - ev_.outgoing.clear(); - ev_.outgoing.reserve(old_outgoing.size()); + outgoing.clear(); + outgoing.reserve(old_outgoing.size()); for(size_t i: idx) { - ev_.outgoing.emplace_back(std::move(old_outgoing[i])); + outgoing.emplace_back(std::move(old_outgoing[i])); } // find decays again - if(!ev_.decays.empty()){ - auto old_decays = std::move(ev_.decays); - ev_.decays.clear(); + if(!decays.empty()){ + auto old_decays = std::move(decays); + decays.clear(); for(size_t i=0; i<idx.size(); ++i) { auto decay = old_decays.find(idx[i]); if(decay != old_decays.end()) - ev_.decays.emplace(i, std::move(decay->second)); + decays.emplace(i, std::move(decay->second)); } - assert(old_decays.size() == ev_.decays.size()); + assert(old_decays.size() == decays.size()); } + } - // classify event - type_ = classify(*this); + Event Event::EventData::cluster( + fastjet::JetDefinition const & jet_def, double const min_jet_pt + ){ + sort(); + Event ev{ std::move(incoming), std::move(outgoing), std::move(decays), + std::move(parameters), + jet_def, min_jet_pt + }; + assert(std::is_sorted(begin(ev.outgoing_), end(ev.outgoing_), + rapidity_less{})); + ev.type_ = classify(ev); + return ev; + } - assert(std::is_sorted(begin(outgoing()), end(outgoing()), rapidity_less{})); + namespace { + void connect_incoming(Particle & in, int & colour, int & anti_colour){ + in.colour = std::make_pair(anti_colour, colour); + // gluon + if(in.type == pid::gluon) + return; + if(in.type > 0){ + // quark + assert(is_quark(in)); + in.colour->second = 0; + colour*=-1; + return; + } + // anti-quark + assert(is_antiquark(in)); + in.colour->first = 0; + anti_colour*=-1; + return; + } } + bool Event::generate_colours(RNG & ran){ + // generate only for HEJ events + if(!event_type::is_HEJ(type())) + return false; + assert(std::is_sorted( + begin(outgoing()), end(outgoing()), rapidity_less{})); + assert(incoming()[0].pz() < incoming()[1].pz()); + + // positive (anti-)colour -> can connect + // negative (anti-)colour -> not available/used up by (anti-)quark + int colour = COLOUR_OFFSET; + int anti_colour = colour+1; + // initialise first + connect_incoming(incoming_[0], colour, anti_colour); + + for(auto & part: outgoing_){ + assert(colour>0 || anti_colour>0); + if(part.type == ParticleID::gluon){ + // gluon + if(colour>0 && anti_colour>0){ + // on g line => connect to colour OR anti-colour (random) + if(ran.flat() < 0.5){ + part.colour = std::make_pair(colour+2,colour); + colour+=2; + } else { + part.colour = std::make_pair(anti_colour, anti_colour+2); + anti_colour+=2; + } + } else if(colour > 0){ + // on q line => connect to available colour + part.colour = std::make_pair(colour+2, colour); + colour+=2; + } else { + assert(colour<0 && anti_colour>0); + // on qx line => connect to available anti-colour + part.colour = std::make_pair(anti_colour, anti_colour+2); + anti_colour+=2; + } + } else if(is_quark(part)) { + // quark + assert(anti_colour>0); + if(colour>0){ + // on g line => connect and remove anti-colour + part.colour = std::make_pair(anti_colour, 0); + anti_colour+=2; + anti_colour*=-1; + } else { + // on qx line => new colour + colour*=-1; + part.colour = std::make_pair(colour, 0); + } + } else if(is_antiquark(part)) { + // anti-quark + assert(colour>0); + if(anti_colour>0){ + // on g line => connect and remove colour + part.colour = std::make_pair(0, colour); + colour+=2; + colour*=-1; + } else { + // on q line => new anti-colour + anti_colour*=-1; + part.colour = std::make_pair(0, anti_colour); + } + } + // else not a parton + } + // Connect last + connect_incoming(incoming_[1], anti_colour, colour); + return true; + } // generate_colours + std::vector<fastjet::PseudoJet> Event::jets() const{ return cs_.inclusive_jets(min_jet_pt_); } /** * \brief Returns the invarient mass of the event * @param ev Event * @returns s hat * * Makes use of the FastJet PseudoJet function m2(). * Applies this function to the sum of the incoming partons. */ double shat(Event const & ev){ return (ev.incoming()[0].p + ev.incoming()[1].p).m2(); } - namespace{ - // colour flow according to Les Houches standard - // TODO: stub - std::vector<std::pair<int, int>> colour_flow( - std::array<Particle, 2> const & incoming, - std::vector<Particle> const & outgoing - ){ - std::vector<std::pair<int, int>> result( - incoming.size() + outgoing.size() - ); - for(auto & col: result){ - col = std::make_pair(-1, -1); - } - return result; - } - } - LHEF::HEPEUP to_HEPEUP(Event const & event, LHEF::HEPRUP * heprup){ LHEF::HEPEUP result; result.heprup = heprup; result.weights = {{event.central().weight, nullptr}}; for(auto const & var: event.variations()){ result.weights.emplace_back(var.weight, nullptr); } size_t num_particles = event.incoming().size() + event.outgoing().size(); for(auto const & decay: event.decays()) num_particles += decay.second.size(); result.NUP = num_particles; // the following entries are pretty much meaningless result.IDPRUP = event.type()+1; // event ID result.AQEDUP = 1./128.; // alpha_EW //result.AQCDUP = 0.118 // alpha_QCD // end meaningless part result.XWGTUP = event.central().weight; result.SCALUP = event.central().muf; result.scales.muf = event.central().muf; result.scales.mur = event.central().mur; result.scales.SCALUP = event.central().muf; result.pdfinfo.p1 = event.incoming().front().type; result.pdfinfo.p2 = event.incoming().back().type; result.pdfinfo.scale = event.central().muf; + + result.IDUP.reserve(num_particles); // PID + result.ISTUP.reserve(num_particles); // status (in, out, decay) + result.PUP.reserve(num_particles); // momentum + result.MOTHUP.reserve(num_particles); // index mother particle + result.ICOLUP.reserve(num_particles); // colour + // incoming for(Particle const & in: event.incoming()){ result.IDUP.emplace_back(in.type); result.ISTUP.emplace_back(status_in); result.PUP.push_back({in.p[0], in.p[1], in.p[2], in.p[3], in.p.m()}); result.MOTHUP.emplace_back(0, 0); + assert(in.colour); + result.ICOLUP.emplace_back(*in.colour); } + // outgoing for(size_t i = 0; i < event.outgoing().size(); ++i){ Particle const & out = event.outgoing()[i]; result.IDUP.emplace_back(out.type); const int status = event.decays().count(i)?status_decayed:status_out; result.ISTUP.emplace_back(status); result.PUP.push_back({out.p[0], out.p[1], out.p[2], out.p[3], out.p.m()}); result.MOTHUP.emplace_back(1, 2); + if(out.colour) + result.ICOLUP.emplace_back(*out.colour); + else{ + assert(is_AWZH_boson(out)); + result.ICOLUP.emplace_back(std::make_pair(0,0)); + } } - result.ICOLUP = colour_flow( - event.incoming(), filter_partons(event.outgoing()) - ); - if(result.ICOLUP.size() < num_particles){ - const size_t AWZH_boson_idx = std::find_if( - begin(event.outgoing()), end(event.outgoing()), - [](Particle const & s){ return is_AWZH_boson(s); } - ) - begin(event.outgoing()) + event.incoming().size(); - assert(AWZH_boson_idx <= result.ICOLUP.size()); - result.ICOLUP.insert( - begin(result.ICOLUP) + AWZH_boson_idx, - std::make_pair(0,0) - ); - } + // decays for(auto const & decay: event.decays()){ for(auto const out: decay.second){ result.IDUP.emplace_back(out.type); result.ISTUP.emplace_back(status_out); result.PUP.push_back({out.p[0], out.p[1], out.p[2], out.p[3], out.p.m()}); const size_t mother_idx = 1 + event.incoming().size() + decay.first; result.MOTHUP.emplace_back(mother_idx, mother_idx); result.ICOLUP.emplace_back(0,0); } } + assert(result.ICOLUP.size() == num_particles); static constexpr double unknown_spin = 9.; //per Les Houches accord result.VTIMUP = std::vector<double>(num_particles, unknown_spin); result.SPINUP = result.VTIMUP; return result; } } diff --git a/src/EventReweighter.cc b/src/EventReweighter.cc index 28cebd9..3bca11c 100644 --- a/src/EventReweighter.cc +++ b/src/EventReweighter.cc @@ -1,333 +1,309 @@ /** * \authors Jeppe Andersen, Tuomas Hapola, Marian Heil, Andreas Maier, Jennifer Smillie * \date 2019 * \copyright GPLv2 or later */ #include "HEJ/EventReweighter.hh" #include <algorithm> #include <assert.h> #include <limits> #include <math.h> #include <stddef.h> #include <string> #include <unordered_map> #include "fastjet/ClusterSequence.hh" #include "LHEF/LHEF.h" #include "HEJ/Event.hh" #include "HEJ/exceptions.hh" #include "HEJ/Particle.hh" #include "HEJ/PDG_codes.hh" #include "HEJ/PhaseSpacePoint.hh" -#include "HEJ/Weights.hh" namespace HEJ{ using EventType = event_type::EventType; namespace { static_assert( std::numeric_limits<double>::has_quiet_NaN, "no quiet NaN for double" ); constexpr double NaN = std::numeric_limits<double>::quiet_NaN(); - UnclusteredEvent to_UnclusteredEvent(PhaseSpacePoint const & psp){ - UnclusteredEvent result; - result.incoming = psp.incoming(); - std::sort( - begin(result.incoming), end(result.incoming), - [](Particle o1, Particle o2){return o1.p.pz()<o2.p.pz();} - ); + Event::EventData to_EventData(PhaseSpacePoint const & psp){ + Event::EventData result; + result.incoming=psp.incoming(); assert(result.incoming.size() == 2); - result.outgoing = psp.outgoing(); + result.outgoing=psp.outgoing(); + // technically Event::EventData doesn't have to be sorted, + // but PhaseSpacePoint should be anyway assert( std::is_sorted( begin(result.outgoing), end(result.outgoing), rapidity_less{} ) ); assert(result.outgoing.size() >= 2); result.decays = psp.decays(); - result.central.mur = NaN; - result.central.muf = NaN; - result.central.weight = psp.weight(); + result.parameters.central = {NaN, NaN, psp.weight()}; return result; } } // namespace anonymous EventReweighter::EventReweighter( LHEF::HEPRUP const & heprup, ScaleGenerator scale_gen, EventReweighterConfig conf, HEJ::RNG & ran ): EventReweighter{ HEJ::Beam{ heprup.EBMUP.first, {{ static_cast<HEJ::ParticleID>(heprup.IDBMUP.first), static_cast<HEJ::ParticleID>(heprup.IDBMUP.second) }} }, heprup.PDFSUP.first, std::move(scale_gen), std::move(conf), ran } { if(heprup.EBMUP.second != E_beam_){ throw std::invalid_argument( "asymmetric beam: " + std::to_string(E_beam_) + " ---> <--- " + std::to_string(heprup.EBMUP.second) ); }; if(heprup.PDFSUP.second != pdf_.id()){ throw std::invalid_argument( "conflicting PDF ids: " + std::to_string(pdf_.id()) + " vs. " + std::to_string(heprup.PDFSUP.second) ); } } EventReweighter::EventReweighter( Beam beam, int pdf_id, ScaleGenerator scale_gen, EventReweighterConfig conf, HEJ::RNG & ran ): param_{std::move(conf)}, E_beam_{beam.E}, pdf_{pdf_id, beam.type.front(), beam.type.back()}, MEt2_{ [this](double mu){ return pdf_.Halphas(mu); }, param_.ME_config }, scale_gen_(std::move(scale_gen)), ran_{ran} {} PDF const & EventReweighter::pdf() const{ return pdf_; } std::vector<Event> EventReweighter::reweight( Event const & input_ev, int num_events ){ auto res_events = gen_res_events(input_ev, num_events); if(res_events.empty()) return {}; for(auto & event: res_events) event = scale_gen_(event); return rescale(input_ev, std::move(res_events)); } /** * \brief main generation/reweighting function: * generate phase space points and divide out Born factors */ std::vector<Event> EventReweighter::gen_res_events( Event const & ev, int phase_space_points ){ assert(ev.variations().empty()); switch(param_.treat.at(ev.type())){ case EventTreatment::discard: return {}; case EventTreatment::keep: if(! jets_pass_resummation_cuts(ev)) return {}; else return {ev}; default:; } const double Born_shat = shat(ev); std::vector<Event> resummation_events; for(int psp_number = 0; psp_number < phase_space_points; ++psp_number){ PhaseSpacePoint psp{ev, param_.psp_config, ran_}; if(psp.weight() == 0.) continue; if(psp.incoming()[0].E() > E_beam_ || psp.incoming()[1].E() > E_beam_) continue; resummation_events.emplace_back( - to_UnclusteredEvent(std::move(psp)), + to_EventData( std::move(psp) ).cluster( param_.jet_param.def, param_.jet_param.min_pt + ) ); auto & new_event = resummation_events.back(); + assert(new_event.type() == ev.type()); + new_event.generate_colours(ran_); assert(new_event.variations().empty()); new_event.central().mur = ev.central().mur; new_event.central().muf = ev.central().muf; const double resum_shat = shat(new_event); new_event.central().weight *= ev.central().weight*Born_shat*Born_shat/ (phase_space_points*resum_shat*resum_shat); } return resummation_events; } std::vector<Event> EventReweighter::rescale( Event const & Born_ev, std::vector<Event> events ) const{ const double Born_pdf = pdf_factors(Born_ev).central; const double Born_ME = tree_matrix_element(Born_ev); for(auto & cur_event: events){ const auto pdf = pdf_factors(cur_event); assert(pdf.variations.size() == cur_event.variations().size()); const auto ME = matrix_elements(cur_event); assert(ME.variations.size() == cur_event.variations().size()); - cur_event.central().weight *= pdf.central*ME.central/(Born_pdf*Born_ME); - for(size_t i = 0; i < cur_event.variations().size(); ++i){ - cur_event.variations(i).weight *= - pdf.variations[i]*ME.variations[i]/(Born_pdf*Born_ME); - } + cur_event.parameters() *= pdf*ME/(Born_pdf*Born_ME); } return events; }; /** * \brief Do the Jets pass the resummation Cuts? * * @param ev Event in Question * @returns 0 or 1 depending on if ev passes Jet Cuts */ bool EventReweighter::jets_pass_resummation_cuts( Event const & ev ) const{ const auto out_as_PseudoJet = to_PseudoJet(filter_partons(ev.outgoing())); fastjet::ClusterSequence cs{out_as_PseudoJet, param_.jet_param.def}; return cs.inclusive_jets(param_.jet_param.min_pt).size() == ev.jets().size(); } - - /** * \brief pdf_factors Function * * @param ev Event in Question * @returns Event weights due to PDFs * * Calculates the Central value and the variation due * to the PDF choice made. */ - Weights - EventReweighter::pdf_factors(Event const & ev) const{ + Weights EventReweighter::pdf_factors(Event const & ev) const{ auto const & a = ev.incoming().front(); auto const & b = ev.incoming().back(); const double xa = a.p.e()/E_beam_; const double xb = b.p.e()/E_beam_; Weights result; std::unordered_map<double, double> known_pdf; result.central = pdf_.pdfpt(0,xa,ev.central().muf,a.type)* pdf_.pdfpt(1,xb,ev.central().muf,b.type); known_pdf.emplace(ev.central().muf, result.central); result.variations.reserve(ev.variations().size()); for(auto const & ev_param: ev.variations()){ const double muf = ev_param.muf; auto cur_pdf = known_pdf.find(muf); if(cur_pdf == known_pdf.end()){ cur_pdf = known_pdf.emplace( muf, pdf_.pdfpt(0,xa,muf,a.type)*pdf_.pdfpt(1,xb,muf,b.type) ).first; } result.variations.emplace_back(cur_pdf->second); } assert(result.variations.size() == ev.variations().size()); return result; } /** * \brief matrix_elements Function * * @param ev Event in question * @returns Event Weights due to MatrixElements * * Calculates the Central value and the variation due * to the Matrix Element. */ Weights EventReweighter::matrix_elements(Event const & ev) const{ assert(param_.treat.count(ev.type()) > 0); if(param_.treat.find(ev.type())->second == EventTreatment::keep){ return fixed_order_scale_ME(ev); } return MEt2_(ev); } /** * \brief Computes the tree level matrix element * * @param ev Event in Question * @returns HEJ approximation to Tree level Matrix Element * * This computes the HEJ approximation to the tree level FO * Matrix element which is used within the LO weighting process. */ double EventReweighter::tree_matrix_element(Event const & ev) const{ assert(ev.variations().empty()); assert(param_.treat.count(ev.type()) > 0); if(param_.treat.find(ev.type())->second == EventTreatment::keep){ return fixed_order_scale_ME(ev).central; } return MEt2_.tree(ev).central; } /** * \brief Scale-dependent part of fixed-order matrix element * * @param ev Event in question * @returns Scale variation due to FO-ME. * * This is only called to compute the scale variation for events where * we don't do resummation (e.g. non-FKL). * Since at tree level the scale dependence is just due to alpha_s, * it is enough to return the alpha_s(mur) factors in the matrix element. * The rest drops out in the ratio of (output event ME)/(input event ME), * so we never have to compute it. */ Weights EventReweighter::fixed_order_scale_ME(Event const & ev) const{ int alpha_s_power = 0; for(auto const & part: ev.outgoing()){ if(is_parton(part)) ++alpha_s_power; - else { - switch(part.type){ - case pid::Higgs: { - alpha_s_power += 2; - break; - } - // TODO - case pid::Wp:{ - break; - } - case pid::Wm:{ - break; - } - case pid::photon: - case pid::Z: - default: - throw not_implemented("Emission of boson of unsupported type"); - } + else if(part.type == pid::Higgs) { + alpha_s_power += 2; } + // nothing to do for other uncoloured particles } Weights result; result.central = pow(pdf_.Halphas(ev.central().mur), alpha_s_power); for(auto const & var: ev.variations()){ result.variations.emplace_back( pow(pdf_.Halphas(var.mur), alpha_s_power) ); } return result; } } // namespace HEJ diff --git a/src/MatrixElement.cc b/src/MatrixElement.cc index 17322b6..ee6ab55 100644 --- a/src/MatrixElement.cc +++ b/src/MatrixElement.cc @@ -1,1757 +1,1754 @@ /** * \authors Jeppe Andersen, Tuomas Hapola, Marian Heil, Andreas Maier, Jennifer Smillie * \date 2019 * \copyright GPLv2 or later */ #include "HEJ/MatrixElement.hh" #include <algorithm> #include <assert.h> #include <limits> #include <math.h> #include <stddef.h> #include <unordered_map> #include <utility> #include "CLHEP/Vector/LorentzVector.h" #include "fastjet/ClusterSequence.hh" #include "HEJ/Constants.hh" #include "HEJ/currents.hh" #include "HEJ/PDG_codes.hh" #include "HEJ/event_types.hh" #include "HEJ/Event.hh" #include "HEJ/exceptions.hh" #include "HEJ/Particle.hh" #include "HEJ/utility.hh" namespace HEJ{ //cf. last line of eq. (22) in \ref Andersen:2011hs double MatrixElement::omega0( double alpha_s, double mur, fastjet::PseudoJet const & q_j ) const { const double lambda = param_.regulator_lambda; const double result = - alpha_s*N_C/M_PI*log(q_j.perp2()/(lambda*lambda)); if(! param_.log_correction) return result; // use alpha_s(sqrt(q_j*lambda)), evolved to mur return ( 1. + alpha_s/(4.*M_PI)*beta0*log(mur*mur/(q_j.perp()*lambda)) )*result; } Weights MatrixElement::operator()( Event const & event ) const { return tree(event)*virtual_corrections(event); } Weights MatrixElement::tree( Event const & event ) const { return tree_param(event)*tree_kin(event); } Weights MatrixElement::tree_param( Event const & event ) const { if(! is_HEJ(event.type())) { return Weights{0., std::vector<double>(event.variations().size(), 0.)}; } Weights result; // only compute once for each renormalisation scale std::unordered_map<double, double> known; result.central = tree_param(event, event.central().mur); known.emplace(event.central().mur, result.central); for(auto const & var: event.variations()) { const auto ME_it = known.find(var.mur); if(ME_it == end(known)) { const double wt = tree_param(event, var.mur); result.variations.emplace_back(wt); known.emplace(var.mur, wt); } else { result.variations.emplace_back(ME_it->second); } } return result; } Weights MatrixElement::virtual_corrections( Event const & event ) const { if(! is_HEJ(event.type())) { return Weights{0., std::vector<double>(event.variations().size(), 0.)}; } Weights result; // only compute once for each renormalisation scale std::unordered_map<double, double> known; result.central = virtual_corrections(event, event.central().mur); known.emplace(event.central().mur, result.central); for(auto const & var: event.variations()) { const auto ME_it = known.find(var.mur); if(ME_it == end(known)) { const double wt = virtual_corrections(event, var.mur); result.variations.emplace_back(wt); known.emplace(var.mur, wt); } else { result.variations.emplace_back(ME_it->second); } } return result; } double MatrixElement::virtual_corrections_W( Event const & event, double mur, Particle const & WBoson ) const{ auto const & in = event.incoming(); const auto partons = filter_partons(event.outgoing()); fastjet::PseudoJet const & pa = in.front().p; #ifndef NDEBUG fastjet::PseudoJet const & pb = in.back().p; double const norm = (in.front().p + in.back().p).E(); #endif assert(std::is_sorted(partons.begin(), partons.end(), rapidity_less{})); assert(partons.size() >= 2); assert(pa.pz() < pb.pz()); fastjet::PseudoJet q = pa - partons[0].p; size_t first_idx = 0; size_t last_idx = partons.size() - 1; bool wc = true; bool wqq = false; // With extremal qqx or unordered gluon outside the extremal // partons then it is not part of the FKL ladder and does not // contribute to the virtual corrections. W emitted from the // most backward leg must be taken into account in t-channel if (event.type() == event_type::FKL) { if (in[0].type != partons[0].type ){ q -= WBoson.p; wc = false; } } else if (event.type() == event_type::unob) { q -= partons[1].p; ++first_idx; if (in[0].type != partons[1].type ){ q -= WBoson.p; wc = false; } } else if (event.type() == event_type::qqxexb) { q -= partons[1].p; ++first_idx; if (abs(partons[0].type) != abs(partons[1].type)){ q -= WBoson.p; wc = false; } } if(event.type() == event_type::unof || event.type() == event_type::qqxexf){ --last_idx; } size_t first_idx_qqx = last_idx; size_t last_idx_qqx = last_idx; //if qqxMid event, virtual correction do not occur between //qqx pair. if(event.type() == event_type::qqxmid){ const auto backquark = std::find_if( begin(partons) + 1, end(partons) - 1 , [](Particle const & s){ return (s.type != pid::gluon); } ); if(backquark == end(partons) || (backquark+1)->type==pid::gluon) return 0; if(abs(backquark->type) != abs((backquark+1)->type)) { wqq=true; wc=false; } last_idx = std::distance(begin(partons), backquark); first_idx_qqx = last_idx+1; } double exponent = 0; const double alpha_s = alpha_s_(mur); for(size_t j = first_idx; j < last_idx; ++j){ exponent += omega0(alpha_s, mur, q)*( partons[j+1].rapidity() - partons[j].rapidity() ); q -=partons[j+1].p; } // End Loop one if (last_idx != first_idx_qqx) q -= partons[last_idx+1].p; if (wqq) q -= WBoson.p; for(size_t j = first_idx_qqx; j < last_idx_qqx; ++j){ exponent += omega0(alpha_s, mur, q)*( partons[j+1].rapidity() - partons[j].rapidity() ); q -= partons[j+1].p; } if (wc) q -= WBoson.p; assert( nearby(q, -1*pb, norm) || is_AWZH_boson(partons.back().type) || event.type() == event_type::unof || event.type() == event_type::qqxexf ); return exp(exponent); } double MatrixElement::virtual_corrections( Event const & event, double mur ) const{ auto const & in = event.incoming(); auto const & out = event.outgoing(); fastjet::PseudoJet const & pa = in.front().p; #ifndef NDEBUG fastjet::PseudoJet const & pb = in.back().p; double const norm = (in.front().p + in.back().p).E(); #endif const auto AWZH_boson = std::find_if( begin(out), end(out), [](Particle const & p){ return is_AWZH_boson(p); } ); if(AWZH_boson != end(out) && abs(AWZH_boson->type) == pid::Wp){ return virtual_corrections_W(event, mur, *AWZH_boson); } assert(std::is_sorted(out.begin(), out.end(), rapidity_less{})); assert(out.size() >= 2); assert(pa.pz() < pb.pz()); fastjet::PseudoJet q = pa - out[0].p; size_t first_idx = 0; size_t last_idx = out.size() - 1; // if there is a Higgs boson, extremal qqx or unordered gluon // outside the extremal partons then it is not part of the FKL // ladder and does not contribute to the virtual corrections if((out.front().type == pid::Higgs) || event.type() == event_type::unob || event.type() == event_type::qqxexb){ q -= out[1].p; ++first_idx; } if((out.back().type == pid::Higgs) || event.type() == event_type::unof || event.type() == event_type::qqxexf){ --last_idx; } size_t first_idx_qqx = last_idx; size_t last_idx_qqx = last_idx; //if qqxMid event, virtual correction do not occur between //qqx pair. if(event.type() == event_type::qqxmid){ const auto backquark = std::find_if( begin(out) + 1, end(out) - 1 , [](Particle const & s){ return (s.type != pid::gluon && is_parton(s.type)); } ); if(backquark == end(out) || (backquark+1)->type==pid::gluon) return 0; last_idx = std::distance(begin(out), backquark); first_idx_qqx = last_idx+1; } double exponent = 0; const double alpha_s = alpha_s_(mur); for(size_t j = first_idx; j < last_idx; ++j){ exponent += omega0(alpha_s, mur, q)*( out[j+1].rapidity() - out[j].rapidity() ); q -= out[j+1].p; } if (last_idx != first_idx_qqx) q -= out[last_idx+1].p; for(size_t j = first_idx_qqx; j < last_idx_qqx; ++j){ exponent += omega0(alpha_s, mur, q)*( out[j+1].rapidity() - out[j].rapidity() ); q -= out[j+1].p; } assert( nearby(q, -1*pb, norm) || out.back().type == pid::Higgs || event.type() == event_type::unof || event.type() == event_type::qqxexf ); return exp(exponent); } } // namespace HEJ namespace { //! Lipatov vertex for partons emitted into extremal jets double C2Lipatov(CLHEP::HepLorentzVector qav, CLHEP::HepLorentzVector qbv, CLHEP::HepLorentzVector p1, CLHEP::HepLorentzVector p2) { CLHEP::HepLorentzVector temptrans=-(qav+qbv); CLHEP::HepLorentzVector p5=qav-qbv; CLHEP::HepLorentzVector CL=temptrans + p1*(qav.m2()/p5.dot(p1) + 2.*p5.dot(p2)/p1.dot(p2)) - p2*(qbv.m2()/p5.dot(p2) + 2.*p5.dot(p1)/p1.dot(p2)); return -CL.dot(CL); } //! Lipatov vertex with soft subtraction for partons emitted into extremal jets double C2Lipatovots( CLHEP::HepLorentzVector qav, CLHEP::HepLorentzVector qbv, CLHEP::HepLorentzVector p1, CLHEP::HepLorentzVector p2, double lambda ) { double kperp=(qav-qbv).perp(); if (kperp>lambda) return C2Lipatov(qav, qbv, p1, p2)/(qav.m2()*qbv.m2()); else { double Cls=(C2Lipatov(qav, qbv, p1, p2)/(qav.m2()*qbv.m2())); return Cls-4./(kperp*kperp); } } //! Lipatov vertex double C2Lipatov(CLHEP::HepLorentzVector qav, CLHEP::HepLorentzVector qbv, CLHEP::HepLorentzVector pim, CLHEP::HepLorentzVector pip, CLHEP::HepLorentzVector pom, CLHEP::HepLorentzVector pop) // B { CLHEP::HepLorentzVector temptrans=-(qav+qbv); CLHEP::HepLorentzVector p5=qav-qbv; CLHEP::HepLorentzVector CL=temptrans + qav.m2()*(1./p5.dot(pip)*pip + 1./p5.dot(pop)*pop)/2. - qbv.m2()*(1./p5.dot(pim)*pim + 1./p5.dot(pom)*pom)/2. + ( pip*(p5.dot(pim)/pip.dot(pim) + p5.dot(pom)/pip.dot(pom)) + pop*(p5.dot(pim)/pop.dot(pim) + p5.dot(pom)/pop.dot(pom)) - pim*(p5.dot(pip)/pip.dot(pim) + p5.dot(pop)/pop.dot(pim)) - pom*(p5.dot(pip)/pip.dot(pom) + p5.dot(pop)/pop.dot(pom)) )/2.; return -CL.dot(CL); } //! Lipatov vertex with soft subtraction double C2Lipatovots( CLHEP::HepLorentzVector qav, CLHEP::HepLorentzVector qbv, CLHEP::HepLorentzVector pa, CLHEP::HepLorentzVector pb, CLHEP::HepLorentzVector p1, CLHEP::HepLorentzVector p2, double lambda ) { double kperp=(qav-qbv).perp(); if (kperp>lambda) return C2Lipatov(qav, qbv, pa, pb, p1, p2)/(qav.m2()*qbv.m2()); else { double Cls=(C2Lipatov(qav, qbv, pa, pb, p1, p2)/(qav.m2()*qbv.m2())); double temp=Cls-4./(kperp*kperp); return temp; } } /** Matrix element squared for tree-level current-current scattering * @param aptype Particle a PDG ID * @param bptype Particle b PDG ID * @param pn Particle n Momentum * @param pb Particle b Momentum * @param p1 Particle 1 Momentum * @param pa Particle a Momentum * @returns ME Squared for Tree-Level Current-Current Scattering */ double ME_current( int aptype, int bptype, CLHEP::HepLorentzVector const & pn, CLHEP::HepLorentzVector const & pb, CLHEP::HepLorentzVector const & p1, CLHEP::HepLorentzVector const & pa ){ if (aptype==21&&bptype==21) { return jM2gg(pn,pb,p1,pa); } else if (aptype==21&&bptype!=21) { if (bptype > 0) return jM2qg(pn,pb,p1,pa); else return jM2qbarg(pn,pb,p1,pa); } else if (bptype==21&&aptype!=21) { // ----- || ----- if (aptype > 0) return jM2qg(p1,pa,pn,pb); else return jM2qbarg(p1,pa,pn,pb); } else { // they are both quark if (bptype>0) { if (aptype>0) return jM2qQ(pn,pb,p1,pa); else return jM2qQbar(pn,pb,p1,pa); } else { if (aptype>0) return jM2qQbar(p1,pa,pn,pb); else return jM2qbarQbar(pn,pb,p1,pa); } } throw std::logic_error("unknown particle types"); } /** Matrix element squared for tree-level current-current scattering With W+Jets * @param aptype Particle a PDG ID * @param bptype Particle b PDG ID * @param pn Particle n Momentum * @param pb Particle b Momentum * @param p1 Particle 1 Momentum * @param pa Particle a Momentum * @param wc Boolean. True->W Emitted from b. Else; emitted from leg a * @returns ME Squared for Tree-Level Current-Current Scattering */ double ME_W_current( int aptype, int bptype, CLHEP::HepLorentzVector const & pn, CLHEP::HepLorentzVector const & pb, CLHEP::HepLorentzVector const & p1, CLHEP::HepLorentzVector const & pa, CLHEP::HepLorentzVector const & plbar, CLHEP::HepLorentzVector const & pl, bool const wc ){ // We know it cannot be gg incoming. assert(!(aptype==21 && bptype==21)); if (aptype==21&&bptype!=21) { if (bptype > 0) return jMWqg(pn,plbar,pl,pb,p1,pa); else return jMWqbarg(pn,plbar,pl,pb,p1,pa); } else if (bptype==21&&aptype!=21) { // ----- || ----- if (aptype > 0) return jMWqg(p1,plbar,pl,pa,pn,pb); else return jMWqbarg(p1,plbar,pl,pa,pn,pb); } else { // they are both quark if (wc==true){ // emission off b, (first argument pbout) if (bptype>0) { if (aptype>0) return jMWqQ(pn,plbar,pl,pb,p1,pa); else return jMWqQbar(pn,plbar,pl,pb,p1,pa); } else { if (aptype>0) return jMWqbarQ(pn,plbar,pl,pb,p1,pa); else return jMWqbarQbar(pn,plbar,pl,pb,p1,pa); } } else{ // emission off a, (first argument paout) if (aptype > 0) { if (bptype > 0) return jMWqQ(p1,plbar,pl,pa,pn,pb); else return jMWqQbar(p1,plbar,pl,pa,pn,pb); } else { // a is anti-quark if (bptype > 0) return jMWqbarQ(p1,plbar,pl,pa,pn,pb); else return jMWqbarQbar(p1,plbar,pl,pa,pn,pb); } } } throw std::logic_error("unknown particle types"); } /** Matrix element squared for backwards uno tree-level current-current scattering With W+Jets * @param aptype Particle a PDG ID * @param bptype Particle b PDG ID * @param pn Particle n Momentum * @param pb Particle b Momentum * @param p1 Particle 1 Momentum * @param pa Particle a Momentum * @param pg Unordered gluon momentum * @param wc Boolean. True->W Emitted from b. Else; emitted from leg a * @returns ME Squared for unob Tree-Level Current-Current Scattering */ double ME_W_unob_current( int aptype, int bptype, CLHEP::HepLorentzVector const & pn, CLHEP::HepLorentzVector const & pb, CLHEP::HepLorentzVector const & p1, CLHEP::HepLorentzVector const & pa, CLHEP::HepLorentzVector const & pg, CLHEP::HepLorentzVector const & plbar, CLHEP::HepLorentzVector const & pl, bool const wc ){ // we know they are not both gluons if (bptype == 21 && aptype != 21) { // b gluon => W emission off a if (aptype > 0) return jM2Wunogqg(pg,p1,plbar,pl,pa,pn,pb); else return jM2Wunogqbarg(pg,p1,plbar,pl,pa,pn,pb); } else { // they are both quark if (wc==true) {// emission off b, i.e. b is first current if (bptype>0){ if (aptype>0) return junobMWqQg(pn,plbar,pl,pb,p1,pa,pg); else return junobMWqQbarg(pn,plbar,pl,pb,p1,pa,pg); } else{ if (aptype>0) return junobMWqbarQg(pn,plbar,pl,pb,p1,pa,pg); else return junobMWqbarQbarg(pn,plbar,pl,pb,p1,pa,pg); } } else {// wc == false, emission off a, i.e. a is first current if (aptype > 0) { if (bptype > 0) //qq return jM2WunogqQ(pg,p1,plbar,pl,pa,pn,pb); else //qqbar return jM2WunogqQbar(pg,p1,plbar,pl,pa,pn,pb); } else { // a is anti-quark if (bptype > 0) //qbarq return jM2WunogqbarQ(pg,p1,plbar,pl,pa,pn,pb); else //qbarqbar return jM2WunogqbarQbar(pg,p1,plbar,pl,pa,pn,pb); } } } } /** Matrix element squared for uno forward tree-level current-current scattering With W+Jets * @param aptype Particle a PDG ID * @param bptype Particle b PDG ID * @param pn Particle n Momentum * @param pb Particle b Momentum * @param p1 Particle 1 Momentum * @param pa Particle a Momentum * @param pg Unordered gluon momentum * @param wc Boolean. True->W Emitted from b. Else; emitted from leg a * @returns ME Squared for unof Tree-Level Current-Current Scattering */ double ME_W_unof_current( int aptype, int bptype, CLHEP::HepLorentzVector const & pn, CLHEP::HepLorentzVector const & pb, CLHEP::HepLorentzVector const & p1, CLHEP::HepLorentzVector const & pa, CLHEP::HepLorentzVector const & pg, CLHEP::HepLorentzVector const & plbar, CLHEP::HepLorentzVector const & pl, bool const wc ){ // we know they are not both gluons if (aptype==21 && bptype!=21) {//a gluon => W emission off b if (bptype > 0) return jM2Wunogqg(pg, pn,plbar, pl, pb, p1, pa); else return jM2Wunogqbarg(pg, pn,plbar, pl, pb, p1, pa); } else { // they are both quark if (wc==true) {// emission off b, i.e. b is first current if (bptype>0){ if (aptype>0) return jM2WunogqQ(pg,pn,plbar,pl,pb,p1,pa); else return jM2WunogqQbar(pg,pn,plbar,pl,pb,p1,pa); } else{ if (aptype>0) return jM2WunogqbarQ(pg,pn,plbar,pl,pb,p1,pa); else return jM2WunogqbarQbar(pg,pn,plbar,pl,pb,p1,pa); } } else {// wc == false, emission off a, i.e. a is first current if (aptype > 0) { if (bptype > 0) //qq return junofMWgqQ(pg,pn,pb,p1,plbar,pl,pa); else //qqbar return junofMWgqQbar(pg,pn,pb,p1,plbar,pl,pa); } else { // a is anti-quark if (bptype > 0) //qbarq return junofMWgqbarQ(pg,pn,pb,p1,plbar,pl,pa); else //qbarqbar return junofMWgqbarQbar(pg,pn,pb,p1,plbar,pl,pa); } } } } /** \brief Matrix element squared for backward qqx tree-level current-current scattering With W+Jets * @param aptype Particle a PDG ID * @param bptype Particle b PDG ID * @param pa Initial state a Momentum * @param pb Initial state b Momentum * @param pq Final state q Momentum * @param pqbar Final state qbar Momentum * @param pn Final state n Momentum * @param plbar Final state anti-lepton momentum * @param pl Final state lepton momentum * @param wc Boolean. True->W Emitted from b. Else; emitted from leg a * @returns ME Squared for qqxb Tree-Level Current-Current Scattering */ double ME_W_qqxb_current( int aptype, int bptype, CLHEP::HepLorentzVector const & pa, CLHEP::HepLorentzVector const & pb, CLHEP::HepLorentzVector const & pq, CLHEP::HepLorentzVector const & pqbar, CLHEP::HepLorentzVector const & pn, CLHEP::HepLorentzVector const & plbar, CLHEP::HepLorentzVector const & pl, bool const wc ){ // CAM factors for the qqx amps, and qqbar ordering (default, qbar extremal) bool swapQuarkAntiquark=false; double CFbackward; if (pqbar.rapidity() > pq.rapidity()){ swapQuarkAntiquark=true; CFbackward = (0.5*(3.-1./3.)*(pa.minus()/(pq.minus())+(pq.minus())/pa.minus())+1./3.)*3./4.; } else{ CFbackward = (0.5*(3.-1./3.)*(pa.minus()/(pqbar.minus())+(pqbar.minus())/pa.minus())+1./3.)*3./4.; } // With qqbar we could have 2 incoming gluons and W Emission if (aptype==21&&bptype==21) {//a gluon, b gluon gg->qqbarWg // This will be a wqqx emission as there is no other possible W Emission Site. if (swapQuarkAntiquark){ return jM2Wggtoqqbarg(pa, pqbar, plbar, pl, pq, pn,pb)*CFbackward;} else { return jM2Wggtoqbarqg(pa, pq, plbar, pl, pqbar, pn,pb)*CFbackward;} } else if (aptype==21&&bptype!=21 ) {//a gluon => W emission off b leg or qqx if (wc!=1){ // W Emitted from backwards qqx if (swapQuarkAntiquark){ return jM2WgQtoqqbarQ(pa, pq, plbar, pl, pqbar, pn, pb)*CFbackward;} else{ return jM2WgQtoqbarqQ(pa, pq, plbar, pl, pqbar, pn, pb)*CFbackward;} } else { // W Must be emitted from forwards leg. if(bptype > 0){ if (swapQuarkAntiquark){ return jM2WgqtoQQqW(pb, pa, pn, pqbar, pq, plbar, pl, false)*CFbackward;} else{ return jM2WgqtoQQqW(pb, pa, pn, pq, pqbar, plbar, pl, false)*CFbackward;} } else { if (swapQuarkAntiquark){ return jM2WgqtoQQqW(pb, pa, pn, pqbar, pq, plbar, pl, true)*CFbackward;} else{ return jM2WgqtoQQqW(pb, pa, pn, pq, pqbar, plbar, pl, true)*CFbackward;} } } } else{ throw std::logic_error("Incompatible incoming particle types with qqxb"); } } /* \brief Matrix element squared for forward qqx tree-level current-current scattering With W+Jets * @param aptype Particle a PDG ID * @param bptype Particle b PDG ID * @param pa Initial state a Momentum * @param pb Initial state b Momentum * @param pq Final state q Momentum * @param pqbar Final state qbar Momentum * @param p1 Final state 1 Momentum * @param plbar Final state anti-lepton momentum * @param pl Final state lepton momentum * @param wc Boolean. True->W Emitted from b. Else; emitted from leg a * @returns ME Squared for qqxf Tree-Level Current-Current Scattering */ double ME_W_qqxf_current( int aptype, int bptype, CLHEP::HepLorentzVector const & pa, CLHEP::HepLorentzVector const & pb, CLHEP::HepLorentzVector const & pq, CLHEP::HepLorentzVector const & pqbar, CLHEP::HepLorentzVector const & p1, CLHEP::HepLorentzVector const & plbar, CLHEP::HepLorentzVector const & pl, bool const wc ){ // CAM factors for the qqx amps, and qqbar ordering (default, qbar extremal) bool swapQuarkAntiquark=false; double CFforward; if (pqbar.rapidity() < pq.rapidity()){ swapQuarkAntiquark=true; CFforward = (0.5*(3.-1./3.)*(pb.plus()/(pq.plus())+(pq.plus())/pb.plus())+1./3.)*3./4.; } else{ CFforward = (0.5*(3.-1./3.)*(pb.plus()/(pqbar.plus())+(pqbar.plus())/pb.plus())+1./3.)*3./4.; } // With qqbar we could have 2 incoming gluons and W Emission if (aptype==21&&bptype==21) {//a gluon, b gluon gg->qqbarWg // This will be a wqqx emission as there is no other possible W Emission Site. if (swapQuarkAntiquark){ return jM2Wggtoqqbarg(pb, pqbar, plbar, pl, pq, p1,pa)*CFforward;} else { return jM2Wggtoqbarqg(pb, pq, plbar, pl, pqbar, p1,pa)*CFforward;} } else if (bptype==21&&aptype!=21) {// b gluon => W emission off a or qqx if (wc==1){ // W Emitted from forwards qqx if (swapQuarkAntiquark){ return jM2WgQtoqbarqQ(pb, pq, plbar,pl, pqbar, p1, pa)*CFforward;} else { return jM2WgQtoqqbarQ(pb, pq, plbar,pl, pqbar, p1, pa)*CFforward;} } // W Must be emitted from backwards leg. if (aptype > 0){ if (swapQuarkAntiquark){ return jM2WgqtoQQqW(pa,pb, p1, pqbar, pq, plbar, pl, false)*CFforward;} else{ return jM2WgqtoQQqW(pa,pb, p1, pq, pqbar, plbar, pl, false)*CFforward;} } else { if (swapQuarkAntiquark){ return jM2WgqtoQQqW(pa,pb, p1, pqbar, pq, plbar, pl, true)*CFforward;} else{ return jM2WgqtoQQqW(pa,pb, p1, pq, pqbar, plbar, pl, true)*CFforward;} } } else{ throw std::logic_error("Incompatible incoming particle types with qqxf"); } } /* \brief Matrix element squared for central qqx tree-level current-current scattering With W+Jets * @param aptype Particle a PDG ID * @param bptype Particle b PDG ID * @param nabove Number of gluons emitted before central qqxpair * @param nbelow Number of gluons emitted after central qqxpair * @param pa Initial state a Momentum * @param pb Initial state b Momentum\ * @param pq Final state qbar Momentum * @param pqbar Final state q Momentum * @param partons Vector of all outgoing partons * @param plbar Final state anti-lepton momentum * @param pl Final state lepton momentum * @param wqq Boolean. True siginfies W boson is emitted from Central qqx * @param wc Boolean. wc=true signifies w boson emitted from leg b; if wqq=false. * @returns ME Squared for qqxmid Tree-Level Current-Current Scattering */ double ME_W_qqxmid_current( int aptype, int bptype, int nabove, int nbelow, CLHEP::HepLorentzVector const & pa, CLHEP::HepLorentzVector const & pb, CLHEP::HepLorentzVector const & pq, CLHEP::HepLorentzVector const & pqbar, std::vector<HLV> partons, CLHEP::HepLorentzVector const & plbar, CLHEP::HepLorentzVector const & pl, bool const wqq, bool const wc ){ // CAM factors for the qqx amps, and qqbar ordering (default, pq backwards) bool swapQuarkAntiquark=false; if (pqbar.rapidity() < pq.rapidity()){ swapQuarkAntiquark=true; } double CFforward = (0.5*(3.-1./3.)*(pb.plus()/(partons[partons.size()-1].plus())+(partons[partons.size()-1].plus())/pb.plus())+1./3.)*3./4.; double CFbackward = (0.5*(3.-1./3.)*(pa.minus()/(partons[0].minus())+(partons[0].minus())/pa.minus())+1./3.)*3./4.; double wt=1.; if (aptype==21) wt*=CFbackward; if (bptype==21) wt*=CFforward; if (aptype <=0 && bptype <=0){ // Both External AntiQuark if (wqq==1){//emission from central qqbar return wt*jM2WqqtoqQQq(pa, pb, pl,plbar, partons,true,true, swapQuarkAntiquark, nabove); } else if (wc==1){//emission from b leg return wt*jM2WqqtoqQQqW(pa, pb, pl,plbar, partons, true,true, swapQuarkAntiquark, nabove, nbelow, true); } else { // emission from a leg return wt*jM2WqqtoqQQqW(pa, pb, pl,plbar, partons, true,true, swapQuarkAntiquark, nabove, nbelow, false); } } // end both antiquark else if (aptype<=0){ // a is antiquark if (wqq==1){//emission from central qqbar return wt*jM2WqqtoqQQq(pa, pb, pl,plbar, partons, false, true, swapQuarkAntiquark, nabove); } else if (wc==1){//emission from b leg return wt*jM2WqqtoqQQqW(pa, pb, pl,plbar, partons,false,true, swapQuarkAntiquark, nabove, nbelow, true); } else { // emission from a leg return wt*jM2WqqtoqQQqW(pa, pb, pl,plbar, partons, false, true, swapQuarkAntiquark, nabove, nbelow, false); } } // end a is antiquark else if (bptype<=0){ // b is antiquark if (wqq==1){//emission from central qqbar return wt*jM2WqqtoqQQq(pa, pb, pl,plbar, partons, true, false, swapQuarkAntiquark, nabove); } else if (wc==1){//emission from b leg return wt*jM2WqqtoqQQqW(pa, pb, pl,plbar, partons, true, false, swapQuarkAntiquark, nabove, nbelow, true); } else { // emission from a leg return wt*jM2WqqtoqQQqW(pa, pb, pl,plbar, partons, true, false, swapQuarkAntiquark, nabove, nbelow, false); } } //end b is antiquark else{ //Both Quark or gluon if (wqq==1){//emission from central qqbar return wt*jM2WqqtoqQQq(pa, pb, pl, plbar, partons, false, false, swapQuarkAntiquark, nabove);} else if (wc==1){//emission from b leg return wt*jM2WqqtoqQQqW(pa, pb, pl,plbar, partons, false, false, swapQuarkAntiquark, nabove, nbelow, true); } else { // emission from a leg return wt*jM2WqqtoqQQqW(pa, pb, pl,plbar, partons, false, false, swapQuarkAntiquark, nabove, nbelow, false); } } } /** \brief Matrix element squared for tree-level current-current scattering with Higgs * @param aptype Particle a PDG ID * @param bptype Particle b PDG ID * @param pn Particle n Momentum * @param pb Particle b Momentum * @param p1 Particle 1 Momentum * @param pa Particle a Momentum * @param qH t-channel momentum before Higgs * @param qHp1 t-channel momentum after Higgs * @returns ME Squared for Tree-Level Current-Current Scattering with Higgs */ double ME_Higgs_current( int aptype, int bptype, CLHEP::HepLorentzVector const & pn, CLHEP::HepLorentzVector const & pb, CLHEP::HepLorentzVector const & p1, CLHEP::HepLorentzVector const & pa, CLHEP::HepLorentzVector const & qH, // t-channel momentum before Higgs CLHEP::HepLorentzVector const & qHp1, // t-channel momentum after Higgs double mt, bool include_bottom, double mb ){ if (aptype==21&&bptype==21) // gg initial state return MH2gg(pn,pb,p1,pa,-qHp1,-qH,mt,include_bottom,mb); else if (aptype==21&&bptype!=21) { if (bptype > 0) return MH2qg(pn,pb,p1,pa,-qHp1,-qH,mt,include_bottom,mb)*4./9.; else return MH2qbarg(pn,pb,p1,pa,-qHp1,-qH,mt,include_bottom,mb)*4./9.; } else if (bptype==21&&aptype!=21) { if (aptype > 0) return MH2qg(p1,pa,pn,pb,-qH,-qHp1,mt,include_bottom,mb)*4./9.; else return MH2qbarg(p1,pa,pn,pb,-qH,-qHp1,mt,include_bottom,mb)*4./9.; } else { // they are both quark if (bptype>0) { if (aptype>0) return MH2qQ(pn,pb,p1,pa,-qHp1,-qH,mt,include_bottom,mb)*4.*4./(9.*9.); else return MH2qQbar(pn,pb,p1,pa,-qHp1,-qH,mt,include_bottom,mb)*4.*4./(9.*9.); } else { if (aptype>0) return MH2qQbar(p1,pa,pn,pb,-qH,-qHp1,mt,include_bottom,mb)*4.*4./(9.*9.); else return MH2qbarQbar(pn,pb,p1,pa,-qHp1,-qH,mt,include_bottom,mb)*4.*4./(9.*9.); } } throw std::logic_error("unknown particle types"); } /** \brief Current matrix element squared with Higgs and unordered forward emission * @param aptype Particle A PDG ID * @param bptype Particle B PDG ID * @param punof Unordered Particle Momentum * @param pn Particle n Momentum * @param pb Particle b Momentum * @param p1 Particle 1 Momentum * @param pa Particle a Momentum * @param qH t-channel momentum before Higgs * @param qHp1 t-channel momentum after Higgs * @returns ME Squared with Higgs and unordered forward emission */ double ME_Higgs_current_unof( int aptype, int bptype, CLHEP::HepLorentzVector const & punof, CLHEP::HepLorentzVector const & pn, CLHEP::HepLorentzVector const & pb, CLHEP::HepLorentzVector const & p1, CLHEP::HepLorentzVector const & pa, CLHEP::HepLorentzVector const & qH, // t-channel momentum before Higgs CLHEP::HepLorentzVector const & qHp1, // t-channel momentum after Higgs double mt, bool include_bottom, double mb ){ if (aptype==21&&bptype!=21) { if (bptype > 0) return jM2unogqHg(punof,pn,pb,p1,pa,-qHp1,-qH,mt,include_bottom,mb); else return jM2unogqbarHg(punof,pn,pb,p1,pa,-qHp1,-qH,mt,include_bottom,mb); } else { // they are both quark if (bptype>0) { if (aptype>0) return jM2unogqHQ(punof,pn,pb,p1,pa,-qHp1,-qH,mt,include_bottom,mb); else return jM2unogqHQbar(punof,pn,pb,p1,pa,-qHp1,-qH,mt,include_bottom,mb); } else { if (aptype>0) return jM2unogqbarHQ(punof,pn,pb,p1,pa,-qHp1,-qH,mt,include_bottom,mb); else return jM2unogqbarHQbar(punof,pn,pb,p1,pa,-qHp1,-qH,mt,include_bottom,mb); } } throw std::logic_error("unknown particle types"); } /** \brief Current matrix element squared with Higgs and unordered backward emission * @param aptype Particle A PDG ID * @param bptype Particle B PDG ID * @param pn Particle n Momentum * @param pb Particle b Momentum * @param punob Unordered back Particle Momentum * @param p1 Particle 1 Momentum * @param pa Particle a Momentum * @param qH t-channel momentum before Higgs * @param qHp1 t-channel momentum after Higgs * @returns ME Squared with Higgs and unordered backward emission */ double ME_Higgs_current_unob( int aptype, int bptype, CLHEP::HepLorentzVector const & pn, CLHEP::HepLorentzVector const & pb, CLHEP::HepLorentzVector const & punob, CLHEP::HepLorentzVector const & p1, CLHEP::HepLorentzVector const & pa, CLHEP::HepLorentzVector const & qH, // t-channel momentum before Higgs CLHEP::HepLorentzVector const & qHp1, // t-channel momentum after Higgs double mt, bool include_bottom, double mb ){ if (bptype==21&&aptype!=21) { if (aptype > 0) return jM2unobgHQg(pn,pb,punob,p1,pa,-qHp1,-qH,mt,include_bottom,mb); else return jM2unobgHQbarg(pn,pb,punob,p1,pa,-qHp1,-qH,mt,include_bottom,mb); } else { // they are both quark if (aptype>0) { if (bptype>0) return jM2unobqHQg(pn,pb,punob,p1,pa,-qHp1,-qH,mt,include_bottom,mb); else return jM2unobqbarHQg(pn,pb,punob,p1,pa,-qHp1,-qH,mt,include_bottom,mb); } else { if (bptype>0) return jM2unobqHQbarg(pn,pb,punob,p1,pa,-qHp1,-qH,mt,include_bottom,mb); else return jM2unobqbarHQbarg(pn,pb,punob,p1,pa,-qHp1,-qH,mt,include_bottom,mb); } } throw std::logic_error("unknown particle types"); } CLHEP::HepLorentzVector to_HepLorentzVector(HEJ::Particle const & particle){ return {particle.p.px(), particle.p.py(), particle.p.pz(), particle.p.E()}; } void validate(HEJ::MatrixElementConfig const & config) { #ifndef HEJ_BUILD_WITH_QCDLOOP if(!config.Higgs_coupling.use_impact_factors) { throw std::invalid_argument{ "Invalid Higgs coupling settings.\n" "HEJ without QCDloop support can only use impact factors.\n" "Set use_impact_factors to true or recompile HEJ.\n" }; } #endif if(config.Higgs_coupling.use_impact_factors && config.Higgs_coupling.mt != std::numeric_limits<double>::infinity()) { throw std::invalid_argument{ "Conflicting settings: " "impact factors may only be used in the infinite top mass limit" }; } } } // namespace anonymous namespace HEJ{ MatrixElement::MatrixElement( std::function<double (double)> alpha_s, MatrixElementConfig conf ): alpha_s_{std::move(alpha_s)}, param_{std::move(conf)} { validate(param_); } double MatrixElement::tree_kin( Event const & ev ) const { if(! is_HEJ(ev.type())) return 0.; auto AWZH_boson = std::find_if( begin(ev.outgoing()), end(ev.outgoing()), [](Particle const & p){return is_AWZH_boson(p);} ); if(AWZH_boson == end(ev.outgoing())){ return tree_kin_jets(ev); } switch(AWZH_boson->type){ case pid::Higgs: { return tree_kin_Higgs(ev); } case pid::Wp: case pid::Wm: return tree_kin_W(ev); // TODO case pid::photon: case pid::Z: default: throw not_implemented("Emission of boson of unsupported type"); } } namespace{ constexpr int extremal_jet_idx = 1; constexpr int no_extremal_jet_idx = 0; bool treat_as_extremal(Particle const & parton){ return parton.p.user_index() == extremal_jet_idx; } template<class InputIterator> double FKL_ladder_weight( InputIterator begin_gluon, InputIterator end_gluon, CLHEP::HepLorentzVector const & q0, CLHEP::HepLorentzVector const & pa, CLHEP::HepLorentzVector const & pb, CLHEP::HepLorentzVector const & p1, CLHEP::HepLorentzVector const & pn, double lambda ){ double wt = 1; auto qi = q0; for(auto gluon_it = begin_gluon; gluon_it != end_gluon; ++gluon_it){ assert(gluon_it->type == pid::gluon); const auto g = to_HepLorentzVector(*gluon_it); const auto qip1 = qi - g; if(treat_as_extremal(*gluon_it)){ wt *= C2Lipatovots(qip1, qi, pa, pb, lambda)*C_A; } else{ wt *= C2Lipatovots(qip1, qi, pa, pb, p1, pn, lambda)*C_A; } qi = qip1; } return wt; } } // namespace anonymous std::vector<Particle> MatrixElement::tag_extremal_jet_partons( Event const & ev ) const{ auto out_partons = filter_partons(ev.outgoing()); if(out_partons.size() == ev.jets().size()){ // no additional emissions in extremal jets, don't need to tag anything for(auto & parton: out_partons){ parton.p.set_user_index(no_extremal_jet_idx); } return out_partons; } // TODO: avoid reclustering fastjet::ClusterSequence cs(to_PseudoJet(out_partons), ev.jet_def()); const auto jets = sorted_by_rapidity(cs.inclusive_jets(ev.min_jet_pt())); assert(jets.size() >= 2); auto most_backward = begin(jets); auto most_forward = end(jets) - 1; // skip jets caused by unordered emission or qqx if(ev.type() == event_type::unob || ev.type() == event_type::qqxexb){ assert(jets.size() >= 3); ++most_backward; } else if(ev.type() == event_type::unof || ev.type() == event_type::qqxexf){ assert(jets.size() >= 3); --most_forward; } const auto extremal_jet_indices = cs.particle_jet_indices( {*most_backward, *most_forward} ); assert(extremal_jet_indices.size() == out_partons.size()); for(size_t i = 0; i < out_partons.size(); ++i){ assert(HEJ::is_parton(out_partons[i])); const int idx = (extremal_jet_indices[i]>=0)? extremal_jet_idx: no_extremal_jet_idx; out_partons[i].p.set_user_index(idx); } return out_partons; } double MatrixElement::tree_kin_jets( Event const & ev ) const { auto const & incoming = ev.incoming(); const auto partons = tag_extremal_jet_partons(ev); if(is_uno(ev.type())){ throw not_implemented("unordered emission not implemented for pure jets"); } const auto pa = to_HepLorentzVector(incoming[0]); const auto pb = to_HepLorentzVector(incoming[1]); const auto p1 = to_HepLorentzVector(partons.front()); const auto pn = to_HepLorentzVector(partons.back()); return ME_current( incoming[0].type, incoming[1].type, pn, pb, p1, pa )/(4*(N_C*N_C - 1))*FKL_ladder_weight( begin(partons) + 1, end(partons) - 1, pa - p1, pa, pb, p1, pn, param_.regulator_lambda ); } namespace{ double tree_kin_W_FKL( int aptype, int bptype, HLV pa, HLV pb, std::vector<Particle> const & partons, HLV plbar, HLV pl, double lambda ) { auto p1 = to_HepLorentzVector(partons[0]); auto pn = to_HepLorentzVector(partons[partons.size() - 1]); auto begin_ladder = begin(partons) + 1; auto end_ladder = end(partons) - 1; bool wc = true; auto q0 = pa - p1; if (aptype!=partons[0].type) { //leg a emits w wc = false; q0 -=pl + plbar; } const double current_factor = ME_W_current( aptype, bptype, pn, pb, p1, pa, plbar, pl, wc ); const double ladder_factor = FKL_ladder_weight( begin_ladder, end_ladder, q0, pa, pb, p1, pn, lambda ); return current_factor*ladder_factor; } double tree_kin_W_unob( int aptype, int bptype, HLV pa, HLV pb, std::vector<Particle> const & partons, HLV plbar, HLV pl, double lambda ) { auto pg = to_HepLorentzVector(partons[0]); auto p1 = to_HepLorentzVector(partons[1]); auto pn = to_HepLorentzVector(partons[partons.size() - 1]); auto begin_ladder = begin(partons) + 2; auto end_ladder = end(partons) - 1; bool wc = true; auto q0 = pa - p1 -pg; if (aptype!=partons[1].type) { //leg a emits w wc = false; q0 -=pl + plbar; } const double current_factor = ME_W_unob_current( aptype, bptype, pn, pb, p1, pa, pg, plbar, pl, wc ); const double ladder_factor = FKL_ladder_weight( begin_ladder, end_ladder, q0, pa, pb, p1, pn, lambda ); return current_factor*C_A*C_A/(N_C*N_C-1.)*ladder_factor; } double tree_kin_W_unof( int aptype, int bptype, HLV pa, HLV pb, std::vector<Particle> const & partons, HLV plbar, HLV pl, double lambda ) { auto p1 = to_HepLorentzVector(partons[0]); auto pn = to_HepLorentzVector(partons[partons.size() - 2]); auto pg = to_HepLorentzVector(partons[partons.size() - 1]); auto begin_ladder = begin(partons) + 1; auto end_ladder = end(partons) - 2; bool wc = true; auto q0 = pa - p1; if (aptype!=partons[0].type) { //leg a emits w wc = false; q0 -=pl + plbar; } const double current_factor = ME_W_unof_current( aptype, bptype, pn, pb, p1, pa, pg, plbar, pl, wc ); const double ladder_factor = FKL_ladder_weight( begin_ladder, end_ladder, q0, pa, pb, p1, pn, lambda ); return current_factor*C_A*C_A/(N_C*N_C-1.)*ladder_factor; } double tree_kin_W_qqxb( int aptype, int bptype, HLV pa, HLV pb, std::vector<Particle> const & partons, HLV plbar, HLV pl, double lambda ) { HLV pq,pqbar; if(is_quark(partons[0])){ pq = to_HepLorentzVector(partons[0]); pqbar = to_HepLorentzVector(partons[1]); } else{ pq = to_HepLorentzVector(partons[1]); pqbar = to_HepLorentzVector(partons[0]); } auto p1 = to_HepLorentzVector(partons[0]); auto pn = to_HepLorentzVector(partons[partons.size() - 1]); auto begin_ladder = begin(partons) + 2; auto end_ladder = end(partons) - 1; bool wc = true; auto q0 = pa - pq - pqbar; if (partons[1].type!=partons[0].type) { //leg a emits w wc = false; q0 -=pl + plbar; } const double current_factor = ME_W_qqxb_current( aptype, bptype, pa, pb, pq, pqbar, pn, plbar, pl, wc ); const double ladder_factor = FKL_ladder_weight( begin_ladder, end_ladder, q0, pa, pb, p1, pn, lambda ); return current_factor*C_A*C_A/(N_C*N_C-1.)*ladder_factor; } double tree_kin_W_qqxf( int aptype, int bptype, HLV pa, HLV pb, std::vector<Particle> const & partons, HLV plbar, HLV pl, double lambda ) { HLV pq,pqbar; if(is_quark(partons[partons.size() - 1])){ pq = to_HepLorentzVector(partons[partons.size() - 1]); pqbar = to_HepLorentzVector(partons[partons.size() - 2]); } else{ pq = to_HepLorentzVector(partons[partons.size() - 2]); pqbar = to_HepLorentzVector(partons[partons.size() - 1]); } auto p1 = to_HepLorentzVector(partons[0]); auto pn = to_HepLorentzVector(partons[partons.size() - 1]); auto begin_ladder = begin(partons) + 1; auto end_ladder = end(partons) - 2; bool wc = true; auto q0 = pa - p1; if (aptype!=partons[0].type) { //leg a emits w wc = false; q0 -=pl + plbar; } const double current_factor = ME_W_qqxf_current( aptype, bptype, pa, pb, pq, pqbar, p1, plbar, pl, wc ); const double ladder_factor = FKL_ladder_weight( begin_ladder, end_ladder, q0, pa, pb, p1, pn, lambda ); return current_factor*C_A*C_A/(N_C*N_C-1.)*ladder_factor; } double tree_kin_W_qqxmid( int aptype, int bptype, HLV pa, HLV pb, std::vector<Particle> const & partons, HLV plbar, HLV pl, double lambda ) { HLV pq,pqbar; const auto backmidquark = std::find_if( begin(partons)+1, end(partons)-1, [](Particle const & s){ return s.type != pid::gluon; } ); assert(backmidquark!=end(partons)-1); if (is_quark(backmidquark->type)){ pq = to_HepLorentzVector(*backmidquark); pqbar = to_HepLorentzVector(*(backmidquark+1)); } else { pqbar = to_HepLorentzVector(*backmidquark); pq = to_HepLorentzVector(*(backmidquark+1)); } auto p1 = to_HepLorentzVector(partons[0]); auto pn = to_HepLorentzVector(partons[partons.size() - 1]); auto q0 = pa - p1; // t-channel momentum after qqx auto qqxt = q0; bool wc, wqq; if (backmidquark->type == -(backmidquark+1)->type){ // Central qqx does not emit wqq=false; if (aptype==partons[0].type) { wc = true; } else{ wc = false; q0-=pl+plbar; } } else{ wqq = true; wc = false; qqxt-=pl+plbar; } auto begin_ladder = begin(partons) + 1; auto end_ladder_1 = (backmidquark); auto begin_ladder_2 = (backmidquark+2); auto end_ladder = end(partons) - 1; for(auto parton_it = begin_ladder; parton_it < begin_ladder_2; ++parton_it){ qqxt -= to_HepLorentzVector(*parton_it); } int nabove = std::distance(begin_ladder, backmidquark); int nbelow = std::distance(begin_ladder_2, end_ladder); std::vector<HLV> partonsHLV; partonsHLV.reserve(partons.size()); for (size_t i = 0; i != partons.size(); ++i) { partonsHLV.push_back(to_HepLorentzVector(partons[i])); } const double current_factor = ME_W_qqxmid_current( aptype, bptype, nabove, nbelow, pa, pb, pq, pqbar, partonsHLV, plbar, pl, wqq, wc ); const double ladder_factor = FKL_ladder_weight( begin_ladder, end_ladder_1, q0, pa, pb, p1, pn, lambda )*FKL_ladder_weight( begin_ladder_2, end_ladder, qqxt, pa, pb, p1, pn, lambda ); return current_factor*C_A*C_A/(N_C*N_C-1.)*ladder_factor; } } // namespace anonymous double MatrixElement::tree_kin_W(Event const & ev) const { using namespace event_type; auto const & incoming(ev.incoming()); auto const & decays(ev.decays()); HLV plbar, pl; for (auto& x: decays) { if (x.second.at(0).type < 0){ plbar = to_HepLorentzVector(x.second.at(0)); pl = to_HepLorentzVector(x.second.at(1)); } else{ pl = to_HepLorentzVector(x.second.at(0)); plbar = to_HepLorentzVector(x.second.at(1)); } } const auto pa = to_HepLorentzVector(incoming[0]); const auto pb = to_HepLorentzVector(incoming[1]); const auto partons = tag_extremal_jet_partons(ev); if(ev.type() == unordered_backward){ return tree_kin_W_unob(incoming[0].type, incoming[1].type, pa, pb, partons, plbar, pl, param_.regulator_lambda); } if(ev.type() == unordered_forward){ return tree_kin_W_unof(incoming[0].type, incoming[1].type, pa, pb, partons, plbar, pl, param_.regulator_lambda); } if(ev.type() == extremal_qqxb){ return tree_kin_W_qqxb(incoming[0].type, incoming[1].type, pa, pb, partons, plbar, pl, param_.regulator_lambda); } if(ev.type() == extremal_qqxf){ return tree_kin_W_qqxf(incoming[0].type, incoming[1].type, pa, pb, partons, plbar, pl, param_.regulator_lambda); } if(ev.type() == central_qqx){ return tree_kin_W_qqxmid(incoming[0].type, incoming[1].type, pa, pb, partons, plbar, pl, param_.regulator_lambda); } return tree_kin_W_FKL(incoming[0].type, incoming[1].type, pa, pb, partons, plbar, pl, param_.regulator_lambda); } double MatrixElement::tree_kin_Higgs( Event const & ev ) const { if(is_uno(ev.type())){ return tree_kin_Higgs_between(ev); } if(ev.outgoing().front().type == pid::Higgs){ return tree_kin_Higgs_first(ev); } if(ev.outgoing().back().type == pid::Higgs){ return tree_kin_Higgs_last(ev); } return tree_kin_Higgs_between(ev); } namespace { // Colour acceleration multipliers, for gluons see eq. (7) in arXiv:0910.5113 #ifdef HEJ_BUILD_WITH_QCDLOOP // TODO: code duplication with currents.cc double K_g(double p1minus, double paminus) { return 1./2.*(p1minus/paminus + paminus/p1minus)*(C_A - 1./C_A) + 1./C_A; } double K_g( CLHEP::HepLorentzVector const & pout, CLHEP::HepLorentzVector const & pin ) { if(pin.z() > 0) return K_g(pout.plus(), pin.plus()); return K_g(pout.minus(), pin.minus()); } double K( ParticleID type, CLHEP::HepLorentzVector const & pout, CLHEP::HepLorentzVector const & pin ) { if(type == ParticleID::gluon) return K_g(pout, pin); return C_F; } #endif // Colour factor in strict MRK limit double K_MRK(ParticleID type) { return (type == ParticleID::gluon)?C_A:C_F; } } double MatrixElement::MH2_forwardH( CLHEP::HepLorentzVector p1out, CLHEP::HepLorentzVector p1in, ParticleID type2, CLHEP::HepLorentzVector p2out, CLHEP::HepLorentzVector p2in, CLHEP::HepLorentzVector pH, double t1, double t2 ) const{ ignore(p2out, p2in); const double shat = p1in.invariantMass2(p2in); // gluon case #ifdef HEJ_BUILD_WITH_QCDLOOP if(!param_.Higgs_coupling.use_impact_factors){ return K(type2, p2out, p2in)*C_A*1./(16*M_PI*M_PI)*t1/t2*MH2gq_outsideH( p1out, p1in, p2out, p2in, pH, param_.Higgs_coupling.mt, param_.Higgs_coupling.include_bottom, param_.Higgs_coupling.mb )/(4*(N_C*N_C - 1)); } #endif return K_MRK(type2)/C_A*9./2.*shat*shat*( C2gHgp(p1in,p1out,pH) + C2gHgm(p1in,p1out,pH) )/(t1*t2); } double MatrixElement::tree_kin_Higgs_first( Event const & ev ) const { auto const & incoming = ev.incoming(); auto const & outgoing = ev.outgoing(); assert(outgoing.front().type == pid::Higgs); if(outgoing[1].type != pid::gluon) { assert(incoming.front().type == outgoing[1].type); return tree_kin_Higgs_between(ev); } const auto pH = to_HepLorentzVector(outgoing.front()); const auto partons = tag_extremal_jet_partons( ev ); const auto pa = to_HepLorentzVector(incoming[0]); const auto pb = to_HepLorentzVector(incoming[1]); const auto p1 = to_HepLorentzVector(partons.front()); const auto pn = to_HepLorentzVector(partons.back()); const auto q0 = pa - p1 - pH; const double t1 = q0.m2(); const double t2 = (pn - pb).m2(); return MH2_forwardH( p1, pa, incoming[1].type, pn, pb, pH, t1, t2 )*FKL_ladder_weight( begin(partons) + 1, end(partons) - 1, q0, pa, pb, p1, pn, param_.regulator_lambda ); } double MatrixElement::tree_kin_Higgs_last( Event const & ev ) const { auto const & incoming = ev.incoming(); auto const & outgoing = ev.outgoing(); assert(outgoing.back().type == pid::Higgs); if(outgoing[outgoing.size()-2].type != pid::gluon) { assert(incoming.back().type == outgoing[outgoing.size()-2].type); return tree_kin_Higgs_between(ev); } const auto pH = to_HepLorentzVector(outgoing.back()); const auto partons = tag_extremal_jet_partons( ev ); const auto pa = to_HepLorentzVector(incoming[0]); const auto pb = to_HepLorentzVector(incoming[1]); auto p1 = to_HepLorentzVector(partons.front()); const auto pn = to_HepLorentzVector(partons.back()); auto q0 = pa - p1; const double t1 = q0.m2(); const double t2 = (pn + pH - pb).m2(); return MH2_forwardH( pn, pb, incoming[0].type, p1, pa, pH, t2, t1 )*FKL_ladder_weight( begin(partons) + 1, end(partons) - 1, q0, pa, pb, p1, pn, param_.regulator_lambda ); } double MatrixElement::tree_kin_Higgs_between( Event const & ev ) const { using namespace event_type; auto const & incoming = ev.incoming(); auto const & outgoing = ev.outgoing(); const auto the_Higgs = std::find_if( begin(outgoing), end(outgoing), [](Particle const & s){ return s.type == pid::Higgs; } ); assert(the_Higgs != end(outgoing)); const auto pH = to_HepLorentzVector(*the_Higgs); const auto partons = tag_extremal_jet_partons(ev); const auto pa = to_HepLorentzVector(incoming[0]); const auto pb = to_HepLorentzVector(incoming[1]); auto p1 = to_HepLorentzVector( partons[(ev.type() == unob)?1:0] ); auto pn = to_HepLorentzVector( partons[partons.size() - ((ev.type() == unof)?2:1)] ); auto first_after_Higgs = begin(partons) + (the_Higgs-begin(outgoing)); assert( (first_after_Higgs == end(partons) && ( (ev.type() == unob) || partons.back().type != pid::gluon )) || first_after_Higgs->rapidity() >= the_Higgs->rapidity() ); assert( (first_after_Higgs == begin(partons) && ( (ev.type() == unof) || partons.front().type != pid::gluon )) || (first_after_Higgs-1)->rapidity() <= the_Higgs->rapidity() ); // always treat the Higgs as if it were in between the extremal FKL partons if(first_after_Higgs == begin(partons)) ++first_after_Higgs; else if(first_after_Higgs == end(partons)) --first_after_Higgs; // t-channel momentum before Higgs auto qH = pa; for(auto parton_it = begin(partons); parton_it != first_after_Higgs; ++parton_it){ qH -= to_HepLorentzVector(*parton_it); } auto q0 = pa - p1; auto begin_ladder = begin(partons) + 1; auto end_ladder = end(partons) - 1; double current_factor; if(ev.type() == unob){ current_factor = C_A*C_A/2.*ME_Higgs_current_unob( // 1/2 = "K_uno" incoming[0].type, incoming[1].type, pn, pb, to_HepLorentzVector(partons.front()), p1, pa, qH, qH - pH, param_.Higgs_coupling.mt, param_.Higgs_coupling.include_bottom, param_.Higgs_coupling.mb ); const auto p_unob = to_HepLorentzVector(partons.front()); q0 -= p_unob; p1 += p_unob; ++begin_ladder; } else if(ev.type() == unof){ current_factor = C_A*C_A/2.*ME_Higgs_current_unof( // 1/2 = "K_uno" incoming[0].type, incoming[1].type, to_HepLorentzVector(partons.back()), pn, pb, p1, pa, qH, qH - pH, param_.Higgs_coupling.mt, param_.Higgs_coupling.include_bottom, param_.Higgs_coupling.mb ); pn += to_HepLorentzVector(partons.back()); --end_ladder; } else{ current_factor = ME_Higgs_current( incoming[0].type, incoming[1].type, pn, pb, p1, pa, qH, qH - pH, param_.Higgs_coupling.mt, param_.Higgs_coupling.include_bottom, param_.Higgs_coupling.mb ); } const double ladder_factor = FKL_ladder_weight( begin_ladder, first_after_Higgs, q0, pa, pb, p1, pn, param_.regulator_lambda )*FKL_ladder_weight( first_after_Higgs, end_ladder, qH - pH, pa, pb, p1, pn, param_.regulator_lambda ); return current_factor*C_A*C_A/(N_C*N_C-1.)*ladder_factor; } double MatrixElement::tree_param_partons( double alpha_s, double mur, std::vector<Particle> const & partons ) const{ const double gs2 = 4.*M_PI*alpha_s; double wt = std::pow(gs2, partons.size()); if(param_.log_correction){ // use alpha_s(q_perp), evolved to mur assert(partons.size() >= 2); for(size_t i = 1; i < partons.size()-1; ++i){ wt *= 1 + alpha_s/(2*M_PI)*beta0*log(mur/partons[i].p.perp()); } } return wt; } + namespace { + double get_AWZH_coupling(Event const & ev, double alpha_s) { + const auto AWZH_boson = std::find_if( + begin(ev.outgoing()), end(ev.outgoing()), + [](auto const & p){return is_AWZH_boson(p);} + ); + if(AWZH_boson == end(ev.outgoing())) return 1.; + switch(AWZH_boson->type){ + case pid::Higgs: + return alpha_s*alpha_s; + case pid::Wp: + case pid::Wm: + return gw*gw*gw*gw/4; + // TODO + case pid::photon: + case pid::Z: + default: + throw not_implemented("Emission of boson of unsupported type"); + } + } + } + double MatrixElement::tree_param( Event const & ev, double mur ) const{ assert(is_HEJ(ev.type())); const auto & out = ev.outgoing(); const double alpha_s = alpha_s_(mur); - auto AWZH_boson = std::find_if( - begin(out), end(out), - [](auto const & p){return is_AWZH_boson(p);} - ); - double AWZH_coupling = 1.; - if(AWZH_boson != end(out)){ - switch(AWZH_boson->type){ - case pid::Higgs: { - AWZH_coupling = alpha_s*alpha_s; - break; - } - // TODO - case pid::Wp:{ - AWZH_coupling = gw*gw*gw*gw/4; - break; - } - case pid::Wm:{ - AWZH_coupling = gw*gw*gw*gw/4; - break; - } - case pid::photon: - case pid::Z: - default: - throw not_implemented("Emission of boson of unsupported type"); - } - } + const double AWZH_coupling = get_AWZH_coupling(ev, alpha_s); if(ev.type() == event_type::unob || ev.type() == event_type::qqxexb){ return AWZH_coupling*4*M_PI*alpha_s*tree_param_partons( alpha_s, mur, filter_partons({begin(out) + 1, end(out)}) ); } if(ev.type() == event_type::unof || ev.type() == event_type::qqxexf){ return AWZH_coupling*4*M_PI*alpha_s*tree_param_partons( alpha_s, mur, filter_partons({begin(out), end(out) - 1}) ); } return AWZH_coupling*tree_param_partons(alpha_s, mur, filter_partons(out)); } } // namespace HEJ diff --git a/src/PhaseSpacePoint.cc b/src/PhaseSpacePoint.cc index 1d581f3..9ea93fa 100644 --- a/src/PhaseSpacePoint.cc +++ b/src/PhaseSpacePoint.cc @@ -1,625 +1,631 @@ /** * \authors Jeppe Andersen, Tuomas Hapola, Marian Heil, Andreas Maier, Jennifer Smillie * \date 2019 * \copyright GPLv2 or later */ #include "HEJ/PhaseSpacePoint.hh" #include <algorithm> #include <assert.h> #include <numeric> #include <random> #include "fastjet/ClusterSequence.hh" #include "HEJ/Constants.hh" #include "HEJ/Event.hh" #include "HEJ/JetSplitter.hh" #include "HEJ/kinematics.hh" #include "HEJ/resummation_jet.hh" #include "HEJ/utility.hh" #include "HEJ/PDG_codes.hh" #include "HEJ/event_types.hh" namespace HEJ{ namespace { constexpr int max_jet_user_idx = PhaseSpacePoint::ng_max; bool is_nonjet_parton(fastjet::PseudoJet const & parton){ assert(parton.user_index() != -1); return parton.user_index() > max_jet_user_idx; } bool is_jet_parton(fastjet::PseudoJet const & parton){ assert(parton.user_index() != -1); return parton.user_index() <= max_jet_user_idx; } // user indices for partons with extremal rapidity constexpr int qqxb_idx = -7; constexpr int qqxf_idx = -6; constexpr int unob_idx = -5; constexpr int unof_idx = -4; constexpr int backward_FKL_idx = -3; constexpr int forward_FKL_idx = -2; } namespace { double estimate_ng_mean(std::vector<fastjet::PseudoJet> const & Born_jets){ const double delta_y = Born_jets.back().rapidity() - Born_jets.front().rapidity(); assert(delta_y > 0); // Formula derived from fit in arXiv:1805.04446 (see Fig. 2) return 0.975052*delta_y; } } std::vector<fastjet::PseudoJet> PhaseSpacePoint::cluster_jets( std::vector<fastjet::PseudoJet> const & partons ) const{ fastjet::ClusterSequence cs(partons, param_.jet_param.def); return cs.inclusive_jets(param_.jet_param.min_pt); } bool PhaseSpacePoint::pass_resummation_cuts( std::vector<fastjet::PseudoJet> const & jets ) const{ return cluster_jets(jets).size() == jets.size(); } int PhaseSpacePoint::sample_ng(std::vector<fastjet::PseudoJet> const & Born_jets){ const double ng_mean = estimate_ng_mean(Born_jets); std::poisson_distribution<int> dist(ng_mean); const int ng = dist(ran_.get()); assert(ng >= 0); assert(ng < ng_max); weight_ *= std::tgamma(ng + 1)*std::exp(ng_mean)*std::pow(ng_mean, -ng); return ng; } void PhaseSpacePoint::copy_AWZH_boson_from(Event const & event){ auto const & from = event.outgoing(); const auto AWZH_boson = std::find_if( begin(from), end(from), [](Particle const & p){ return is_AWZH_boson(p); } ); if(AWZH_boson == end(from)) return; auto insertion_point = std::lower_bound( begin(outgoing_), end(outgoing_), *AWZH_boson, rapidity_less{} ); outgoing_.insert(insertion_point, *AWZH_boson); // copy decay products const int idx = std::distance(begin(from), AWZH_boson); assert(idx >= 0); const auto decay_it = event.decays().find(idx); if(decay_it != end(event.decays())){ const int new_idx = std::distance(begin(outgoing_), insertion_point); assert(new_idx >= 0); assert(outgoing_[new_idx].type == AWZH_boson->type); decays_.emplace(new_idx, decay_it->second); } assert(std::is_sorted(begin(outgoing_), end(outgoing_), rapidity_less{})); } //! \brief relabels qqx-pair with its PDG IDs. //*@param ev Born Event // // This function will label the qqx pair in a qqx event back to // their original types from the input event. void PhaseSpacePoint::label_qqx(Event const & event){ auto const & bornout = event.outgoing(); const auto backquark = std::find_if( begin(bornout) + 1 - ((qqxb_)?1:0), end(bornout) - 1 + ((qqxf_)?1:0) , - [](Particle const & s){ return (s.type != pid::gluon && is_parton(s.type)); } + [](Particle const & s){ return (is_anyquark(s.type)); } ); - assert(backquark->type !=pid::gluon); if(backquark == end(bornout) || (backquark+1)->type==pid::gluon) weight_= 0; + auto quark1type = backquark->type; + auto quark2type = (backquark+1)->type; + + if(is_AWZ_boson((backquark+1)->type)) quark2type = (backquark+2)->type; + + if( !((is_quark(quark1type) && is_antiquark(quark2type)) + && !(is_quark(quark2type) && is_antiquark(quark1type))) + ){ + weight_=0; + } + auto partons = to_PseudoJet(filter_partons(outgoing_)); fastjet::ClusterSequence cs(partons, event.jet_def()); const auto jets = fastjet::sorted_by_rapidity(cs.inclusive_jets(event.min_jet_pt())); const auto indices = cs.particle_jet_indices({jets}); assert(partons.size() == indices.size()); - int qpart=0; + int qpart=-1; // Find Parton in res event closest to most backward qqx jet in born - for (size_t i=0; i<indices.size(); i++){ - if( (indices[i] != -1) && indices[i]!=indices[i+1] - && nearby_rap(backquark->rapidity(), partons[i].rapidity(), 0.1)){ + for (size_t i=0; i<indices.size(); i++) { + if( (indices[i] != -1) && (indices[i]==indices[i+1]-1) + && nearby_ep(backquark->rapidity(), partons[i].rapidity(), 0.1)){ qpart=i; + outgoing_.at(qpart).type = quark1type; + outgoing_.at(qpart+1).type = quark2type; + break; } } - if (indices[qpart] == -1) weight_= 0; - else if (indices[qpart] == 0 && (!qqxb_)) weight_=0; - else if (indices[qpart] == signed(jets.size()-2) && (!qqxf_)) weight_=0; - - // Ensure qqx in separate jets and adjacent in rapidity - if (indices[qpart] == indices[qpart+1]-1){ - outgoing_.at(qpart).type = backquark->type; - outgoing_.at(qpart+1).type = (backquark+1)->type; - } - else weight_=0; + if(qpart==-1) weight_=0; assert(std::is_sorted(begin(outgoing_), end(outgoing_), rapidity_less{})); } PhaseSpacePoint::PhaseSpacePoint( Event const & ev, PhaseSpacePointConfig conf, HEJ::RNG & ran ): unob_{ev.type() == event_type::unob}, unof_{ev.type() == event_type::unof}, qqxb_{ev.type() == event_type::qqxexb}, qqxf_{ev.type() == event_type::qqxexf}, qqxmid_{ev.type() == event_type::qqxmid}, param_{std::move(conf)}, ran_{ran} { weight_ = 1; const auto Born_jets = sorted_by_rapidity(ev.jets()); const int ng = sample_ng(Born_jets); weight_ /= std::tgamma(ng + 1); const int ng_jets = sample_ng_jets(ng, Born_jets); std::vector<fastjet::PseudoJet> out_partons = gen_non_jet( ng - ng_jets, CMINPT, param_.jet_param.min_pt ); const auto qperp = std::accumulate( begin(out_partons), end(out_partons), fastjet::PseudoJet{} ); const auto jets = reshuffle(Born_jets, qperp); if(weight_ == 0.) return; if(! pass_resummation_cuts(jets)){ weight_ = 0.; return; } std::vector<fastjet::PseudoJet> jet_partons = split(jets, ng_jets); if(weight_ == 0.) return; rescale_rapidities( out_partons, most_backward_FKL(jet_partons).rapidity(), most_forward_FKL(jet_partons).rapidity() ); if(! cluster_jets(out_partons).empty()){ weight_ = 0.; return; } std::sort(begin(out_partons), end(out_partons), rapidity_less{}); assert( std::is_sorted(begin(jet_partons), end(jet_partons), rapidity_less{}) ); const auto first_jet_parton = out_partons.insert( end(out_partons), begin(jet_partons), end(jet_partons) ); std::inplace_merge( begin(out_partons), first_jet_parton, end(out_partons), rapidity_less{} ); if(! jets_ok(Born_jets, out_partons)){ weight_ = 0.; return; } weight_ *= phase_space_normalisation(Born_jets.size(), out_partons.size()); outgoing_.reserve(out_partons.size() + 1); // one slot for possible A, W, Z, H for(auto & p: out_partons){ - outgoing_.emplace_back(Particle{pid::gluon, std::move(p)}); + outgoing_.emplace_back(Particle{pid::gluon, std::move(p), {}}); } const auto WEmit = std::find_if( begin(ev.outgoing()), end(ev.outgoing()), [](Particle const & s){ return abs(s.type) == pid::Wp; } ); - if (abs(WEmit->type) == pid::Wp){ - outgoing_[unob_].type = filter_partons(ev.outgoing())[unob_].type; - outgoing_.rbegin()[unof_].type = filter_partons(ev.outgoing()).rbegin()[unof_].type; + if (WEmit != end(ev.outgoing())){ + if(!qqxb_) + outgoing_[unob_].type = filter_partons(ev.outgoing())[unob_].type; + if(!qqxf_) + outgoing_.rbegin()[unof_].type = filter_partons(ev.outgoing()).rbegin()[unof_].type; } else{ most_backward_FKL(outgoing_).type = ev.incoming().front().type; most_forward_FKL(outgoing_).type = ev.incoming().back().type; } if(qqxmid_||qqxb_||qqxf_){ label_qqx(ev); } copy_AWZH_boson_from(ev); assert(!outgoing_.empty()); reconstruct_incoming(ev.incoming()); } std::vector<fastjet::PseudoJet> PhaseSpacePoint::gen_non_jet( int count, double ptmin, double ptmax ){ // heuristic parameters for pt sampling const double ptpar = 1.3 + count/5.; const double temp1 = atan((ptmax - ptmin)/ptpar); std::vector<fastjet::PseudoJet> partons(count); for(size_t i = 0; i < (size_t) count; ++i){ const double r1 = ran_.get().flat(); const double pt = ptmin + ptpar*tan(r1*temp1); const double temp2 = cos(r1*temp1); const double phi = 2*M_PI*ran_.get().flat(); weight_ *= 2.0*M_PI*pt*ptpar*temp1/(temp2*temp2); // we don't know the allowed rapidity span yet, // set a random value to be rescaled later on const double y = ran_.get().flat(); partons[i].reset_PtYPhiM(pt, y, phi); // Set user index higher than any jet-parton index // in order to assert that these are not inside jets partons[i].set_user_index(i + 1 + ng_max); assert(ptmin-1e-5 <= partons[i].pt() && partons[i].pt() <= ptmax+1e-5); } assert(std::all_of(partons.cbegin(), partons.cend(), is_nonjet_parton)); return partons; } void PhaseSpacePoint::rescale_rapidities( std::vector<fastjet::PseudoJet> & partons, double ymin, double ymax ){ constexpr double ep = 1e-7; for(auto & parton: partons){ assert(0 <= parton.rapidity() && parton.rapidity() <= 1); const double dy = ymax - ymin - 2*ep; const double y = ymin + ep + dy*parton.rapidity(); parton.reset_momentum_PtYPhiM(parton.pt(), y, parton.phi()); weight_ *= dy; assert(ymin <= parton.rapidity() && parton.rapidity() <= ymax); } } namespace { template<typename T, typename... Rest> auto min(T const & a, T const & b, Rest&&... r) { using std::min; return min(a, min(b, std::forward<Rest>(r)...)); } } double PhaseSpacePoint::probability_in_jet( std::vector<fastjet::PseudoJet> const & Born_jets ) const{ assert(std::is_sorted(begin(Born_jets), end(Born_jets), rapidity_less{})); assert(Born_jets.size() >= 2); const double dy = Born_jets.back().rapidity() - Born_jets.front().rapidity(); const double R = param_.jet_param.def.R(); const int njets = Born_jets.size(); const double p_J_y_large = (njets-1)*R*R/(2.*dy); const double p_J_y0 = njets*R/M_PI; return min(p_J_y_large, p_J_y0, 1.); } int PhaseSpacePoint::sample_ng_jets( int ng, std::vector<fastjet::PseudoJet> const & Born_jets ){ const double p_J = probability_in_jet(Born_jets); std::binomial_distribution<> bin_dist(ng, p_J); const int ng_J = bin_dist(ran_.get()); weight_ *= std::pow(p_J, -ng_J)*std::pow(1 - p_J, ng_J - ng); return ng_J; } std::vector<fastjet::PseudoJet> PhaseSpacePoint::reshuffle( std::vector<fastjet::PseudoJet> const & Born_jets, fastjet::PseudoJet const & q ){ if(q == fastjet::PseudoJet{0, 0, 0, 0}) return Born_jets; const auto jets = resummation_jet_momenta(Born_jets, q); if(jets.empty()){ weight_ = 0; return {}; } // additional Jacobian to ensure Born integration over delta gives 1 weight_ *= resummation_jet_weight(Born_jets, q); return jets; } std::vector<int> PhaseSpacePoint::distribute_jet_partons( int ng_jets, std::vector<fastjet::PseudoJet> const & jets ){ size_t first_valid_jet = 0; size_t num_valid_jets = jets.size(); const double R_eff = 5./3.*param_.jet_param.def.R(); // if there is an unordered jet too far away from the FKL jets // then extra gluon constituents of the unordered jet would // violate the FKL rapidity ordering if((unob_||qqxb_) && jets[0].delta_R(jets[1]) > R_eff){ ++first_valid_jet; --num_valid_jets; } else if((unof_||qqxf_) && jets[jets.size()-1].delta_R(jets[jets.size()-2]) > R_eff){ --num_valid_jets; } std::vector<int> np(jets.size(), 1); for(int i = 0; i < ng_jets; ++i){ ++np[first_valid_jet + ran_.get().flat() * num_valid_jets]; } weight_ *= std::pow(num_valid_jets, ng_jets); return np; } #ifndef NDEBUG namespace{ bool tagged_FKL_backward( std::vector<fastjet::PseudoJet> const & jet_partons ){ return std::find_if( begin(jet_partons), end(jet_partons), [](fastjet::PseudoJet const & p){ return p.user_index() == backward_FKL_idx; } ) != end(jet_partons); } bool tagged_FKL_forward( std::vector<fastjet::PseudoJet> const & jet_partons ){ // the most forward FKL parton is most likely near the end of jet_partons; // start search from there return std::find_if( jet_partons.rbegin(), jet_partons.rend(), [](fastjet::PseudoJet const & p){ return p.user_index() == forward_FKL_idx; } ) != jet_partons.rend(); } bool tagged_FKL_extremal( std::vector<fastjet::PseudoJet> const & jet_partons ){ return tagged_FKL_backward(jet_partons) && tagged_FKL_forward(jet_partons); } } // namespace anonymous #endif std::vector<fastjet::PseudoJet> PhaseSpacePoint::split( std::vector<fastjet::PseudoJet> const & jets, int ng_jets ){ return split(jets, distribute_jet_partons(ng_jets, jets)); } bool PhaseSpacePoint::pass_extremal_cuts( fastjet::PseudoJet const & ext_parton, fastjet::PseudoJet const & jet ) const{ if(ext_parton.pt() < param_.min_extparton_pt) return false; return (ext_parton - jet).pt()/jet.pt() < param_.max_ext_soft_pt_fraction; } std::vector<fastjet::PseudoJet> PhaseSpacePoint::split( std::vector<fastjet::PseudoJet> const & jets, std::vector<int> const & np ){ assert(! jets.empty()); assert(jets.size() == np.size()); assert(pass_resummation_cuts(jets)); const size_t most_backward_FKL_idx = 0 + unob_ + qqxb_; const size_t most_forward_FKL_idx = jets.size() - 1 - unof_ - qqxf_; const auto & jet = param_.jet_param; const JetSplitter jet_splitter{jet.def, jet.min_pt, ran_}; std::vector<fastjet::PseudoJet> jet_partons; // randomly distribute jet gluons among jets for(size_t i = 0; i < jets.size(); ++i){ auto split_res = jet_splitter.split(jets[i], np[i]); weight_ *= split_res.weight; if(weight_ == 0) return {}; assert( std::all_of( begin(split_res.constituents), end(split_res.constituents), is_jet_parton ) ); const auto first_new_parton = jet_partons.insert( end(jet_partons), begin(split_res.constituents), end(split_res.constituents) ); // mark uno and extremal FKL emissions here so we can check // their position once all emissions are generated auto extremal = end(jet_partons); if (i == most_backward_FKL_idx){ //FKL backward emission extremal = std::min_element( first_new_parton, end(jet_partons), rapidity_less{} ); extremal->set_user_index(backward_FKL_idx); } else if(((unob_ || qqxb_) && i == 0)){ // unordered/qqxb extremal = std::min_element( first_new_parton, end(jet_partons), rapidity_less{} ); extremal->set_user_index((unob_)?unob_idx:qqxb_idx); } else if (i == most_forward_FKL_idx){ extremal = std::max_element( first_new_parton, end(jet_partons), rapidity_less{} ); extremal->set_user_index(forward_FKL_idx); } else if(((unof_ || qqxf_) && i == jets.size() - 1)){ // unordered/qqxf extremal = std::max_element( first_new_parton, end(jet_partons), rapidity_less{} ); extremal->set_user_index((unof_)?unof_idx:qqxf_idx); } if( extremal != end(jet_partons) && !pass_extremal_cuts(*extremal, jets[i]) ){ weight_ = 0; return {}; } } assert(tagged_FKL_extremal(jet_partons)); std::sort(begin(jet_partons), end(jet_partons), rapidity_less{}); if( !extremal_ok(jet_partons) || !split_preserved_jets(jets, jet_partons) ){ weight_ = 0.; return {}; } return jet_partons; } bool PhaseSpacePoint::extremal_ok( std::vector<fastjet::PseudoJet> const & partons ) const{ assert(std::is_sorted(begin(partons), end(partons), rapidity_less{})); if(unob_ && partons.front().user_index() != unob_idx) return false; if(unof_ && partons.back().user_index() != unof_idx) return false; if(qqxb_ && partons.front().user_index() != qqxb_idx) return false; if(qqxf_ && partons.back().user_index() != qqxf_idx) return false; return most_backward_FKL(partons).user_index() == backward_FKL_idx && most_forward_FKL(partons).user_index() == forward_FKL_idx; } bool PhaseSpacePoint::split_preserved_jets( std::vector<fastjet::PseudoJet> const & jets, std::vector<fastjet::PseudoJet> const & jet_partons ) const{ assert(std::is_sorted(begin(jets), end(jets), rapidity_less{})); const auto split_jets = sorted_by_rapidity(cluster_jets(jet_partons)); // this can happen if two overlapping jets // are both split into more than one parton if(split_jets.size() != jets.size()) return false; for(size_t i = 0; i < split_jets.size(); ++i){ // this can happen if there are two overlapping jets // and a parton is assigned to the "wrong" jet if(!nearby_ep(jets[i].rapidity(), split_jets[i].rapidity(), 1e-2)){ return false; } } return true; } template<class Particle> Particle const & PhaseSpacePoint::most_backward_FKL( std::vector<Particle> const & partons ) const{ return partons[0 + unob_ + qqxb_]; } template<class Particle> Particle const & PhaseSpacePoint::most_forward_FKL( std::vector<Particle> const & partons ) const{ const size_t idx = partons.size() - 1 - unof_ - qqxf_; assert(idx < partons.size()); return partons[idx]; } template<class Particle> Particle & PhaseSpacePoint::most_backward_FKL( std::vector<Particle> & partons ) const{ return partons[0 + unob_ + qqxb_]; } template<class Particle> Particle & PhaseSpacePoint::most_forward_FKL( std::vector<Particle> & partons ) const{ const size_t idx = partons.size() - 1 - unof_ - qqxf_; assert(idx < partons.size()); return partons[idx]; } namespace { bool contains_idx( fastjet::PseudoJet const & jet, fastjet::PseudoJet const & parton ){ auto const & constituents = jet.constituents(); const int idx = parton.user_index(); return std::find_if( begin(constituents), end(constituents), [idx](fastjet::PseudoJet const & con){return con.user_index() == idx;} ) != end(constituents); } } /** * final jet test: * - number of jets must match Born kinematics * - no partons designated as nonjet may end up inside jets * - all other outgoing partons *must* end up inside jets * - the extremal (in rapidity) partons must be inside the extremal jets * - rapidities must be the same (by construction) */ bool PhaseSpacePoint::jets_ok( std::vector<fastjet::PseudoJet> const & Born_jets, std::vector<fastjet::PseudoJet> const & partons ) const{ fastjet::ClusterSequence cs(partons, param_.jet_param.def); const auto jets = sorted_by_rapidity(cs.inclusive_jets(param_.jet_param.min_pt)); if(jets.size() != Born_jets.size()) return false; int in_jet = 0; for(size_t i = 0; i < jets.size(); ++i){ assert(jets[i].has_constituents()); for(auto && parton: jets[i].constituents()){ if(is_nonjet_parton(parton)) return false; } in_jet += jets[i].constituents().size(); } const int expect_in_jet = std::count_if( partons.cbegin(), partons.cend(), is_jet_parton ); if(in_jet != expect_in_jet) return false; // note that PseudoJet::contains does not work here if(! ( contains_idx(most_backward_FKL(jets), most_backward_FKL(partons)) && contains_idx(most_forward_FKL(jets), most_forward_FKL(partons)) )) return false; if(unob_ && !contains_idx(jets.front(), partons.front())) return false; if(unof_ && !contains_idx(jets.back(), partons.back())) return false; for(size_t i = 0; i < jets.size(); ++i){ assert(nearby_ep(jets[i].rapidity(), Born_jets[i].rapidity(), 1e-2)); } return true; } void PhaseSpacePoint::reconstruct_incoming( std::array<Particle, 2> const & Born_incoming ){ std::tie(incoming_[0].p, incoming_[1].p) = incoming_momenta(outgoing_); for(size_t i = 0; i < incoming_.size(); ++i){ incoming_[i].type = Born_incoming[i].type; } assert(momentum_conserved()); } double PhaseSpacePoint::phase_space_normalisation( int num_Born_jets, int num_out_partons ) const{ return pow(16*pow(M_PI,3), num_Born_jets - num_out_partons); } bool PhaseSpacePoint::momentum_conserved() const{ fastjet::PseudoJet diff; for(auto const & in: incoming()) diff += in.p; const double norm = diff.E(); for(auto const & out: outgoing()) diff -= out.p; return nearby(diff, fastjet::PseudoJet{}, norm); } } //namespace HEJ diff --git a/src/Tensor.cc b/src/Tensor.cc index 351e505..0fba3bb 100644 --- a/src/Tensor.cc +++ b/src/Tensor.cc @@ -1,801 +1,790 @@ #include "HEJ/currents.hh" #include "HEJ/Tensor.hh" #include <array> #include <iostream> - - namespace{ // Tensor Template definitions short int sigma_index5[1024]; short int sigma_index3[64]; std::valarray<COM> permfactor5; std::valarray<COM> permfactor3; short int helfactor5[2][1024]; short int helfactor3[2][64]; - //2D sigma matrices - const COM sigma0[2][2] = { {1.,0.}, {0.,1.}}; - const COM sigma1[2][2] = { {0.,1.}, {1.,0.}}; - const COM sigma2[2][2] = { {0,-1.*COM(0,1)}, {1.*COM(0,1),0}}; - const COM sigma3[2][2] = { {1.,0.}, {0.,-1.}}; + // 2D sigma matrices + const COM sigma0[2][2] = { {1.,0.}, {0., 1.} }; + const COM sigma1[2][2] = { {0.,1.}, {1., 0.} }; + const COM sigma2[2][2] = { {0,-1.*COM(0,1)}, {1.*COM(0,1), 0.} }; + const COM sigma3[2][2] = { {1.,0.}, {0., -1.} }; Tensor<1,4> Sigma(int i, int j, bool hel){ Tensor<1,4> newT; if(hel){ newT.components[0]=sigma0[i][j]; newT.components[1]=sigma1[i][j]; newT.components[2]=sigma2[i][j]; newT.components[3]=sigma3[i][j]; - }else{ - newT.components[0]=sigma0[i][j]; + } else { + newT.components[0]= sigma0[i][j]; newT.components[1]=-sigma1[i][j]; newT.components[2]=-sigma2[i][j]; newT.components[3]=-sigma3[i][j]; } return newT; } + // map from a list of 5 tensor lorentz indices onto a single index 0<=i<1024 + // in 4 dimensional spacetime - //map from a list of 5 tensor lorentz indices onto a single index 0<=i<1024 in 4 dimensional spacetime int tensor2listindex(std::array<int,5> indexlist){ int mu=indexlist[0]; int nu=indexlist[1]; int sigma=indexlist[2]; int tau=indexlist[3]; int rho=indexlist[4]; int myindex; myindex = 256*mu+64*nu+16*sigma+4*tau+rho; if(myindex<0||myindex>1023){ std::cerr<<"bad index in tensor2listindex "<<std::endl; return 1024; } - else - return myindex; + return myindex; } - //overload - //map from a list of 3 tensor lorentz indices onto a single index 0<=i<64 in 4 dimensional spacetime + // map from a list of 3 tensor lorentz indices onto a single index 0<=i<64 in + // 4 dimensional spacetime int tensor2listindex(std::array<int,3> indexlist){ int mu=indexlist[0]; int nu=indexlist[1]; int sigma=indexlist[2]; int myindex; myindex = 16*mu+4*nu+sigma; if(myindex<0||myindex>64){ std::cerr<<"bad index in tensor2listindex "<<std::endl; return 64; } - else - return myindex; + return myindex; } - - //generate all unique perms of vectors {a,a,a,a,b}, return in perms - //set_permfactor is a bool which encodes the anticommutation relations of the sigma matrices - //namely if we have sigma0, set_permfactor= false because it commutes with all others - //otherwise we need to assign a minus sign to odd permutations, set in permfactor - //note, inital perm is always even - void perms41(int same4, int diff, std::vector<std::array<int,5>> * perms){ + // generate all unique perms of vectors {a,a,a,a,b}, return in perms + // set_permfactor is a bool which encodes the anticommutation relations of the + // sigma matrices namely if we have sigma0, set_permfactor= false because it + // commutes with all others otherwise we need to assign a minus sign to odd + // permutations, set in permfactor + // note, inital perm is always even + void perms41(int same4, int diff, std::vector<std::array<int,5>> & perms){ bool set_permfactor(true); if(same4==0||diff==0) set_permfactor=false; for(int diffpos=0;diffpos<5;diffpos++){ std::array<int,5> tempvec={same4,same4,same4,same4,same4}; tempvec[diffpos]=diff; - perms->push_back(tempvec); + perms.push_back(tempvec); if(set_permfactor){ - if(diffpos%2==1) - permfactor5[tensor2listindex(tempvec)]=-1.; + if(diffpos%2==1) + permfactor5[tensor2listindex(tempvec)]=-1.; } } } - //generate all unique perms of vectors {a,a,a,b,b}, return in perms - //note, inital perm is always even - void perms32(int same3, int diff, std::vector<std::array<int,5>> * perms){ + // generate all unique perms of vectors {a,a,a,b,b}, return in perms + // note, inital perm is always even + void perms32(int same3, int diff, std::vector<std::array<int,5>> & perms){ bool set_permfactor(true); if(same3==0||diff==0) set_permfactor=false; for(int diffpos1=0;diffpos1<5;diffpos1++){ for(int diffpos2=diffpos1+1;diffpos2<5;diffpos2++){ - std::array<int,5> tempvec={same3,same3,same3,same3,same3}; - tempvec[diffpos1]=diff; - tempvec[diffpos2]=diff; - perms->push_back(tempvec); - if(set_permfactor){ - if((diffpos2-diffpos1)%2==0) - permfactor5[tensor2listindex(tempvec)]=-1.; - } + std::array<int,5> tempvec={same3,same3,same3,same3,same3}; + tempvec[diffpos1]=diff; + tempvec[diffpos2]=diff; + perms.push_back(tempvec); + if(set_permfactor){ + if((diffpos2-diffpos1)%2==0) + permfactor5[tensor2listindex(tempvec)]=-1.; + } } } } - //generate all unique perms of vectors {a,a,a,b,c}, return in perms - //we have two bools since there are three distinct type of sigma matrices to permute, so need to test if the 3xrepeated = sigma0 - //or if one of the singles is sigma0 - //if diff1/diff2 are distinct, flipping them results in a change of perm, otherwise it'll be symmetric under flip -> encode this in set_permfactor2 - //as long as diff2!=0 can ensure inital perm is even - //if diff2=0 then it is not possible to ensure inital perm even -> encode in bool signflip - void perms311(int same3, int diff1, int diff2, std::vector<std::array<int,5>> * perms){ + // generate all unique perms of vectors {a,a,a,b,c}, return in perms + // we have two bools since there are three distinct type of sigma matrices to + // permute, so need to test if the 3xrepeated = sigma0 or if one of the + // singles is sigma0 + // if diff1/diff2 are distinct, flipping them results in a change of perm, + // otherwise it'll be symmetric under flip -> encode this in set_permfactor2 + // as long as diff2!=0 can ensure inital perm is even + // if diff2=0 then it is not possible to ensure inital perm even -> encode in + // bool signflip + void perms311(int same3, int diff1, int diff2, + std::vector<std::array<int,5>> & perms + ){ bool set_permfactor2(true); bool same0(false); bool diff0(false); - bool signflip(false); //if true, inital perm is odd + bool signflip(false); // if true, inital perm is odd - if(same3==0) //is the repeated matrix sigma0? + if(same3==0) // is the repeated matrix sigma0? same0 = true; - else if(diff2==0){ //is one of the single matrices sigma0 + else if(diff2==0){ // is one of the single matrices sigma0 diff0=true; if((diff1%3-same3)!=-1) - signflip=true; - } - else if(diff1==0){ - std::cerr<<"Note, only first and last argument may be zero"<<std::endl; - return; + signflip=true; + } else if(diff1==0){ + std::cerr<<"Note, only first and last argument may be zero"<<std::endl; + return; } - //possible outcomes: tt, ft, tf + // possible outcomes: tt, ft, tf for(int diffpos1=0;diffpos1<5;diffpos1++){ for(int diffpos2=diffpos1+1;diffpos2<5;diffpos2++){ - std::array<int,5> tempvec={same3,same3,same3,same3,same3}; - tempvec[diffpos1]=diff1; - tempvec[diffpos2]=diff2; - perms->push_back(tempvec); - - if(!same0&&!diff0){//full antisymmetric under exchange of same3,diff1,diff2 - if((diffpos2-diffpos1)%2==0){ - permfactor5[tensor2listindex(tempvec)]=-1.*COM(0,1); //odd perm - set_permfactor2=false; //if this perm is odd, swapping diff1<->diff2 automatically even - }else{ - permfactor5[tensor2listindex(tempvec)]=COM(0,1); //even perm - set_permfactor2=true; //if this perm is even, swapping diff1<->diff2 automatically odd - } - }else if(diff0){//one of the single matrices is sigma0 - if(signflip){ //initial config is odd! - if(diffpos1%2==1){ - permfactor5[tensor2listindex(tempvec)]=COM(0,1); //even perm - //initally symmetric under diff1<->diff2 => - set_permfactor2=false; //if this perm is even, automatically even for first diffpos2 - }else{ - permfactor5[tensor2listindex(tempvec)]=-1.*COM(0,1); //odd perm - //initally symmetric under diff1<->diff2 => - set_permfactor2=true; //if this perm is odd, automatically odd for first diffpos2 - } - }else{//diff0=true, initial config is even - if(diffpos1%2==1){ - permfactor5[tensor2listindex(tempvec)]=-1.*COM(0,1); //odd perm - //initally symmetric under diff1<->diff2 => - set_permfactor2=true; //if this perm is odd, automatically odd for first diffpos2 - } - else{ - permfactor5[tensor2listindex(tempvec)]=COM(0,1); //even perm - //initally symmetric under diff1<->diff2 => - set_permfactor2=false; //if this perm is even, automatically even for first diffpos2 - } - } - if((diffpos2-diffpos1-1)%2==1) - set_permfactor2=!set_permfactor2; //change to account for diffpos2 - }else if(same0){//the repeated matrix is sigma0 => only relative positions of diff1, diff2 matter. - permfactor5[tensor2listindex(tempvec)]=COM(0,1); //always even before flip because diff1pos<diff2pos - set_permfactor2=true; //if this perm is odd, swapping diff1<->diff2 automatically odd - } - - tempvec[diffpos1]=diff2; - tempvec[diffpos2]=diff1; - perms->push_back(tempvec); - - if(set_permfactor2) - permfactor5[tensor2listindex(tempvec)]=-1.*COM(0,1); - - else - permfactor5[tensor2listindex(tempvec)]=COM(0,1); + std::array<int,5> tempvec={same3,same3,same3,same3,same3}; + tempvec[diffpos1]=diff1; + tempvec[diffpos2]=diff2; + perms.push_back(tempvec); + + if(!same0 && !diff0){ + // full antisymmetric under exchange of same3,diff1,diff2 + if((diffpos2-diffpos1)%2==0){ + permfactor5[tensor2listindex(tempvec)]=-1.*COM(0,1); // odd perm + // if this perm is odd, swapping diff1<->diff2 automatically even + set_permfactor2=false; + } else { + permfactor5[tensor2listindex(tempvec)]=COM(0,1); // even perm + // if this perm is even, swapping diff1<->diff2 automatically odd + set_permfactor2=true; + } + } else if(diff0){// one of the single matrices is sigma0 + if(signflip){ // initial config is odd! + if(diffpos1%2==1){ + permfactor5[tensor2listindex(tempvec)]=COM(0,1); // even perm + // initally symmetric under diff1<->diff2 => + // if this perm is even, automatically even for first diffpos2 + set_permfactor2=false; + } else { + permfactor5[tensor2listindex(tempvec)]=-1.*COM(0,1); // odd perm + // initally symmetric under diff1<->diff2 => + // if this perm is odd, automatically odd for first diffpos2 + set_permfactor2=true; + } + } else {// diff0=true, initial config is even + if(diffpos1%2==1){ + permfactor5[tensor2listindex(tempvec)]=-1.*COM(0,1); // odd perm + // initally symmetric under diff1<->diff2 => + // if this perm is odd, automatically odd for first diffpos2 + set_permfactor2=true; + } else { + permfactor5[tensor2listindex(tempvec)]=COM(0,1); // even perm + // initally symmetric under diff1<->diff2 => + // if this perm is even, automatically even for first diffpos2 + set_permfactor2=false; + } + } + if((diffpos2-diffpos1-1)%2==1) + set_permfactor2=!set_permfactor2; // change to account for diffpos2 + } else if(same0){ + // the repeated matrix is sigma0 + // => only relative positions of diff1, diff2 matter. + // always even before flip because diff1pos<diff2pos + permfactor5[tensor2listindex(tempvec)]=COM(0,1); + // if this perm is odd, swapping diff1<->diff2 automatically odd + set_permfactor2=true; + } + + tempvec[diffpos1]=diff2; + tempvec[diffpos2]=diff1; + perms.push_back(tempvec); + + if(set_permfactor2) + permfactor5[tensor2listindex(tempvec)]=-1.*COM(0,1); + else + permfactor5[tensor2listindex(tempvec)]=COM(0,1); } } + } // end perms311 - } - - //generate all unique perms of vectors {a,a,b,b,c}, return in perms - void perms221(int same2a, int same2b, int diff, std::vector<std::array<int,5>> * perms){ + // generate all unique perms of vectors {a,a,b,b,c}, return in perms + void perms221(int same2a, int same2b, int diff, + std::vector<std::array<int,5>> & perms + ){ bool set_permfactor1(true); bool set_permfactor2(true); if(same2b==0){ std::cerr<<"second entry in perms221() shouldn't be zero" <<std::endl; return; - } - else if(same2a==0) + } else if(same2a==0) set_permfactor1=false; else if(diff==0) set_permfactor2=false; for(int samepos=0;samepos<5;samepos++){ int permcount = 0; for(int samepos2=samepos+1;samepos2<5;samepos2++){ - for(int diffpos=0;diffpos<5;diffpos++){ - if(diffpos==samepos||diffpos==samepos2) continue; - - std::array<int,5> tempvec={same2a,same2a,same2a,same2a,same2a}; - tempvec[samepos]=same2b; - tempvec[samepos2]=same2b; - tempvec[diffpos]=diff; - perms->push_back(tempvec); - - if(set_permfactor1){ - if(set_permfactor2){//full anti-symmetry - if(permcount%2==1) - permfactor5[tensor2listindex(tempvec)]=-1.; - }else{ //diff is sigma0 - if(((samepos2-samepos-1)%3>0)&&(abs(abs(samepos2-diffpos)-abs(samepos-diffpos))%3>0)) - permfactor5[tensor2listindex(tempvec)]=-1.; + for(int diffpos=0;diffpos<5;diffpos++){ + if(diffpos==samepos||diffpos==samepos2) continue; + + std::array<int,5> tempvec={same2a,same2a,same2a,same2a,same2a}; + tempvec[samepos]=same2b; + tempvec[samepos2]=same2b; + tempvec[diffpos]=diff; + perms.push_back(tempvec); + + if(set_permfactor1){ + if(set_permfactor2){// full anti-symmetry + if(permcount%2==1) + permfactor5[tensor2listindex(tempvec)]=-1.; + } else { // diff is sigma0 + if( ((samepos2-samepos-1)%3>0) + && (abs(abs(samepos2-diffpos)-abs(samepos-diffpos))%3>0) ) + permfactor5[tensor2listindex(tempvec)]=-1.; + } + } else { // same2a is sigma0 + if((diffpos>samepos) && (diffpos<samepos2)) + permfactor5[tensor2listindex(tempvec)]=-1.; + } + permcount++; + } } - }else{ //same2a is sigma0 - if((diffpos>samepos)&&(diffpos<samepos2)) - permfactor5[tensor2listindex(tempvec)]=-1.; } - permcount++; - } - } - } - } - //generate all unique perms of vectors {a,a,b,b,c}, return in perms - //there must be a sigma zero if we have 4 different matrices in string - //bool is true if sigma0 is the repeated matrix - void perms2111(int same2, int diff1,int diff2,int diff3, std::vector<std::array<int,5>> * perms){ + // generate all unique perms of vectors {a,a,b,b,c}, return in perms + // there must be a sigma zero if we have 4 different matrices in string + // bool is true if sigma0 is the repeated matrix + void perms2111(int same2, int diff1,int diff2,int diff3, + std::vector<std::array<int,5>> & perms + ){ bool twozero(false); if(same2==0) twozero=true; else if (diff1!=0){ std::cerr<<"One of first or second argurments must be a zero"<<std::endl; return; - } - else if(diff2==0|| diff3==0){ + } else if(diff2==0|| diff3==0){ std::cerr<<"Only first and second arguments may be a zero."<<std::endl; return; } int permcount = 0; for(int diffpos1=0;diffpos1<5;diffpos1++){ for(int diffpos2=0;diffpos2<5;diffpos2++){ - if(diffpos2==diffpos1) continue; - for(int diffpos3=0;diffpos3<5;diffpos3++){ - if(diffpos3==diffpos2||diffpos3==diffpos1) continue; - - std::array<int,5> tempvec={same2,same2,same2,same2,same2}; - tempvec[diffpos1]=diff1; - tempvec[diffpos2]=diff2; - tempvec[diffpos3]=diff3; - perms->push_back(tempvec); - - if(twozero){//don't care about exact positions of singles, just order - if(diffpos2>diffpos3&&diffpos3>diffpos1) - permfactor5[tensor2listindex(tempvec)]=-1.*COM(0,1);//odd - else if(diffpos1>diffpos2&&diffpos2>diffpos3) - permfactor5[tensor2listindex(tempvec)]=-1.*COM(0,1);//odd - else if(diffpos3>diffpos1&&diffpos1>diffpos2) - permfactor5[tensor2listindex(tempvec)]=-1.*COM(0,1);//odd - else - permfactor5[tensor2listindex(tempvec)]=COM(0,1);//evwn - }else{ - if(permcount%2==1) - permfactor5[tensor2listindex(tempvec)]=-1.*COM(0,1); - else - permfactor5[tensor2listindex(tempvec)]=COM(0,1); - } - permcount++; - - } + if(diffpos2==diffpos1) continue; + for(int diffpos3=0;diffpos3<5;diffpos3++){ + if(diffpos3==diffpos2||diffpos3==diffpos1) continue; + + std::array<int,5> tempvec={same2,same2,same2,same2,same2}; + tempvec[diffpos1]=diff1; + tempvec[diffpos2]=diff2; + tempvec[diffpos3]=diff3; + perms.push_back(tempvec); + + if(twozero){// don't care about exact positions of singles, just order + if(diffpos2>diffpos3 && diffpos3>diffpos1) + permfactor5[tensor2listindex(tempvec)]=-1.*COM(0,1);// odd + else if(diffpos1>diffpos2 && diffpos2>diffpos3) + permfactor5[tensor2listindex(tempvec)]=-1.*COM(0,1);// odd + else if(diffpos3>diffpos1 && diffpos1>diffpos2) + permfactor5[tensor2listindex(tempvec)]=-1.*COM(0,1);// odd + else + permfactor5[tensor2listindex(tempvec)]=COM(0,1);// evwn + } else { + if(permcount%2==1) + permfactor5[tensor2listindex(tempvec)]=-1.*COM(0,1); + else + permfactor5[tensor2listindex(tempvec)]=COM(0,1); + } + permcount++; + } } } } - void perms21(int same, int diff, std::vector<std::array<int,3>> * perms){ + void perms21(int same, int diff, std::vector<std::array<int,3>> & perms){ bool set_permfactor(true); if(same==0||diff==0) set_permfactor=false; - for(int diffpos=0; diffpos<3;diffpos++){ std::array<int,3> tempvec={same,same,same}; tempvec[diffpos]=diff; - perms->push_back(tempvec); - if(set_permfactor&&diffpos==1) - permfactor3[tensor2listindex(tempvec)]=-1.; + perms.push_back(tempvec); + if(set_permfactor && diffpos==1) + permfactor3[tensor2listindex(tempvec)]=-1.; } - } - void perms111(int diff1, int diff2, int diff3, std::vector<std::array<int,3>> * perms){ + void perms111(int diff1, int diff2, int diff3, + std::vector<std::array<int,3>> & perms + ){ bool sig_zero(false); if(diff1==0) sig_zero=true; else if(diff2==0||diff3==0){ std::cerr<<"Only first argument may be a zero."<<std::endl; return; } int permcount=0; for(int pos1=0;pos1<3;pos1++){ for(int pos2=pos1+1;pos2<3;pos2++){ - std::array<int,3> tempvec={diff1,diff1,diff1}; - tempvec[pos1]=diff2; - tempvec[pos2]=diff3; - perms->push_back(tempvec); - if(sig_zero){ - permfactor3[tensor2listindex(tempvec)]=1.*COM(0,1); //even - }else if(permcount%2==1){ - permfactor3[tensor2listindex(tempvec)]=-1.*COM(0,1); //odd - }else{ - permfactor3[tensor2listindex(tempvec)]=1.*COM(0,1); //even - } - - tempvec[pos1]=diff3; - tempvec[pos2]=diff2; - perms->push_back(tempvec); - - if(sig_zero){ - permfactor3[tensor2listindex(tempvec)]=-1.*COM(0,1); //odd - }else if(permcount%2==1){ - permfactor3[tensor2listindex(tempvec)]=1.*COM(0,1); //even - }else{ - permfactor3[tensor2listindex(tempvec)]=-1.*COM(0,1); //odd - } - permcount++; + std::array<int,3> tempvec={diff1,diff1,diff1}; + tempvec[pos1]=diff2; + tempvec[pos2]=diff3; + perms.push_back(tempvec); + if(sig_zero){ + permfactor3[tensor2listindex(tempvec)]=1.*COM(0,1); // even + } else if(permcount%2==1){ + permfactor3[tensor2listindex(tempvec)]=-1.*COM(0,1); // odd + } else { + permfactor3[tensor2listindex(tempvec)]=1.*COM(0,1); // even + } + + tempvec[pos1]=diff3; + tempvec[pos2]=diff2; + perms.push_back(tempvec); + + if(sig_zero){ + permfactor3[tensor2listindex(tempvec)]=-1.*COM(0,1); // odd + } else if(permcount%2==1){ + permfactor3[tensor2listindex(tempvec)]=1.*COM(0,1); // even + } else { + permfactor3[tensor2listindex(tempvec)]=-1.*COM(0,1); // odd + } + permcount++; } - } - } + void SpinorO(CLHEP::HepLorentzVector p, bool hel, COM *sp){ + // sp is pointer to COM sp[2] + COM pplus = p.e() +p.z(); + COM pminus = p.e() -p.z(); + COM sqpp= sqrt(pplus); + COM sqpm= sqrt(pminus); + COM pperp = p.x() + COM(0,1)*p.y(); -void SpinorO(CLHEP::HepLorentzVector p, bool hel, COM *sp){ - //sp is pointer to COM sp[2] - COM pplus = p.e() +p.z(); - COM pminus = p.e() -p.z(); - COM sqpp= sqrt(pplus); - COM sqpm= sqrt(pminus); - COM pperp = p.x() + COM(0,1)*p.y(); - - //if hel=+ - if(hel){ - sp[0] = sqpp; - sp[1] = sqpm*pperp/abs(pperp); - }else{ - sp[0] = sqpm*conj(pperp)/abs(pperp); - sp[1] = -sqpp; + // if hel=+ + if(hel){ + sp[0] = sqpp; + sp[1] = sqpm*pperp/abs(pperp); + } else { + sp[0] = sqpm*conj(pperp)/abs(pperp); + sp[1] = -sqpp; + } } -} -void SpinorIp(COM sqpp, bool hel, COM *sp){ - //if hel=+ - if(hel){ - sp[0] = sqpp; - sp[1] = 0.; - }else{ - sp[0] = 0.; - sp[1] = -sqpp; + void SpinorIp(COM sqpp, bool hel, COM *sp){ + // if hel=+ + if(hel){ + sp[0] = sqpp; + sp[1] = 0.; + } else { + sp[0] = 0.; + sp[1] = -sqpp; + } } -} -void SpinorIm(COM sqpm, bool hel, COM *sp){ - //if hel=+ - if(hel){ - sp[0] = 0; - sp[1] = -sqpm; - }else{ - sp[0] = -sqpm; - sp[1] = 0.; + void SpinorIm(COM sqpm, bool hel, COM *sp){ + // if hel=+ + if(hel){ + sp[0] = 0; + sp[1] = -sqpm; + } else { + sp[0] = -sqpm; + sp[1] = 0.; + } } -} - -void Spinor(CLHEP::HepLorentzVector p, bool hel, COM *sp){ - COM pplus = p.e() +p.z(); - COM pminus = p.e() -p.z(); + void Spinor(CLHEP::HepLorentzVector p, bool hel, COM *sp){ + COM pplus = p.e() +p.z(); + COM pminus = p.e() -p.z(); - //If incoming along +ve z - if (pminus==0.){ - COM sqpp= sqrt(pplus); - SpinorIp(sqpp,hel,sp); - } - //If incoming along -ve z - else if(pplus==0.){ - COM sqpm= sqrt(pminus); - SpinorIm(sqpm,hel,sp); - } - //Outgoing - else{ - SpinorO(p,hel,sp); + // If incoming along +ve z + if (pminus==0.){ + COM sqpp= sqrt(pplus); + SpinorIp(sqpp,hel,sp); + } + // If incoming along -ve z + else if(pplus==0.){ + COM sqpm= sqrt(pminus); + SpinorIm(sqpm,hel,sp); + } + // Outgoing + else { + SpinorO(p,hel,sp); + } } -} } // anonymous namespace -Tensor<2,4> Metric(void){ +Tensor<2,4> Metric(){ Tensor<2,4> g(0.); g.Set(0,0, 1.); g.Set(1,1, -1.); g.Set(2,2, -1.); g.Set(3,3, -1.); return g; } -//<1|mu|2> -Tensor<1,4> TCurrent(CLHEP::HepLorentzVector p1, bool h1,CLHEP::HepLorentzVector p2, bool h2){ +// <1|mu|2> +Tensor<1,4> TCurrent(CLHEP::HepLorentzVector p1, bool h1, + CLHEP::HepLorentzVector p2, bool h2 +){ COM sp1[2]; COM sp2[2]; Tensor<1,4> newT(0.); - CLHEP::HepLorentzVector p1new=p1; - CLHEP::HepLorentzVector p2new=p2; - - if(p1.e()<0){ - p1new=-p1; - } - - if(p2.e()<0){ - p2new=-p2; - } + CLHEP::HepLorentzVector p1new{ p1.e()<0?-p1:p1 }; + CLHEP::HepLorentzVector p2new{ p2.e()<0?-p2:p2 }; if(h1!=h2){ return newT; } Spinor(p1new, h1, sp1); Spinor(p2new, h2, sp2); for(int i=0;i<2;i++){ for(int j=0; j<2; j++){ newT+=(Sigma(i,j,h2)*sp2[j])*conj(sp1[i]); } } return newT; } -//<1|mu nu sigma|2> -Tensor<3,4> T3Current(CLHEP::HepLorentzVector p1, bool h1,CLHEP::HepLorentzVector p2, bool h2){ +// <1|mu nu sigma|2> +Tensor<3,4> T3Current(CLHEP::HepLorentzVector p1, bool h1, + CLHEP::HepLorentzVector p2, bool h2 +){ COM sp1[2]; COM sp2[2]; Tensor<3,4> newT(0.); - CLHEP::HepLorentzVector p1new=p1; - CLHEP::HepLorentzVector p2new=p2; - - if(p1.e()<0){ - p1new=-p1; - } - - if(p2.e()<0){ - p2new=-p2; - } + CLHEP::HepLorentzVector p1new{ p1.e()<0?-p1:p1 }; + CLHEP::HepLorentzVector p2new{ p2.e()<0?-p2:p2 }; if(h1!=h2){ return newT; } Spinor(p1new, h1, sp1); Spinor(p2new, h2, sp2); - COM current[4]; for(int i=0; i<2;i++){ for(int j=0; j<2;j++){ current[0]+=conj(sp1[i])*sigma0[i][j]*sp2[j]; current[1]+=conj(sp1[i])*sigma1[i][j]*sp2[j]; current[2]+=conj(sp1[i])*sigma2[i][j]*sp2[j]; current[3]+=conj(sp1[i])*sigma3[i][j]*sp2[j]; } } - for(int itensor=0;itensor<newT.len();itensor++){ - double hfact = double(helfactor3[h2][itensor]); - newT.components[itensor]=current[sigma_index3[itensor]]*hfact*permfactor3[itensor]; + for( int itensor=0; itensor<newT.len(); itensor++ ){ + double hfact = double( helfactor3[h2][itensor] ); + newT.components[itensor] = current[sigma_index3[itensor]] * hfact + * permfactor3[itensor]; } return newT; } -//<1|mu nu sigma tau rho|2> -Tensor<5,4> T5Current(CLHEP::HepLorentzVector p1, bool h1,CLHEP::HepLorentzVector p2, bool h2){ +// <1|mu nu sigma tau rho|2> +Tensor<5,4> T5Current(CLHEP::HepLorentzVector p1, bool h1, + CLHEP::HepLorentzVector p2, bool h2 +){ COM sp1[2]; COM sp2[2]; Tensor<5,4> newT(0.); - CLHEP::HepLorentzVector p1new=p1; - CLHEP::HepLorentzVector p2new=p2; - - if(p1.e()<0){ - p1new=-p1; - } - - if(p2.e()<0){ - p2new=-p2; - } + CLHEP::HepLorentzVector p1new{ p1.e()<0?-p1:p1 }; + CLHEP::HepLorentzVector p2new{ p2.e()<0?-p2:p2 }; if(h1!=h2){ return newT; } Spinor(p1new, h1, sp1); Spinor(p2new, h2, sp2); - COM current[4]; for(int i=0; i<2;i++){ for(int j=0; j<2;j++){ current[0]+=conj(sp1[i])*sigma0[i][j]*sp2[j]; current[1]+=conj(sp1[i])*sigma1[i][j]*sp2[j]; current[2]+=conj(sp1[i])*sigma2[i][j]*sp2[j]; current[3]+=conj(sp1[i])*sigma3[i][j]*sp2[j]; } } for(int itensor=0;itensor<newT.len();itensor++){ double hfact = double(helfactor5[h2][itensor]); - newT.components[itensor]=current[sigma_index5[itensor]]*hfact*permfactor5[itensor]; + newT.components[itensor] = current[sigma_index5[itensor]] * hfact + * permfactor5[itensor]; } return newT; } Tensor<1,4> Construct1Tensor(CCurrent j){ Tensor<1,4> newT; newT.components={j.c0,j.c1,j.c2,j.c3}; return newT; } Tensor<1,4> Construct1Tensor(CLHEP::HepLorentzVector p){ Tensor<1,4> newT; newT.components={p.e(),p.x(),p.y(),p.z()}; return newT; } Tensor<1,4> eps(CLHEP::HepLorentzVector k, CLHEP::HepLorentzVector ref, bool pol){ Tensor<1,4> polvec; COM spk[2]; COM spr[2]; COM denom; - CLHEP::HepLorentzVector knew=k; - - if(k.e()<0){ - knew=-k; - } + CLHEP::HepLorentzVector knew{ k.e()<0?-k:k }; Spinor(knew, pol, spk); Spinor(ref, !pol, spr); denom = pow(-1.,pol)*sqrt(2)*(conj(spr[0])*spk[0] + conj(spr[1])*spk[1]); polvec = TCurrent(ref,!pol,knew,!pol)/denom; return polvec; } - //slow function! - but only need to evaluate once. - bool init_sigma_index(void) - { - - //initialize permfactor(3) to list of ones (change to minus one for each odd permutation - //and multiply by i for all permutations in perms2111, perms311, perms111) - permfactor5.resize(1024,1.); - permfactor3.resize(64,1.); - - //first set sigma_index (5) - std::vector<std::array<int,5>> sigma0indices; - std::vector<std::array<int,5>> sigma1indices; - std::vector<std::array<int,5>> sigma2indices; - std::vector<std::array<int,5>> sigma3indices; - - //need to generate all possible permuations of {i,j,k,l,m} - //where each index can be {0,1,2,3,4} - //1024 possibilities - - //perms with 5 same - sigma0indices.push_back({0,0,0,0,0}); - sigma1indices.push_back({1,1,1,1,1}); - sigma2indices.push_back({2,2,2,2,2}); - sigma3indices.push_back({3,3,3,3,3}); - - //perms with 4 same - perms41(1,0,&sigma0indices); - perms41(2,0,&sigma0indices); - perms41(3,0,&sigma0indices); - perms41(0,1,&sigma1indices); - perms41(2,1,&sigma1indices); - perms41(3,1,&sigma1indices); - perms41(0,2,&sigma2indices); - perms41(1,2,&sigma2indices); - perms41(3,2,&sigma2indices); - perms41(0,3,&sigma3indices); - perms41(1,3,&sigma3indices); - perms41(2,3,&sigma3indices); - - //perms with 3 same, 2 same - perms32(0,1,&sigma0indices); - perms32(0,2,&sigma0indices); - perms32(0,3,&sigma0indices); - perms32(1,0,&sigma1indices); - perms32(1,2,&sigma1indices); - perms32(1,3,&sigma1indices); - perms32(2,0,&sigma2indices); - perms32(2,1,&sigma2indices); - perms32(2,3,&sigma2indices); - perms32(3,0,&sigma3indices); - perms32(3,1,&sigma3indices); - perms32(3,2,&sigma3indices); - - //perms with 3 same, 2 different - perms311(1,2,3,&sigma0indices); - perms311(2,3,1,&sigma0indices); - perms311(3,1,2,&sigma0indices); - perms311(0,2,3,&sigma1indices); - perms311(2,3,0,&sigma1indices); - perms311(3,2,0,&sigma1indices); - perms311(0,3,1,&sigma2indices); - perms311(1,3,0,&sigma2indices); - perms311(3,1,0,&sigma2indices); - perms311(0,1,2,&sigma3indices); - perms311(1,2,0,&sigma3indices); - perms311(2,1,0,&sigma3indices); - - perms221(1,2,0,&sigma0indices); - perms221(1,3,0,&sigma0indices); - perms221(2,3,0,&sigma0indices); - perms221(0,2,1,&sigma1indices); - perms221(0,3,1,&sigma1indices); - perms221(2,3,1,&sigma1indices); - perms221(0,1,2,&sigma2indices); - perms221(0,3,2,&sigma2indices); - perms221(1,3,2,&sigma2indices); - perms221(0,1,3,&sigma3indices); - perms221(0,2,3,&sigma3indices); - perms221(1,2,3,&sigma3indices); - - perms2111(0,1,2,3,&sigma0indices); - perms2111(1,0,2,3,&sigma1indices); - perms2111(2,0,3,1,&sigma2indices); - perms2111(3,0,1,2,&sigma3indices); - - if(sigma0indices.size()!=256){ - std::cerr<<"sigma_index not set: "; - std::cerr<<"sigma0indices has "<< sigma0indices.size() << " components" << std::endl; - return false; - } - else if(sigma1indices.size()!=256){ - std::cerr<<"sigma_index not set: "; - std::cerr<<"sigma1indices has "<< sigma0indices.size() << " components" << std::endl; - return false; - } - else if(sigma2indices.size()!=256){ - std::cerr<<"sigma_index not set: "; - std::cerr<<"sigma2indices has "<< sigma0indices.size() << " components" << std::endl; - return false; - } - else if(sigma3indices.size()!=256){ - std::cerr<<"sigma_index not set: "; - std::cerr<<"sigma3indices has "<< sigma0indices.size() << " components" << std::endl; - return false; - } - - for(int i=0;i<256;i++){ - //map each unique set of tensor indices to its position in a list - int index0 = tensor2listindex(sigma0indices.at(i)); - int index1 = tensor2listindex(sigma1indices.at(i)); - int index2 = tensor2listindex(sigma2indices.at(i)); - int index3 = tensor2listindex(sigma3indices.at(i)); - sigma_index5[index0]=0; - sigma_index5[index1]=1; - sigma_index5[index2]=2; - sigma_index5[index3]=3; - - short int sign[4]={1,-1,-1,-1}; - //plus->true->1 - helfactor5[1][index0]=sign[sigma0indices.at(i)[1]]*sign[sigma0indices.at(i)[3]]; - helfactor5[1][index1]=sign[sigma1indices.at(i)[1]]*sign[sigma1indices.at(i)[3]]; - helfactor5[1][index2]=sign[sigma2indices.at(i)[1]]*sign[sigma2indices.at(i)[3]]; - helfactor5[1][index3]=sign[sigma3indices.at(i)[1]]*sign[sigma3indices.at(i)[3]]; - //minus->false->0 - helfactor5[0][index0]=sign[sigma0indices.at(i)[0]]*sign[sigma0indices.at(i)[2]]*sign[sigma0indices.at(i)[4]]; - helfactor5[0][index1]=sign[sigma1indices.at(i)[0]]*sign[sigma1indices.at(i)[2]]*sign[sigma1indices.at(i)[4]]; - helfactor5[0][index2]=sign[sigma2indices.at(i)[0]]*sign[sigma2indices.at(i)[2]]*sign[sigma2indices.at(i)[4]]; - helfactor5[0][index3]=sign[sigma3indices.at(i)[0]]*sign[sigma3indices.at(i)[2]]*sign[sigma3indices.at(i)[4]]; - - } - - //now set sigma_index3 - std::vector<std::array<int,3>> sigma0indices3; - std::vector<std::array<int,3>> sigma1indices3; - std::vector<std::array<int,3>> sigma2indices3; - std::vector<std::array<int,3>> sigma3indices3; - - //perms with 3 same - sigma0indices3.push_back({0,0,0}); - sigma1indices3.push_back({1,1,1}); - sigma2indices3.push_back({2,2,2}); - sigma3indices3.push_back({3,3,3}); - - //2 same - perms21(1,0,&sigma0indices3); - perms21(2,0,&sigma0indices3); - perms21(3,0,&sigma0indices3); - perms21(0,1,&sigma1indices3); - perms21(2,1,&sigma1indices3); - perms21(3,1,&sigma1indices3); - perms21(0,2,&sigma2indices3); - perms21(1,2,&sigma2indices3); - perms21(3,2,&sigma2indices3); - perms21(0,3,&sigma3indices3); - perms21(1,3,&sigma3indices3); - perms21(2,3,&sigma3indices3); - - //none same - perms111(1,2,3,&sigma0indices3); - perms111(0,2,3,&sigma1indices3); - perms111(0,3,1,&sigma2indices3); - perms111(0,1,2,&sigma3indices3); - - if(sigma0indices3.size()!=16){ - std::cerr<<"sigma_index3 not set: "; - std::cerr<<"sigma0indices3 has "<< sigma0indices3.size() << " components" << std::endl; - return false; - } - else if(sigma1indices3.size()!=16){ - std::cerr<<"sigma_index3 not set: "; - std::cerr<<"sigma1indices3 has "<< sigma0indices3.size() << " components" << std::endl; - return false; - } - else if(sigma2indices3.size()!=16){ - std::cerr<<"sigma_index3 not set: "; - std::cerr<<"sigma2indices3 has "<< sigma0indices3.size() << " components" << std::endl; - return false; - } - else if(sigma3indices3.size()!=16){ - std::cerr<<"sigma_index3 not set: "; - std::cerr<<"sigma3indices3 has "<< sigma0indices3.size() << " components" << std::endl; - return false; - } - - - for(int i=0;i<16;i++){ - int index0 = tensor2listindex(sigma0indices3.at(i)); - int index1 = tensor2listindex(sigma1indices3.at(i)); - int index2 = tensor2listindex(sigma2indices3.at(i)); - int index3 = tensor2listindex(sigma3indices3.at(i)); - sigma_index3[index0]=0; - sigma_index3[index1]=1; - sigma_index3[index2]=2; - sigma_index3[index3]=3; - - short int sign[4]={1,-1,-1,-1}; - //plus->true->1 - helfactor3[1][index0]=sign[sigma0indices3.at(i)[1]]; - helfactor3[1][index1]=sign[sigma1indices3.at(i)[1]]; - helfactor3[1][index2]=sign[sigma2indices3.at(i)[1]]; - helfactor3[1][index3]=sign[sigma3indices3.at(i)[1]]; - //minus->false->0 - helfactor3[0][index0]=sign[sigma0indices3.at(i)[0]]*sign[sigma0indices3.at(i)[2]]; - helfactor3[0][index1]=sign[sigma1indices3.at(i)[0]]*sign[sigma1indices3.at(i)[2]]; - helfactor3[0][index2]=sign[sigma2indices3.at(i)[0]]*sign[sigma2indices3.at(i)[2]]; - helfactor3[0][index3]=sign[sigma3indices3.at(i)[0]]*sign[sigma3indices3.at(i)[2]]; - - } - - //std::cout<<"sigma_index_set!!"<<std::endl; - return true; - } +// slow function! - but only need to evaluate once. +bool init_sigma_index(){ + + // initialize permfactor(3) to list of ones (change to minus one for each odd + // permutation and multiply by i for all permutations in perms2111, perms311, + // perms111) + permfactor5.resize(1024,1.); + permfactor3.resize(64,1.); + + // first set sigma_index (5) + std::vector<std::array<int,5>> sigma0indices; + std::vector<std::array<int,5>> sigma1indices; + std::vector<std::array<int,5>> sigma2indices; + std::vector<std::array<int,5>> sigma3indices; + + // need to generate all possible permuations of {i,j,k,l,m} + // where each index can be {0,1,2,3,4} + // 1024 possibilities + + // perms with 5 same + sigma0indices.push_back({0,0,0,0,0}); + sigma1indices.push_back({1,1,1,1,1}); + sigma2indices.push_back({2,2,2,2,2}); + sigma3indices.push_back({3,3,3,3,3}); + + // perms with 4 same + perms41(1,0,sigma0indices); + perms41(2,0,sigma0indices); + perms41(3,0,sigma0indices); + perms41(0,1,sigma1indices); + perms41(2,1,sigma1indices); + perms41(3,1,sigma1indices); + perms41(0,2,sigma2indices); + perms41(1,2,sigma2indices); + perms41(3,2,sigma2indices); + perms41(0,3,sigma3indices); + perms41(1,3,sigma3indices); + perms41(2,3,sigma3indices); + + // perms with 3 same, 2 same + perms32(0,1,sigma0indices); + perms32(0,2,sigma0indices); + perms32(0,3,sigma0indices); + perms32(1,0,sigma1indices); + perms32(1,2,sigma1indices); + perms32(1,3,sigma1indices); + perms32(2,0,sigma2indices); + perms32(2,1,sigma2indices); + perms32(2,3,sigma2indices); + perms32(3,0,sigma3indices); + perms32(3,1,sigma3indices); + perms32(3,2,sigma3indices); + + // perms with 3 same, 2 different + perms311(1,2,3,sigma0indices); + perms311(2,3,1,sigma0indices); + perms311(3,1,2,sigma0indices); + perms311(0,2,3,sigma1indices); + perms311(2,3,0,sigma1indices); + perms311(3,2,0,sigma1indices); + perms311(0,3,1,sigma2indices); + perms311(1,3,0,sigma2indices); + perms311(3,1,0,sigma2indices); + perms311(0,1,2,sigma3indices); + perms311(1,2,0,sigma3indices); + perms311(2,1,0,sigma3indices); + + perms221(1,2,0,sigma0indices); + perms221(1,3,0,sigma0indices); + perms221(2,3,0,sigma0indices); + perms221(0,2,1,sigma1indices); + perms221(0,3,1,sigma1indices); + perms221(2,3,1,sigma1indices); + perms221(0,1,2,sigma2indices); + perms221(0,3,2,sigma2indices); + perms221(1,3,2,sigma2indices); + perms221(0,1,3,sigma3indices); + perms221(0,2,3,sigma3indices); + perms221(1,2,3,sigma3indices); + + perms2111(0,1,2,3,sigma0indices); + perms2111(1,0,2,3,sigma1indices); + perms2111(2,0,3,1,sigma2indices); + perms2111(3,0,1,2,sigma3indices); + + if(sigma0indices.size()!=256){ + std::cerr<<"sigma_index not set: "; + std::cerr<<"sigma0indices has "<< sigma0indices.size() << " components" << std::endl; + return false; + } else if(sigma1indices.size()!=256){ + std::cerr<<"sigma_index not set: "; + std::cerr<<"sigma1indices has "<< sigma0indices.size() << " components" << std::endl; + return false; + } else if(sigma2indices.size()!=256){ + std::cerr<<"sigma_index not set: "; + std::cerr<<"sigma2indices has "<< sigma0indices.size() << " components" << std::endl; + return false; + } else if(sigma3indices.size()!=256){ + std::cerr<<"sigma_index not set: "; + std::cerr<<"sigma3indices has "<< sigma0indices.size() << " components" << std::endl; + return false; + } + + for(int i=0;i<256;i++){ + // map each unique set of tensor indices to its position in a list + int index0 = tensor2listindex(sigma0indices.at(i)); + int index1 = tensor2listindex(sigma1indices.at(i)); + int index2 = tensor2listindex(sigma2indices.at(i)); + int index3 = tensor2listindex(sigma3indices.at(i)); + sigma_index5[index0]=0; + sigma_index5[index1]=1; + sigma_index5[index2]=2; + sigma_index5[index3]=3; + + short int sign[4]={1,-1,-1,-1}; + // plus->true->1 + helfactor5[1][index0] = sign[sigma0indices.at(i)[1]] + * sign[sigma0indices.at(i)[3]]; + helfactor5[1][index1] = sign[sigma1indices.at(i)[1]] + * sign[sigma1indices.at(i)[3]]; + helfactor5[1][index2] = sign[sigma2indices.at(i)[1]] + * sign[sigma2indices.at(i)[3]]; + helfactor5[1][index3] = sign[sigma3indices.at(i)[1]] + * sign[sigma3indices.at(i)[3]]; + // minus->false->0 + helfactor5[0][index0] = sign[sigma0indices.at(i)[0]] + * sign[sigma0indices.at(i)[2]] + * sign[sigma0indices.at(i)[4]]; + helfactor5[0][index1] = sign[sigma1indices.at(i)[0]] + * sign[sigma1indices.at(i)[2]] + * sign[sigma1indices.at(i)[4]]; + helfactor5[0][index2] = sign[sigma2indices.at(i)[0]] + * sign[sigma2indices.at(i)[2]] + * sign[sigma2indices.at(i)[4]]; + helfactor5[0][index3] = sign[sigma3indices.at(i)[0]] + * sign[sigma3indices.at(i)[2]] + * sign[sigma3indices.at(i)[4]]; + } + + // now set sigma_index3 + std::vector<std::array<int,3>> sigma0indices3; + std::vector<std::array<int,3>> sigma1indices3; + std::vector<std::array<int,3>> sigma2indices3; + std::vector<std::array<int,3>> sigma3indices3; + + // perms with 3 same + sigma0indices3.push_back({0,0,0}); + sigma1indices3.push_back({1,1,1}); + sigma2indices3.push_back({2,2,2}); + sigma3indices3.push_back({3,3,3}); + + // 2 same + perms21(1,0,sigma0indices3); + perms21(2,0,sigma0indices3); + perms21(3,0,sigma0indices3); + perms21(0,1,sigma1indices3); + perms21(2,1,sigma1indices3); + perms21(3,1,sigma1indices3); + perms21(0,2,sigma2indices3); + perms21(1,2,sigma2indices3); + perms21(3,2,sigma2indices3); + perms21(0,3,sigma3indices3); + perms21(1,3,sigma3indices3); + perms21(2,3,sigma3indices3); + + // none same + perms111(1,2,3,sigma0indices3); + perms111(0,2,3,sigma1indices3); + perms111(0,3,1,sigma2indices3); + perms111(0,1,2,sigma3indices3); + + if(sigma0indices3.size()!=16){ + std::cerr<<"sigma_index3 not set: "; + std::cerr<<"sigma0indices3 has "<< sigma0indices3.size() << " components" << std::endl; + return false; + } else if(sigma1indices3.size()!=16){ + std::cerr<<"sigma_index3 not set: "; + std::cerr<<"sigma1indices3 has "<< sigma0indices3.size() << " components" << std::endl; + return false; + } else if(sigma2indices3.size()!=16){ + std::cerr<<"sigma_index3 not set: "; + std::cerr<<"sigma2indices3 has "<< sigma0indices3.size() << " components" << std::endl; + return false; + } else if(sigma3indices3.size()!=16){ + std::cerr<<"sigma_index3 not set: "; + std::cerr<<"sigma3indices3 has "<< sigma0indices3.size() << " components" << std::endl; + return false; + } + + for(int i=0;i<16;i++){ + int index0 = tensor2listindex(sigma0indices3.at(i)); + int index1 = tensor2listindex(sigma1indices3.at(i)); + int index2 = tensor2listindex(sigma2indices3.at(i)); + int index3 = tensor2listindex(sigma3indices3.at(i)); + sigma_index3[index0]=0; + sigma_index3[index1]=1; + sigma_index3[index2]=2; + sigma_index3[index3]=3; + + short int sign[4]={1,-1,-1,-1}; + // plus->true->1 + helfactor3[1][index0] = sign[sigma0indices3.at(i)[1]]; + helfactor3[1][index1] = sign[sigma1indices3.at(i)[1]]; + helfactor3[1][index2] = sign[sigma2indices3.at(i)[1]]; + helfactor3[1][index3] = sign[sigma3indices3.at(i)[1]]; + // minus->false->0 + helfactor3[0][index0] = sign[sigma0indices3.at(i)[0]] + * sign[sigma0indices3.at(i)[2]]; + helfactor3[0][index1] = sign[sigma1indices3.at(i)[0]] + * sign[sigma1indices3.at(i)[2]]; + helfactor3[0][index2] = sign[sigma2indices3.at(i)[0]] + * sign[sigma2indices3.at(i)[2]]; + helfactor3[0][index3] = sign[sigma3indices3.at(i)[0]] + * sign[sigma3indices3.at(i)[2]]; + } + return true; +} // end init_sigma_index diff --git a/src/Weights.cc b/src/Weights.cc deleted file mode 100644 index de2d561..0000000 --- a/src/Weights.cc +++ /dev/null @@ -1,46 +0,0 @@ -/** - * \authors Jeppe Andersen, Tuomas Hapola, Marian Heil, Andreas Maier, Jennifer Smillie - * \date 2019 - * \copyright GPLv2 or later - */ -#include "HEJ/Weights.hh" - -#include <stdexcept> - -namespace HEJ { - - Weights& Weights::operator*=(Weights const & other) { - if(other.variations.size() != variations.size()) { - throw std::invalid_argument{"Wrong number of weights"}; - } - central *= other.central; - for(std::size_t i = 0; i < variations.size(); ++i) { - variations[i] *= other.variations[i]; - } - return *this; - } - - Weights& Weights::operator*=(double factor) { - central *= factor; - for(auto & wt: variations) wt *= factor; - return *this; - } - - Weights& Weights::operator/=(Weights const & other) { - if(other.variations.size() != variations.size()) { - throw std::invalid_argument{"Wrong number of weights"}; - } - central /= other.central; - for(std::size_t i = 0; i < variations.size(); ++i) { - variations[i] /= other.variations[i]; - } - return *this; - } - - Weights& Weights::operator/=(double factor) { - central /= factor; - for(auto & wt: variations) wt /= factor; - return *this; - } - -} diff --git a/src/bin/HEJ.cc b/src/bin/HEJ.cc index 6793c30..77d6f1e 100644 --- a/src/bin/HEJ.cc +++ b/src/bin/HEJ.cc @@ -1,188 +1,189 @@ /** * \authors Jeppe Andersen, Tuomas Hapola, Marian Heil, Andreas Maier, Jennifer Smillie * \date 2019 * \copyright GPLv2 or later */ #include <array> #include <chrono> #include <iostream> #include <limits> #include <memory> #include <numeric> #include "yaml-cpp/yaml.h" #include "LHEF/LHEF.h" #include "fastjet/ClusterSequence.hh" #include "HEJ/CombinedEventWriter.hh" #include "HEJ/config.hh" #include "HEJ/CrossSectionAccumulator.hh" #include "HEJ/Event.hh" #include "HEJ/EventReweighter.hh" #include "HEJ/get_analysis.hh" #include "HEJ/make_RNG.hh" #include "HEJ/ProgressBar.hh" #include "HEJ/stream.hh" #include "HEJ/Version.hh" #include "HEJ/YAMLreader.hh" int event_number(std::string const & record){ size_t start = record.rfind("Number of Events"); start = record.find_first_of("123456789", start); if(start == std::string::npos) { throw std::invalid_argument("no event number record found"); } const size_t end = record.find_first_not_of("0123456789", start); return std::stoi(record.substr(start, end - start)); } HEJ::Config load_config(char const * filename){ try{ return HEJ::load_config(filename); } catch(std::exception const & exc){ std::cerr << "Error: " << exc.what() << '\n'; std::exit(EXIT_FAILURE); } } std::unique_ptr<HEJ::Analysis> get_analysis( YAML::Node const & parameters ){ try{ return HEJ::get_analysis(parameters); } catch(std::exception const & exc){ std::cerr << "Failed to load analysis: " << exc.what() << '\n'; std::exit(EXIT_FAILURE); } } // unique_ptr is a workaround: // HEJ::optional is a better fit, but gives spurious errors with g++ 7.3.0 std::unique_ptr<HEJ::ProgressBar<double>> make_progress_bar( std::vector<double> const & xs ) { if(xs.empty()) return {}; const double Born_xs = std::accumulate(begin(xs), end(xs), 0.); return std::make_unique<HEJ::ProgressBar<double>>(std::cout, Born_xs); } std::string time_to_string(const time_t time){ char s[30]; struct tm * p = localtime(&time); strftime(s, 30, "%a %b %d %Y %H:%M:%S", p); return s; } int main(int argn, char** argv) { using clock = std::chrono::system_clock; if (argn < 3) { std::cerr << "\n# Usage:\n."<< argv[0] <<" config_file input_file\n\n"; return EXIT_FAILURE; } const auto start_time = clock::now(); { std::cout << "Starting " << HEJ::Version::package_name_full() << ", revision " << HEJ::Version::revision() << " (" << time_to_string(clock::to_time_t(start_time)) << ")" << std::endl; } fastjet::ClusterSequence::print_banner(); // read configuration const HEJ::Config config = load_config(argv[1]); HEJ::istream in{argv[2]}; std::unique_ptr<HEJ::Analysis> analysis = get_analysis( config.analysis_parameters ); assert(analysis != nullptr); LHEF::Reader reader{in}; auto heprup = reader.heprup; heprup.generators.emplace_back(LHEF::XMLTag{}); heprup.generators.back().name = HEJ::Version::package_name(); heprup.generators.back().version = HEJ::Version::String(); HEJ::CombinedEventWriter writer{config.output, std::move(heprup)}; double global_reweight = 1.; int max_events = std::numeric_limits<int>::max(); if(argn > 3){ max_events = std::stoi(argv[3]); const int input_events = event_number(reader.headerBlock); global_reweight = input_events/static_cast<double>(max_events); std::cout << "Processing " << max_events << " out of " << input_events << " events\n"; } HEJ::ScaleGenerator scale_gen{ config.scales.base, config.scales.factors, config.scales.max_ratio }; auto ran = HEJ::make_RNG(config.rng.name, config.rng.seed); assert(ran != nullptr); HEJ::EventReweighter hej{ reader.heprup, std::move(scale_gen), to_EventReweighterConfig(config), *ran }; int nevent = 0; std::array<int, HEJ::event_type::last_type + 1> nevent_type{0}, nfailed_type{0}; auto progress = make_progress_bar(reader.heprup.XSECUP); HEJ::CrossSectionAccumulator xs; // Loop over the events in the inputfile while(reader.readEvent()){ // reweight events so that the total cross section is conserved reader.hepeup.setWeight(0, global_reweight * reader.hepeup.weight()); if(nevent == max_events) break; ++nevent; // calculate HEJ weight HEJ::Event FO_event{ - HEJ::UnclusteredEvent{reader.hepeup}, - config.fixed_order_jets.def, config.fixed_order_jets.min_pt, + HEJ::Event::EventData{reader.hepeup}( + config.fixed_order_jets.def, config.fixed_order_jets.min_pt + ) }; auto resummed_events = hej.reweight(FO_event, config.trials); ++nevent_type[FO_event.type()]; if(resummed_events.empty()) ++nfailed_type[FO_event.type()]; for(auto const & ev: resummed_events){ //TODO: move pass_cuts to after phase space point generation if(analysis->pass_cuts(ev, FO_event)){ analysis->fill(ev, FO_event); writer.write(ev); xs.fill(ev); } } if(progress) progress->increment(FO_event.central().weight); } // main event loop std::cout << '\n'; analysis->finalise(); using namespace HEJ::event_type; std::cout<< "Events processed: " << nevent << '\n'; for(size_t ev_type = first_type; ev_type <= last_type; ++ev_type){ std::cout << '\t' << names[ev_type] << ": " << nevent_type[ev_type] << ", failed to reconstruct " << nfailed_type[ev_type] << '\n'; } std::cout << '\n' << xs << '\n'; std::chrono::duration<double> run_time = (clock::now() - start_time); std::cout << "Finished " << HEJ::Version::package_name() << " at " << time_to_string(clock::to_time_t(clock::now())) << "\n=> Runtime: " << run_time.count() << " sec (" << nevent/run_time.count() << " Events/sec).\n"; } diff --git a/t/check_res.cc b/t/check_res.cc index 294a8d1..88fdd47 100644 --- a/t/check_res.cc +++ b/t/check_res.cc @@ -1,101 +1,134 @@ #include <iostream> #include "LHEF/LHEF.h" #include "HEJ/Event.hh" #include "HEJ/EventReweighter.hh" #include "HEJ/Mixmax.hh" #include "HEJ/stream.hh" +#define ASSERT(x) if(!(x)) { \ + std::cerr << "Assertion '" #x "' failed.\n"; \ + return EXIT_FAILURE; \ + } + namespace{ const fastjet::JetDefinition jet_def{fastjet::kt_algorithm, 0.4}; const fastjet::JetDefinition Born_jet_def{jet_def}; constexpr double Born_jetptmin = 30; constexpr double extpartonptmin = 30; constexpr double max_ext_soft_pt_fraction = std::numeric_limits<double>::infinity(); constexpr double jetptmin = 35; constexpr bool log_corr = false; using EventTreatment = HEJ::EventTreatment; using namespace HEJ::event_type; HEJ::EventTreatMap treat{ {no_2_jets, EventTreatment::discard}, {bad_final_state, EventTreatment::discard}, {nonHEJ, EventTreatment::discard}, {unof, EventTreatment::discard}, {unob, EventTreatment::discard}, {qqxexb, EventTreatment::discard}, {qqxexf, EventTreatment::discard}, {qqxmid, EventTreatment::discard}, {FKL, EventTreatment::reweight} }; + + /// true if colour is allowed for particle + bool correct_colour(HEJ::Particle const & part){ + if(HEJ::is_AWZH_boson(part) && !part.colour) return true; + if(!part.colour) return false; + int const colour = part.colour->first; + int const anti_colour = part.colour->second; + if(part.type == HEJ::ParticleID::gluon) + return colour != anti_colour && colour > 0 && anti_colour > 0; + if(HEJ::is_quark(part)) + return anti_colour == 0 && colour > 0; + return colour == 0 && anti_colour > 0; + } + bool correct_colour(HEJ::Event const & ev){ + if(!HEJ::event_type::is_HEJ(ev.type())) + return true; + for(auto const & part: ev.incoming()){ + if(!correct_colour(part)) + return false; + } + for(auto const & part: ev.outgoing()){ + if(!correct_colour(part)) + return false; + } + return true; + } }; int main(int argn, char** argv) { if(argn == 5 && std::string(argv[4]) == "uno"){ --argn; treat[unof] = EventTreatment::reweight; treat[unob] = EventTreatment::reweight; treat[FKL] = EventTreatment::discard; } if(argn != 4){ std::cerr << "Usage: check_res eventfile xsection tolerance [uno]"; return EXIT_FAILURE; } const double xsec_ref = std::stod(argv[2]); const double tolerance = std::stod(argv[3]); HEJ::istream in{argv[1]}; LHEF::Reader reader{in}; HEJ::PhaseSpacePointConfig psp_conf; psp_conf.jet_param = HEJ::JetParameters{jet_def, jetptmin}; psp_conf.min_extparton_pt = extpartonptmin; psp_conf.max_ext_soft_pt_fraction = max_ext_soft_pt_fraction; HEJ::MatrixElementConfig ME_conf; ME_conf.log_correction = log_corr; ME_conf.Higgs_coupling = HEJ::HiggsCouplingSettings{}; HEJ::EventReweighterConfig conf; conf.psp_config = std::move(psp_conf); conf.ME_config = std::move(ME_conf); conf.jet_param = psp_conf.jet_param; conf.treat = treat; reader.readEvent(); const bool has_Higgs = std::find( begin(reader.hepeup.IDUP), end(reader.hepeup.IDUP), 25 ) != end(reader.hepeup.IDUP); const double mu = has_Higgs?125.:91.188; HEJ::ScaleGenerator scale_gen{ {{std::to_string(mu), HEJ::FixedScale{mu}}}, {}, 1. }; HEJ::Mixmax ran{}; HEJ::EventReweighter hej{reader.heprup, std::move(scale_gen), conf, ran}; double xsec = 0.; double xsec_err = 0.; do{ HEJ::Event ev{ - HEJ::UnclusteredEvent{reader.hepeup}, - Born_jet_def, Born_jetptmin + HEJ::Event::EventData{reader.hepeup}.cluster( + Born_jet_def, Born_jetptmin + ) }; auto resummed_events = hej.reweight(ev, 20); for(auto const & ev: resummed_events) { + ASSERT(correct_colour(ev)); xsec += ev.central().weight; xsec_err += ev.central().weight*ev.central().weight; } } while(reader.readEvent()); xsec_err = std::sqrt(xsec_err); const double significance = std::abs(xsec - xsec_ref) / std::sqrt( xsec_err*xsec_err + tolerance*tolerance ); std::cout << xsec_ref << " +/- " << tolerance << " ~ " << xsec << " +- " << xsec_err << " => " << significance << " sigma\n"; if(significance > 3.){ std::cerr << "Cross section is off by over 3 sigma!\n"; return EXIT_FAILURE; } } diff --git a/t/test_ME_generic.cc b/t/test_ME_generic.cc index 8430431..7ad3f45 100644 --- a/t/test_ME_generic.cc +++ b/t/test_ME_generic.cc @@ -1,116 +1,132 @@ // Generic tester for the ME for a given set of PSP // reference weights and PSP (as LHE file) have to be given as _individual_ files #include <fstream> +#include <random> +#include <algorithm> #include "LHEF/LHEF.h" #include "HEJ/MatrixElement.hh" #include "HEJ/Event.hh" #include "HEJ/YAMLreader.hh" #include "HEJ/stream.hh" constexpr double alpha_s = 0.118; constexpr double ep = 1e-5; +void shuffle_particles(HEJ::Event::EventData & ev) { + static std::mt19937_64 ran{0}; + std::shuffle(begin(ev.incoming), end(ev.incoming), ran); + std::shuffle(begin(ev.outgoing), end(ev.outgoing), ran); +} + void dump(HEJ::Event const & ev){ { LHEF::Writer writer{std::cout}; std::cout << std::setprecision(6); writer.hepeup = to_HEPEUP(std::move(ev), nullptr); writer.writeEvent(); } std::cout << "Rapidity ordering:\n"; for(const auto & part: ev.outgoing()){ std::cout << std::setw(2) << part.type << ": "<< std::setw(7) << part.rapidity() << std::endl; } } enum MEComponent {tree, virt}; MEComponent guess_component(std::string const & data_file) { if(data_file.find("virt") != data_file.npos) return MEComponent::virt; return MEComponent::tree; } int main(int argn, char** argv){ if(argn != 4 && argn != 5){ std::cerr << "\n# Usage:\n."<< argv[0] <<" config.yml ME_weights input_file.lhe\n\n"; return EXIT_FAILURE; } bool OUTPUT_MODE = false; if(argn == 5 && std::string("OUTPUT")==std::string(argv[4])) OUTPUT_MODE = true; const HEJ::Config config = HEJ::load_config(argv[1]); std::fstream wgt_file; if ( OUTPUT_MODE ) { std::cout << "_______________________USING OUTPUT MODE!_______________________" << std::endl; wgt_file.open(argv[2], std::fstream::out); wgt_file.precision(10); } else { wgt_file.open(argv[2], std::fstream::in); } HEJ::istream in{argv[3]}; LHEF::Reader reader{in}; const auto component = guess_component(argv[2]); HEJ::MatrixElement ME{ [](double){ return alpha_s; }, HEJ::to_MatrixElementConfig(config) }; double max_ratio = 0.; size_t idx_max_ratio = 0; - HEJ::Event ev_max_ratio; + + HEJ::Event ev_max_ratio(HEJ::Event::EventData{}.cluster( + config.resummation_jets.def,0 + ) + ); double av_ratio = 0; size_t i = 0; while(reader.readEvent()){ ++i; + HEJ::Event::EventData data{reader.hepeup}; + shuffle_particles(data); + HEJ::Event event{ - HEJ::UnclusteredEvent{reader.hepeup}, - config.resummation_jets.def, - config.resummation_jets.min_pt + data.cluster( + config.resummation_jets.def, + config.resummation_jets.min_pt + ) }; const double our_ME = (component == MEComponent::tree)? ME.tree(event).central: ME.virtual_corrections(event).central ; if ( OUTPUT_MODE ) { wgt_file << our_ME << std::endl; } else { std::string line; if(!std::getline(wgt_file,line)) break; const double ref_ME = std::stod(line); const double diff = std::abs(our_ME/ref_ME-1.); av_ratio+=diff; if( diff > max_ratio ) { max_ratio = diff; idx_max_ratio = i; ev_max_ratio = event; } if( diff > ep ){ size_t precision(std::cout.precision()); std::cout.precision(16); std::cout<< "Large difference in PSP " << i << "\nis: "<<our_ME << " should: " << ref_ME << " => difference: " << diff << std::endl; std::cout.precision(precision); dump(event); return EXIT_FAILURE; } } } wgt_file.close(); if ( !OUTPUT_MODE ) { size_t precision(std::cout.precision()); std::cout.precision(16); std::cout << "Avg ratio after " << i << " PSP: " << av_ratio/i << std::endl; std::cout << "maximal ratio at " << idx_max_ratio << ": " << max_ratio << std::endl; std::cout.precision(precision); } return EXIT_SUCCESS; } diff --git a/t/test_classify.cc b/t/test_classify.cc index c7c93d8..13081e3 100644 --- a/t/test_classify.cc +++ b/t/test_classify.cc @@ -1,50 +1,62 @@ +#include <random> +#include <algorithm> + #include "LHEF/LHEF.h" #include "HEJ/stream.hh" #include "HEJ/event_types.hh" #include "HEJ/Event.hh" namespace{ constexpr double min_jet_pt = 30.; const fastjet::JetDefinition jet_def{fastjet::kt_algorithm, 0.4}; using namespace HEJ::event_type; static const std::vector<EventType> results{ unob,FKL,FKL,FKL,FKL,FKL,FKL,unob,FKL,unob,FKL,FKL,FKL,unof,FKL,unob,FKL, FKL,unob,unob,FKL,FKL,unob,FKL,FKL,FKL,FKL,FKL,FKL,FKL,FKL,FKL,FKL,FKL,unof, FKL,FKL,unof,FKL,FKL,FKL,FKL,FKL,unof,FKL,FKL,FKL,unof,FKL,FKL,unob,unof, FKL,unof,FKL,unob,FKL,FKL,unob,FKL,unob,unof,unob,unof,FKL,FKL,FKL,FKL,FKL, FKL,FKL,FKL,FKL,FKL,FKL,FKL,unob,FKL,FKL,FKL,FKL,FKL,FKL,FKL,FKL,unob,FKL, FKL,FKL,FKL,unof,FKL,unob,FKL,FKL,FKL,FKL,FKL,FKL,FKL,FKL,FKL,FKL,unob,FKL, FKL,FKL,FKL,FKL,unob,FKL,unob,unob,FKL,FKL,FKL,FKL,FKL,FKL,FKL,FKL,FKL,unof,unob,FKL }; + + void shuffle_particles(HEJ::Event::EventData & ev) { + static std::mt19937_64 ran{0}; + std::shuffle(begin(ev.incoming), end(ev.incoming), ran); + std::shuffle(begin(ev.outgoing), end(ev.outgoing), ran); + } } int main(int argn, char** argv) { if(argn != 2){ std::cerr << "Usage: test_classify eventfile"; return EXIT_FAILURE; } HEJ::istream in{argv[1]}; LHEF::Reader reader{in}; LHEF::Writer writer{std::cerr}; writer.heprup = reader.heprup; for(auto const & expected: results){ reader.readEvent(); + HEJ::Event::EventData data{reader.hepeup}; + shuffle_particles(data); const HEJ::Event ev{ - HEJ::UnclusteredEvent{reader.hepeup}, + data.cluster( jet_def, min_jet_pt + ) }; if(ev.type() != expected){ using HEJ::event_type::names; writer.hepeup = reader.hepeup; std::cerr << "wrong classification of event:\n"; writer.writeEvent(); std::cerr << "classified as " << names[ev.type()] << ", is " << names[expected] << '\n'; return EXIT_FAILURE; } } } diff --git a/t/test_colours.cc b/t/test_colours.cc new file mode 100644 index 0000000..d2c29e6 --- /dev/null +++ b/t/test_colours.cc @@ -0,0 +1,221 @@ +#include <random> +#include <stdexcept> +#include <utility> + +#include "HEJ/Event.hh" +#include "HEJ/RNG.hh" + +#define ASSERT(x) if(!(x)) { \ + throw std::logic_error("Assertion '" #x "' failed."); \ + } + +/// biased RNG to connect always to colour +class dum_rnd: public HEJ::DefaultRNG { +public: + dum_rnd() = default; + double flat() override { + return 0.; + }; +}; + +void shuffle_particles(HEJ::Event::EventData & ev) { + static std::mt19937_64 ran{0}; + std::shuffle(begin(ev.incoming), end(ev.incoming), ran); + std::shuffle(begin(ev.outgoing), end(ev.outgoing), ran); +} + +void dump_event(HEJ::Event const & ev){ + for(auto const & in: ev.incoming()){ + std::cerr << "in type=" << in.type + << ", colour={" << (*in.colour).first + << ", " << (*in.colour).second << "}\n"; + } + for(auto const & out: ev.outgoing()){ + std::cerr << "out type=" << out.type << ", colour={"; + if(out.colour) + std::cerr << (*out.colour).first << ", " << (*out.colour).second; + else + std::cerr << "non, non"; + std::cerr << "}\n"; + } +} + +/// true if colour is allowed for particle +bool correct_colour(HEJ::Particle const & part){ + if(HEJ::is_AWZH_boson(part) && !part.colour) return true; + if(!part.colour) return false; + int const colour = part.colour->first; + int const anti_colour = part.colour->second; + if(part.type == HEJ::ParticleID::gluon) + return colour != anti_colour && colour > 0 && anti_colour > 0; + if(HEJ::is_quark(part)) + return anti_colour == 0 && colour > 0; + return colour == 0 && anti_colour > 0; +} + +bool correct_colour(HEJ::Event const & ev){ + for(auto const & part: ev.incoming()){ + if(!correct_colour(part)) + return false; + } + for(auto const & part: ev.outgoing()){ + if(!correct_colour(part)) + return false; + } + return true; +} + +bool match_expected( + HEJ::Event const & ev, + std::vector<HEJ::Colour> const & expected +){ + ASSERT(ev.outgoing().size()+2==expected.size()); + for(size_t i=0; i<ev.incoming().size(); ++i){ + ASSERT(ev.incoming()[i].colour); + if( *ev.incoming()[i].colour != expected[i]) + return false; + } + for(size_t i=2; i<ev.outgoing().size()+2; ++i){ + if( ev.outgoing()[i-2].colour ){ + if( *ev.outgoing()[i-2].colour != expected[i] ) + return false; + } else if( expected[i].first != 0 || expected[i].second != 0) + return false; + } + return true; +} + +void check_event( + HEJ::Event::EventData unc_ev, std::vector<HEJ::Colour> const & expected_colours +){ + shuffle_particles(unc_ev); // make sure incoming order doesn't matter + HEJ::Event ev{unc_ev.cluster( + fastjet::JetDefinition(fastjet::JetAlgorithm::antikt_algorithm, 0.4), 30.) + }; + ASSERT(HEJ::event_type::is_HEJ(ev.type())); + dum_rnd rng; + ASSERT(ev.generate_colours(rng)); + if(!correct_colour(ev)){ + std::cerr << "Found illegal colours for event\n"; + dump_event(ev); + throw std::invalid_argument("Illegal colour set"); + } + if(!match_expected(ev, expected_colours)){ + std::cerr << "Colours didn't match expectation. Found\n"; + dump_event(ev); + std::cerr << "but expected\n"; + for(auto const & col: expected_colours){ + std::cerr << "colour={" << col.first << ", " << col.second << "}\n"; + } + throw std::logic_error("Colours did not match expectation"); + } +} + +int main() { + HEJ::Event::EventData ev; + std::vector<HEJ::Colour> expected_colours(7); + + /// pure gluon + ev.incoming[0] = { HEJ::ParticleID::gluon, { 0, 0,-427, 427}, {}}; + ev.incoming[1] = { HEJ::ParticleID::gluon, { 0, 0, 851, 851}, {}}; + ev.outgoing.push_back({ HEJ::ParticleID::gluon, { 196, 124, -82, 246}, {}}); + ev.outgoing.push_back({ HEJ::ParticleID::gluon, {-167,-184, 16, 249}, {}}); + ev.outgoing.push_back({ HEJ::ParticleID::higgs, { 197, 180, 168, 339}, {}}); + ev.outgoing.push_back({ HEJ::ParticleID::gluon, {-190, -57, 126, 235}, {}}); + ev.outgoing.push_back({ HEJ::ParticleID::gluon, { -36, -63, 196, 209}, {}}); + + expected_colours[0] = {502, 501}; + expected_colours[1] = {509, 502}; + expected_colours[2] = {503, 501}; + expected_colours[3] = {505, 503}; + expected_colours[4] = { 0, 0}; + expected_colours[5] = {507, 505}; + expected_colours[6] = {509, 507}; + check_event(ev, expected_colours); + + /// last g to Qx (=> gQx -> g ... Qx) + ev.incoming[1].type = HEJ::ParticleID::d_bar; + ev.outgoing[4].type = HEJ::ParticleID::d_bar; + // => only end changes + expected_colours[1].first = 0; + expected_colours[6].first = 0; + check_event(ev, expected_colours); + + { + // don't overwrite + auto new_expected = expected_colours; + auto new_ev = ev; + /// uno forward (=> gQx -> g ... Qx g) + std::swap(new_ev.outgoing[3].type, new_ev.outgoing[4].type); + // => uno quarks eats colour and gluon connects to anti-colour + new_expected[5] = {0, expected_colours[3].first}; + new_expected[6] = {expected_colours[0].first, expected_colours[0].first+2}; + new_expected[1].second += 2; // one more anti-colour in line + check_event(new_ev, new_expected); + } + + /// swap Qx <-> Q (=> gQ -> g ... Q) + ev.incoming[1].type = HEJ::ParticleID::d; + ev.outgoing[4].type = HEJ::ParticleID::d; + // => swap: colour<->anti && inital<->final + std::swap(expected_colours[1], expected_colours[6]); + std::swap(expected_colours[1].first, expected_colours[1].second); + std::swap(expected_colours[6].first, expected_colours[6].second); + check_event(ev, expected_colours); + + /// first g to qx (=> qxQ -> qx ... Q) + ev.incoming[0].type = HEJ::ParticleID::u_bar; + ev.outgoing[0].type = HEJ::ParticleID::u_bar; + expected_colours[0] = { 0, 501}; + // => shift anti-colour index one up + expected_colours[1].first -= 2; + expected_colours[5] = expected_colours[3]; + expected_colours[3] = expected_colours[2]; + expected_colours[2] = { 0, 502}; + check_event(ev, expected_colours); + + { + // don't overwrite + auto new_expected = expected_colours; + auto new_ev = ev; + /// uno backward (=> qxQ -> g qx ... Q) + std::swap(new_ev.outgoing[0].type, new_ev.outgoing[1].type); + // => uno gluon connects to quark colour + new_expected[3] = expected_colours[2]; + new_expected[2] = {expected_colours[0].second+2, expected_colours[0].second}; + check_event(new_ev, new_expected); + + /// swap qx <-> q (=> qQ -> g q ... Q) + new_ev.incoming[0].type = HEJ::ParticleID::u; + new_ev.outgoing[1].type = HEJ::ParticleID::u; + // => swap: colour<->anti && inital<->final + std::swap(new_expected[0], new_expected[3]); + std::swap(new_expected[0].first, new_expected[0].second); + std::swap(new_expected[3].first, new_expected[3].second); + // => & connect first gluon with remaining anti-colour + new_expected[2] = {new_expected[0].first, new_expected[0].first+2}; + // shift colour line one down + new_expected[1].first-=2; + new_expected[5].first-=2; + new_expected[5].second-=2; + // shift anti-colour line one up + new_expected[6].first+=2; + check_event(new_ev, new_expected); + } + + { + // don't overwrite + auto new_expected = expected_colours; + auto new_ev = ev; + /// uno forward (=> qxQ -> qx ... Q g) + std::swap(new_ev.outgoing[3].type, new_ev.outgoing[4].type); + // => uno gluon connects to remaining colour + new_expected[5] = expected_colours[6]; + new_expected[6] = {expected_colours[3].first+2, expected_colours[3].first}; + check_event(new_ev, new_expected); + } + + /// @TODO add qqx test when implemented (it should work) + + return EXIT_SUCCESS; +} diff --git a/t/test_descriptions.cc b/t/test_descriptions.cc index d212ad5..30c66a6 100644 --- a/t/test_descriptions.cc +++ b/t/test_descriptions.cc @@ -1,62 +1,62 @@ #include <iostream> #include <cstddef> #include "HEJ/Event.hh" #include "HEJ/EventReweighter.hh" #include "HEJ/ScaleFunction.hh" #define ASSERT(x) if(!(x)) { \ std::cerr << "Assertion '" #x "' failed.\n"; \ return EXIT_FAILURE; \ } int main() { constexpr double mu = 125.; HEJ::ScaleFunction fun{"125", HEJ::FixedScale{mu}}; ASSERT(fun.name() == "125"); HEJ::ScaleGenerator scale_gen{ {std::move(fun)}, {0.5, 1, 2.}, 2.1 }; - HEJ::UnclusteredEvent tmp; + HEJ::Event::EventData tmp; tmp.outgoing.push_back( - {HEJ::ParticleID::gluon, fastjet::PtYPhiM(50., -1., 0.3, 0.)} + {HEJ::ParticleID::gluon, fastjet::PtYPhiM(50., -1., 0.3, 0.), {}} ); tmp.outgoing.push_back( - {HEJ::ParticleID::gluon, fastjet::PtYPhiM(30., 1., -0.3, 0.)} + {HEJ::ParticleID::gluon, fastjet::PtYPhiM(30., 1., -0.3, 0.), {}} ); HEJ::Event ev{ - std::move(tmp), - fastjet::JetDefinition{fastjet::kt_algorithm, 0.4}, - 20. + tmp.cluster( + fastjet::JetDefinition{fastjet::kt_algorithm, 0.4}, 20. + ) }; auto rescaled = scale_gen(std::move(ev)); ASSERT(rescaled.central().description->scale_name == "125"); for(auto const & var: rescaled.variations()) { ASSERT(var.description->scale_name == "125"); } ASSERT(rescaled.central().description->mur_factor == 1.); ASSERT(rescaled.central().description->muf_factor == 1.); ASSERT(rescaled.variations(0).description->mur_factor == 1.); ASSERT(rescaled.variations(0).description->muf_factor == 1.); ASSERT(rescaled.variations(1).description->mur_factor == 0.5); ASSERT(rescaled.variations(1).description->muf_factor == 0.5); ASSERT(rescaled.variations(2).description->mur_factor == 0.5); ASSERT(rescaled.variations(2).description->muf_factor == 1.); ASSERT(rescaled.variations(3).description->mur_factor == 1.); ASSERT(rescaled.variations(3).description->muf_factor == 0.5); ASSERT(rescaled.variations(4).description->mur_factor == 1.); ASSERT(rescaled.variations(4).description->muf_factor == 2.); ASSERT(rescaled.variations(5).description->mur_factor == 2.); ASSERT(rescaled.variations(5).description->muf_factor == 1.); ASSERT(rescaled.variations(6).description->mur_factor == 2.); ASSERT(rescaled.variations(6).description->muf_factor == 2.); } diff --git a/t/test_parameters.cc b/t/test_parameters.cc new file mode 100644 index 0000000..2261167 --- /dev/null +++ b/t/test_parameters.cc @@ -0,0 +1,71 @@ +#include <iostream> +#include <stdexcept> + +#include "HEJ/Parameters.hh" + +#define ASSERT(x) if(!(x)) { \ + throw std::logic_error("Assertion '" #x "' failed."); \ + } + + +namespace { + bool same_description( + HEJ::EventParameters const & par1, HEJ::EventParameters const & par2 + ){ + if( par1.mur!=par2.mur || par1.muf!=par2.muf ) return false; + auto const & des1 = par1.description; + auto const & des2 = par2.description; + if(bool(des1) != bool(des2)) return false; // only one set + if(!des1) return true; // both not set + return (des1->mur_factor == des2->mur_factor) + && (des1->muf_factor == des2->muf_factor) + && (des1->scale_name == des2->scale_name); + } + + void same_description( + HEJ::Parameters<HEJ::EventParameters> const & par1, + HEJ::Parameters<HEJ::EventParameters> const & par2 + ){ + ASSERT(same_description(par1.central, par2.central)); + ASSERT(par1.variations.size() == par2.variations.size()); + for(size_t i=0; i<par1.variations.size(); ++i) + ASSERT( same_description(par1.variations[i], par2.variations[i]) ); + } +} + +int main() { + HEJ::Parameters<HEJ::EventParameters> ev_param; + ev_param.central = HEJ::EventParameters{ 1,1,1.1, + std::make_shared<HEJ::ParameterDescription>("a", 1.,1.) }; + ev_param.variations.emplace_back( + HEJ::EventParameters{ 2,2,2.2, + std::make_shared<HEJ::ParameterDescription>("b", 2.,2.) + }); + ev_param.variations.emplace_back( + HEJ::EventParameters{ 3,3,3.3, + std::make_shared<HEJ::ParameterDescription>("c", 3.,3.) + }); + + HEJ::Weights weights; + weights.central = 4.4; + weights.variations.push_back(5.5); + weights.variations.push_back(6.6); + + HEJ::Parameters<HEJ::EventParameters> mult_param; + mult_param = ev_param*weights; + same_description(ev_param, mult_param); + ASSERT(mult_param.central.weight == ev_param.central.weight*weights.central); + for(size_t i=0; i<weights.variations.size(); ++i) + ASSERT(mult_param.variations[i].weight + == ev_param.variations[i].weight*weights.variations[i]); + + HEJ::Parameters<HEJ::EventParameters> div_param; + div_param = ev_param/weights; + same_description(ev_param, div_param); + ASSERT(div_param.central.weight == ev_param.central.weight/weights.central); + for(size_t i=0; i<weights.variations.size(); ++i) + ASSERT(div_param.variations[i].weight + == ev_param.variations[i].weight/weights.variations[i]); + + return EXIT_SUCCESS; +} diff --git a/t/test_psp.cc b/t/test_psp.cc index 0d6f486..87355c6 100644 --- a/t/test_psp.cc +++ b/t/test_psp.cc @@ -1,69 +1,65 @@ #include "LHEF/LHEF.h" #include "HEJ/stream.hh" #include "HEJ/config.hh" #include "HEJ/event_types.hh" #include "HEJ/Event.hh" #include "HEJ/PhaseSpacePoint.hh" #include "HEJ/Ranlux64.hh" namespace{ constexpr int max_trials = 100; constexpr double extpartonptmin = 45.; constexpr double max_ext_soft_pt_fraction = std::numeric_limits<double>::infinity(); const fastjet::JetDefinition jet_def{fastjet::kt_algorithm, 0.4}; constexpr double min_jet_pt = 50; }; int main(int argn, char** argv) { if(argn != 2){ - std::cerr << "Usage: test_psp eventfile"; + std::cerr << "Usage: " << argv[0] << " eventfile"; return EXIT_FAILURE; } HEJ::istream in{argv[1]}; LHEF::Reader reader{in}; LHEF::Writer writer{std::cerr}; writer.heprup = reader.heprup; HEJ::PhaseSpacePointConfig conf; conf.jet_param = HEJ::JetParameters{jet_def, min_jet_pt}; conf.min_extparton_pt = extpartonptmin; conf.max_ext_soft_pt_fraction = max_ext_soft_pt_fraction; HEJ::Ranlux64 ran{}; while(reader.readEvent()){ const HEJ::Event ev{ - HEJ::UnclusteredEvent{reader.hepeup}, - jet_def, min_jet_pt + HEJ::Event::EventData{reader.hepeup}( jet_def, min_jet_pt ) }; for(int trial = 0; trial < max_trials; ++trial){ HEJ::PhaseSpacePoint psp{ev, conf, ran}; if(psp.weight() != 0){ - HEJ::UnclusteredEvent tmp_ev; + HEJ::Event::EventData tmp_ev; tmp_ev.incoming = psp.incoming(); tmp_ev.outgoing = psp.outgoing(); - tmp_ev.central = {0,0,0}; - HEJ::Event out_ev{ - std::move(tmp_ev), - jet_def, min_jet_pt - }; + tmp_ev.parameters.central = {0,0,0}; + HEJ::Event out_ev{ tmp_ev(jet_def, min_jet_pt) }; if(out_ev.type() != ev.type()){ using HEJ::event_type::names; std::cerr << "Wrong class of phase space point:\n" "original event of class " << names[ev.type()] << ":\n"; writer.hepeup = reader.hepeup; writer.writeEvent(); std::cerr << "Phase space point of class " << names[out_ev.type()] << ":\n"; writer.hepeup = to_HEPEUP(out_ev, &writer.heprup); writer.writeEvent(); return EXIT_FAILURE; } } } } } diff --git a/t/test_scale_arithmetics.cc b/t/test_scale_arithmetics.cc index 015dd6a..b640726 100644 --- a/t/test_scale_arithmetics.cc +++ b/t/test_scale_arithmetics.cc @@ -1,87 +1,99 @@ // Generic tester for the ME for a given set of PSP // reference weights and PSP (as LHE file) have to be given as _individual_ files #include <fstream> +#include <random> +#include <algorithm> #include "LHEF/LHEF.h" #include "HEJ/EventReweighter.hh" #include "HEJ/make_RNG.hh" #include "HEJ/Event.hh" #include "HEJ/YAMLreader.hh" #include "HEJ/stream.hh" constexpr double alpha_s = 0.118; constexpr double ep = 1e-13; void dump(HEJ::Event const & ev){ { LHEF::Writer writer{std::cout}; std::cout << std::setprecision(6); writer.hepeup = to_HEPEUP(std::move(ev), nullptr); writer.writeEvent(); } std::cout << "Rapidity ordering:\n"; for(const auto & part: ev.outgoing()){ std::cout << std::setw(2) << part.type << ": "<< std::setw(7) << part.rapidity() << std::endl; } } +void shuffle_particles(HEJ::Event::EventData & ev) { + static std::mt19937_64 ran{0}; + std::shuffle(begin(ev.incoming), end(ev.incoming), ran); + std::shuffle(begin(ev.outgoing), end(ev.outgoing), ran); +} + int main(int argn, char** argv){ if(argn != 3){ std::cerr << "\n# Usage:\n."<< argv[0] <<" config.yml input_file.lhe\n\n"; return EXIT_FAILURE; } HEJ::Config config = HEJ::load_config(argv[1]); config.scales = HEJ::to_ScaleConfig( YAML::Load("scales: [H_T, 1 * H_T, 2/2 * H_T, 2*H_T/2, H_T/2*2, H_T/2/2*4, H_T*H_T/H_T]") ); HEJ::istream in{argv[2]}; LHEF::Reader reader{in}; auto ran = HEJ::make_RNG(config.rng.name, config.rng.seed); HEJ::ScaleGenerator scale_gen{ config.scales.base, config.scales.factors, config.scales.max_ratio }; HEJ::EventReweighter resum{ reader.heprup, std::move(scale_gen), to_EventReweighterConfig(config), *ran }; size_t i = 0; while(reader.readEvent()){ ++i; + HEJ::Event::EventData data{reader.hepeup}; + shuffle_particles(data); + HEJ::Event event{ - HEJ::UnclusteredEvent{reader.hepeup}, - config.resummation_jets.def, - config.resummation_jets.min_pt + data.cluster( + config.resummation_jets.def, + config.resummation_jets.min_pt + ) }; auto resummed = resum.reweight(event, config.trials); for(auto && ev: resummed) { for(auto &&var: ev.variations()) { if(std::abs(var.muf - ev.central().muf) > ep) { std::cerr << std::setprecision(15) << "unequal scales: " << var.muf << " != " << ev.central().muf << '\n' << "in resummed event:\n"; dump(ev); std::cerr << "\noriginal event:\n"; dump(event); return EXIT_FAILURE; } } } } } diff --git a/t/test_scale_import.cc b/t/test_scale_import.cc index 7a194c3..0b3fd88 100644 --- a/t/test_scale_import.cc +++ b/t/test_scale_import.cc @@ -1,31 +1,29 @@ #include <stdexcept> #include <iostream> #include "HEJ/YAMLreader.hh" #include "HEJ/Event.hh" int main(int argc, char** argv) { constexpr double ep = 1e-7; if (argc != 2) { throw std::logic_error{"wrong number of args"}; } const HEJ::Config config = HEJ::load_config(argv[1]); - HEJ::UnclusteredEvent tmp; + HEJ::Event::EventData tmp; tmp.outgoing.push_back( - {HEJ::ParticleID::gluon, fastjet::PtYPhiM(50., -1., 0.3, 0.)} + {HEJ::ParticleID::gluon, fastjet::PtYPhiM(50., -1., 0.3, 0.), {}} ); tmp.outgoing.push_back( - {HEJ::ParticleID::gluon, fastjet::PtYPhiM(30., 1., -0.3, 0.)} + {HEJ::ParticleID::gluon, fastjet::PtYPhiM(30., 1., -0.3, 0.), {}} ); HEJ::Event ev{ - std::move(tmp), - fastjet::JetDefinition{fastjet::kt_algorithm, 0.4}, - 20. + tmp(fastjet::JetDefinition{fastjet::kt_algorithm, 0.4}, 20.) }; const double softest_pt = config.scales.base[0](ev); if(std::abs(softest_pt-30.) > ep){ throw std::logic_error{"wrong softest pt"}; } }