From 11bb00124047638d58fc04b99851e907a08e92ee Mon Sep 17 00:00:00 2001 From: Whitney Armstrong <warmstrong@anl.gov> Date: Sat, 17 Apr 2021 23:33:58 +0000 Subject: [PATCH] - Moved scripts into benchmarks directory. - Using local storage to avoid large artifacts. - Make sure new jobs inherit the setup of local storage. --- .gitlab-ci.yml | 188 +- README.md | 7 +- .../calorimeters}/.gitignore | 0 .../calorimeters}/.rootrc | 0 .../calorimeters}/Crystal_example.xml | 0 .../calorimeters}/ZDC_example.xml | 0 benchmarks/calorimeters/config.yml | 96 + .../calorimeters}/elements.xml | 0 .../calorimeters}/makeplot_pion.C | 0 .../calorimeters}/materials.xml | 0 .../calorimeters}/rootlogon.C | 0 .../run_emcal_barrel_electrons.sh | 19 +- .../calorimeters}/run_emcal_barrel_pions.sh | 16 +- .../calorimeters}/run_simulation_crystal.sh | 2 +- .../run_simulation_crystal_pion.sh | 2 +- .../calorimeters}/run_simulation_zdc.sh | 4 +- .../scripts/emcal_barrel_electrons.cxx | 0 .../emcal_barrel_electrons_analysis.cxx | 143 + .../scripts/emcal_barrel_electrons_reader.cxx | 0 .../scripts/emcal_barrel_pions.cxx | 0 .../scripts/emcal_barrel_pions_analysis.cxx | 8 +- .../scripts/emcal_barrel_pions_reader.cxx | 0 .../calorimeters}/simple_checking.cxx | 0 .../calorimeters}/simple_checking_crystal.cxx | 6 +- .../simple_info_plot_histograms.cxx | 4 +- .../calorimeters}/zdc_neutrons_reader.cxx | 0 {others => benchmarks/others}/.gitignore | 0 {pid => benchmarks/pid}/.gitignore | 0 .../pid/config.yml | 4 +- {pid => benchmarks/pid}/dummy_test.sh | 0 {trackers => benchmarks/trackers}/.gitignore | 0 benchmarks/trackers/config.yml | 24 + benchmarks/trackers/elements.xml | 884 +++ benchmarks/trackers/materials.xml | 169 + .../trackers}/roman_pot.xml | 3 +- .../trackers}/roman_pot_hit_eta.cxx | 0 .../trackers}/roman_pot_simu.sh | 2 +- .../trackers}/simple_tracking.cxx | 2 +- calorimeters/calorimeters_config.yml | 150 - calorimeters/dummy_test.sh | 6 - calorimeters/dummy_test2.sh | 6 - calorimeters/edep_histo_zdc_photons.png | Bin 14992 -> 0 bytes .../hit_postion_histo_zdc_photons.png | Bin 37017 -> 0 bytes calorimeters/nhits_histo_zdc_photons.png | Bin 15471 -> 0 bytes calorimeters/options/emcal_barrel_reco.py | 63 - .../emcal_barrel_electrons_analysis.cxx | 124 - calorimeters/volID_histo_zdc_photons.png | Bin 16504 -> 0 bytes include/benchmark.h | 124 + include/clipp.h | 6248 +++++++++++++++++ include/exception.h | 22 + include/mt.h | 11 + include/plot.h | 42 + include/util.h | 161 + options/env.sh | 117 + tools/dev-shell | 84 + tools/download.sh | 73 + trackers/dummy_test.sh | 9 - trackers/trackers_config.yml | 12 - util/build_detector.sh | 64 + util/collect_benchmarks.py | 181 + util/collect_tests.py | 204 + util/compile_analyses.py | 119 + util/parse_cmd.sh | 127 + util/print_env.sh | 13 + util/run_many.py | 124 + 65 files changed, 9199 insertions(+), 468 deletions(-) rename {calorimeters => benchmarks/calorimeters}/.gitignore (100%) rename {calorimeters => benchmarks/calorimeters}/.rootrc (100%) rename {calorimeters => benchmarks/calorimeters}/Crystal_example.xml (100%) rename {calorimeters => benchmarks/calorimeters}/ZDC_example.xml (100%) create mode 100644 benchmarks/calorimeters/config.yml rename {calorimeters => benchmarks/calorimeters}/elements.xml (100%) rename {calorimeters => benchmarks/calorimeters}/makeplot_pion.C (100%) rename {calorimeters => benchmarks/calorimeters}/materials.xml (100%) rename {calorimeters => benchmarks/calorimeters}/rootlogon.C (100%) rename {calorimeters => benchmarks/calorimeters}/run_emcal_barrel_electrons.sh (65%) rename {calorimeters => benchmarks/calorimeters}/run_emcal_barrel_pions.sh (71%) rename {calorimeters => benchmarks/calorimeters}/run_simulation_crystal.sh (72%) rename {calorimeters => benchmarks/calorimeters}/run_simulation_crystal_pion.sh (76%) rename {calorimeters => benchmarks/calorimeters}/run_simulation_zdc.sh (52%) rename {calorimeters => benchmarks/calorimeters}/scripts/emcal_barrel_electrons.cxx (100%) create mode 100644 benchmarks/calorimeters/scripts/emcal_barrel_electrons_analysis.cxx rename {calorimeters => benchmarks/calorimeters}/scripts/emcal_barrel_electrons_reader.cxx (100%) rename {calorimeters => benchmarks/calorimeters}/scripts/emcal_barrel_pions.cxx (100%) rename {calorimeters => benchmarks/calorimeters}/scripts/emcal_barrel_pions_analysis.cxx (94%) rename {calorimeters => benchmarks/calorimeters}/scripts/emcal_barrel_pions_reader.cxx (100%) rename {calorimeters => benchmarks/calorimeters}/simple_checking.cxx (100%) rename {calorimeters => benchmarks/calorimeters}/simple_checking_crystal.cxx (78%) rename {calorimeters => benchmarks/calorimeters}/simple_info_plot_histograms.cxx (98%) rename {calorimeters => benchmarks/calorimeters}/zdc_neutrons_reader.cxx (100%) rename {others => benchmarks/others}/.gitignore (100%) rename {pid => benchmarks/pid}/.gitignore (100%) rename pid/pid_config.yml => benchmarks/pid/config.yml (73%) rename {pid => benchmarks/pid}/dummy_test.sh (100%) rename {trackers => benchmarks/trackers}/.gitignore (100%) create mode 100644 benchmarks/trackers/config.yml create mode 100644 benchmarks/trackers/elements.xml create mode 100644 benchmarks/trackers/materials.xml rename {trackers => benchmarks/trackers}/roman_pot.xml (97%) rename {trackers => benchmarks/trackers}/roman_pot_hit_eta.cxx (100%) rename {trackers => benchmarks/trackers}/roman_pot_simu.sh (72%) rename {trackers => benchmarks/trackers}/simple_tracking.cxx (99%) delete mode 100644 calorimeters/calorimeters_config.yml delete mode 100644 calorimeters/dummy_test.sh delete mode 100644 calorimeters/dummy_test2.sh delete mode 100644 calorimeters/edep_histo_zdc_photons.png delete mode 100644 calorimeters/hit_postion_histo_zdc_photons.png delete mode 100644 calorimeters/nhits_histo_zdc_photons.png delete mode 100644 calorimeters/options/emcal_barrel_reco.py delete mode 100644 calorimeters/scripts/emcal_barrel_electrons_analysis.cxx delete mode 100644 calorimeters/volID_histo_zdc_photons.png create mode 100644 include/benchmark.h create mode 100644 include/clipp.h create mode 100644 include/exception.h create mode 100644 include/mt.h create mode 100644 include/plot.h create mode 100644 include/util.h create mode 100755 options/env.sh create mode 100755 tools/dev-shell create mode 100755 tools/download.sh delete mode 100644 trackers/dummy_test.sh delete mode 100644 trackers/trackers_config.yml create mode 100755 util/build_detector.sh create mode 100755 util/collect_benchmarks.py create mode 100755 util/collect_tests.py create mode 100755 util/compile_analyses.py create mode 100755 util/parse_cmd.sh create mode 100755 util/print_env.sh create mode 100755 util/run_many.py diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 804c834b..84a180cb 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,80 +1,171 @@ image: eicweb.phy.anl.gov:4567/eic/juggler/juggler:$JUGGLER_TAG default: - artifacts: - expire_in: 20 weeks - paths: - - results/ - - datasets/ - - sim_output/ - - data - reports: - dotenv: juggler.env before_script: - - git clone https://eicweb.phy.anl.gov/EIC/NPDet.git - #- git clone https://eicweb.phy.anl.gov/EIC/NPDet.git && mkdir NPDet/build && cd NPDet/build && cmake ../. -DCMAKE_INSTALL_PREFIX=/usr/local && make -j20 install && cd ../.. - git clone https://eicweb.phy.anl.gov/EIC/detectors/topside.git && mkdir topside/build && cd topside/build && cmake ../. -DCMAKE_INSTALL_PREFIX=/usr/local && make -j20 install && cd ../.. - - git clone https://eicweb.phy.anl.gov/EIC/detectors/accelerator.git && cd topside && ln -s ../accelerator/eic && cd ../. + artifacts: + expire_in: 72 hours + paths: + - .local/detector + - .local/lib + - results + - config stages: + - config + - initialize - data_init - simulate - benchmarks - deploy -get_data: - stage: data_init - tags: - - silicon +env: + stage: config script: - - git clone https://eicweb.phy.anl.gov/EIC/datasets.git datasets - - ln -s datasets/data - - mkdir -p results - - mkdir -p sim_output + - ./util/print_env.sh + - mkdir -p /scratch/${CI_PROJECT_NAME}_${CI_PIPELINE_ID} + - mkdir -p /scratch/${CI_PROJECT_NAME}_${CI_PIPELINE_ID}/sim_output -roman_pot_simu: - stage: simulate - needs: - - ["get_data"] - tags: - - silicon +detector: + stage: initialize + needs: ["env"] + timeout: 1 hours + cache: + key: + files: + - options/env.sh + - util/build_detector.sh + prefix: "$CI_COMMIT_REF_SLUG" + paths: + - .local/detector + - .local/lib script: - - cp NPDet/src/detectors/trackers/compact/elements.xml ./. - - cp NPDet/src/detectors/trackers/compact/materials.xml ./. - - bash trackers/roman_pot_simu.sh + - ./util/print_env.sh + - ./util/build_detector.sh + - ./util/print_env.sh + - mkdir -p results + - mkdir -p config -roman_pot_nhits: - stage: benchmarks - needs: - - ["roman_pot_simu"] - tags: - - silicon +get_data: + stage: data_init + needs: ["detector"] script: - - root -b -q trackers/simple_tracking.cxx+ - allow_failure: true + - source options/env.sh + - ln -s ${LOCAL_DATA_PATH}/sim_output sim_output + - ln -s ${LOCAL_DATA_PATH}/datasets/data data + - cd ${LOCAL_DATA_PATH} && git clone --depth=1 https://eicweb.phy.anl.gov/EIC/datasets.git datasets -roman_pot_eta: - stage: benchmarks - tags: - - silicon +.det_benchmark: + image: eicweb.phy.anl.gov:4567/eic/juggler/juggler:$JUGGLER_TAG needs: - - ["roman_pot_simu"] - script: - - root -b -q trackers/roman_pot_hit_eta.cxx+ - allow_failure: true + - ["get_data"] + before_script: + - source options/env.sh + - ln -s ${LOCAL_DATA_PATH}/sim_output sim_output + - ln -s ${LOCAL_DATA_PATH}/datasets/data data + artifacts: + expire_in: 20 weeks + paths: + - results/ -include: - - local: 'calorimeters/calorimeters_config.yml' + +include: + - local: 'benchmarks/trackers/config.yml' + - local: 'benchmarks/calorimeters/config.yml' + - local: 'benchmarks/pid/config.yml' + + #roman_pot_simu: + # stage: simulate + # needs: + # - ["get_data","detector"] + # script: + # - bash benchmarks/trackers/roman_pot_simu.sh + # + #roman_pot_nhits: + # stage: benchmarks + # needs: + # - ["roman_pot_simu","detector"] + # script: + # - root -b -q benchmarks/trackers/simple_tracking.cxx+ + # allow_failure: true + # + #roman_pot_eta: + # stage: benchmarks + # needs: + # - ["roman_pot_simu","detector"] + # script: + # - root -b -q benchmarks/trackers/roman_pot_hit_eta.cxx+ + # allow_failure: true + # + #zdc_simulation: + # stage: simulate + # needs: + # - ["get_data","detector"] + # script: + # - bash benchmarks/calorimeters/run_simulation_zdc.sh + # + #cal_test_3_zdc_neutrons_reader: + # stage: benchmarks + # needs: + # - ["zdc_simulation","detector"] + # script: + # - root -b -q benchmarks/calorimeters/zdc_neutrons_reader.cxx + # allow_failure: true + # + # + #zdc_benchmark: + # stage: benchmarks + # needs: + # - ["zdc_simulation","detector"] + # script: + # - ls -lrth sim_output + # - root -b -q benchmarks/calorimeters/simple_checking.cxx+ + # allow_failure: true + # + #zdc_benchmark_info_histogram: + # stage: benchmarks + # needs: + # - ["zdc_simulation","detector"] + # script: + # - root -b -q benchmarks/calorimeters/simple_info_plot_histograms.cxx+ + # allow_failure: true + # + #crystal_emcal_simulation: + # stage: simulate + # needs: + # - ["get_data"] + # script: + # bash benchmarks/calorimeters/run_simulation_crystal.sh + # + #crystal_benchmark: + # stage: benchmarks + # needs: + # - ["crystal_emcal_simulation","detector"] + # script: + # - ls -lrth sim_output + # - root -b -q benchmarks/calorimeters/simple_checking_crystal.cxx+ + # allow_failure: true + # + #crystal_pion_simulation: + # stage: simulate + # needs: + # - ["get_data","detector"] + # tags: + # - silicon + # script: + # - source options/env.sh + # - npsim --runType batch --numberOfEvents 100 --compactFile ${DETECTOR_PATH}/topside.xml --inputFiles data/emcal_electrons.hepmc --outputFile sim_output/output_emcal_electrons.root deploy_results: stage: deploy needs: - - ["ben_emcal_barrel_electrons"] + - ["cal_bench:zdc_benchmark"] tags: - silicon script: - echo "deploy results!" + pages: stage: deploy rules: @@ -89,3 +180,4 @@ pages: paths: - public + diff --git a/README.md b/README.md index 8a58d6d3..322745e1 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,9 @@ EIC Detector Benchmarks ## Overview Detector benchmarks are meant to test for regressions in individual detector subsystems. -The analysis is meant to avoid a complex reconstruction. -So for now this precludes using [juggler](https://eicweb.phy.anl.gov/EIC/juggler) for processing the events. -[Physics benchmarks](https://eicweb.phy.anl.gov/EIC/benchmarks/physics_benchmarks) will -include the full reconstruction chain processing. +The analysis is meant to avoid a reconstruction step. +So this precludes using [juggler](https://eicweb.phy.anl.gov/EIC/juggler) for processing the events. + ## Adding new benchmarks diff --git a/calorimeters/.gitignore b/benchmarks/calorimeters/.gitignore similarity index 100% rename from calorimeters/.gitignore rename to benchmarks/calorimeters/.gitignore diff --git a/calorimeters/.rootrc b/benchmarks/calorimeters/.rootrc similarity index 100% rename from calorimeters/.rootrc rename to benchmarks/calorimeters/.rootrc diff --git a/calorimeters/Crystal_example.xml b/benchmarks/calorimeters/Crystal_example.xml similarity index 100% rename from calorimeters/Crystal_example.xml rename to benchmarks/calorimeters/Crystal_example.xml diff --git a/calorimeters/ZDC_example.xml b/benchmarks/calorimeters/ZDC_example.xml similarity index 100% rename from calorimeters/ZDC_example.xml rename to benchmarks/calorimeters/ZDC_example.xml diff --git a/benchmarks/calorimeters/config.yml b/benchmarks/calorimeters/config.yml new file mode 100644 index 00000000..153241ac --- /dev/null +++ b/benchmarks/calorimeters/config.yml @@ -0,0 +1,96 @@ +##################### +# Simulations +# - Generate datasets +# - Run Geant4 +# - Run Juggler +##################### + +cal_sim:emcal_barrel_pions: + extends: .det_benchmark + stage: simulate + script: + - bash benchmarks/calorimeters/run_emcal_barrel_pions.sh + +cal_sim:emcal_barrel_electrons: + extends: .det_benchmark + stage: simulate + script: + - bash benchmarks/calorimeters/run_emcal_barrel_electrons.sh + +cal_sim:crystal_emcal: + extends: .det_benchmark + stage: simulate + script: + - bash benchmarks/calorimeters/run_simulation_crystal.sh + +cal_sim:crystal_pion: + extends: .det_benchmark + stage: simulate + script: + - npsim --runType batch --numberOfEvents 100 --compactFile ${DETECTOR_PATH}/topside.xml --inputFiles data/emcal_electrons.hepmc --outputFile sim_output/output_emcal_electrons.root + +cal_sim:zdc: + extends: .det_benchmark + stage: simulate + script: + - bash benchmarks/calorimeters/run_simulation_zdc.sh + +################### +# Benchmarks +################### + +cal_bench:emcal_barrel_pions: + extends: .det_benchmark + stage: benchmarks + needs: + - ["cal_sim:emcal_barrel_pions"] + script: + - root -b -q benchmarks/calorimeters/scripts/emcal_barrel_pions_analysis.cxx+ + +cal_bench:emcal_barrel_electrons: + extends: .det_benchmark + stage: benchmarks + needs: + - ["cal_sim:emcal_barrel_electrons"] + script: + - rootls -t sim_output/sim_emcal_barrel_uniform_electrons.root + - root -b -q benchmarks/calorimeters/scripts/emcal_barrel_electrons_analysis.cxx+ + +cal_bench:crystal_benchmark: + extends: .det_benchmark + stage: benchmarks + needs: + - ["cal_sim:crystal_emcal"] + script: + - rootls -t sim_output/output_emcal_electrons.root + - echo " Not yet complete" + #- root -b -q benchmarks/calorimeters/simple_checking_crystal.cxx+ + + #cal_test_3_zdc_neutrons_reader: + #stage: benchmarks + #tags: + #- sodium + #script: + #- root -b -q benchmarks/calorimeters/zdc_neutrons_reader.cxx + #artifact: + # paths: + # - results/ + #allow_failure: true + +cal_bench:zdc_benchmark: + extends: .det_benchmark + stage: benchmarks + needs: + - ["cal_sim:zdc"] + script: + - echo " Not yet complete" + #- root -b -q benchmarks/calorimeters/simple_checking.cxx+ + +cal_bench:zdc_benchmark_info_histogram: + extends: .det_benchmark + stage: benchmarks + needs: + - ["cal_sim:zdc"] + script: + - root -b -q benchmarks/calorimeters/simple_info_plot_histograms.cxx+ + diff --git a/calorimeters/elements.xml b/benchmarks/calorimeters/elements.xml similarity index 100% rename from calorimeters/elements.xml rename to benchmarks/calorimeters/elements.xml diff --git a/calorimeters/makeplot_pion.C b/benchmarks/calorimeters/makeplot_pion.C similarity index 100% rename from calorimeters/makeplot_pion.C rename to benchmarks/calorimeters/makeplot_pion.C diff --git a/calorimeters/materials.xml b/benchmarks/calorimeters/materials.xml similarity index 100% rename from calorimeters/materials.xml rename to benchmarks/calorimeters/materials.xml diff --git a/calorimeters/rootlogon.C b/benchmarks/calorimeters/rootlogon.C similarity index 100% rename from calorimeters/rootlogon.C rename to benchmarks/calorimeters/rootlogon.C diff --git a/calorimeters/run_emcal_barrel_electrons.sh b/benchmarks/calorimeters/run_emcal_barrel_electrons.sh similarity index 65% rename from calorimeters/run_emcal_barrel_electrons.sh rename to benchmarks/calorimeters/run_emcal_barrel_electrons.sh index b735f190..f6ef5391 100755 --- a/calorimeters/run_emcal_barrel_electrons.sh +++ b/benchmarks/calorimeters/run_emcal_barrel_electrons.sh @@ -8,9 +8,6 @@ if [[ ! -n "${JUGGLER_N_EVENTS}" ]] ; then export JUGGLER_N_EVENTS=1000 fi -if [[ ! -n "${JUGGLER_INSTALL_PREFIX}" ]] ; then - export JUGGLER_INSTALL_PREFIX="/usr/local" -fi if [[ ! -n "${E_start}" ]] ; then export E_start=5.0 @@ -30,13 +27,13 @@ echo "JUGGLER_N_EVENTS = ${JUGGLER_N_EVENTS}" echo "JUGGLER_DETECTOR = ${JUGGLER_DETECTOR}" # Generate the input events -root -b -q "calorimeters/scripts/emcal_barrel_electrons.cxx(${JUGGLER_N_EVENTS}, ${E_start}, ${E_end}, \"${JUGGLER_FILE_NAME_TAG}.hepmc\")" +root -b -q "benchmarks/calorimeters/scripts/emcal_barrel_electrons.cxx(${JUGGLER_N_EVENTS}, ${E_start}, ${E_end}, \"${JUGGLER_FILE_NAME_TAG}.hepmc\")" if [[ "$?" -ne "0" ]] ; then echo "ERROR running script: generating input events" exit 1 fi # Plot the input events -root -b -q "calorimeters/scripts/emcal_barrel_electrons_reader.cxx(${E_start}, ${E_end}, \"${JUGGLER_FILE_NAME_TAG}.hepmc\")" +root -b -q "benchmarks/calorimeters/scripts/emcal_barrel_electrons_reader.cxx(${E_start}, ${E_end}, \"${JUGGLER_FILE_NAME_TAG}.hepmc\")" if [[ "$?" -ne "0" ]] ; then echo "ERROR running script: plotting input events" exit 1 @@ -47,7 +44,7 @@ npsim --runType batch \ -v WARNING \ --part.minimalKineticEnergy 0.5*GeV \ --numberOfEvents ${JUGGLER_N_EVENTS} \ - --compactFile ${JUGGLER_DETECTOR}/${JUGGLER_DETECTOR}.xml \ + --compactFile ${DETECTOR_PATH}/${JUGGLER_DETECTOR}.xml \ --inputFiles ${JUGGLER_FILE_NAME_TAG}.hepmc \ --outputFile sim_output/${JUGGLER_SIM_FILE} @@ -56,17 +53,9 @@ if [[ "$?" -ne "0" ]] ; then exit 1 fi -# Run Juggler -xenv -x ${JUGGLER_INSTALL_PREFIX}/Juggler.xenv \ - gaudirun.py calorimeters/options/emcal_barrel_reco.py -if [[ "$?" -ne "0" ]] ; then - echo "ERROR running juggler" - exit 1 -fi - # Directory for plots mkdir -p results # Move ROOT output file -mv ${JUGGLER_REC_FILE} sim_output/ +#mv ${JUGGLER_REC_FILE} sim_output/ diff --git a/calorimeters/run_emcal_barrel_pions.sh b/benchmarks/calorimeters/run_emcal_barrel_pions.sh similarity index 71% rename from calorimeters/run_emcal_barrel_pions.sh rename to benchmarks/calorimeters/run_emcal_barrel_pions.sh index f82bfaa9..35cc1425 100755 --- a/calorimeters/run_emcal_barrel_pions.sh +++ b/benchmarks/calorimeters/run_emcal_barrel_pions.sh @@ -30,13 +30,13 @@ echo "JUGGLER_N_EVENTS = ${JUGGLER_N_EVENTS}" echo "JUGGLER_DETECTOR = ${JUGGLER_DETECTOR}" # Generate the input events -root -b -q "calorimeters/scripts/emcal_barrel_pions.cxx(${JUGGLER_N_EVENTS}, ${E_start}, ${E_end}, \"${JUGGLER_FILE_NAME_TAG}.hepmc\")" +root -b -q "benchmarks/calorimeters/scripts/emcal_barrel_pions.cxx(${JUGGLER_N_EVENTS}, ${E_start}, ${E_end}, \"${JUGGLER_FILE_NAME_TAG}.hepmc\")" if [[ "$?" -ne "0" ]] ; then echo "ERROR running script: generating input events" exit 1 fi # Plot the input events -root -b -q "calorimeters/scripts/emcal_barrel_pions_reader.cxx(${E_start}, ${E_end}, \"${JUGGLER_FILE_NAME_TAG}.hepmc\")" +root -b -q "benchmarks/calorimeters/scripts/emcal_barrel_pions_reader.cxx(${E_start}, ${E_end}, \"${JUGGLER_FILE_NAME_TAG}.hepmc\")" if [[ "$?" -ne "0" ]] ; then echo "ERROR running script: plotting input events" exit 1 @@ -47,7 +47,7 @@ npsim --runType batch \ -v WARNING \ --part.minimalKineticEnergy 0.5*GeV \ --numberOfEvents ${JUGGLER_N_EVENTS} \ - --compactFile ${JUGGLER_DETECTOR}/${JUGGLER_DETECTOR}.xml \ + --compactFile ${DETECTOR_PATH}/${JUGGLER_DETECTOR}.xml \ --inputFiles ${JUGGLER_FILE_NAME_TAG}.hepmc \ --outputFile sim_output/${JUGGLER_SIM_FILE} @@ -56,17 +56,9 @@ if [[ "$?" -ne "0" ]] ; then exit 1 fi -# Run Juggler -xenv -x ${JUGGLER_INSTALL_PREFIX}/Juggler.xenv \ - gaudirun.py calorimeters/options/emcal_barrel_reco.py -if [[ "$?" -ne "0" ]] ; then - echo "ERROR running juggler" - exit 1 -fi - # Directory for plots mkdir -p results # Move ROOT output file -mv ${JUGGLER_REC_FILE} sim_output/ +#mv ${JUGGLER_REC_FILE} sim_output/ diff --git a/calorimeters/run_simulation_crystal.sh b/benchmarks/calorimeters/run_simulation_crystal.sh similarity index 72% rename from calorimeters/run_simulation_crystal.sh rename to benchmarks/calorimeters/run_simulation_crystal.sh index aabd4230..f00e8033 100755 --- a/calorimeters/run_simulation_crystal.sh +++ b/benchmarks/calorimeters/run_simulation_crystal.sh @@ -1,6 +1,6 @@ #!/bin/bash ddsim --runType batch --numberOfEvents 100 \ - --compactFile ./calorimeters/Crystal_example.xml \ + --compactFile benchmarks/calorimeters/Crystal_example.xml \ --inputFiles ./data/emcal_electrons.hepmc \ --outputFile ./sim_output/output_emcal_electrons.root diff --git a/calorimeters/run_simulation_crystal_pion.sh b/benchmarks/calorimeters/run_simulation_crystal_pion.sh similarity index 76% rename from calorimeters/run_simulation_crystal_pion.sh rename to benchmarks/calorimeters/run_simulation_crystal_pion.sh index 0d4934ac..f8eb44f9 100644 --- a/calorimeters/run_simulation_crystal_pion.sh +++ b/benchmarks/calorimeters/run_simulation_crystal_pion.sh @@ -1,6 +1,6 @@ #!/bin/bash npsim --runType batch --numberOfEvents 10000 \ - --compactFile ./calorimeters/topside.xml \ + --compactFile benchmarks/calorimeters/topside.xml \ --inputFiles ./data/emcal_pions_upto1GeV_10kevents.hepmc \ --outputFile ./sim_output/sim_crystal_pion_input.root diff --git a/calorimeters/run_simulation_zdc.sh b/benchmarks/calorimeters/run_simulation_zdc.sh similarity index 52% rename from calorimeters/run_simulation_zdc.sh rename to benchmarks/calorimeters/run_simulation_zdc.sh index a5d84972..c8486d56 100755 --- a/calorimeters/run_simulation_zdc.sh +++ b/benchmarks/calorimeters/run_simulation_zdc.sh @@ -1,6 +1,6 @@ #!/bin/bash -ddsim --runType batch --numberOfEvents 100 \ - --compactFile ./calorimeters/ZDC_example.xml \ +ddsim --runType batch --numberOfEvents 10 \ + --compactFile benchmarks/calorimeters/ZDC_example.xml \ --inputFiles ./data/zdc_photons.hepmc \ --outputFile ./sim_output/output_zdc_photons.root diff --git a/calorimeters/scripts/emcal_barrel_electrons.cxx b/benchmarks/calorimeters/scripts/emcal_barrel_electrons.cxx similarity index 100% rename from calorimeters/scripts/emcal_barrel_electrons.cxx rename to benchmarks/calorimeters/scripts/emcal_barrel_electrons.cxx diff --git a/benchmarks/calorimeters/scripts/emcal_barrel_electrons_analysis.cxx b/benchmarks/calorimeters/scripts/emcal_barrel_electrons_analysis.cxx new file mode 100644 index 00000000..6327820d --- /dev/null +++ b/benchmarks/calorimeters/scripts/emcal_barrel_electrons_analysis.cxx @@ -0,0 +1,143 @@ +//////////////////////////////////////// +// Read reconstruction ROOT output file +// Plot variables +//////////////////////////////////////// + +#include "ROOT/RDataFrame.hxx" +#include <iostream> + +#include "dd4pod/Geant4ParticleCollection.h" +#include "dd4pod/CalorimeterHitCollection.h" + +#include "TCanvas.h" +#include "TStyle.h" +#include "TMath.h" +#include "TH1.h" +#include "TF1.h" +#include "TH1D.h" + +using ROOT::RDataFrame; +using namespace ROOT::VecOps; + +void emcal_barrel_electrons_analysis(const char* input_fname = "sim_output/sim_emcal_barrel_uniform_electrons.root") +{ + // Setting for graphs + gROOT->SetStyle("Plain"); + gStyle->SetOptFit(1); + gStyle->SetLineWidth(2); + gStyle->SetPadTickX(1); + gStyle->SetPadTickY(1); + gStyle->SetPadGridX(1); + gStyle->SetPadGridY(1); + gStyle->SetPadLeftMargin(0.14); + gStyle->SetPadRightMargin(0.14); + + ROOT::EnableImplicitMT(); + ROOT::RDataFrame d0("events", input_fname); + + // Thrown Energy [GeV] + auto Ethr = [](std::vector<dd4pod::Geant4ParticleData> const& input) { + std::vector<double> result; + result.push_back(TMath::Sqrt(input[2].psx*input[2].psx + input[2].psy*input[2].psy + input[2].psz*input[2].psz + input[2].mass*input[2].mass)); + return result; + }; + + // Number of hits + auto nhits = [] (const std::vector<dd4pod::CalorimeterHitData>& evt) {return (int) evt.size(); }; + + // Energy deposition [GeV] + auto Esim = [](const std::vector<dd4pod::CalorimeterHitData>& evt) { + std::vector<double> result; + auto total_edep = 0.0; + for (const auto& i: evt) + total_edep += i.energyDeposit; + result.push_back(total_edep); + return result; + }; + + // Sampling fraction = Esampling / Ethrown + auto fsam = [](const std::vector<double>& sampled, + const std::vector<double>& thrown) { + std::vector<double> result; + for (const auto& E1 : thrown) { + for (const auto& E2 : sampled) + result.push_back(E2 / E1); + } + return result; + }; + + // Define variables + auto d1 = d0.Define("Ethr", Ethr, {"mcparticles"}) + .Define("nhits", nhits, {"EcalBarrelHits"}) + .Define("Esim", Esim, {"EcalBarrelHits"}) + .Define("fsam", fsam, {"Esim", "Ethr"}); + + // Define Histograms + auto hEthr = d1.Histo1D( + {"hEthr", "Thrown Energy; Thrown Energy [GeV]; Events", 100, 0.0, 7.5}, + "Ethr"); + auto hNhits = + d1.Histo1D({"hNhits", "Number of hits per events; Number of hits; Events", + 100, 0.0, 2000.0}, + "nhits"); + auto hEsim = d1.Histo1D( + {"hEsim", "Energy Deposit; Energy Deposit [GeV]; Events", 100, 0.0, 1.0}, + "Esim"); + auto hfsam = d1.Histo1D( + {"hfsam", "Sampling Fraction; Sampling Fraction; Events", 100, 0.0, 0.1}, + "fsam"); + + // Event Counts + auto nevents_thrown = d1.Count(); + std::cout << "Number of Thrown Events: " << (*nevents_thrown) << "\n"; + + // Draw Histograms + { + TCanvas* c1 = new TCanvas("c1", "c1", 700, 500); + c1->SetLogy(1); + auto h = hEthr->DrawCopy(); + //h->GetYaxis()->SetTitleOffset(1.4); + h->SetLineWidth(2); + h->SetLineColor(kBlue); + c1->SaveAs("results/emcal_barrel_electrons_Ethr.png"); + c1->SaveAs("results/emcal_barrel_electrons_Ethr.pdf"); + } + std::cout << "derp1\n"; + + { + TCanvas* c2 = new TCanvas("c2", "c2", 700, 500); + c2->SetLogy(1); + auto h = hNhits->DrawCopy(); + //h->GetYaxis()->SetTitleOffset(1.4); + h->SetLineWidth(2); + h->SetLineColor(kBlue); + c2->SaveAs("results/emcal_barrel_electrons_nhits.png"); + c2->SaveAs("results/emcal_barrel_electrons_nhits.pdf"); + } + + { + TCanvas* c3 = new TCanvas("c3", "c3", 700, 500); + c3->SetLogy(1); + auto h = hEsim->DrawCopy(); + //h->GetYaxis()->SetTitleOffset(1.4); + h->SetLineWidth(2); + h->SetLineColor(kBlue); + c3->SaveAs("results/emcal_barrel_electrons_Esim.png"); + c3->SaveAs("results/emcal_barrel_electrons_Esim.pdf"); + } + + std::cout << "derp4\n"; + { + TCanvas* c4 = new TCanvas("c4", "c4", 700, 500); + c4->SetLogy(1); + auto h = hfsam->DrawCopy(); + //h->GetYaxis()->SetTitleOffset(1.4); + h->SetLineWidth(2); + h->SetLineColor(kBlue); + //h->Fit("gaus", "", "", 0.01, 0.1); + //h->GetFunction("gaus")->SetLineWidth(2); + //h->GetFunction("gaus")->SetLineColor(kRed); + c4->SaveAs("results/emcal_barrel_electrons_fsam.png"); + c4->SaveAs("results/emcal_barrel_electrons_fsam.pdf"); + } +} diff --git a/calorimeters/scripts/emcal_barrel_electrons_reader.cxx b/benchmarks/calorimeters/scripts/emcal_barrel_electrons_reader.cxx similarity index 100% rename from calorimeters/scripts/emcal_barrel_electrons_reader.cxx rename to benchmarks/calorimeters/scripts/emcal_barrel_electrons_reader.cxx diff --git a/calorimeters/scripts/emcal_barrel_pions.cxx b/benchmarks/calorimeters/scripts/emcal_barrel_pions.cxx similarity index 100% rename from calorimeters/scripts/emcal_barrel_pions.cxx rename to benchmarks/calorimeters/scripts/emcal_barrel_pions.cxx diff --git a/calorimeters/scripts/emcal_barrel_pions_analysis.cxx b/benchmarks/calorimeters/scripts/emcal_barrel_pions_analysis.cxx similarity index 94% rename from calorimeters/scripts/emcal_barrel_pions_analysis.cxx rename to benchmarks/calorimeters/scripts/emcal_barrel_pions_analysis.cxx index cde99f32..69e4c641 100644 --- a/calorimeters/scripts/emcal_barrel_pions_analysis.cxx +++ b/benchmarks/calorimeters/scripts/emcal_barrel_pions_analysis.cxx @@ -19,7 +19,7 @@ using ROOT::RDataFrame; using namespace ROOT::VecOps; -void emcal_barrel_pions_analysis(const char* input_fname = "sim_output/rec_emcal_barrel_uniform_pions.root") +void emcal_barrel_pions_analysis(const char* input_fname = "sim_output/sim_emcal_barrel_uniform_pions.root") { // Setting for graphs gROOT->SetStyle("Plain"); @@ -66,9 +66,9 @@ void emcal_barrel_pions_analysis(const char* input_fname = "sim_output/rec_emcal }; // Define variables - auto d1 = d0.Define("Ethr", Ethr, {"mcparticles2"}) - .Define("nhits", nhits, {"EcalBarrelAstroPixHits2"}) - .Define("Esim", Esim, {"EcalBarrelAstroPixHits2"}) + auto d1 = d0.Define("Ethr", Ethr, {"mcparticles"}) + .Define("nhits", nhits, {"EcalBarrelHits"}) + .Define("Esim", Esim, {"EcalBarrelHits"}) .Define("fsam", fsam, {"Esim","Ethr"}) ; diff --git a/calorimeters/scripts/emcal_barrel_pions_reader.cxx b/benchmarks/calorimeters/scripts/emcal_barrel_pions_reader.cxx similarity index 100% rename from calorimeters/scripts/emcal_barrel_pions_reader.cxx rename to benchmarks/calorimeters/scripts/emcal_barrel_pions_reader.cxx diff --git a/calorimeters/simple_checking.cxx b/benchmarks/calorimeters/simple_checking.cxx similarity index 100% rename from calorimeters/simple_checking.cxx rename to benchmarks/calorimeters/simple_checking.cxx diff --git a/calorimeters/simple_checking_crystal.cxx b/benchmarks/calorimeters/simple_checking_crystal.cxx similarity index 78% rename from calorimeters/simple_checking_crystal.cxx rename to benchmarks/calorimeters/simple_checking_crystal.cxx index 32997fb6..d60c2d6c 100644 --- a/calorimeters/simple_checking_crystal.cxx +++ b/benchmarks/calorimeters/simple_checking_crystal.cxx @@ -9,14 +9,14 @@ void simple_checking_crystal(const char* fname = "sim_output/output_emcal_electr ROOT::EnableImplicitMT(); // Tell ROOT you want to go parallel double degree = TMath::Pi()/180.0; - TChain* t = new TChain("EVENT"); + TChain* t = new TChain("events"); t->Add(fname); ROOT::RDataFrame d0(*t);//, {"EcalHits","MCParticles"}); - auto nhits = [] (std::vector<dd4hep::sim::Geant4Calorimeter::Hit*>& hits){ return (int) hits.size(); }; + auto nhits = [] (const std::vector<dd4pod::CalorimeterHit>& hits){ return (int) hits.size(); }; - auto d1 = d0.Define("nhits", nhits, {"EcalHits"}); + auto d1 = d0.Define("nhits", nhits, {"CrystalEcalHits"}); auto h0 = d1.Histo1D(TH1D("h0", "nhits; ", 20, 0,20), "nhits"); auto n0 = d1.Filter([](int n){ return (n>0); },{"nhits"}).Count(); diff --git a/calorimeters/simple_info_plot_histograms.cxx b/benchmarks/calorimeters/simple_info_plot_histograms.cxx similarity index 98% rename from calorimeters/simple_info_plot_histograms.cxx rename to benchmarks/calorimeters/simple_info_plot_histograms.cxx index 980b856a..9dd2cf42 100644 --- a/calorimeters/simple_info_plot_histograms.cxx +++ b/benchmarks/calorimeters/simple_info_plot_histograms.cxx @@ -54,7 +54,7 @@ void simple_info_plot_histograms(const char* fname = "sim_output/output_zdc_phot // Detector dd4hep::Detector& detector = dd4hep::Detector::getInstance(); - detector.fromCompact("./calorimeters/ZDC_example.xml"); + detector.fromCompact("benchmarks/calorimeters/ZDC_example.xml"); // Volume dd4hep::VolumeManager volman = dd4hep::VolumeManager::getVolumeManager(detector); // CellID Coverter @@ -209,7 +209,7 @@ void simple_info_plot_histograms(const char* fname = "sim_output/output_zdc_phot h6->DrawClone(); c4->SaveAs("sim_output/detID_volID_histo_zdc_photons.png"); - if(*n0<5) { + if(*n0<1) { std::quick_exit(1); } } diff --git a/calorimeters/zdc_neutrons_reader.cxx b/benchmarks/calorimeters/zdc_neutrons_reader.cxx similarity index 100% rename from calorimeters/zdc_neutrons_reader.cxx rename to benchmarks/calorimeters/zdc_neutrons_reader.cxx diff --git a/others/.gitignore b/benchmarks/others/.gitignore similarity index 100% rename from others/.gitignore rename to benchmarks/others/.gitignore diff --git a/pid/.gitignore b/benchmarks/pid/.gitignore similarity index 100% rename from pid/.gitignore rename to benchmarks/pid/.gitignore diff --git a/pid/pid_config.yml b/benchmarks/pid/config.yml similarity index 73% rename from pid/pid_config.yml rename to benchmarks/pid/config.yml index b133b27f..6e6d226d 100644 --- a/pid/pid_config.yml +++ b/benchmarks/pid/config.yml @@ -4,8 +4,8 @@ pid_test_1_dummy_test: stage: benchmarks script: - - bash pid/dummy_test.sh - artifact: + - bash benchmarks/pid/dummy_test.sh + artifacts: paths: - results/ allow_failure: true diff --git a/pid/dummy_test.sh b/benchmarks/pid/dummy_test.sh similarity index 100% rename from pid/dummy_test.sh rename to benchmarks/pid/dummy_test.sh diff --git a/trackers/.gitignore b/benchmarks/trackers/.gitignore similarity index 100% rename from trackers/.gitignore rename to benchmarks/trackers/.gitignore diff --git a/benchmarks/trackers/config.yml b/benchmarks/trackers/config.yml new file mode 100644 index 00000000..e9f3c2b1 --- /dev/null +++ b/benchmarks/trackers/config.yml @@ -0,0 +1,24 @@ +sim_trackers:roman_pot: + stage: simulate + extends: .det_benchmark + script: + - bash benchmarks/trackers/roman_pot_simu.sh + +roman_pot_nhits: + stage: benchmarks + extends: .det_benchmark + needs: + - ["sim_trackers:roman_pot"] + script: + - root -b -q benchmarks/trackers/simple_tracking.cxx+ + +roman_pot_eta: + stage: benchmarks + extends: .det_benchmark + needs: + - ["sim_trackers:roman_pot"] + script: + - root -b -q benchmarks/trackers/roman_pot_hit_eta.cxx+ + allow_failure: true + + diff --git a/benchmarks/trackers/elements.xml b/benchmarks/trackers/elements.xml new file mode 100644 index 00000000..e714c3a5 --- /dev/null +++ b/benchmarks/trackers/elements.xml @@ -0,0 +1,884 @@ +<materials> + <element Z="89" formula="Ac" name="Ac" > + <atom type="A" unit="g/mol" value="227.028" /> + </element> + <material formula="Ac" name="Actinium" state="solid" > + <RL type="X0" unit="cm" value="0.601558" /> + <NIL type="lambda" unit="cm" value="21.2048" /> + <D type="density" unit="g/cm3" value="10.07" /> + <composite n="1" ref="Ac" /> + </material> + <element Z="47" formula="Ag" name="Ag" > + <atom type="A" unit="g/mol" value="107.868" /> + </element> + <material formula="Ag" name="Silver" state="solid" > + <RL type="X0" unit="cm" value="0.854292" /> + <NIL type="lambda" unit="cm" value="15.8546" /> + <D type="density" unit="g/cm3" value="10.5" /> + <composite n="1" ref="Ag" /> + </material> + <element Z="13" formula="Al" name="Al" > + <atom type="A" unit="g/mol" value="26.9815" /> + </element> + <material formula="Al" name="Aluminum" state="solid" > + <RL type="X0" unit="cm" value="8.89632" /> + <NIL type="lambda" unit="cm" value="38.8766" /> + <D type="density" unit="g/cm3" value="2.699" /> + <composite n="1" ref="Al" /> + </material> + <element Z="95" formula="Am" name="Am" > + <atom type="A" unit="g/mol" value="243.061" /> + </element> + <material formula="Am" name="Americium" state="solid" > + <RL type="X0" unit="cm" value="0.42431" /> + <NIL type="lambda" unit="cm" value="15.9812" /> + <D type="density" unit="g/cm3" value="13.67" /> + <composite n="1" ref="Am" /> + </material> + <element Z="18" formula="Ar" name="Ar" > + <atom type="A" unit="g/mol" value="39.9477" /> + </element> + <material formula="Ar" name="Argon" state="gas" > + <RL type="X0" unit="cm" value="11762.1" /> + <NIL type="lambda" unit="cm" value="71926" /> + <D type="density" unit="g/cm3" value="0.00166201" /> + <composite n="1" ref="Ar" /> + </material> + <element Z="33" formula="As" name="As" > + <atom type="A" unit="g/mol" value="74.9216" /> + </element> + <material formula="As" name="Arsenic" state="solid" > + <RL type="X0" unit="cm" value="2.0838" /> + <NIL type="lambda" unit="cm" value="25.7324" /> + <D type="density" unit="g/cm3" value="5.73" /> + <composite n="1" ref="As" /> + </material> + <element Z="85" formula="At" name="At" > + <atom type="A" unit="g/mol" value="209.987" /> + </element> + <material formula="At" name="Astatine" state="solid" > + <RL type="X0" unit="cm" value="0.650799" /> + <NIL type="lambda" unit="cm" value="22.3202" /> + <D type="density" unit="g/cm3" value="9.32" /> + <composite n="1" ref="At" /> + </material> + <element Z="79" formula="Au" name="Au" > + <atom type="A" unit="g/mol" value="196.967" /> + </element> + <material formula="Au" name="Gold" state="solid" > + <RL type="X0" unit="cm" value="0.334436" /> + <NIL type="lambda" unit="cm" value="10.5393" /> + <D type="density" unit="g/cm3" value="19.32" /> + <composite n="1" ref="Au" /> + </material> + <element Z="5" formula="B" name="B" > + <atom type="A" unit="g/mol" value="10.811" /> + </element> + <material formula="B" name="Boron" state="solid" > + <RL type="X0" unit="cm" value="22.2307" /> + <NIL type="lambda" unit="cm" value="32.2793" /> + <D type="density" unit="g/cm3" value="2.37" /> + <composite n="1" ref="B" /> + </material> + <element Z="56" formula="Ba" name="Ba" > + <atom type="A" unit="g/mol" value="137.327" /> + </element> + <material formula="Ba" name="Barium" state="solid" > + <RL type="X0" unit="cm" value="2.37332" /> + <NIL type="lambda" unit="cm" value="51.6743" /> + <D type="density" unit="g/cm3" value="3.5" /> + <composite n="1" ref="Ba" /> + </material> + <element Z="4" formula="Be" name="Be" > + <atom type="A" unit="g/mol" value="9.01218" /> + </element> + <material formula="Be" name="Beryllium" state="solid" > + <RL type="X0" unit="cm" value="35.276" /> + <NIL type="lambda" unit="cm" value="39.4488" /> + <D type="density" unit="g/cm3" value="1.848" /> + <composite n="1" ref="Be" /> + </material> + <element Z="83" formula="Bi" name="Bi" > + <atom type="A" unit="g/mol" value="208.98" /> + </element> + <material formula="Bi" name="Bismuth" state="solid" > + <RL type="X0" unit="cm" value="0.645388" /> + <NIL type="lambda" unit="cm" value="21.3078" /> + <D type="density" unit="g/cm3" value="9.747" /> + <composite n="1" ref="Bi" /> + </material> + <element Z="97" formula="Bk" name="Bk" > + <atom type="A" unit="g/mol" value="247.07" /> + </element> + <material formula="Bk" name="Berkelium" state="solid" > + <RL type="X0" unit="cm" value="0.406479" /> + <NIL type="lambda" unit="cm" value="15.6902" /> + <D type="density" unit="g/cm3" value="14" /> + <composite n="1" ref="Bk" /> + </material> + <element Z="35" formula="Br" name="Br" > + <atom type="A" unit="g/mol" value="79.9035" /> + </element> + <material formula="Br" name="Bromine" state="gas" > + <RL type="X0" unit="cm" value="1615.12" /> + <NIL type="lambda" unit="cm" value="21299" /> + <D type="density" unit="g/cm3" value="0.0070721" /> + <composite n="1" ref="Br" /> + </material> + <element Z="6" formula="C" name="C" > + <atom type="A" unit="g/mol" value="12.0107" /> + </element> + <material formula="C" name="Carbon" state="solid" > + <RL type="X0" unit="cm" value="21.3485" /> + <NIL type="lambda" unit="cm" value="40.1008" /> + <D type="density" unit="g/cm3" value="2" /> + <composite n="1" ref="C" /> + </material> + <element Z="20" formula="Ca" name="Ca" > + <atom type="A" unit="g/mol" value="40.078" /> + </element> + <material formula="Ca" name="Calcium" state="solid" > + <RL type="X0" unit="cm" value="10.4151" /> + <NIL type="lambda" unit="cm" value="77.3754" /> + <D type="density" unit="g/cm3" value="1.55" /> + <composite n="1" ref="Ca" /> + </material> + <element Z="48" formula="Cd" name="Cd" > + <atom type="A" unit="g/mol" value="112.411" /> + </element> + <material formula="Cd" name="Cadmium" state="solid" > + <RL type="X0" unit="cm" value="1.03994" /> + <NIL type="lambda" unit="cm" value="19.46" /> + <D type="density" unit="g/cm3" value="8.65" /> + <composite n="1" ref="Cd" /> + </material> + <element Z="58" formula="Ce" name="Ce" > + <atom type="A" unit="g/mol" value="140.115" /> + </element> + <material formula="Ce" name="Cerium" state="solid" > + <RL type="X0" unit="cm" value="1.19506" /> + <NIL type="lambda" unit="cm" value="27.3227" /> + <D type="density" unit="g/cm3" value="6.657" /> + <composite n="1" ref="Ce" /> + </material> + <element Z="98" formula="Cf" name="Cf" > + <atom type="A" unit="g/mol" value="251.08" /> + </element> + <material formula="Cf" name="Californium" state="solid" > + <RL type="X0" unit="cm" value="0.568328" /> + <NIL type="lambda" unit="cm" value="22.085" /> + <D type="density" unit="g/cm3" value="10" /> + <composite n="1" ref="Cf" /> + </material> + <element Z="17" formula="Cl" name="Cl" > + <atom type="A" unit="g/mol" value="35.4526" /> + </element> + <material formula="Cl" name="Chlorine" state="gas" > + <RL type="X0" unit="cm" value="6437.34" /> + <NIL type="lambda" unit="cm" value="38723.9" /> + <D type="density" unit="g/cm3" value="0.00299473" /> + <composite n="1" ref="Cl" /> + </material> + <element Z="96" formula="Cm" name="Cm" > + <atom type="A" unit="g/mol" value="247.07" /> + </element> + <material formula="Cm" name="Curium" state="solid" > + <RL type="X0" unit="cm" value="0.428706" /> + <NIL type="lambda" unit="cm" value="16.2593" /> + <D type="density" unit="g/cm3" value="13.51" /> + <composite n="1" ref="Cm" /> + </material> + <element Z="27" formula="Co" name="Co" > + <atom type="A" unit="g/mol" value="58.9332" /> + </element> + <material formula="Co" name="Cobalt" state="solid" > + <RL type="X0" unit="cm" value="1.53005" /> + <NIL type="lambda" unit="cm" value="15.2922" /> + <D type="density" unit="g/cm3" value="8.9" /> + <composite n="1" ref="Co" /> + </material> + <element Z="24" formula="Cr" name="Cr" > + <atom type="A" unit="g/mol" value="51.9961" /> + </element> + <material formula="Cr" name="Chromium" state="solid" > + <RL type="X0" unit="cm" value="2.0814" /> + <NIL type="lambda" unit="cm" value="18.1933" /> + <D type="density" unit="g/cm3" value="7.18" /> + <composite n="1" ref="Cr" /> + </material> + <element Z="55" formula="Cs" name="Cs" > + <atom type="A" unit="g/mol" value="132.905" /> + </element> + <material formula="Cs" name="Cesium" state="solid" > + <RL type="X0" unit="cm" value="4.4342" /> + <NIL type="lambda" unit="cm" value="95.317" /> + <D type="density" unit="g/cm3" value="1.873" /> + <composite n="1" ref="Cs" /> + </material> + <element Z="29" formula="Cu" name="Cu" > + <atom type="A" unit="g/mol" value="63.5456" /> + </element> + <material formula="Cu" name="Copper" state="solid" > + <RL type="X0" unit="cm" value="1.43558" /> + <NIL type="lambda" unit="cm" value="15.5141" /> + <D type="density" unit="g/cm3" value="8.96" /> + <composite n="1" ref="Cu" /> + </material> + <element Z="66" formula="Dy" name="Dy" > + <atom type="A" unit="g/mol" value="162.497" /> + </element> + <material formula="Dy" name="Dysprosium" state="solid" > + <RL type="X0" unit="cm" value="0.85614" /> + <NIL type="lambda" unit="cm" value="22.2923" /> + <D type="density" unit="g/cm3" value="8.55" /> + <composite n="1" ref="Dy" /> + </material> + <element Z="68" formula="Er" name="Er" > + <atom type="A" unit="g/mol" value="167.256" /> + </element> + <material formula="Er" name="Erbium" state="solid" > + <RL type="X0" unit="cm" value="0.788094" /> + <NIL type="lambda" unit="cm" value="21.2923" /> + <D type="density" unit="g/cm3" value="9.066" /> + <composite n="1" ref="Er" /> + </material> + <element Z="63" formula="Eu" name="Eu" > + <atom type="A" unit="g/mol" value="151.964" /> + </element> + <material formula="Eu" name="Europium" state="solid" > + <RL type="X0" unit="cm" value="1.41868" /> + <NIL type="lambda" unit="cm" value="35.6178" /> + <D type="density" unit="g/cm3" value="5.243" /> + <composite n="1" ref="Eu" /> + </material> + <element Z="9" formula="F" name="F" > + <atom type="A" unit="g/mol" value="18.9984" /> + </element> + <material formula="F" name="Fluorine" state="gas" > + <RL type="X0" unit="cm" value="20838.2" /> + <NIL type="lambda" unit="cm" value="59094.3" /> + <D type="density" unit="g/cm3" value="0.00158029" /> + <composite n="1" ref="F" /> + </material> + <element Z="26" formula="Fe" name="Fe" > + <atom type="A" unit="g/mol" value="55.8451" /> + </element> + <material formula="Fe" name="Iron" state="solid" > + <RL type="X0" unit="cm" value="1.75749" /> + <NIL type="lambda" unit="cm" value="16.959" /> + <D type="density" unit="g/cm3" value="7.874" /> + <composite n="1" ref="Fe" /> + </material> + <element Z="87" formula="Fr" name="Fr" > + <atom type="A" unit="g/mol" value="223.02" /> + </element> + <material formula="Fr" name="Francium" state="solid" > + <RL type="X0" unit="cm" value="6.18826" /> + <NIL type="lambda" unit="cm" value="212.263" /> + <D type="density" unit="g/cm3" value="1" /> + <composite n="1" ref="Fr" /> + </material> + <element Z="31" formula="Ga" name="Ga" > + <atom type="A" unit="g/mol" value="69.7231" /> + </element> + <material formula="Ga" name="Gallium" state="solid" > + <RL type="X0" unit="cm" value="2.1128" /> + <NIL type="lambda" unit="cm" value="24.3351" /> + <D type="density" unit="g/cm3" value="5.904" /> + <composite n="1" ref="Ga" /> + </material> + <element Z="64" formula="Gd" name="Gd" > + <atom type="A" unit="g/mol" value="157.252" /> + </element> + <material formula="Gd" name="Gadolinium" state="solid" > + <RL type="X0" unit="cm" value="0.947208" /> + <NIL type="lambda" unit="cm" value="23.9377" /> + <D type="density" unit="g/cm3" value="7.9004" /> + <composite n="1" ref="Gd" /> + </material> + <element Z="32" formula="Ge" name="Ge" > + <atom type="A" unit="g/mol" value="72.6128" /> + </element> + <material formula="Ge" name="Germanium" state="solid" > + <RL type="X0" unit="cm" value="2.3013" /> + <NIL type="lambda" unit="cm" value="27.3344" /> + <D type="density" unit="g/cm3" value="5.323" /> + <composite n="1" ref="Ge" /> + </material> + <element Z="1" formula="H" name="H" > + <atom type="A" unit="g/mol" value="1.00794" /> + </element> + <material formula="H" name="Hydrogen" state="gas" > + <RL type="X0" unit="cm" value="752776" /> + <NIL type="lambda" unit="cm" value="421239" /> + <D type="density" unit="g/cm3" value="8.3748e-05" /> + <composite n="1" ref="H" /> + </material> + <element Z="2" formula="He" name="He" > + <atom type="A" unit="g/mol" value="4.00264" /> + </element> + <material formula="He" name="Helium" state="gas" > + <RL type="X0" unit="cm" value="567113" /> + <NIL type="lambda" unit="cm" value="334266" /> + <D type="density" unit="g/cm3" value="0.000166322" /> + <composite n="1" ref="He" /> + </material> + <element Z="72" formula="Hf" name="Hf" > + <atom type="A" unit="g/mol" value="178.485" /> + </element> + <material formula="Hf" name="Hafnium" state="solid" > + <RL type="X0" unit="cm" value="0.517717" /> + <NIL type="lambda" unit="cm" value="14.7771" /> + <D type="density" unit="g/cm3" value="13.31" /> + <composite n="1" ref="Hf" /> + </material> + <element Z="80" formula="Hg" name="Hg" > + <atom type="A" unit="g/mol" value="200.599" /> + </element> + <material formula="Hg" name="Mercury" state="solid" > + <RL type="X0" unit="cm" value="0.475241" /> + <NIL type="lambda" unit="cm" value="15.105" /> + <D type="density" unit="g/cm3" value="13.546" /> + <composite n="1" ref="Hg" /> + </material> + <element Z="67" formula="Ho" name="Ho" > + <atom type="A" unit="g/mol" value="164.93" /> + </element> + <material formula="Ho" name="Holmium" state="solid" > + <RL type="X0" unit="cm" value="0.822447" /> + <NIL type="lambda" unit="cm" value="21.8177" /> + <D type="density" unit="g/cm3" value="8.795" /> + <composite n="1" ref="Ho" /> + </material> + <element Z="53" formula="I" name="I" > + <atom type="A" unit="g/mol" value="126.904" /> + </element> + <material formula="I" name="Iodine" state="solid" > + <RL type="X0" unit="cm" value="1.72016" /> + <NIL type="lambda" unit="cm" value="35.6583" /> + <D type="density" unit="g/cm3" value="4.93" /> + <composite n="1" ref="I" /> + </material> + <element Z="49" formula="In" name="In" > + <atom type="A" unit="g/mol" value="114.818" /> + </element> + <material formula="In" name="Indium" state="solid" > + <RL type="X0" unit="cm" value="1.21055" /> + <NIL type="lambda" unit="cm" value="23.2468" /> + <D type="density" unit="g/cm3" value="7.31" /> + <composite n="1" ref="In" /> + </material> + <element Z="77" formula="Ir" name="Ir" > + <atom type="A" unit="g/mol" value="192.216" /> + </element> + <material formula="Ir" name="Iridium" state="solid" > + <RL type="X0" unit="cm" value="0.294142" /> + <NIL type="lambda" unit="cm" value="9.01616" /> + <D type="density" unit="g/cm3" value="22.42" /> + <composite n="1" ref="Ir" /> + </material> + <element Z="19" formula="K" name="K" > + <atom type="A" unit="g/mol" value="39.0983" /> + </element> + <material formula="K" name="Potassium" state="solid" > + <RL type="X0" unit="cm" value="20.0871" /> + <NIL type="lambda" unit="cm" value="138.041" /> + <D type="density" unit="g/cm3" value="0.862" /> + <composite n="1" ref="K" /> + </material> + <element Z="36" formula="Kr" name="Kr" > + <atom type="A" unit="g/mol" value="83.7993" /> + </element> + <material formula="Kr" name="Krypton" state="gas" > + <RL type="X0" unit="cm" value="3269.44" /> + <NIL type="lambda" unit="cm" value="43962.9" /> + <D type="density" unit="g/cm3" value="0.00347832" /> + <composite n="1" ref="Kr" /> + </material> + <element Z="57" formula="La" name="La" > + <atom type="A" unit="g/mol" value="138.905" /> + </element> + <material formula="La" name="Lanthanum" state="solid" > + <RL type="X0" unit="cm" value="1.32238" /> + <NIL type="lambda" unit="cm" value="29.441" /> + <D type="density" unit="g/cm3" value="6.154" /> + <composite n="1" ref="La" /> + </material> + <element Z="3" formula="Li" name="Li" > + <atom type="A" unit="g/mol" value="6.94003" /> + </element> + <material formula="Li" name="Lithium" state="solid" > + <RL type="X0" unit="cm" value="154.997" /> + <NIL type="lambda" unit="cm" value="124.305" /> + <D type="density" unit="g/cm3" value="0.534" /> + <composite n="1" ref="Li" /> + </material> + <element Z="71" formula="Lu" name="Lu" > + <atom type="A" unit="g/mol" value="174.967" /> + </element> + <material formula="Lu" name="Lutetium" state="solid" > + <RL type="X0" unit="cm" value="0.703651" /> + <NIL type="lambda" unit="cm" value="19.8916" /> + <D type="density" unit="g/cm3" value="9.84" /> + <composite n="1" ref="Lu" /> + </material> + <element Z="12" formula="Mg" name="Mg" > + <atom type="A" unit="g/mol" value="24.305" /> + </element> + <material formula="Mg" name="Magnesium" state="solid" > + <RL type="X0" unit="cm" value="14.3859" /> + <NIL type="lambda" unit="cm" value="58.7589" /> + <D type="density" unit="g/cm3" value="1.74" /> + <composite n="1" ref="Mg" /> + </material> + <element Z="25" formula="Mn" name="Mn" > + <atom type="A" unit="g/mol" value="54.938" /> + </element> + <material formula="Mn" name="Manganese" state="solid" > + <RL type="X0" unit="cm" value="1.96772" /> + <NIL type="lambda" unit="cm" value="17.8701" /> + <D type="density" unit="g/cm3" value="7.44" /> + <composite n="1" ref="Mn" /> + </material> + <element Z="42" formula="Mo" name="Mo" > + <atom type="A" unit="g/mol" value="95.9313" /> + </element> + <material formula="Mo" name="Molybdenum" state="solid" > + <RL type="X0" unit="cm" value="0.959107" /> + <NIL type="lambda" unit="cm" value="15.6698" /> + <D type="density" unit="g/cm3" value="10.22" /> + <composite n="1" ref="Mo" /> + </material> + <element Z="7" formula="N" name="N" > + <atom type="A" unit="g/mol" value="14.0068" /> + </element> + <material formula="N" name="Nitrogen" state="gas" > + <RL type="X0" unit="cm" value="32602.2" /> + <NIL type="lambda" unit="cm" value="72430.3" /> + <D type="density" unit="g/cm3" value="0.0011652" /> + <composite n="1" ref="N" /> + </material> + <element Z="11" formula="Na" name="Na" > + <atom type="A" unit="g/mol" value="22.9898" /> + </element> + <material formula="Na" name="Sodium" state="solid" > + <RL type="X0" unit="cm" value="28.5646" /> + <NIL type="lambda" unit="cm" value="102.463" /> + <D type="density" unit="g/cm3" value="0.971" /> + <composite n="1" ref="Na" /> + </material> + <element Z="41" formula="Nb" name="Nb" > + <atom type="A" unit="g/mol" value="92.9064" /> + </element> + <material formula="Nb" name="Niobium" state="solid" > + <RL type="X0" unit="cm" value="1.15783" /> + <NIL type="lambda" unit="cm" value="18.4846" /> + <D type="density" unit="g/cm3" value="8.57" /> + <composite n="1" ref="Nb" /> + </material> + <element Z="60" formula="Nd" name="Nd" > + <atom type="A" unit="g/mol" value="144.236" /> + </element> + <material formula="Nd" name="Neodymium" state="solid" > + <RL type="X0" unit="cm" value="1.11667" /> + <NIL type="lambda" unit="cm" value="26.6308" /> + <D type="density" unit="g/cm3" value="6.9" /> + <composite n="1" ref="Nd" /> + </material> + <element Z="10" formula="Ne" name="Ne" > + <atom type="A" unit="g/mol" value="20.18" /> + </element> + <material formula="Ne" name="Neon" state="gas" > + <RL type="X0" unit="cm" value="34504.8" /> + <NIL type="lambda" unit="cm" value="114322" /> + <D type="density" unit="g/cm3" value="0.000838505" /> + <composite n="1" ref="Ne" /> + </material> + <element Z="28" formula="Ni" name="Ni" > + <atom type="A" unit="g/mol" value="58.6933" /> + </element> + <material formula="Ni" name="Nickel" state="solid" > + <RL type="X0" unit="cm" value="1.42422" /> + <NIL type="lambda" unit="cm" value="15.2265" /> + <D type="density" unit="g/cm3" value="8.902" /> + <composite n="1" ref="Ni" /> + </material> + <element Z="93" formula="Np" name="Np" > + <atom type="A" unit="g/mol" value="237.048" /> + </element> + <material formula="Np" name="Neptunium" state="solid" > + <RL type="X0" unit="cm" value="0.289676" /> + <NIL type="lambda" unit="cm" value="10.6983" /> + <D type="density" unit="g/cm3" value="20.25" /> + <composite n="1" ref="Np" /> + </material> + <element Z="8" formula="O" name="O" > + <atom type="A" unit="g/mol" value="15.9994" /> + </element> + <material formula="O" name="Oxygen" state="gas" > + <RL type="X0" unit="cm" value="25713.8" /> + <NIL type="lambda" unit="cm" value="66233.9" /> + <D type="density" unit="g/cm3" value="0.00133151" /> + <composite n="1" ref="O" /> + </material> + <element Z="76" formula="Os" name="Os" > + <atom type="A" unit="g/mol" value="190.225" /> + </element> + <material formula="Os" name="Osmium" state="solid" > + <RL type="X0" unit="cm" value="0.295861" /> + <NIL type="lambda" unit="cm" value="8.92553" /> + <D type="density" unit="g/cm3" value="22.57" /> + <composite n="1" ref="Os" /> + </material> + <element Z="15" formula="P" name="P" > + <atom type="A" unit="g/mol" value="30.9738" /> + </element> + <material formula="P" name="Phosphorus" state="solid" > + <RL type="X0" unit="cm" value="9.63879" /> + <NIL type="lambda" unit="cm" value="49.9343" /> + <D type="density" unit="g/cm3" value="2.2" /> + <composite n="1" ref="P" /> + </material> + <element Z="91" formula="Pa" name="Pa" > + <atom type="A" unit="g/mol" value="231.036" /> + </element> + <material formula="Pa" name="Protactinium" state="solid" > + <RL type="X0" unit="cm" value="0.38607" /> + <NIL type="lambda" unit="cm" value="13.9744" /> + <D type="density" unit="g/cm3" value="15.37" /> + <composite n="1" ref="Pa" /> + </material> + <element Z="82" formula="Pb" name="Pb" > + <atom type="A" unit="g/mol" value="207.217" /> + </element> + <material formula="Pb" name="Lead" state="solid" > + <RL type="X0" unit="cm" value="0.561253" /> + <NIL type="lambda" unit="cm" value="18.2607" /> + <D type="density" unit="g/cm3" value="11.35" /> + <composite n="1" ref="Pb" /> + </material> + <element Z="46" formula="Pd" name="Pd" > + <atom type="A" unit="g/mol" value="106.415" /> + </element> + <material formula="Pd" name="Palladium" state="solid" > + <RL type="X0" unit="cm" value="0.765717" /> + <NIL type="lambda" unit="cm" value="13.7482" /> + <D type="density" unit="g/cm3" value="12.02" /> + <composite n="1" ref="Pd" /> + </material> + <element Z="61" formula="Pm" name="Pm" > + <atom type="A" unit="g/mol" value="144.913" /> + </element> + <material formula="Pm" name="Promethium" state="solid" > + <RL type="X0" unit="cm" value="1.04085" /> + <NIL type="lambda" unit="cm" value="25.4523" /> + <D type="density" unit="g/cm3" value="7.22" /> + <composite n="1" ref="Pm" /> + </material> + <element Z="84" formula="Po" name="Po" > + <atom type="A" unit="g/mol" value="208.982" /> + </element> + <material formula="Po" name="Polonium" state="solid" > + <RL type="X0" unit="cm" value="0.661092" /> + <NIL type="lambda" unit="cm" value="22.2842" /> + <D type="density" unit="g/cm3" value="9.32" /> + <composite n="1" ref="Po" /> + </material> + <element Z="59" formula="Pr" name="Pr" > + <atom type="A" unit="g/mol" value="140.908" /> + </element> + <material formula="Pr" name="Praseodymium" state="solid" > + <RL type="X0" unit="cm" value="1.1562" /> + <NIL type="lambda" unit="cm" value="27.1312" /> + <D type="density" unit="g/cm3" value="6.71" /> + <composite n="1" ref="Pr" /> + </material> + <element Z="78" formula="Pt" name="Pt" > + <atom type="A" unit="g/mol" value="195.078" /> + </element> + <material formula="Pt" name="Platinum" state="solid" > + <RL type="X0" unit="cm" value="0.305053" /> + <NIL type="lambda" unit="cm" value="9.46584" /> + <D type="density" unit="g/cm3" value="21.45" /> + <composite n="1" ref="Pt" /> + </material> + <element Z="94" formula="Pu" name="Pu" > + <atom type="A" unit="g/mol" value="244.064" /> + </element> + <material formula="Pu" name="Plutonium" state="solid" > + <RL type="X0" unit="cm" value="0.298905" /> + <NIL type="lambda" unit="cm" value="11.0265" /> + <D type="density" unit="g/cm3" value="19.84" /> + <composite n="1" ref="Pu" /> + </material> + <element Z="88" formula="Ra" name="Ra" > + <atom type="A" unit="g/mol" value="226.025" /> + </element> + <material formula="Ra" name="Radium" state="solid" > + <RL type="X0" unit="cm" value="1.22987" /> + <NIL type="lambda" unit="cm" value="42.6431" /> + <D type="density" unit="g/cm3" value="5" /> + <composite n="1" ref="Ra" /> + </material> + <element Z="37" formula="Rb" name="Rb" > + <atom type="A" unit="g/mol" value="85.4677" /> + </element> + <material formula="Rb" name="Rubidium" state="solid" > + <RL type="X0" unit="cm" value="7.19774" /> + <NIL type="lambda" unit="cm" value="100.218" /> + <D type="density" unit="g/cm3" value="1.532" /> + <composite n="1" ref="Rb" /> + </material> + <element Z="75" formula="Re" name="Re" > + <atom type="A" unit="g/mol" value="186.207" /> + </element> + <material formula="Re" name="Rhenium" state="solid" > + <RL type="X0" unit="cm" value="0.318283" /> + <NIL type="lambda" unit="cm" value="9.5153" /> + <D type="density" unit="g/cm3" value="21.02" /> + <composite n="1" ref="Re" /> + </material> + <element Z="45" formula="Rh" name="Rh" > + <atom type="A" unit="g/mol" value="102.906" /> + </element> + <material formula="Rh" name="Rhodium" state="solid" > + <RL type="X0" unit="cm" value="0.746619" /> + <NIL type="lambda" unit="cm" value="13.2083" /> + <D type="density" unit="g/cm3" value="12.41" /> + <composite n="1" ref="Rh" /> + </material> + <element Z="86" formula="Rn" name="Rn" > + <atom type="A" unit="g/mol" value="222.018" /> + </element> + <material formula="Rn" name="Radon" state="gas" > + <RL type="X0" unit="cm" value="697.777" /> + <NIL type="lambda" unit="cm" value="23532" /> + <D type="density" unit="g/cm3" value="0.00900662" /> + <composite n="1" ref="Rn" /> + </material> + <element Z="44" formula="Ru" name="Ru" > + <atom type="A" unit="g/mol" value="101.065" /> + </element> + <material formula="Ru" name="Ruthenium" state="solid" > + <RL type="X0" unit="cm" value="0.764067" /> + <NIL type="lambda" unit="cm" value="13.1426" /> + <D type="density" unit="g/cm3" value="12.41" /> + <composite n="1" ref="Ru" /> + </material> + <element Z="16" formula="S" name="S" > + <atom type="A" unit="g/mol" value="32.0661" /> + </element> + <material formula="S" name="Sulfur" state="solid" > + <RL type="X0" unit="cm" value="9.74829" /> + <NIL type="lambda" unit="cm" value="55.6738" /> + <D type="density" unit="g/cm3" value="2" /> + <composite n="1" ref="S" /> + </material> + <element Z="51" formula="Sb" name="Sb" > + <atom type="A" unit="g/mol" value="121.76" /> + </element> + <material formula="Sb" name="Antimony" state="solid" > + <RL type="X0" unit="cm" value="1.30401" /> + <NIL type="lambda" unit="cm" value="25.8925" /> + <D type="density" unit="g/cm3" value="6.691" /> + <composite n="1" ref="Sb" /> + </material> + <element Z="21" formula="Sc" name="Sc" > + <atom type="A" unit="g/mol" value="44.9559" /> + </element> + <material formula="Sc" name="Scandium" state="solid" > + <RL type="X0" unit="cm" value="5.53545" /> + <NIL type="lambda" unit="cm" value="41.609" /> + <D type="density" unit="g/cm3" value="2.989" /> + <composite n="1" ref="Sc" /> + </material> + <element Z="34" formula="Se" name="Se" > + <atom type="A" unit="g/mol" value="78.9594" /> + </element> + <material formula="Se" name="Selenium" state="solid" > + <RL type="X0" unit="cm" value="2.64625" /> + <NIL type="lambda" unit="cm" value="33.356" /> + <D type="density" unit="g/cm3" value="4.5" /> + <composite n="1" ref="Se" /> + </material> + <element Z="14" formula="Si" name="Si" > + <atom type="A" unit="g/mol" value="28.0854" /> + </element> + <material formula="Si" name="Silicon" state="solid" > + <RL type="X0" unit="cm" value="9.36607" /> + <NIL type="lambda" unit="cm" value="45.7531" /> + <D type="density" unit="g/cm3" value="2.33" /> + <composite n="1" ref="Si" /> + </material> + <element Z="62" formula="Sm" name="Sm" > + <atom type="A" unit="g/mol" value="150.366" /> + </element> + <material formula="Sm" name="Samarium" state="solid" > + <RL type="X0" unit="cm" value="1.01524" /> + <NIL type="lambda" unit="cm" value="24.9892" /> + <D type="density" unit="g/cm3" value="7.46" /> + <composite n="1" ref="Sm" /> + </material> + <element Z="50" formula="Sn" name="Sn" > + <atom type="A" unit="g/mol" value="118.71" /> + </element> + <material formula="Sn" name="Tin" state="solid" > + <RL type="X0" unit="cm" value="1.20637" /> + <NIL type="lambda" unit="cm" value="23.4931" /> + <D type="density" unit="g/cm3" value="7.31" /> + <composite n="1" ref="Sn" /> + </material> + <element Z="38" formula="Sr" name="Sr" > + <atom type="A" unit="g/mol" value="87.6166" /> + </element> + <material formula="Sr" name="Strontium" state="solid" > + <RL type="X0" unit="cm" value="4.237" /> + <NIL type="lambda" unit="cm" value="61.0238" /> + <D type="density" unit="g/cm3" value="2.54" /> + <composite n="1" ref="Sr" /> + </material> + <element Z="73" formula="Ta" name="Ta" > + <atom type="A" unit="g/mol" value="180.948" /> + </element> + <material formula="Ta" name="Tantalum" state="solid" > + <RL type="X0" unit="cm" value="0.409392" /> + <NIL type="lambda" unit="cm" value="11.8846" /> + <D type="density" unit="g/cm3" value="16.654" /> + <composite n="1" ref="Ta" /> + </material> + <element Z="65" formula="Tb" name="Tb" > + <atom type="A" unit="g/mol" value="158.925" /> + </element> + <material formula="Tb" name="Terbium" state="solid" > + <RL type="X0" unit="cm" value="0.893977" /> + <NIL type="lambda" unit="cm" value="23.0311" /> + <D type="density" unit="g/cm3" value="8.229" /> + <composite n="1" ref="Tb" /> + </material> + <element Z="43" formula="Tc" name="Tc" > + <atom type="A" unit="g/mol" value="97.9072" /> + </element> + <material formula="Tc" name="Technetium" state="solid" > + <RL type="X0" unit="cm" value="0.833149" /> + <NIL type="lambda" unit="cm" value="14.0185" /> + <D type="density" unit="g/cm3" value="11.5" /> + <composite n="1" ref="Tc" /> + </material> + <element Z="52" formula="Te" name="Te" > + <atom type="A" unit="g/mol" value="127.603" /> + </element> + <material formula="Te" name="Tellurium" state="solid" > + <RL type="X0" unit="cm" value="1.41457" /> + <NIL type="lambda" unit="cm" value="28.1797" /> + <D type="density" unit="g/cm3" value="6.24" /> + <composite n="1" ref="Te" /> + </material> + <element Z="90" formula="Th" name="Th" > + <atom type="A" unit="g/mol" value="232.038" /> + </element> + <material formula="Th" name="Thorium" state="solid" > + <RL type="X0" unit="cm" value="0.51823" /> + <NIL type="lambda" unit="cm" value="18.353" /> + <D type="density" unit="g/cm3" value="11.72" /> + <composite n="1" ref="Th" /> + </material> + <element Z="22" formula="Ti" name="Ti" > + <atom type="A" unit="g/mol" value="47.8667" /> + </element> + <material formula="Ti" name="Titanium" state="solid" > + <RL type="X0" unit="cm" value="3.5602" /> + <NIL type="lambda" unit="cm" value="27.9395" /> + <D type="density" unit="g/cm3" value="4.54" /> + <composite n="1" ref="Ti" /> + </material> + <element Z="81" formula="Tl" name="Tl" > + <atom type="A" unit="g/mol" value="204.383" /> + </element> + <material formula="Tl" name="Thallium" state="solid" > + <RL type="X0" unit="cm" value="0.547665" /> + <NIL type="lambda" unit="cm" value="17.6129" /> + <D type="density" unit="g/cm3" value="11.72" /> + <composite n="1" ref="Tl" /> + </material> + <element Z="69" formula="Tm" name="Tm" > + <atom type="A" unit="g/mol" value="168.934" /> + </element> + <material formula="Tm" name="Thulium" state="solid" > + <RL type="X0" unit="cm" value="0.754428" /> + <NIL type="lambda" unit="cm" value="20.7522" /> + <D type="density" unit="g/cm3" value="9.321" /> + <composite n="1" ref="Tm" /> + </material> + <element Z="92" formula="U" name="U" > + <atom type="A" unit="g/mol" value="238.029" /> + </element> + <material formula="U" name="Uranium" state="solid" > + <RL type="X0" unit="cm" value="0.31663" /> + <NIL type="lambda" unit="cm" value="11.4473" /> + <D type="density" unit="g/cm3" value="18.95" /> + <composite n="1" ref="U" /> + </material> + <element Z="23" formula="V" name="V" > + <atom type="A" unit="g/mol" value="50.9415" /> + </element> + <material formula="V" name="Vanadium" state="solid" > + <RL type="X0" unit="cm" value="2.59285" /> + <NIL type="lambda" unit="cm" value="21.2187" /> + <D type="density" unit="g/cm3" value="6.11" /> + <composite n="1" ref="V" /> + </material> + <element Z="74" formula="W" name="W" > + <atom type="A" unit="g/mol" value="183.842" /> + </element> + <material formula="W" name="Tungsten" state="solid" > + <RL type="X0" unit="cm" value="0.350418" /> + <NIL type="lambda" unit="cm" value="10.3057" /> + <D type="density" unit="g/cm3" value="19.3" /> + <composite n="1" ref="W" /> + </material> + <element Z="54" formula="Xe" name="Xe" > + <atom type="A" unit="g/mol" value="131.292" /> + </element> + <material formula="Xe" name="Xenon" state="gas" > + <RL type="X0" unit="cm" value="1546.2" /> + <NIL type="lambda" unit="cm" value="32477.9" /> + <D type="density" unit="g/cm3" value="0.00548536" /> + <composite n="1" ref="Xe" /> + </material> + <element Z="39" formula="Y" name="Y" > + <atom type="A" unit="g/mol" value="88.9058" /> + </element> + <material formula="Y" name="Yttrium" state="solid" > + <RL type="X0" unit="cm" value="2.32943" /> + <NIL type="lambda" unit="cm" value="34.9297" /> + <D type="density" unit="g/cm3" value="4.469" /> + <composite n="1" ref="Y" /> + </material> + <element Z="70" formula="Yb" name="Yb" > + <atom type="A" unit="g/mol" value="173.038" /> + </element> + <material formula="Yb" name="Ytterbium" state="solid" > + <RL type="X0" unit="cm" value="1.04332" /> + <NIL type="lambda" unit="cm" value="28.9843" /> + <D type="density" unit="g/cm3" value="6.73" /> + <composite n="1" ref="Yb" /> + </material> + <element Z="30" formula="Zn" name="Zn" > + <atom type="A" unit="g/mol" value="65.3955" /> + </element> + <material formula="Zn" name="Zinc" state="solid" > + <RL type="X0" unit="cm" value="1.74286" /> + <NIL type="lambda" unit="cm" value="19.8488" /> + <D type="density" unit="g/cm3" value="7.133" /> + <composite n="1" ref="Zn" /> + </material> + <element Z="40" formula="Zr" name="Zr" > + <atom type="A" unit="g/mol" value="91.2236" /> + </element> + <material formula="Zr" name="Zirconium" state="solid" > + <RL type="X0" unit="cm" value="1.56707" /> + <NIL type="lambda" unit="cm" value="24.2568" /> + <D type="density" unit="g/cm3" value="6.506" /> + <composite n="1" ref="Zr" /> + </material> +</materials> \ No newline at end of file diff --git a/benchmarks/trackers/materials.xml b/benchmarks/trackers/materials.xml new file mode 100644 index 00000000..34d6c1f4 --- /dev/null +++ b/benchmarks/trackers/materials.xml @@ -0,0 +1,169 @@ +<?xml version="1.0" encoding="UTF-8"?> +<materials> + + <!-- + Air by weight from + + http://www.engineeringtoolbox.com/air-composition-24_212.html + --> + <material name="Air"> + <D type="density" unit="g/cm3" value="0.0012"/> + <fraction n="0.754" ref="N"/> + <fraction n="0.234" ref="O"/> + <fraction n="0.012" ref="Ar"/> + </material> + <material name="air"> + <D type="density" unit="g/cm3" value="0.0012"/> + <fraction n="0.754" ref="N"/> + <fraction n="0.234" ref="O"/> + <fraction n="0.012" ref="Ar"/> + </material> + + <!-- We model vakuum just as very thin air --> + <material name="Vacuum"> + <D type="density" unit="g/cm3" value="0.0000000001" /> + <fraction n="0.754" ref="N"/> + <fraction n="0.234" ref="O"/> + <fraction n="0.012" ref="Ar"/> + </material> + + <material name="Epoxy"> + <D type="density" value="1.3" unit="g/cm3"/> + <composite n="44" ref="H"/> + <composite n="15" ref="C"/> + <composite n="7" ref="O"/> + </material> + + <material name="Quartz"> + <D type="density" value="2.2" unit="g/cm3"/> + <composite n="1" ref="Si"/> + <composite n="2" ref="O"/> + </material> + + <material name="G10"> + <D type="density" value="1.7" unit="g/cm3"/> + <fraction n="0.08" ref="Cl"/> + <fraction n="0.773" ref="Quartz"/> + <fraction n="0.147" ref="Epoxy"/> + </material> + + <material name="Polystyrene"> + <D value="1.032" unit="g/cm3"/> + <composite n="19" ref="C"/> + <composite n="21" ref="H"/> + </material> + + <material name="Steel235"> + <D value="7.85" unit="g/cm3"/> + <fraction n="0.998" ref="Fe"/> + <fraction n=".002" ref="C"/> + </material> + + <material name="SiliconOxide"> + <D type="density" value="2.65" unit="g/cm3"/> + <composite n="1" ref="Si"/> + <composite n="2" ref="O"/> + </material> + + <material name="SiliconNitride"> + <D type="density" value="3.17" unit="g/cm3"/> + <composite n="3" ref="Si"/> + <composite n="4" ref="N"/> + </material> + + <material name="BoronOxide"> + <D type="density" value="2.46" unit="g/cm3"/> + <composite n="2" ref="B"/> + <composite n="3" ref="O"/> + </material> + + <material name="SodiumOxide"> + <D type="density" value="2.65" unit="g/cm3"/> + <composite n="2" ref="Na"/> + <composite n="1" ref="O"/> + </material> + + <material name="AluminumOxide"> + <D type="density" value="3.89" unit="g/cm3"/> + <composite n="2" ref="Al"/> + <composite n="3" ref="O"/> + </material> + + <material name="PyrexGlass"> + <D type="density" value="2.23" unit="g/cm3"/> + <fraction n="0.806" ref="SiliconOxide"/> + <fraction n="0.130" ref="BoronOxide"/> + <fraction n="0.040" ref="SodiumOxide"/> + <fraction n="0.023" ref="AluminumOxide"/> + </material> + + <material name="CarbonFiber"> + <D type="density" value="1.5" unit="g/cm3"/> + <fraction n="0.65" ref="C"/> + <fraction n="0.35" ref="Epoxy"/> + </material> + + <material name="CarbonFiber_50D"> + <D type="density" value="0.75" unit="g/cm3"/> + <fraction n="0.65" ref="C"/> + <fraction n="0.35" ref="Epoxy"/> + </material> + + <material name="Rohacell31"> + <D type="density" value="0.032" unit="g/cm3"/> + <composite n="9" ref="C"/> + <composite n="13" ref="H"/> + <composite n="2" ref="O"/> + <composite n="1" ref="N"/> + </material> + + <material name="Rohacell31_50D"> + <D type="density" value="0.016" unit="g/cm3"/> + <composite n="9" ref="C"/> + <composite n="13" ref="H"/> + <composite n="2" ref="O"/> + <composite n="1" ref="N"/> + </material> + + <material name="RPCGasDefault" state="gas"> + <D type="density" value="0.0037" unit="g/cm3"/> + <composite n="209" ref="C"/> + <composite n="239" ref="H"/> + <composite n="381" ref="F"/> + </material> + + <material name="PolystyreneFoam"> + <D type="density" value="0.0056" unit="g/cm3"/> + <fraction n="1.0" ref="Polystyrene"/> + </material> + + <material name="Kapton"> + <D value="1.43" unit="g/cm3" /> + <composite n="22" ref="C"/> + <composite n="10" ref="H" /> + <composite n="2" ref="N" /> + <composite n="5" ref="O" /> + </material> + + <material name="PEEK"> + <D value="1.37" unit="g/cm3" /> + <composite n="19" ref="C"/> + <composite n="12" ref="H" /> + <composite n="3" ref="O" /> + </material> + + <material name="FR4"> + <D type="density" value="1.025*g/cm3" /> + <fraction n="0.18077359" ref="Si" /> + <fraction n="0.4056325" ref="O" /> + <fraction n="0.27804208" ref="C" /> + <fraction n="0.068442752" ref="H" /> + <fraction n="0.067109079" ref="Br" /> + </material> + + <material name="GEMGas" state="gas"> + <D type="density" value="0.0037" unit="g/cm3"/> + <composite n="209" ref="C"/> + <composite n="239" ref="H"/> + <composite n="381" ref="F"/> + </material> diff --git a/trackers/roman_pot.xml b/benchmarks/trackers/roman_pot.xml similarity index 97% rename from trackers/roman_pot.xml rename to benchmarks/trackers/roman_pot.xml index 0174a502..bbddf628 100644 --- a/trackers/roman_pot.xml +++ b/benchmarks/trackers/roman_pot.xml @@ -62,8 +62,7 @@ </display> <detectors> - <detector id = "1" name = "MyRomanPot" type = "RomanPot" readout = - "ForwardRomanPotHits" vis = "InvisibleWithDaughters"> + <detector id="1" name="MyRomanPot" type="RomanPot" readout="ForwardRomanPotHits" vis="InvisibleWithDaughters"> <dimensions x = "5.0*cm" y = "5.0*cm" delta = "0.05*mm" /> <frame x = "20.0*cm" y = "10.0*cm" z = "2*cm" /> <position z_offset = "20.0*m" rotation = "false" vmax = "10*cm" v = "0.5*cm" /> diff --git a/trackers/roman_pot_hit_eta.cxx b/benchmarks/trackers/roman_pot_hit_eta.cxx similarity index 100% rename from trackers/roman_pot_hit_eta.cxx rename to benchmarks/trackers/roman_pot_hit_eta.cxx diff --git a/trackers/roman_pot_simu.sh b/benchmarks/trackers/roman_pot_simu.sh similarity index 72% rename from trackers/roman_pot_simu.sh rename to benchmarks/trackers/roman_pot_simu.sh index bd1064ea..4520c52d 100755 --- a/trackers/roman_pot_simu.sh +++ b/benchmarks/trackers/roman_pot_simu.sh @@ -2,5 +2,5 @@ ddsim --runType batch -N 300 \ --inputFiles ./data/forward_ions.hepmc \ - --compactFile ./trackers/roman_pot.xml \ + --compactFile benchmarks/trackers/roman_pot.xml \ --outputFile ./sim_output/roman_pot_out.root diff --git a/trackers/simple_tracking.cxx b/benchmarks/trackers/simple_tracking.cxx similarity index 99% rename from trackers/simple_tracking.cxx rename to benchmarks/trackers/simple_tracking.cxx index fc2febb2..0afe2a11 100644 --- a/trackers/simple_tracking.cxx +++ b/benchmarks/trackers/simple_tracking.cxx @@ -116,7 +116,7 @@ void simple_tracking(const char* fname = "./sim_output/roman_pot_out.root"){ h0->DrawClone(); std::cout << *n0 << " events with nonzero hits\n"; - if(*n0<5) { + if(*n0<1) { std::quick_exit(1); } diff --git a/calorimeters/calorimeters_config.yml b/calorimeters/calorimeters_config.yml deleted file mode 100644 index 124d8c5b..00000000 --- a/calorimeters/calorimeters_config.yml +++ /dev/null @@ -1,150 +0,0 @@ -##################### -# Simulations -# - Generate datasets -# - Run Geant4 -# - Run Juggler -##################### - -sim_emcal_barrel_pions: - image: eicweb.phy.anl.gov:4567/eic/juggler/juggler:$JUGGLER_TAG - stage: simulate - tags: - - silicon - artifacts: - expire_in: 20 weeks - paths: - - results/ - - sim_output/ - script: - - bash calorimeters/run_emcal_barrel_pions.sh - allow_failure: true - -sim_emcal_barrel_electrons: - image: eicweb.phy.anl.gov:4567/eic/juggler/juggler:$JUGGLER_TAG - stage: simulate - tags: - - silicon - artifacts: - expire_in: 20 weeks - paths: - - results/ - - sim_output/ - script: - - bash calorimeters/run_emcal_barrel_electrons.sh - allow_failure: true - -crystal_emcal_simulation: - stage: simulate - needs: - - ["get_data"] - tags: - - silicon - script: - - cp NPDet/src/detectors/calorimeters/compact/elements.xml ./. - - cp NPDet/src/detectors/calorimeters/compact/materials.xml ./. - - bash calorimeters/run_simulation_crystal.sh - -crystal_pion_simulation: - stage: simulate - needs: - - ["get_data"] - tags: - - silicon - script: - - cd topside && ls -l - - npsim --runType batch --numberOfEvents 100 --compactFile topside.xml --inputFiles ../data/emcal_electrons.hepmc --outputFile ../sim_output/output_emcal_electrons.root - - #zdc_simulation: - #stage: simulate - #needs: - #- ["get_data"] - #tags: - #- silicon - #script: - #- cp NPDet/src/detectors/calorimeters/compact/elements.xml ./. - #- cp NPDet/src/detectors/calorimeters/compact/materials.xml ./. - #- bash calorimeters/run_simulation_zdc.sh - -################### -# Benchmarks -################### - -ben_emcal_barrel_pions: - stage: benchmarks - tags: - - silicon - artifacts: - expire_in: 20 weeks - paths: - - results/ - needs: - - ["sim_emcal_barrel_pions"] - script: - - ls -lrth sim_output - - root -b -q calorimeters/scripts/emcal_barrel_pions_analysis.cxx+ - allow_failure: true - -ben_emcal_barrel_electrons: - stage: benchmarks - tags: - - silicon - artifacts: - expire_in: 20 weeks - paths: - - results/ - needs: - - ["sim_emcal_barrel_electrons"] - script: - - ls -lrth sim_output - - root -b -q calorimeters/scripts/emcal_barrel_electrons_analysis.cxx+ - allow_failure: true - -crystal_benchmark: - stage: benchmarks - tags: - - silicon - needs: - - ["crystal_emcal_simulation"] - script: - - ls -lrth sim_output - - root -b -q calorimeters/simple_checking_crystal.cxx+ - allow_failure: true - - #cal_test_3_zdc_neutrons_reader: - #stage: benchmarks - #tags: - #- sodium - #script: - #- root -b -q calorimeters/zdc_neutrons_reader.cxx - #artifact: - # paths: - # - results/ - #allow_failure: true - - #zdc_benchmark: - #stage: benchmarks - #tags: - #- silicon - #needs: - #- ["zdc_simulation"] - #dependencies: - #- zdc_simulation - #script: - #- ls -lrth sim_output - #- root -b -q calorimeters/simple_checking.cxx+ - #allow_failure: true - - #zdc_benchmark_info_histogram: - #stage: benchmarks - #needs: - #- ["zdc_simulation"] - #tags: - #- silicon - #dependencies: - #- zdc_simulation - #script: - #- cp NPDet/src/detectors/calorimeters/compact/elements.xml calorimeters/ - #- cp NPDet/src/detectors/calorimeters/compact/materials.xml calorimeters/ - #- root -b -q calorimeters/simple_info_plot_histograms.cxx+ - #allow_failure: true - diff --git a/calorimeters/dummy_test.sh b/calorimeters/dummy_test.sh deleted file mode 100644 index 968865ec..00000000 --- a/calorimeters/dummy_test.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/bash - -echo "Calorimeter Dummy Test..." -echo "fails." - -exit 1 diff --git a/calorimeters/dummy_test2.sh b/calorimeters/dummy_test2.sh deleted file mode 100644 index bfca0dfa..00000000 --- a/calorimeters/dummy_test2.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/bash - -echo "Calorimeter Dummy Test 2..." -echo "passes." - -exit 0 diff --git a/calorimeters/edep_histo_zdc_photons.png b/calorimeters/edep_histo_zdc_photons.png deleted file mode 100644 index 5e1985b21c7316b75ea4c70b42c2927caff48a6f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14992 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 Xi+`bA@Z<q#F*$>$tDnm{r-UW|@j6%& diff --git a/calorimeters/hit_postion_histo_zdc_photons.png b/calorimeters/hit_postion_histo_zdc_photons.png deleted file mode 100644 index da904eaa47c323bf762c50f9722fc470cda94f4c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 37017 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%0A`W_@>D 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 G5}E+rt@O_T diff --git a/calorimeters/nhits_histo_zdc_photons.png b/calorimeters/nhits_histo_zdc_photons.png deleted file mode 100644 index 3e519f1ff0f7edef6c23d38aaede1b1cd0ecf8df..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15471 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 diff --git a/calorimeters/options/emcal_barrel_reco.py b/calorimeters/options/emcal_barrel_reco.py deleted file mode 100644 index 37565e42..00000000 --- a/calorimeters/options/emcal_barrel_reco.py +++ /dev/null @@ -1,63 +0,0 @@ -####################################### -# EMCAL Barrel detector Reconstruction -# J.KIM 04/02/2021 -####################################### - -from Gaudi.Configuration import * -import os - -from GaudiKernel.DataObjectHandleBase import DataObjectHandleBase -from Configurables import ApplicationMgr, EICDataSvc, PodioOutput, GeoSvc -from GaudiKernel import SystemOfUnits as units - -detector_name = "topside" -if "JUGGLER_DETECTOR" in os.environ : - detector_name = str(os.environ["JUGGLER_DETECTOR"])+"/"+detector_name - -input_sim_file = "jug_input.root" -if "JUGGLER_SIM_FILE" in os.environ : - input_sim_file = str(os.environ["JUGGLER_SIM_FILE"]) -else : - print(" ERROR : JUGGLER_SIM_FILE not set" ) - -output_rec_file = "jug_rec.root" -if "JUGGLER_REC_FILE" in os.environ : - output_rec_file = str(os.environ["JUGGLER_REC_FILE"]) -else : - print(" ERROR : JUGGLER_REC_FILE not set" ) - -n_events = 100 -if "JUGGLER_N_EVENTS" in os.environ : - n_events = str(os.environ["JUGGLER_N_EVENTS"]) - -geo_service = GeoSvc("GeoSvc", detectors=["{}.xml".format(detector_name)]) -podioevent = EICDataSvc("EventDataSvc", inputs=["sim_output/{}".format(input_sim_file)], OutputLevel=DEBUG) - -from Configurables import PodioInput -from Configurables import Jug__Base__InputCopier_dd4pod__Geant4ParticleCollection_dd4pod__Geant4ParticleCollection_ as MCCopier -from Configurables import Jug__Base__InputCopier_dd4pod__CalorimeterHitCollection_dd4pod__CalorimeterHitCollection_ as CalCopier - -podioinput = PodioInput("PodioReader", collections=["mcparticles","EcalBarrelAstroPixHits"], OutputLevel=DEBUG) - -# Thrown Information -copier = MCCopier("MCCopier", - inputCollection="mcparticles", - outputCollection="mcparticles2", - OutputLevel=DEBUG) -# Geant4 Information -embarrelcopier = CalCopier("CalBarrelCopier", - inputCollection="EcalBarrelAstroPixHits", - outputCollection="EcalBarrelAstroPixHits2", - OutputLevel=DEBUG) - -out = PodioOutput("out", filename=output_rec_file) - -out.outputCommands = ["keep *"] - -ApplicationMgr( - TopAlg = [podioinput, copier, embarrelcopier, out], - EvtSel = 'NONE', - EvtMax = n_events, - ExtSvc = [podioevent], - OutputLevel=DEBUG - ) diff --git a/calorimeters/scripts/emcal_barrel_electrons_analysis.cxx b/calorimeters/scripts/emcal_barrel_electrons_analysis.cxx deleted file mode 100644 index 06c5ee80..00000000 --- a/calorimeters/scripts/emcal_barrel_electrons_analysis.cxx +++ /dev/null @@ -1,124 +0,0 @@ -//////////////////////////////////////// -// Read reconstruction ROOT output file -// Plot variables -//////////////////////////////////////// - -#include "ROOT/RDataFrame.hxx" -#include <iostream> - -#include "dd4pod/Geant4ParticleCollection.h" -#include "dd4pod/CalorimeterHitCollection.h" - -#include "TCanvas.h" -#include "TStyle.h" -#include "TMath.h" -#include "TH1.h" -#include "TF1.h" -#include "TH1D.h" - -using ROOT::RDataFrame; -using namespace ROOT::VecOps; - -void emcal_barrel_electrons_analysis(const char* input_fname = "sim_output/rec_emcal_barrel_uniform_electrons.root") -{ - // Setting for graphs - gROOT->SetStyle("Plain"); - gStyle->SetOptFit(1); - gStyle->SetLineWidth(2); - gStyle->SetPadTickX(1); - gStyle->SetPadTickY(1); - gStyle->SetPadGridX(1); - gStyle->SetPadGridY(1); - gStyle->SetPadLeftMargin(0.14); - gStyle->SetPadRightMargin(0.14); - - ROOT::EnableImplicitMT(); - ROOT::RDataFrame d0("events", input_fname); - - // Thrown Energy [GeV] - auto Ethr = [](std::vector<dd4pod::Geant4ParticleData> const& input) { - std::vector<double> result; - result.push_back(TMath::Sqrt(input[2].psx*input[2].psx + input[2].psy*input[2].psy + input[2].psz*input[2].psz + input[2].mass*input[2].mass)); - return result; - }; - - // Number of hits - auto nhits = [] (const std::vector<dd4pod::CalorimeterHitData>& evt) {return (int) evt.size(); }; - - // Energy deposition [GeV] - auto Esim = [](const std::vector<dd4pod::CalorimeterHitData>& evt) { - std::vector<double> result; - auto total_edep = 0.0; - for (const auto& i: evt) - total_edep += i.energyDeposit; - result.push_back(total_edep); - return result; - }; - - // Sampling fraction = Esampling / Ethrown - auto fsam = [](const std::vector<double>& sampled, const std::vector<double>& thrown) { - std::vector<double> result; - for (const auto& E1 : thrown) { - for (const auto& E2 : sampled) - result.push_back(E2/E1); - } - return result; - }; - - // Define variables - auto d1 = d0.Define("Ethr", Ethr, {"mcparticles2"}) - .Define("nhits", nhits, {"EcalBarrelAstroPixHits2"}) - .Define("Esim", Esim, {"EcalBarrelAstroPixHits2"}) - .Define("fsam", fsam, {"Esim","Ethr"}) - ; - - // Define Histograms - auto hEthr = d1.Histo1D({"hEthr", "Thrown Energy; Thrown Energy [GeV]; Events", 100, 0.0, 7.5}, "Ethr"); - auto hNhits = d1.Histo1D({"hNhits", "Number of hits per events; Number of hits; Events", 100, 0.0, 2000.0}, "nhits"); - auto hEsim = d1.Histo1D({"hEsim", "Energy Deposit; Energy Deposit [GeV]; Events", 100, 0.0, 1.0}, "Esim"); - auto hfsam = d1.Histo1D({"hfsam", "Sampling Fraction; Sampling Fraction; Events", 100, 0.0, 0.1}, "fsam"); - - // Event Counts - auto nevents_thrown = d1.Count(); - std::cout << "Number of Thrown Events: " << (*nevents_thrown) << "\n"; - - // Draw Histograms - TCanvas *c1 = new TCanvas("c1", "c1", 700, 500); - c1->SetLogy(1); - hEthr->GetYaxis()->SetTitleOffset(1.4); - hEthr->SetLineWidth(2); - hEthr->SetLineColor(kBlue); - hEthr->DrawClone(); - c1->SaveAs("results/emcal_barrel_electrons_Ethr.png"); - c1->SaveAs("results/emcal_barrel_electrons_Ethr.pdf"); - - TCanvas *c2 = new TCanvas("c2", "c2", 700, 500); - c2->SetLogy(1); - hNhits->GetYaxis()->SetTitleOffset(1.4); - hNhits->SetLineWidth(2); - hNhits->SetLineColor(kBlue); - hNhits->DrawClone(); - c2->SaveAs("results/emcal_barrel_electrons_nhits.png"); - c2->SaveAs("results/emcal_barrel_electrons_nhits.pdf"); - - TCanvas *c3 = new TCanvas("c3", "c3", 700, 500); - c3->SetLogy(1); - hEsim->GetYaxis()->SetTitleOffset(1.4); - hEsim->SetLineWidth(2); - hEsim->SetLineColor(kBlue); - hEsim->DrawClone(); - c3->SaveAs("results/emcal_barrel_electrons_Esim.png"); - c3->SaveAs("results/emcal_barrel_electrons_Esim.pdf"); - - TCanvas *c4 = new TCanvas("c4", "c4", 700, 500); - c4->SetLogy(1); - hfsam->GetYaxis()->SetTitleOffset(1.4); - hfsam->SetLineWidth(2); - hfsam->SetLineColor(kBlue); - hfsam->Fit("gaus","","",0.01,0.1); - hfsam->GetFunction("gaus")->SetLineWidth(2); - hfsam->GetFunction("gaus")->SetLineColor(kRed); - hfsam->DrawClone(); - c4->SaveAs("results/emcal_barrel_electrons_fsam.png"); - c4->SaveAs("results/emcal_barrel_electrons_fsam.pdf"); -} diff --git a/calorimeters/volID_histo_zdc_photons.png b/calorimeters/volID_histo_zdc_photons.png deleted file mode 100644 index 81479c86ae858b78e474bbb9c409ae0da1606763..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16504 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 D7SKX| diff --git a/include/benchmark.h b/include/benchmark.h new file mode 100644 index 00000000..0634be0a --- /dev/null +++ b/include/benchmark.h @@ -0,0 +1,124 @@ +#ifndef BENCHMARK_H +#define BENCHMARK_H + +#include "exception.h" +#include <fmt/core.h> +#include <fstream> +#include <iomanip> +#include <iostream> +#include <nlohmann/json.hpp> +#include <string> + +// Bookkeeping of test data to store data of one or more tests in a json file to +// facilitate future accounting. +// +// Usage Example 1 (single test): +// ============================== +// 1. define our test +// eic::util::Test test1{ +// {{"name", "example_test"}, +// {"title", "Example Test"}, +// {"description", "This is an example of a test definition"}, +// {"quantity", "efficiency"}, +// {"target", "1"}}}; +// 2. set pass/fail/error status and return value (in this case .99) +// test1.pass(0.99) +// 3. write our test data to a json file +// eic::util::write_test(test1, "test1.json"); +// +// Usage Example 2 (multiple tests): +// ================================= +// 1. define our tests +// eic::util::Test test1{ +// {{"name", "example_test"}, +// {"title", "Example Test"}, +// {"description", "This is an example of a test definition"}, +// {"quantity", "efficiency"}, +// {"target", "1"}}}; +// eic::util::Test test2{ +// {{"name", "another_test"}, +// {"title", "Another example Test"}, +// {"description", "This is a second example of a test definition"}, +// {"quantity", "resolution"}, +// {"target", "3."}}}; +// 2. set pass/fail/error status and return value (in this case .99) +// test1.fail(10) +// 3. write our test data to a json file +// eic::util::write_test({test1, test2}, "test.json"); + +// Namespace for utility scripts, FIXME this should be part of an independent +// library +namespace eic::util { + + struct TestDefinitionError : Exception { + TestDefinitionError(std::string_view msg) : Exception(msg, "test_definition_error") {} + }; + + // Wrapper for our test data json, with three methods to set the status + // after test completion (to pass, fail or error). The default value + // is error. + // The following fields should be defined in the definitions json + // for the test to make sense: + // - name: unique identifier for this test + // - title: Slightly more verbose identifier for this test + // - description: Concise description of what is tested + // - quantity: What quantity is tested? Unites of value/target + // - target: Target value of <quantity> that we want to reach + // - value: Actual value of <quantity> + // - weight: Weight for this test (this is defaulted to 1.0 if not specified) + // - result: pass/fail/error + struct Test { + // note: round braces for the json constructor, as else it will pick the wrong + // initializer-list constructur (it will put everything in an array) + Test(const std::map<std::string, std::string>& definition) : json(definition) + { + // std::cout << json.dump() << std::endl; + // initialize with error (as we don't have a value yet) + error(); + // Check that all required fields are present + for (const auto& field : + {"name", "title", "description", "quantity", "target", "value", "result"}) { + if (json.find(field) == json.end()) { + throw TestDefinitionError{ + fmt::format("Error in test definition: field '{}' missing", field)}; + } + } + // Default "weight" to 1 if not set + if (json.find("weight") == json.end()) { + json["weight"] = 1.0; + } + } + // Set this test to pass/fail/error + void pass(double value) { update_result("pass", value); } + void fail(double value) { update_result("fail", value); } + void error(double value = 0) { update_result("error", value); } + + nlohmann::json json; + + private: + void update_result(std::string_view status, double value) + { + json["result"] = status; + json["value"] = value; + } + }; + + void write_test(const std::vector<Test>& data, const std::string& fname) + { + nlohmann::json test; + for (auto& entry : data) { + test["tests"].push_back(entry.json); + } + std::cout << fmt::format("Writing test data to {}\n", fname); + std::ofstream output_file(fname); + output_file << std::setw(4) << test << "\n"; + } + void write_test(const Test& data, const std::string& fname) + { + std::vector<Test> vtd{data}; + write_test(vtd, fname); + } + +} // namespace eic::util + +#endif diff --git a/include/clipp.h b/include/clipp.h new file mode 100644 index 00000000..a2a0cf8b --- /dev/null +++ b/include/clipp.h @@ -0,0 +1,6248 @@ +/***************************************************************************** + * + * CLIPP - command line interfaces for modern C++ + * + * released under MIT license + * + * (c) 2017 André Müller; foss@andremueller-online.de + * + *****************************************************************************/ + +#ifndef AM_CLIPP_H__ +#define AM_CLIPP_H__ + +#include <cstring> +#include <string> +#include <cstdlib> +#include <cstring> +#include <cctype> +#include <cmath> +#include <memory> +#include <vector> +#include <limits> +#include <stack> +#include <algorithm> +#include <sstream> +#include <utility> +#include <iterator> +#include <functional> + + +/*************************************************************************//** + * + * @brief primary namespace + * + *****************************************************************************/ +namespace clipp { + + + +/***************************************************************************** + * + * basic constants and datatype definitions + * + *****************************************************************************/ +using arg_index = int; + +using arg_string = std::string; +using doc_string = std::string; + +using arg_list = std::vector<arg_string>; + + + +/*************************************************************************//** + * + * @brief tristate + * + *****************************************************************************/ +enum class tri : char { no, yes, either }; + +inline constexpr bool operator == (tri t, bool b) noexcept { + return b ? t != tri::no : t != tri::yes; +} +inline constexpr bool operator == (bool b, tri t) noexcept { return (t == b); } +inline constexpr bool operator != (tri t, bool b) noexcept { return !(t == b); } +inline constexpr bool operator != (bool b, tri t) noexcept { return !(t == b); } + + + +/*************************************************************************//** + * + * @brief (start,size) index range + * + *****************************************************************************/ +class subrange { +public: + using size_type = arg_string::size_type; + + /** @brief default: no match */ + explicit constexpr + subrange() noexcept : + at_{arg_string::npos}, length_{0} + {} + + /** @brief match length & position within subject string */ + explicit constexpr + subrange(size_type pos, size_type len) noexcept : + at_{pos}, length_{len} + {} + + /** @brief position of the match within the subject string */ + constexpr size_type at() const noexcept { return at_; } + /** @brief length of the matching subsequence */ + constexpr size_type length() const noexcept { return length_; } + + /** @brief returns true, if query string is a prefix of the subject string */ + constexpr bool prefix() const noexcept { + return at_ == 0 && length_ > 0; + } + + /** @brief returns true, if query is a substring of the query string */ + constexpr explicit operator bool () const noexcept { + return at_ != arg_string::npos && length_ > 0; + } + +private: + size_type at_; + size_type length_; +}; + + + +/*************************************************************************//** + * + * @brief match predicates + * + *****************************************************************************/ +using match_predicate = std::function<bool(const arg_string&)>; +using match_function = std::function<subrange(const arg_string&)>; + + + + + + +/*************************************************************************//** + * + * @brief type traits (NOT FOR DIRECT USE IN CLIENT CODE!) + * no interface guarantees; might be changed or removed in the future + * + *****************************************************************************/ +namespace traits { + +/*************************************************************************//** + * + * @brief function (class) signature type trait + * + *****************************************************************************/ +template<class Fn, class Ret, class... Args> +constexpr auto +check_is_callable(int) -> decltype( + std::declval<Fn>()(std::declval<Args>()...), + std::integral_constant<bool, + std::is_same<Ret,typename std::result_of<Fn(Args...)>::type>::value>{} ); + +template<class,class,class...> +constexpr auto +check_is_callable(long) -> std::false_type; + +template<class Fn, class Ret> +constexpr auto +check_is_callable_without_arg(int) -> decltype( + std::declval<Fn>()(), + std::integral_constant<bool, + std::is_same<Ret,typename std::result_of<Fn()>::type>::value>{} ); + +template<class,class> +constexpr auto +check_is_callable_without_arg(long) -> std::false_type; + + + +template<class Fn, class... Args> +constexpr auto +check_is_void_callable(int) -> decltype( + std::declval<Fn>()(std::declval<Args>()...), std::true_type{}); + +template<class,class,class...> +constexpr auto +check_is_void_callable(long) -> std::false_type; + +template<class Fn> +constexpr auto +check_is_void_callable_without_arg(int) -> decltype( + std::declval<Fn>()(), std::true_type{}); + +template<class> +constexpr auto +check_is_void_callable_without_arg(long) -> std::false_type; + + + +template<class Fn, class Ret> +struct is_callable; + + +template<class Fn, class Ret, class... Args> +struct is_callable<Fn, Ret(Args...)> : + decltype(check_is_callable<Fn,Ret,Args...>(0)) +{}; + +template<class Fn, class Ret> +struct is_callable<Fn,Ret()> : + decltype(check_is_callable_without_arg<Fn,Ret>(0)) +{}; + + +template<class Fn, class... Args> +struct is_callable<Fn, void(Args...)> : + decltype(check_is_void_callable<Fn,Args...>(0)) +{}; + +template<class Fn> +struct is_callable<Fn,void()> : + decltype(check_is_void_callable_without_arg<Fn>(0)) +{}; + + + +/*************************************************************************//** + * + * @brief input range type trait + * + *****************************************************************************/ +template<class T> +constexpr auto +check_is_input_range(int) -> decltype( + begin(std::declval<T>()), end(std::declval<T>()), + std::true_type{}); + +template<class T> +constexpr auto +check_is_input_range(char) -> decltype( + std::begin(std::declval<T>()), std::end(std::declval<T>()), + std::true_type{}); + +template<class> +constexpr auto +check_is_input_range(long) -> std::false_type; + +template<class T> +struct is_input_range : + decltype(check_is_input_range<T>(0)) +{}; + + + +/*************************************************************************//** + * + * @brief size() member type trait + * + *****************************************************************************/ +template<class T> +constexpr auto +check_has_size_getter(int) -> + decltype(std::declval<T>().size(), std::true_type{}); + +template<class> +constexpr auto +check_has_size_getter(long) -> std::false_type; + +template<class T> +struct has_size_getter : + decltype(check_has_size_getter<T>(0)) +{}; + +} // namespace traits + + + + + + +/*************************************************************************//** + * + * @brief helpers (NOT FOR DIRECT USE IN CLIENT CODE!) + * no interface guarantees; might be changed or removed in the future + * + *****************************************************************************/ +namespace detail { + + +/*************************************************************************//** + * @brief forwards string to first non-whitespace char; + * std string -> unsigned conv yields max value, but we want 0; + * also checks for nullptr + *****************************************************************************/ +inline bool +fwd_to_unsigned_int(const char*& s) +{ + if(!s) return false; + for(; std::isspace(*s); ++s); + if(!s[0] || s[0] == '-') return false; + if(s[0] == '-') return false; + return true; +} + + +/*************************************************************************//** + * + * @brief value limits clamping + * + *****************************************************************************/ +template<class T, class V, bool = (sizeof(V) > sizeof(T))> +struct limits_clamped { + static T from(const V& v) { + if(v > V(std::numeric_limits<T>::max())) { + return std::numeric_limits<T>::max(); + } + if(v < V(std::numeric_limits<T>::lowest())) { + return std::numeric_limits<T>::lowest(); + } + return T(v); + } +}; + +template<class T, class V> +struct limits_clamped<T,V,false> { + static T from(const V& v) { return T(v); } +}; + + +/*************************************************************************//** + * + * @brief returns value of v as a T, clamped at T's maximum + * + *****************************************************************************/ +template<class T, class V> +inline T clamped_on_limits(const V& v) { + return limits_clamped<T,V>::from(v); +} + + + + +/*************************************************************************//** + * + * @brief type conversion helpers + * + *****************************************************************************/ +template<class T> +struct make; + +template<> +struct make<bool> { + static inline bool from(const char* s) { + if(!s) return false; + return static_cast<bool>(s); + } +}; + +template<> +struct make<unsigned char> { + static inline unsigned char from(const char* s) { + if(!fwd_to_unsigned_int(s)) return (0); + return clamped_on_limits<unsigned char>(std::strtoull(s,nullptr,10)); + } +}; + +template<> +struct make<unsigned short int> { + static inline unsigned short int from(const char* s) { + if(!fwd_to_unsigned_int(s)) return (0); + return clamped_on_limits<unsigned short int>(std::strtoull(s,nullptr,10)); + } +}; + +template<> +struct make<unsigned int> { + static inline unsigned int from(const char* s) { + if(!fwd_to_unsigned_int(s)) return (0); + return clamped_on_limits<unsigned int>(std::strtoull(s,nullptr,10)); + } +}; + +template<> +struct make<unsigned long int> { + static inline unsigned long int from(const char* s) { + if(!fwd_to_unsigned_int(s)) return (0); + return clamped_on_limits<unsigned long int>(std::strtoull(s,nullptr,10)); + } +}; + +template<> +struct make<unsigned long long int> { + static inline unsigned long long int from(const char* s) { + if(!fwd_to_unsigned_int(s)) return (0); + return clamped_on_limits<unsigned long long int>(std::strtoull(s,nullptr,10)); + } +}; + +template<> +struct make<char> { + static inline char from(const char* s) { + //parse as single character? + const auto n = std::strlen(s); + if(n == 1) return s[0]; + //parse as integer + return clamped_on_limits<char>(std::strtoll(s,nullptr,10)); + } +}; + +template<> +struct make<short int> { + static inline short int from(const char* s) { + return clamped_on_limits<short int>(std::strtoll(s,nullptr,10)); + } +}; + +template<> +struct make<int> { + static inline int from(const char* s) { + return clamped_on_limits<int>(std::strtoll(s,nullptr,10)); + } +}; + +template<> +struct make<long int> { + static inline long int from(const char* s) { + return clamped_on_limits<long int>(std::strtoll(s,nullptr,10)); + } +}; + +template<> +struct make<long long int> { + static inline long long int from(const char* s) { + return (std::strtoll(s,nullptr,10)); + } +}; + +template<> +struct make<float> { + static inline float from(const char* s) { + return (std::strtof(s,nullptr)); + } +}; + +template<> +struct make<double> { + static inline double from(const char* s) { + return (std::strtod(s,nullptr)); + } +}; + +template<> +struct make<long double> { + static inline long double from(const char* s) { + return (std::strtold(s,nullptr)); + } +}; + +template<> +struct make<std::string> { + static inline std::string from(const char* s) { + return std::string(s); + } +}; + + + +/*************************************************************************//** + * + * @brief assigns boolean constant to one or multiple target objects + * + *****************************************************************************/ +template<class T, class V = T> +class assign_value +{ +public: + template<class X> + explicit constexpr + assign_value(T& target, X&& value) noexcept : + t_{std::addressof(target)}, v_{std::forward<X>(value)} + {} + + void operator () () const { + if(t_) *t_ = v_; + } + +private: + T* t_; + V v_; +}; + + + +/*************************************************************************//** + * + * @brief flips bools + * + *****************************************************************************/ +class flip_bool +{ +public: + explicit constexpr + flip_bool(bool& target) noexcept : + b_{&target} + {} + + void operator () () const { + if(b_) *b_ = !*b_; + } + +private: + bool* b_; +}; + + + +/*************************************************************************//** + * + * @brief increments using operator ++ + * + *****************************************************************************/ +template<class T> +class increment +{ +public: + explicit constexpr + increment(T& target) noexcept : t_{std::addressof(target)} {} + + void operator () () const { + if(t_) ++(*t_); + } + +private: + T* t_; +}; + + + +/*************************************************************************//** + * + * @brief decrements using operator -- + * + *****************************************************************************/ +template<class T> +class decrement +{ +public: + explicit constexpr + decrement(T& target) noexcept : t_{std::addressof(target)} {} + + void operator () () const { + if(t_) --(*t_); + } + +private: + T* t_; +}; + + + +/*************************************************************************//** + * + * @brief increments by a fixed amount using operator += + * + *****************************************************************************/ +template<class T> +class increment_by +{ +public: + explicit constexpr + increment_by(T& target, T by) noexcept : + t_{std::addressof(target)}, by_{std::move(by)} + {} + + void operator () () const { + if(t_) (*t_) += by_; + } + +private: + T* t_; + T by_; +}; + + + + +/*************************************************************************//** + * + * @brief makes a value from a string and assigns it to an object + * + *****************************************************************************/ +template<class T> +class map_arg_to +{ +public: + explicit constexpr + map_arg_to(T& target) noexcept : t_{std::addressof(target)} {} + + void operator () (const char* s) const { + if(t_ && s && (std::strlen(s) > 0)) + *t_ = detail::make<T>::from(s); + } + +private: + T* t_; +}; + + +//------------------------------------------------------------------- +/** + * @brief specialization for vectors: append element + */ +template<class T> +class map_arg_to<std::vector<T>> +{ +public: + map_arg_to(std::vector<T>& target): t_{std::addressof(target)} {} + + void operator () (const char* s) const { + if(t_ && s) t_->push_back(detail::make<T>::from(s)); + } + +private: + std::vector<T>* t_; +}; + + +//------------------------------------------------------------------- +/** + * @brief specialization for bools: + * set to true regardless of string content + */ +template<> +class map_arg_to<bool> +{ +public: + map_arg_to(bool& target): t_{&target} {} + + void operator () (const char* s) const { + if(t_ && s) *t_ = true; + } + +private: + bool* t_; +}; + + +} // namespace detail + + + + + + +/*************************************************************************//** + * + * @brief string matching and processing tools + * + *****************************************************************************/ + +namespace str { + + +/*************************************************************************//** + * + * @brief converts string to value of target type 'T' + * + *****************************************************************************/ +template<class T> +T make(const arg_string& s) +{ + return detail::make<T>::from(s); +} + + + +/*************************************************************************//** + * + * @brief removes trailing whitespace from string + * + *****************************************************************************/ +template<class C, class T, class A> +inline void +trimr(std::basic_string<C,T,A>& s) +{ + if(s.empty()) return; + + s.erase( + std::find_if_not(s.rbegin(), s.rend(), + [](char c) { return std::isspace(c);} ).base(), + s.end() ); +} + + +/*************************************************************************//** + * + * @brief removes leading whitespace from string + * + *****************************************************************************/ +template<class C, class T, class A> +inline void +triml(std::basic_string<C,T,A>& s) +{ + if(s.empty()) return; + + s.erase( + s.begin(), + std::find_if_not(s.begin(), s.end(), + [](char c) { return std::isspace(c);}) + ); +} + + +/*************************************************************************//** + * + * @brief removes leading and trailing whitespace from string + * + *****************************************************************************/ +template<class C, class T, class A> +inline void +trim(std::basic_string<C,T,A>& s) +{ + triml(s); + trimr(s); +} + + +/*************************************************************************//** + * + * @brief removes all whitespaces from string + * + *****************************************************************************/ +template<class C, class T, class A> +inline void +remove_ws(std::basic_string<C,T,A>& s) +{ + if(s.empty()) return; + + s.erase(std::remove_if(s.begin(), s.end(), + [](char c) { return std::isspace(c); }), + s.end() ); +} + + +/*************************************************************************//** + * + * @brief returns true, if the 'prefix' argument + * is a prefix of the 'subject' argument + * + *****************************************************************************/ +template<class C, class T, class A> +inline bool +has_prefix(const std::basic_string<C,T,A>& subject, + const std::basic_string<C,T,A>& prefix) +{ + if(prefix.size() > subject.size()) return false; + return subject.find(prefix) == 0; +} + + +/*************************************************************************//** + * + * @brief returns true, if the 'postfix' argument + * is a postfix of the 'subject' argument + * + *****************************************************************************/ +template<class C, class T, class A> +inline bool +has_postfix(const std::basic_string<C,T,A>& subject, + const std::basic_string<C,T,A>& postfix) +{ + if(postfix.size() > subject.size()) return false; + return (subject.size() - postfix.size()) == subject.find(postfix); +} + + + +/*************************************************************************//** +* +* @brief returns longest common prefix of several +* sequential random access containers +* +* @details InputRange require begin and end (member functions or overloads) +* the elements of InputRange require a size() member +* +*****************************************************************************/ +template<class InputRange> +auto +longest_common_prefix(const InputRange& strs) + -> typename std::decay<decltype(*begin(strs))>::type +{ + static_assert(traits::is_input_range<InputRange>(), + "parameter must satisfy the InputRange concept"); + + static_assert(traits::has_size_getter< + typename std::decay<decltype(*begin(strs))>::type>(), + "elements of input range must have a ::size() member function"); + + using std::begin; + using std::end; + + using item_t = typename std::decay<decltype(*begin(strs))>::type; + using str_size_t = typename std::decay<decltype(begin(strs)->size())>::type; + + const auto n = size_t(distance(begin(strs), end(strs))); + if(n < 1) return item_t(""); + if(n == 1) return *begin(strs); + + //length of shortest string + auto m = std::min_element(begin(strs), end(strs), + [](const item_t& a, const item_t& b) { + return a.size() < b.size(); })->size(); + + //check each character until we find a mismatch + for(str_size_t i = 0; i < m; ++i) { + for(str_size_t j = 1; j < n; ++j) { + if(strs[j][i] != strs[j-1][i]) + return strs[0].substr(0, i); + } + } + return strs[0].substr(0, m); +} + + + +/*************************************************************************//** + * + * @brief returns longest substring range that could be found in 'arg' + * + * @param arg string to be searched in + * @param substrings range of candidate substrings + * + *****************************************************************************/ +template<class C, class T, class A, class InputRange> +subrange +longest_substring_match(const std::basic_string<C,T,A>& arg, + const InputRange& substrings) +{ + using string_t = std::basic_string<C,T,A>; + + static_assert(traits::is_input_range<InputRange>(), + "parameter must satisfy the InputRange concept"); + + static_assert(std::is_same<string_t, + typename std::decay<decltype(*begin(substrings))>::type>(), + "substrings must have same type as 'arg'"); + + auto i = string_t::npos; + auto n = string_t::size_type(0); + for(const auto& s : substrings) { + auto j = arg.find(s); + if(j != string_t::npos && s.size() > n) { + i = j; + n = s.size(); + } + } + return subrange{i,n}; +} + + + +/*************************************************************************//** + * + * @brief returns longest prefix range that could be found in 'arg' + * + * @param arg string to be searched in + * @param prefixes range of candidate prefix strings + * + *****************************************************************************/ +template<class C, class T, class A, class InputRange> +subrange +longest_prefix_match(const std::basic_string<C,T,A>& arg, + const InputRange& prefixes) +{ + using string_t = std::basic_string<C,T,A>; + using s_size_t = typename string_t::size_type; + + static_assert(traits::is_input_range<InputRange>(), + "parameter must satisfy the InputRange concept"); + + static_assert(std::is_same<string_t, + typename std::decay<decltype(*begin(prefixes))>::type>(), + "prefixes must have same type as 'arg'"); + + auto i = string_t::npos; + auto n = s_size_t(0); + for(const auto& s : prefixes) { + auto j = arg.find(s); + if(j == 0 && s.size() > n) { + i = 0; + n = s.size(); + } + } + return subrange{i,n}; +} + + + +/*************************************************************************//** + * + * @brief returns the first occurrence of 'query' within 'subject' + * + *****************************************************************************/ +template<class C, class T, class A> +inline subrange +substring_match(const std::basic_string<C,T,A>& subject, + const std::basic_string<C,T,A>& query) +{ + if(subject.empty() || query.empty()) return subrange{}; + auto i = subject.find(query); + if(i == std::basic_string<C,T,A>::npos) return subrange{}; + return subrange{i,query.size()}; +} + + + +/*************************************************************************//** + * + * @brief returns first substring match (pos,len) within the input string + * that represents a number + * (with at maximum one decimal point and digit separators) + * + *****************************************************************************/ +template<class C, class T, class A> +subrange +first_number_match(std::basic_string<C,T,A> s, + C digitSeparator = C(','), + C decimalPoint = C('.'), + C exponential = C('e')) +{ + using string_t = std::basic_string<C,T,A>; + + str::trim(s); + if(s.empty()) return subrange{}; + + auto i = s.find_first_of("0123456789+-"); + if(i == string_t::npos) { + i = s.find(decimalPoint); + if(i == string_t::npos) return subrange{}; + } + + bool point = false; + bool sep = false; + auto exp = string_t::npos; + auto j = i + 1; + for(; j < s.size(); ++j) { + if(s[j] == digitSeparator) { + if(!sep) sep = true; else break; + } + else { + sep = false; + if(s[j] == decimalPoint) { + //only one decimal point before exponent allowed + if(!point && exp == string_t::npos) point = true; else break; + } + else if(std::tolower(s[j]) == std::tolower(exponential)) { + //only one exponent separator allowed + if(exp == string_t::npos) exp = j; else break; + } + else if(exp != string_t::npos && (exp+1) == j) { + //only sign or digit after exponent separator + if(s[j] != '+' && s[j] != '-' && !std::isdigit(s[j])) break; + } + else if(!std::isdigit(s[j])) { + break; + } + } + } + + //if length == 1 then must be a digit + if(j-i == 1 && !std::isdigit(s[i])) return subrange{}; + + return subrange{i,j-i}; +} + + + +/*************************************************************************//** + * + * @brief returns first substring match (pos,len) + * that represents an integer (with optional digit separators) + * + *****************************************************************************/ +template<class C, class T, class A> +subrange +first_integer_match(std::basic_string<C,T,A> s, + C digitSeparator = C(',')) +{ + using string_t = std::basic_string<C,T,A>; + + str::trim(s); + if(s.empty()) return subrange{}; + + auto i = s.find_first_of("0123456789+-"); + if(i == string_t::npos) return subrange{}; + + bool sep = false; + auto j = i + 1; + for(; j < s.size(); ++j) { + if(s[j] == digitSeparator) { + if(!sep) sep = true; else break; + } + else { + sep = false; + if(!std::isdigit(s[j])) break; + } + } + + //if length == 1 then must be a digit + if(j-i == 1 && !std::isdigit(s[i])) return subrange{}; + + return subrange{i,j-i}; +} + + + +/*************************************************************************//** + * + * @brief returns true if candidate string represents a number + * + *****************************************************************************/ +template<class C, class T, class A> +bool represents_number(const std::basic_string<C,T,A>& candidate, + C digitSeparator = C(','), + C decimalPoint = C('.'), + C exponential = C('e')) +{ + const auto match = str::first_number_match(candidate, digitSeparator, + decimalPoint, exponential); + + return (match && match.length() == candidate.size()); +} + + + +/*************************************************************************//** + * + * @brief returns true if candidate string represents an integer + * + *****************************************************************************/ +template<class C, class T, class A> +bool represents_integer(const std::basic_string<C,T,A>& candidate, + C digitSeparator = C(',')) +{ + const auto match = str::first_integer_match(candidate, digitSeparator); + return (match && match.length() == candidate.size()); +} + +} // namespace str + + + + + + +/*************************************************************************//** + * + * @brief makes function object with a const char* parameter + * that assigns a value to a ref-captured object + * + *****************************************************************************/ +template<class T, class V> +inline detail::assign_value<T,V> +set(T& target, V value) { + return detail::assign_value<T>{target, std::move(value)}; +} + + + +/*************************************************************************//** + * + * @brief makes parameter-less function object + * that assigns value(s) to a ref-captured object; + * value(s) are obtained by converting the const char* argument to + * the captured object types; + * bools are always set to true if the argument is not nullptr + * + *****************************************************************************/ +template<class T> +inline detail::map_arg_to<T> +set(T& target) { + return detail::map_arg_to<T>{target}; +} + + + +/*************************************************************************//** + * + * @brief makes function object that sets a bool to true + * + *****************************************************************************/ +inline detail::assign_value<bool> +set(bool& target) { + return detail::assign_value<bool>{target,true}; +} + +/*************************************************************************//** + * + * @brief makes function object that sets a bool to false + * + *****************************************************************************/ +inline detail::assign_value<bool> +unset(bool& target) { + return detail::assign_value<bool>{target,false}; +} + +/*************************************************************************//** + * + * @brief makes function object that flips the value of a ref-captured bool + * + *****************************************************************************/ +inline detail::flip_bool +flip(bool& b) { + return detail::flip_bool(b); +} + + + + + +/*************************************************************************//** + * + * @brief makes function object that increments using operator ++ + * + *****************************************************************************/ +template<class T> +inline detail::increment<T> +increment(T& target) { + return detail::increment<T>{target}; +} + +/*************************************************************************//** + * + * @brief makes function object that decrements using operator -- + * + *****************************************************************************/ +template<class T> +inline detail::increment_by<T> +increment(T& target, T by) { + return detail::increment_by<T>{target, std::move(by)}; +} + +/*************************************************************************//** + * + * @brief makes function object that increments by a fixed amount using operator += + * + *****************************************************************************/ +template<class T> +inline detail::decrement<T> +decrement(T& target) { + return detail::decrement<T>{target}; +} + + + + + + +/*************************************************************************//** + * + * @brief helpers (NOT FOR DIRECT USE IN CLIENT CODE!) + * + *****************************************************************************/ +namespace detail { + + +/*************************************************************************//** + * + * @brief mixin that provides action definition and execution + * + *****************************************************************************/ +template<class Derived> +class action_provider +{ +private: + //--------------------------------------------------------------- + using simple_action = std::function<void()>; + using arg_action = std::function<void(const char*)>; + using index_action = std::function<void(int)>; + + //----------------------------------------------------- + class simple_action_adapter { + public: + simple_action_adapter() = default; + simple_action_adapter(const simple_action& a): action_(a) {} + simple_action_adapter(simple_action&& a): action_(std::move(a)) {} + void operator() (const char*) const { action_(); } + void operator() (int) const { action_(); } + private: + simple_action action_; + }; + + +public: + //--------------------------------------------------------------- + /** @brief adds an action that has an operator() that is callable + * with a 'const char*' argument */ + Derived& + call(arg_action a) { + argActions_.push_back(std::move(a)); + return *static_cast<Derived*>(this); + } + + /** @brief adds an action that has an operator()() */ + Derived& + call(simple_action a) { + argActions_.push_back(simple_action_adapter(std::move(a))); + return *static_cast<Derived*>(this); + } + + /** @brief adds an action that has an operator() that is callable + * with a 'const char*' argument */ + Derived& operator () (arg_action a) { return call(std::move(a)); } + + /** @brief adds an action that has an operator()() */ + Derived& operator () (simple_action a) { return call(std::move(a)); } + + + //--------------------------------------------------------------- + /** @brief adds an action that will set the value of 't' from + * a 'const char*' arg */ + template<class Target> + Derived& + set(Target& t) { + return call(clipp::set(t)); + } + + /** @brief adds an action that will set the value of 't' to 'v' */ + template<class Target, class Value> + Derived& + set(Target& t, Value&& v) { + return call(clipp::set(t, std::forward<Value>(v))); + } + + + //--------------------------------------------------------------- + /** @brief adds an action that will be called if a parameter + * matches an argument for the 2nd, 3rd, 4th, ... time + */ + Derived& + if_repeated(simple_action a) { + repeatActions_.push_back(simple_action_adapter{std::move(a)}); + return *static_cast<Derived*>(this); + } + /** @brief adds an action that will be called with the argument's + * index if a parameter matches an argument for + * the 2nd, 3rd, 4th, ... time + */ + Derived& + if_repeated(index_action a) { + repeatActions_.push_back(std::move(a)); + return *static_cast<Derived*>(this); + } + + + //--------------------------------------------------------------- + /** @brief adds an action that will be called if a required parameter + * is missing + */ + Derived& + if_missing(simple_action a) { + missingActions_.push_back(simple_action_adapter{std::move(a)}); + return *static_cast<Derived*>(this); + } + /** @brief adds an action that will be called if a required parameter + * is missing; the action will get called with the index of + * the command line argument where the missing event occured first + */ + Derived& + if_missing(index_action a) { + missingActions_.push_back(std::move(a)); + return *static_cast<Derived*>(this); + } + + + //--------------------------------------------------------------- + /** @brief adds an action that will be called if a parameter + * was matched, but was unreachable in the current scope + */ + Derived& + if_blocked(simple_action a) { + blockedActions_.push_back(simple_action_adapter{std::move(a)}); + return *static_cast<Derived*>(this); + } + /** @brief adds an action that will be called if a parameter + * was matched, but was unreachable in the current scope; + * the action will be called with the index of + * the command line argument where the problem occured + */ + Derived& + if_blocked(index_action a) { + blockedActions_.push_back(std::move(a)); + return *static_cast<Derived*>(this); + } + + + //--------------------------------------------------------------- + /** @brief adds an action that will be called if a parameter match + * was in conflict with a different alternative parameter + */ + Derived& + if_conflicted(simple_action a) { + conflictActions_.push_back(simple_action_adapter{std::move(a)}); + return *static_cast<Derived*>(this); + } + /** @brief adds an action that will be called if a parameter match + * was in conflict with a different alternative paramete; + * the action will be called with the index of + * the command line argument where the problem occuredr + */ + Derived& + if_conflicted(index_action a) { + conflictActions_.push_back(std::move(a)); + return *static_cast<Derived*>(this); + } + + + //--------------------------------------------------------------- + /** @brief adds targets = either objects whose values should be + * set by command line arguments or actions that should + * be called in case of a match */ + template<class T, class... Ts> + Derived& + target(T&& t, Ts&&... ts) { + target(std::forward<T>(t)); + target(std::forward<Ts>(ts)...); + return *static_cast<Derived*>(this); + } + + /** @brief adds action that should be called in case of a match */ + template<class T, class = typename std::enable_if< + !std::is_fundamental<typename std::decay<T>::type>() && + (traits::is_callable<T,void()>() || + traits::is_callable<T,void(const char*)>() ) + >::type> + Derived& + target(T&& t) { + call(std::forward<T>(t)); + return *static_cast<Derived*>(this); + } + + /** @brief adds object whose value should be set by command line arguments + */ + template<class T, class = typename std::enable_if< + std::is_fundamental<typename std::decay<T>::type>() || + (!traits::is_callable<T,void()>() && + !traits::is_callable<T,void(const char*)>() ) + >::type> + Derived& + target(T& t) { + set(t); + return *static_cast<Derived*>(this); + } + + //TODO remove ugly empty param list overload + Derived& + target() { + return *static_cast<Derived*>(this); + } + + + //--------------------------------------------------------------- + /** @brief adds target, see member function 'target' */ + template<class Target> + inline friend Derived& + operator << (Target&& t, Derived& p) { + p.target(std::forward<Target>(t)); + return p; + } + /** @brief adds target, see member function 'target' */ + template<class Target> + inline friend Derived&& + operator << (Target&& t, Derived&& p) { + p.target(std::forward<Target>(t)); + return std::move(p); + } + + //----------------------------------------------------- + /** @brief adds target, see member function 'target' */ + template<class Target> + inline friend Derived& + operator >> (Derived& p, Target&& t) { + p.target(std::forward<Target>(t)); + return p; + } + /** @brief adds target, see member function 'target' */ + template<class Target> + inline friend Derived&& + operator >> (Derived&& p, Target&& t) { + p.target(std::forward<Target>(t)); + return std::move(p); + } + + + //--------------------------------------------------------------- + /** @brief executes all argument actions */ + void execute_actions(const arg_string& arg) const { + int i = 0; + for(const auto& a : argActions_) { + ++i; + a(arg.c_str()); + } + } + + /** @brief executes repeat actions */ + void notify_repeated(arg_index idx) const { + for(const auto& a : repeatActions_) a(idx); + } + /** @brief executes missing error actions */ + void notify_missing(arg_index idx) const { + for(const auto& a : missingActions_) a(idx); + } + /** @brief executes blocked error actions */ + void notify_blocked(arg_index idx) const { + for(const auto& a : blockedActions_) a(idx); + } + /** @brief executes conflict error actions */ + void notify_conflict(arg_index idx) const { + for(const auto& a : conflictActions_) a(idx); + } + +private: + //--------------------------------------------------------------- + std::vector<arg_action> argActions_; + std::vector<index_action> repeatActions_; + std::vector<index_action> missingActions_; + std::vector<index_action> blockedActions_; + std::vector<index_action> conflictActions_; +}; + + + + + + +/*************************************************************************//** + * + * @brief mixin that provides basic common settings of parameters and groups + * + *****************************************************************************/ +template<class Derived> +class token +{ +public: + //--------------------------------------------------------------- + using doc_string = clipp::doc_string; + + + //--------------------------------------------------------------- + /** @brief returns documentation string */ + const doc_string& doc() const noexcept { + return doc_; + } + + /** @brief sets documentations string */ + Derived& doc(const doc_string& txt) { + doc_ = txt; + return *static_cast<Derived*>(this); + } + + /** @brief sets documentations string */ + Derived& doc(doc_string&& txt) { + doc_ = std::move(txt); + return *static_cast<Derived*>(this); + } + + + //--------------------------------------------------------------- + /** @brief returns if a group/parameter is repeatable */ + bool repeatable() const noexcept { + return repeatable_; + } + + /** @brief sets repeatability of group/parameter */ + Derived& repeatable(bool yes) noexcept { + repeatable_ = yes; + return *static_cast<Derived*>(this); + } + + + //--------------------------------------------------------------- + /** @brief returns if a group/parameter is blocking/positional */ + bool blocking() const noexcept { + return blocking_; + } + + /** @brief determines, if a group/parameter is blocking/positional */ + Derived& blocking(bool yes) noexcept { + blocking_ = yes; + return *static_cast<Derived*>(this); + } + + +private: + //--------------------------------------------------------------- + doc_string doc_; + bool repeatable_ = false; + bool blocking_ = false; +}; + + + + +/*************************************************************************//** + * + * @brief sets documentation strings on a token + * + *****************************************************************************/ +template<class T> +inline T& +operator % (doc_string docstr, token<T>& p) +{ + return p.doc(std::move(docstr)); +} +//--------------------------------------------------------- +template<class T> +inline T&& +operator % (doc_string docstr, token<T>&& p) +{ + return std::move(p.doc(std::move(docstr))); +} + +//--------------------------------------------------------- +template<class T> +inline T& +operator % (token<T>& p, doc_string docstr) +{ + return p.doc(std::move(docstr)); +} +//--------------------------------------------------------- +template<class T> +inline T&& +operator % (token<T>&& p, doc_string docstr) +{ + return std::move(p.doc(std::move(docstr))); +} + + + + +/*************************************************************************//** + * + * @brief sets documentation strings on a token + * + *****************************************************************************/ +template<class T> +inline T& +doc(doc_string docstr, token<T>& p) +{ + return p.doc(std::move(docstr)); +} +//--------------------------------------------------------- +template<class T> +inline T&& +doc(doc_string docstr, token<T>&& p) +{ + return std::move(p.doc(std::move(docstr))); +} + + + +} // namespace detail + + + +/*************************************************************************//** + * + * @brief contains parameter matching functions and function classes + * + *****************************************************************************/ +namespace match { + + +/*************************************************************************//** + * + * @brief predicate that is always true + * + *****************************************************************************/ +inline bool +any(const arg_string&) { return true; } + +/*************************************************************************//** + * + * @brief predicate that is always false + * + *****************************************************************************/ +inline bool +none(const arg_string&) { return false; } + + + +/*************************************************************************//** + * + * @brief predicate that returns true if the argument string is non-empty string + * + *****************************************************************************/ +inline bool +nonempty(const arg_string& s) { + return !s.empty(); +} + + + +/*************************************************************************//** + * + * @brief predicate that returns true if the argument is a non-empty + * string that consists only of alphanumeric characters + * + *****************************************************************************/ +inline bool +alphanumeric(const arg_string& s) { + if(s.empty()) return false; + return std::all_of(s.begin(), s.end(), [](char c) {return std::isalnum(c); }); +} + + + +/*************************************************************************//** + * + * @brief predicate that returns true if the argument is a non-empty + * string that consists only of alphabetic characters + * + *****************************************************************************/ +inline bool +alphabetic(const arg_string& s) { + return std::all_of(s.begin(), s.end(), [](char c) {return std::isalpha(c); }); +} + + + +/*************************************************************************//** + * + * @brief predicate that returns the first substring match within the input + * string that rmeepresents a number + * (with at maximum one decimal point and digit separators) + * + *****************************************************************************/ +class numbers +{ +public: + explicit + numbers(char decimalPoint = '.', + char digitSeparator = ' ', + char exponentSeparator = 'e') + : + decpoint_{decimalPoint}, separator_{digitSeparator}, + exp_{exponentSeparator} + {} + + subrange operator () (const arg_string& s) const { + return str::first_number_match(s, separator_, decpoint_, exp_); + } + +private: + char decpoint_; + char separator_; + char exp_; +}; + + + +/*************************************************************************//** + * + * @brief predicate that returns true if the input string represents an integer + * (with optional digit separators) + * + *****************************************************************************/ +class integers { +public: + explicit + integers(char digitSeparator = ' '): separator_{digitSeparator} {} + + subrange operator () (const arg_string& s) const { + return str::first_integer_match(s, separator_); + } + +private: + char separator_; +}; + + + +/*************************************************************************//** + * + * @brief predicate that returns true if the input string represents + * a non-negative integer (with optional digit separators) + * + *****************************************************************************/ +class positive_integers { +public: + explicit + positive_integers(char digitSeparator = ' '): separator_{digitSeparator} {} + + subrange operator () (const arg_string& s) const { + auto match = str::first_integer_match(s, separator_); + if(!match) return subrange{}; + if(s[match.at()] == '-') return subrange{}; + return match; + } + +private: + char separator_; +}; + + + +/*************************************************************************//** + * + * @brief predicate that returns true if the input string + * contains a given substring + * + *****************************************************************************/ +class substring +{ +public: + explicit + substring(arg_string str): str_{std::move(str)} {} + + subrange operator () (const arg_string& s) const { + return str::substring_match(s, str_); + } + +private: + arg_string str_; +}; + + + +/*************************************************************************//** + * + * @brief predicate that returns true if the input string starts + * with a given prefix + * + *****************************************************************************/ +class prefix { +public: + explicit + prefix(arg_string p): prefix_{std::move(p)} {} + + bool operator () (const arg_string& s) const { + return s.find(prefix_) == 0; + } + +private: + arg_string prefix_; +}; + + + +/*************************************************************************//** + * + * @brief predicate that returns true if the input string does not start + * with a given prefix + * + *****************************************************************************/ +class prefix_not { +public: + explicit + prefix_not(arg_string p): prefix_{std::move(p)} {} + + bool operator () (const arg_string& s) const { + return s.find(prefix_) != 0; + } + +private: + arg_string prefix_; +}; + + +/** @brief alias for prefix_not */ +using noprefix = prefix_not; + + + +/*************************************************************************//** + * + * @brief predicate that returns true if the length of the input string + * is wihtin a given interval + * + *****************************************************************************/ +class length { +public: + explicit + length(std::size_t exact): + min_{exact}, max_{exact} + {} + + explicit + length(std::size_t min, std::size_t max): + min_{min}, max_{max} + {} + + bool operator () (const arg_string& s) const { + return s.size() >= min_ && s.size() <= max_; + } + +private: + std::size_t min_; + std::size_t max_; +}; + + +/*************************************************************************//** + * + * @brief makes function object that returns true if the input string has a + * given minimum length + * + *****************************************************************************/ +inline length min_length(std::size_t min) +{ + return length{min, arg_string::npos-1}; +} + +/*************************************************************************//** + * + * @brief makes function object that returns true if the input string is + * not longer than a given maximum length + * + *****************************************************************************/ +inline length max_length(std::size_t max) +{ + return length{0, max}; +} + + +} // namespace match + + + + + +/*************************************************************************//** + * + * @brief command line parameter that can match one or many arguments. + * + *****************************************************************************/ +class parameter : + public detail::token<parameter>, + public detail::action_provider<parameter> +{ + class predicate_adapter { + public: + explicit + predicate_adapter(match_predicate pred): match_{std::move(pred)} {} + + subrange operator () (const arg_string& arg) const { + return match_(arg) ? subrange{0,arg.size()} : subrange{}; + } + + private: + match_predicate match_; + }; + +public: + //--------------------------------------------------------------- + /** @brief makes default parameter, that will match nothing */ + parameter(): + flags_{}, + matcher_{predicate_adapter{match::none}}, + label_{}, required_{false} + {} + + /** @brief makes "flag" parameter */ + template<class... Strings> + explicit + parameter(arg_string str, Strings&&... strs): + flags_{}, + matcher_{predicate_adapter{match::none}}, + label_{}, required_{false} + { + add_flags(std::move(str), std::forward<Strings>(strs)...); + } + + /** @brief makes "flag" parameter from range of strings */ + explicit + parameter(const arg_list& flaglist): + flags_{}, + matcher_{predicate_adapter{match::none}}, + label_{}, required_{false} + { + add_flags(flaglist); + } + + //----------------------------------------------------- + /** @brief makes "value" parameter with custom match predicate + * (= yes/no matcher) + */ + explicit + parameter(match_predicate filter): + flags_{}, + matcher_{predicate_adapter{std::move(filter)}}, + label_{}, required_{false} + {} + + /** @brief makes "value" parameter with custom match function + * (= partial matcher) + */ + explicit + parameter(match_function filter): + flags_{}, + matcher_{std::move(filter)}, + label_{}, required_{false} + {} + + + //--------------------------------------------------------------- + /** @brief returns if a parameter is required */ + bool + required() const noexcept { + return required_; + } + + /** @brief determines if a parameter is required */ + parameter& + required(bool yes) noexcept { + required_ = yes; + return *this; + } + + + //--------------------------------------------------------------- + /** @brief returns parameter label; + * will be used for documentation, if flags are empty + */ + const doc_string& + label() const { + return label_; + } + + /** @brief sets parameter label; + * will be used for documentation, if flags are empty + */ + parameter& + label(const doc_string& lbl) { + label_ = lbl; + return *this; + } + + /** @brief sets parameter label; + * will be used for documentation, if flags are empty + */ + parameter& + label(doc_string&& lbl) { + label_ = lbl; + return *this; + } + + + //--------------------------------------------------------------- + /** @brief returns either longest matching prefix of 'arg' in any + * of the flags or the result of the custom match operation + */ + subrange + match(const arg_string& arg) const + { + if(arg.empty()) return subrange{}; + + if(flags_.empty()) { + return matcher_(arg); + } + else { + if(std::find(flags_.begin(), flags_.end(), arg) != flags_.end()) { + return subrange{0,arg.size()}; + } + return str::longest_prefix_match(arg, flags_); + } + } + + + //--------------------------------------------------------------- + /** @brief access range of flag strings */ + const arg_list& + flags() const noexcept { + return flags_; + } + + /** @brief access custom match operation */ + const match_function& + matcher() const noexcept { + return matcher_; + } + + + //--------------------------------------------------------------- + /** @brief prepend prefix to each flag */ + inline friend parameter& + with_prefix(const arg_string& prefix, parameter& p) + { + if(prefix.empty() || p.flags().empty()) return p; + + for(auto& f : p.flags_) { + if(f.find(prefix) != 0) f.insert(0, prefix); + } + return p; + } + + + /** @brief prepend prefix to each flag + */ + inline friend parameter& + with_prefixes_short_long( + const arg_string& shortpfx, const arg_string& longpfx, + parameter& p) + { + if(shortpfx.empty() && longpfx.empty()) return p; + if(p.flags().empty()) return p; + + for(auto& f : p.flags_) { + if(f.size() == 1) { + if(f.find(shortpfx) != 0) f.insert(0, shortpfx); + } else { + if(f.find(longpfx) != 0) f.insert(0, longpfx); + } + } + return p; + } + +private: + //--------------------------------------------------------------- + void add_flags(arg_string str) { + //empty flags are not allowed + str::remove_ws(str); + if(!str.empty()) flags_.push_back(std::move(str)); + } + + //--------------------------------------------------------------- + void add_flags(const arg_list& strs) { + if(strs.empty()) return; + flags_.reserve(flags_.size() + strs.size()); + for(const auto& s : strs) add_flags(s); + } + + template<class String1, class String2, class... Strings> + void + add_flags(String1&& s1, String2&& s2, Strings&&... ss) { + flags_.reserve(2 + sizeof...(ss)); + add_flags(std::forward<String1>(s1)); + add_flags(std::forward<String2>(s2), std::forward<Strings>(ss)...); + } + + arg_list flags_; + match_function matcher_; + doc_string label_; + bool required_ = false; +}; + + + + +/*************************************************************************//** + * + * @brief makes required non-blocking exact match parameter + * + *****************************************************************************/ +template<class String, class... Strings> +inline parameter +command(String&& flag, Strings&&... flags) +{ + return parameter{std::forward<String>(flag), std::forward<Strings>(flags)...} + .required(true).blocking(true).repeatable(false); +} + + + +/*************************************************************************//** + * + * @brief makes required non-blocking exact match parameter + * + *****************************************************************************/ +template<class String, class... Strings> +inline parameter +required(String&& flag, Strings&&... flags) +{ + return parameter{std::forward<String>(flag), std::forward<Strings>(flags)...} + .required(true).blocking(false).repeatable(false); +} + + + +/*************************************************************************//** + * + * @brief makes optional, non-blocking exact match parameter + * + *****************************************************************************/ +template<class String, class... Strings> +inline parameter +option(String&& flag, Strings&&... flags) +{ + return parameter{std::forward<String>(flag), std::forward<Strings>(flags)...} + .required(false).blocking(false).repeatable(false); +} + + + +/*************************************************************************//** + * + * @brief makes required, blocking, repeatable value parameter; + * matches any non-empty string + * + *****************************************************************************/ +template<class... Targets> +inline parameter +value(const doc_string& label, Targets&&... tgts) +{ + return parameter{match::nonempty} + .label(label) + .target(std::forward<Targets>(tgts)...) + .required(true).blocking(true).repeatable(false); +} + +template<class Filter, class... Targets, class = typename std::enable_if< + traits::is_callable<Filter,bool(const char*)>::value || + traits::is_callable<Filter,subrange(const char*)>::value>::type> +inline parameter +value(Filter&& filter, doc_string label, Targets&&... tgts) +{ + return parameter{std::forward<Filter>(filter)} + .label(label) + .target(std::forward<Targets>(tgts)...) + .required(true).blocking(true).repeatable(false); +} + + + +/*************************************************************************//** + * + * @brief makes required, blocking, repeatable value parameter; + * matches any non-empty string + * + *****************************************************************************/ +template<class... Targets> +inline parameter +values(const doc_string& label, Targets&&... tgts) +{ + return parameter{match::nonempty} + .label(label) + .target(std::forward<Targets>(tgts)...) + .required(true).blocking(true).repeatable(true); +} + +template<class Filter, class... Targets, class = typename std::enable_if< + traits::is_callable<Filter,bool(const char*)>::value || + traits::is_callable<Filter,subrange(const char*)>::value>::type> +inline parameter +values(Filter&& filter, doc_string label, Targets&&... tgts) +{ + return parameter{std::forward<Filter>(filter)} + .label(label) + .target(std::forward<Targets>(tgts)...) + .required(true).blocking(true).repeatable(true); +} + + + +/*************************************************************************//** + * + * @brief makes optional, blocking value parameter; + * matches any non-empty string + * + *****************************************************************************/ +template<class... Targets> +inline parameter +opt_value(const doc_string& label, Targets&&... tgts) +{ + return parameter{match::nonempty} + .label(label) + .target(std::forward<Targets>(tgts)...) + .required(false).blocking(false).repeatable(false); +} + +template<class Filter, class... Targets, class = typename std::enable_if< + traits::is_callable<Filter,bool(const char*)>::value || + traits::is_callable<Filter,subrange(const char*)>::value>::type> +inline parameter +opt_value(Filter&& filter, doc_string label, Targets&&... tgts) +{ + return parameter{std::forward<Filter>(filter)} + .label(label) + .target(std::forward<Targets>(tgts)...) + .required(false).blocking(false).repeatable(false); +} + + + +/*************************************************************************//** + * + * @brief makes optional, blocking, repeatable value parameter; + * matches any non-empty string + * + *****************************************************************************/ +template<class... Targets> +inline parameter +opt_values(const doc_string& label, Targets&&... tgts) +{ + return parameter{match::nonempty} + .label(label) + .target(std::forward<Targets>(tgts)...) + .required(false).blocking(false).repeatable(true); +} + +template<class Filter, class... Targets, class = typename std::enable_if< + traits::is_callable<Filter,bool(const char*)>::value || + traits::is_callable<Filter,subrange(const char*)>::value>::type> +inline parameter +opt_values(Filter&& filter, doc_string label, Targets&&... tgts) +{ + return parameter{std::forward<Filter>(filter)} + .label(label) + .target(std::forward<Targets>(tgts)...) + .required(false).blocking(false).repeatable(true); +} + + + +/*************************************************************************//** + * + * @brief makes required, blocking value parameter; + * matches any string consisting of alphanumeric characters + * + *****************************************************************************/ +template<class... Targets> +inline parameter +word(const doc_string& label, Targets&&... tgts) +{ + return parameter{match::alphanumeric} + .label(label) + .target(std::forward<Targets>(tgts)...) + .required(true).blocking(true).repeatable(false); +} + + + +/*************************************************************************//** + * + * @brief makes required, blocking, repeatable value parameter; + * matches any string consisting of alphanumeric characters + * + *****************************************************************************/ +template<class... Targets> +inline parameter +words(const doc_string& label, Targets&&... tgts) +{ + return parameter{match::alphanumeric} + .label(label) + .target(std::forward<Targets>(tgts)...) + .required(true).blocking(true).repeatable(true); +} + + + +/*************************************************************************//** + * + * @brief makes optional, blocking value parameter; + * matches any string consisting of alphanumeric characters + * + *****************************************************************************/ +template<class... Targets> +inline parameter +opt_word(const doc_string& label, Targets&&... tgts) +{ + return parameter{match::alphanumeric} + .label(label) + .target(std::forward<Targets>(tgts)...) + .required(false).blocking(false).repeatable(false); +} + + + +/*************************************************************************//** + * + * @brief makes optional, blocking, repeatable value parameter; + * matches any string consisting of alphanumeric characters + * + *****************************************************************************/ +template<class... Targets> +inline parameter +opt_words(const doc_string& label, Targets&&... tgts) +{ + return parameter{match::alphanumeric} + .label(label) + .target(std::forward<Targets>(tgts)...) + .required(false).blocking(false).repeatable(true); +} + + + +/*************************************************************************//** + * + * @brief makes required, blocking value parameter; + * matches any string that represents a number + * + *****************************************************************************/ +template<class... Targets> +inline parameter +number(const doc_string& label, Targets&&... tgts) +{ + return parameter{match::numbers{}} + .label(label) + .target(std::forward<Targets>(tgts)...) + .required(true).blocking(true).repeatable(false); +} + + + +/*************************************************************************//** + * + * @brief makes required, blocking, repeatable value parameter; + * matches any string that represents a number + * + *****************************************************************************/ +template<class... Targets> +inline parameter +numbers(const doc_string& label, Targets&&... tgts) +{ + return parameter{match::numbers{}} + .label(label) + .target(std::forward<Targets>(tgts)...) + .required(true).blocking(true).repeatable(true); +} + + + +/*************************************************************************//** + * + * @brief makes optional, blocking value parameter; + * matches any string that represents a number + * + *****************************************************************************/ +template<class... Targets> +inline parameter +opt_number(const doc_string& label, Targets&&... tgts) +{ + return parameter{match::numbers{}} + .label(label) + .target(std::forward<Targets>(tgts)...) + .required(false).blocking(false).repeatable(false); +} + + + +/*************************************************************************//** + * + * @brief makes optional, blocking, repeatable value parameter; + * matches any string that represents a number + * + *****************************************************************************/ +template<class... Targets> +inline parameter +opt_numbers(const doc_string& label, Targets&&... tgts) +{ + return parameter{match::numbers{}} + .label(label) + .target(std::forward<Targets>(tgts)...) + .required(false).blocking(false).repeatable(true); +} + + + +/*************************************************************************//** + * + * @brief makes required, blocking value parameter; + * matches any string that represents an integer + * + *****************************************************************************/ +template<class... Targets> +inline parameter +integer(const doc_string& label, Targets&&... tgts) +{ + return parameter{match::integers{}} + .label(label) + .target(std::forward<Targets>(tgts)...) + .required(true).blocking(true).repeatable(false); +} + + + +/*************************************************************************//** + * + * @brief makes required, blocking, repeatable value parameter; + * matches any string that represents an integer + * + *****************************************************************************/ +template<class... Targets> +inline parameter +integers(const doc_string& label, Targets&&... tgts) +{ + return parameter{match::integers{}} + .label(label) + .target(std::forward<Targets>(tgts)...) + .required(true).blocking(true).repeatable(true); +} + + + +/*************************************************************************//** + * + * @brief makes optional, blocking value parameter; + * matches any string that represents an integer + * + *****************************************************************************/ +template<class... Targets> +inline parameter +opt_integer(const doc_string& label, Targets&&... tgts) +{ + return parameter{match::integers{}} + .label(label) + .target(std::forward<Targets>(tgts)...) + .required(false).blocking(false).repeatable(false); +} + + + +/*************************************************************************//** + * + * @brief makes optional, blocking, repeatable value parameter; + * matches any string that represents an integer + * + *****************************************************************************/ +template<class... Targets> +inline parameter +opt_integers(const doc_string& label, Targets&&... tgts) +{ + return parameter{match::integers{}} + .label(label) + .target(std::forward<Targets>(tgts)...) + .required(false).blocking(false).repeatable(true); +} + + + +/*************************************************************************//** + * + * @brief makes catch-all value parameter + * + *****************************************************************************/ +template<class... Targets> +inline parameter +any_other(Targets&&... tgts) +{ + return parameter{match::any} + .target(std::forward<Targets>(tgts)...) + .required(false).blocking(false).repeatable(true); +} + + + + +/*************************************************************************//** + * + * @brief group of parameters and/or other groups; + * can be configured to act as a group of alternatives (exclusive match) + * + *****************************************************************************/ +class group : + public detail::token<group> +{ + //--------------------------------------------------------------- + /** + * @brief tagged union type that either stores a parameter or a group + * and provides a common interface to them + * could be replaced by std::variant in the future + * + * Note to future self: do NOT try again to do this with + * dynamic polymorphism; there are a couple of + * nasty problems associated with it and the implementation + * becomes bloated and needlessly complicated. + */ + template<class Param, class Group> + struct child_t { + enum class type : char {param, group}; + public: + + explicit + child_t(const Param& v) : m_{v}, type_{type::param} {} + child_t( Param&& v) noexcept : m_{std::move(v)}, type_{type::param} {} + + explicit + child_t(const Group& g) : m_{g}, type_{type::group} {} + child_t( Group&& g) noexcept : m_{std::move(g)}, type_{type::group} {} + + child_t(const child_t& src): type_{src.type_} { + switch(type_) { + default: + case type::param: new(&m_)data{src.m_.param}; break; + case type::group: new(&m_)data{src.m_.group}; break; + } + } + + child_t(child_t&& src) noexcept : type_{src.type_} { + switch(type_) { + default: + case type::param: new(&m_)data{std::move(src.m_.param)}; break; + case type::group: new(&m_)data{std::move(src.m_.group)}; break; + } + } + + child_t& operator = (const child_t& src) { + destroy_content(); + type_ = src.type_; + switch(type_) { + default: + case type::param: new(&m_)data{src.m_.param}; break; + case type::group: new(&m_)data{src.m_.group}; break; + } + return *this; + } + + child_t& operator = (child_t&& src) noexcept { + destroy_content(); + type_ = src.type_; + switch(type_) { + default: + case type::param: new(&m_)data{std::move(src.m_.param)}; break; + case type::group: new(&m_)data{std::move(src.m_.group)}; break; + } + return *this; + } + + ~child_t() { + destroy_content(); + } + + const doc_string& + doc() const noexcept { + switch(type_) { + default: + case type::param: return m_.param.doc(); + case type::group: return m_.group.doc(); + } + } + + bool blocking() const noexcept { + switch(type_) { + case type::param: return m_.param.blocking(); + case type::group: return m_.group.blocking(); + default: return false; + } + } + bool repeatable() const noexcept { + switch(type_) { + case type::param: return m_.param.repeatable(); + case type::group: return m_.group.repeatable(); + default: return false; + } + } + bool required() const noexcept { + switch(type_) { + case type::param: return m_.param.required(); + case type::group: + return (m_.group.exclusive() && m_.group.all_required() ) || + (!m_.group.exclusive() && m_.group.any_required() ); + default: return false; + } + } + bool exclusive() const noexcept { + switch(type_) { + case type::group: return m_.group.exclusive(); + case type::param: + default: return false; + } + } + std::size_t param_count() const noexcept { + switch(type_) { + case type::group: return m_.group.param_count(); + case type::param: + default: return std::size_t(1); + } + } + std::size_t depth() const noexcept { + switch(type_) { + case type::group: return m_.group.depth(); + case type::param: + default: return std::size_t(0); + } + } + + void execute_actions(const arg_string& arg) const { + switch(type_) { + default: + case type::group: return; + case type::param: m_.param.execute_actions(arg); break; + } + + } + + void notify_repeated(arg_index idx) const { + switch(type_) { + default: + case type::group: return; + case type::param: m_.param.notify_repeated(idx); break; + } + } + void notify_missing(arg_index idx) const { + switch(type_) { + default: + case type::group: return; + case type::param: m_.param.notify_missing(idx); break; + } + } + void notify_blocked(arg_index idx) const { + switch(type_) { + default: + case type::group: return; + case type::param: m_.param.notify_blocked(idx); break; + } + } + void notify_conflict(arg_index idx) const { + switch(type_) { + default: + case type::group: return; + case type::param: m_.param.notify_conflict(idx); break; + } + } + + bool is_param() const noexcept { return type_ == type::param; } + bool is_group() const noexcept { return type_ == type::group; } + + Param& as_param() noexcept { return m_.param; } + Group& as_group() noexcept { return m_.group; } + + const Param& as_param() const noexcept { return m_.param; } + const Group& as_group() const noexcept { return m_.group; } + + private: + void destroy_content() { + switch(type_) { + default: + case type::param: m_.param.~Param(); break; + case type::group: m_.group.~Group(); break; + } + } + + union data { + data() {} + + data(const Param& v) : param{v} {} + data( Param&& v) noexcept : param{std::move(v)} {} + + data(const Group& g) : group{g} {} + data( Group&& g) noexcept : group{std::move(g)} {} + ~data() {} + + Param param; + Group group; + }; + + data m_; + type type_; + }; + + +public: + //--------------------------------------------------------------- + using child = child_t<parameter,group>; + using value_type = child; + +private: + using children_store = std::vector<child>; + +public: + using const_iterator = children_store::const_iterator; + using iterator = children_store::iterator; + using size_type = children_store::size_type; + + + //--------------------------------------------------------------- + /** + * @brief recursively iterates over all nodes + */ + class depth_first_traverser + { + public: + //----------------------------------------------------- + struct context { + context() = default; + context(const group& p): + parent{&p}, cur{p.begin()}, end{p.end()} + {} + const group* parent = nullptr; + const_iterator cur; + const_iterator end; + }; + using context_list = std::vector<context>; + + //----------------------------------------------------- + class memento { + friend class depth_first_traverser; + int level_; + context context_; + public: + int level() const noexcept { return level_; } + const child* param() const noexcept { return &(*context_.cur); } + }; + + depth_first_traverser() = default; + + explicit + depth_first_traverser(const group& cur): stack_{} { + if(!cur.empty()) stack_.emplace_back(cur); + } + + explicit operator bool() const noexcept { + return !stack_.empty(); + } + + int level() const noexcept { + return int(stack_.size()); + } + + bool is_first_in_group() const noexcept { + if(stack_.empty()) return false; + return (stack_.back().cur == stack_.back().parent->begin()); + } + + bool is_last_in_group() const noexcept { + if(stack_.empty()) return false; + return (stack_.back().cur+1 == stack_.back().end); + } + + bool is_last_in_path() const noexcept { + if(stack_.empty()) return false; + for(const auto& t : stack_) { + if(t.cur+1 != t.end) return false; + } + const auto& top = stack_.back(); + //if we have to descend into group on next ++ => not last in path + if(top.cur->is_group()) return false; + return true; + } + + /** @brief inside a group of alternatives >= minlevel */ + bool is_alternative(int minlevel = 0) const noexcept { + if(stack_.empty()) return false; + if(minlevel > 0) minlevel -= 1; + if(minlevel >= int(stack_.size())) return false; + return std::any_of(stack_.begin() + minlevel, stack_.end(), + [](const context& c) { return c.parent->exclusive(); }); + } + + /** @brief repeatable or inside a repeatable group >= minlevel */ + bool is_repeatable(int minlevel = 0) const noexcept { + if(stack_.empty()) return false; + if(stack_.back().cur->repeatable()) return true; + if(minlevel > 0) minlevel -= 1; + if(minlevel >= int(stack_.size())) return false; + return std::any_of(stack_.begin() + minlevel, stack_.end(), + [](const context& c) { return c.parent->repeatable(); }); + } + /** @brief inside group with joinable flags */ + bool joinable() const noexcept { + if(stack_.empty()) return false; + return std::any_of(stack_.begin(), stack_.end(), + [](const context& c) { return c.parent->joinable(); }); + } + + const context_list& + stack() const { + return stack_; + } + + /** @brief innermost repeat group */ + const group* + repeat_group() const noexcept { + auto i = std::find_if(stack_.rbegin(), stack_.rend(), + [](const context& c) { return c.parent->repeatable(); }); + + return i != stack_.rend() ? i->parent : nullptr; + } + + /** @brief outermost join group */ + const group* + join_group() const noexcept { + auto i = std::find_if(stack_.begin(), stack_.end(), + [](const context& c) { return c.parent->joinable(); }); + return i != stack_.end() ? i->parent : nullptr; + } + + const group* root() const noexcept { + return stack_.empty() ? nullptr : stack_.front().parent; + } + + /** @brief common flag prefix of all flags in current group */ + arg_string common_flag_prefix() const noexcept { + if(stack_.empty()) return ""; + auto g = join_group(); + return g ? g->common_flag_prefix() : arg_string(""); + } + + const child& + operator * () const noexcept { + return *stack_.back().cur; + } + + const child* + operator -> () const noexcept { + return &(*stack_.back().cur); + } + + const group& + parent() const noexcept { + return *(stack_.back().parent); + } + + + /** @brief go to next element of depth first search */ + depth_first_traverser& + operator ++ () { + if(stack_.empty()) return *this; + //at group -> decend into group + if(stack_.back().cur->is_group()) { + stack_.emplace_back(stack_.back().cur->as_group()); + } + else { + next_sibling(); + } + return *this; + } + + /** @brief go to next sibling of current */ + depth_first_traverser& + next_sibling() { + if(stack_.empty()) return *this; + ++stack_.back().cur; + //at the end of current group? + while(stack_.back().cur == stack_.back().end) { + //go to parent + stack_.pop_back(); + if(stack_.empty()) return *this; + //go to next sibling in parent + ++stack_.back().cur; + } + return *this; + } + + /** @brief go to next position after siblings of current */ + depth_first_traverser& + next_after_siblings() { + if(stack_.empty()) return *this; + stack_.back().cur = stack_.back().end-1; + next_sibling(); + return *this; + } + + /** @brief skips to next alternative in innermost group + */ + depth_first_traverser& + next_alternative() { + if(stack_.empty()) return *this; + + //find first exclusive group (from the top of the stack!) + auto i = std::find_if(stack_.rbegin(), stack_.rend(), + [](const context& c) { return c.parent->exclusive(); }); + if(i == stack_.rend()) return *this; + + stack_.erase(i.base(), stack_.end()); + next_sibling(); + return *this; + } + + /** + * @brief + */ + depth_first_traverser& + back_to_parent() { + if(stack_.empty()) return *this; + stack_.pop_back(); + return *this; + } + + /** @brief don't visit next siblings, go back to parent on next ++ + * note: renders siblings unreachable for *this + **/ + depth_first_traverser& + skip_siblings() { + if(stack_.empty()) return *this; + //future increments won't visit subsequent siblings: + stack_.back().end = stack_.back().cur+1; + return *this; + } + + /** @brief skips all other alternatives in surrounding exclusive groups + * on next ++ + * note: renders alternatives unreachable for *this + */ + depth_first_traverser& + skip_alternatives() { + if(stack_.empty()) return *this; + + //exclude all other alternatives in surrounding groups + //by making their current position the last one + for(auto& c : stack_) { + if(c.parent && c.parent->exclusive() && c.cur < c.end) + c.end = c.cur+1; + } + + return *this; + } + + void invalidate() { + stack_.clear(); + } + + inline friend bool operator == (const depth_first_traverser& a, + const depth_first_traverser& b) + { + if(a.stack_.empty() || b.stack_.empty()) return false; + + //parents not the same -> different position + if(a.stack_.back().parent != b.stack_.back().parent) return false; + + bool aEnd = a.stack_.back().cur == a.stack_.back().end; + bool bEnd = b.stack_.back().cur == b.stack_.back().end; + //either both at the end of the same parent => same position + if(aEnd && bEnd) return true; + //or only one at the end => not at the same position + if(aEnd || bEnd) return false; + return std::addressof(*a.stack_.back().cur) == + std::addressof(*b.stack_.back().cur); + } + inline friend bool operator != (const depth_first_traverser& a, + const depth_first_traverser& b) + { + return !(a == b); + } + + memento + undo_point() const { + memento m; + m.level_ = int(stack_.size()); + if(!stack_.empty()) m.context_ = stack_.back(); + return m; + } + + void undo(const memento& m) { + if(m.level_ < 1) return; + if(m.level_ <= int(stack_.size())) { + stack_.erase(stack_.begin() + m.level_, stack_.end()); + stack_.back() = m.context_; + } + else if(stack_.empty() && m.level_ == 1) { + stack_.push_back(m.context_); + } + } + + private: + context_list stack_; + }; + + + //--------------------------------------------------------------- + group() = default; + + template<class Param, class... Params> + explicit + group(doc_string docstr, Param param, Params... params): + children_{}, exclusive_{false}, joinable_{false}, scoped_{true} + { + doc(std::move(docstr)); + push_back(std::move(param), std::move(params)...); + } + + template<class... Params> + explicit + group(parameter param, Params... params): + children_{}, exclusive_{false}, joinable_{false}, scoped_{true} + { + push_back(std::move(param), std::move(params)...); + } + + template<class P2, class... Ps> + explicit + group(group p1, P2 p2, Ps... ps): + children_{}, exclusive_{false}, joinable_{false}, scoped_{true} + { + push_back(std::move(p1), std::move(p2), std::move(ps)...); + } + + + //----------------------------------------------------- + group(const group&) = default; + group(group&&) = default; + + + //--------------------------------------------------------------- + group& operator = (const group&) = default; + group& operator = (group&&) = default; + + + //--------------------------------------------------------------- + /** @brief determines if a command line argument can be matched by a + * combination of (partial) matches through any number of children + */ + group& joinable(bool yes) { + joinable_ = yes; + return *this; + } + + /** @brief returns if a command line argument can be matched by a + * combination of (partial) matches through any number of children + */ + bool joinable() const noexcept { + return joinable_; + } + + + //--------------------------------------------------------------- + /** @brief turns explicit scoping on or off + * operators , & | and other combinating functions will + * not merge groups that are marked as scoped + */ + group& scoped(bool yes) { + scoped_ = yes; + return *this; + } + + /** @brief returns true if operators , & | and other combinating functions + * will merge groups and false otherwise + */ + bool scoped() const noexcept + { + return scoped_; + } + + + //--------------------------------------------------------------- + /** @brief determines if children are mutually exclusive alternatives */ + group& exclusive(bool yes) { + exclusive_ = yes; + return *this; + } + /** @brief returns if children are mutually exclusive alternatives */ + bool exclusive() const noexcept { + return exclusive_; + } + + + //--------------------------------------------------------------- + /** @brief returns true, if any child is required to match */ + bool any_required() const + { + return std::any_of(children_.begin(), children_.end(), + [](const child& n){ return n.required(); }); + } + /** @brief returns true, if all children are required to match */ + bool all_required() const + { + return std::all_of(children_.begin(), children_.end(), + [](const child& n){ return n.required(); }); + } + + + //--------------------------------------------------------------- + /** @brief returns true if any child is optional (=non-required) */ + bool any_optional() const { + return !all_required(); + } + /** @brief returns true if all children are optional (=non-required) */ + bool all_optional() const { + return !any_required(); + } + + + //--------------------------------------------------------------- + /** @brief returns if the entire group is blocking / positional */ + bool blocking() const noexcept { + return token<group>::blocking() || (exclusive() && all_blocking()); + } + //----------------------------------------------------- + /** @brief determines if the entire group is blocking / positional */ + group& blocking(bool yes) { + return token<group>::blocking(yes); + } + + //--------------------------------------------------------------- + /** @brief returns true if any child is blocking */ + bool any_blocking() const + { + return std::any_of(children_.begin(), children_.end(), + [](const child& n){ return n.blocking(); }); + } + //--------------------------------------------------------------- + /** @brief returns true if all children is blocking */ + bool all_blocking() const + { + return std::all_of(children_.begin(), children_.end(), + [](const child& n){ return n.blocking(); }); + } + + + //--------------------------------------------------------------- + /** @brief returns if any child is a value parameter (recursive) */ + bool any_flagless() const + { + return std::any_of(children_.begin(), children_.end(), + [](const child& p){ + return p.is_param() && p.as_param().flags().empty(); + }); + } + /** @brief returns if all children are value parameters (recursive) */ + bool all_flagless() const + { + return std::all_of(children_.begin(), children_.end(), + [](const child& p){ + return p.is_param() && p.as_param().flags().empty(); + }); + } + + + //--------------------------------------------------------------- + /** @brief adds child parameter at the end */ + group& + push_back(const parameter& v) { + children_.emplace_back(v); + return *this; + } + //----------------------------------------------------- + /** @brief adds child parameter at the end */ + group& + push_back(parameter&& v) { + children_.emplace_back(std::move(v)); + return *this; + } + //----------------------------------------------------- + /** @brief adds child group at the end */ + group& + push_back(const group& g) { + children_.emplace_back(g); + return *this; + } + //----------------------------------------------------- + /** @brief adds child group at the end */ + group& + push_back(group&& g) { + children_.emplace_back(std::move(g)); + return *this; + } + + + //----------------------------------------------------- + /** @brief adds children (groups and/or parameters) */ + template<class Param1, class Param2, class... Params> + group& + push_back(Param1&& param1, Param2&& param2, Params&&... params) + { + children_.reserve(children_.size() + 2 + sizeof...(params)); + push_back(std::forward<Param1>(param1)); + push_back(std::forward<Param2>(param2), std::forward<Params>(params)...); + return *this; + } + + + //--------------------------------------------------------------- + /** @brief adds child parameter at the beginning */ + group& + push_front(const parameter& v) { + children_.emplace(children_.begin(), v); + return *this; + } + //----------------------------------------------------- + /** @brief adds child parameter at the beginning */ + group& + push_front(parameter&& v) { + children_.emplace(children_.begin(), std::move(v)); + return *this; + } + //----------------------------------------------------- + /** @brief adds child group at the beginning */ + group& + push_front(const group& g) { + children_.emplace(children_.begin(), g); + return *this; + } + //----------------------------------------------------- + /** @brief adds child group at the beginning */ + group& + push_front(group&& g) { + children_.emplace(children_.begin(), std::move(g)); + return *this; + } + + + //--------------------------------------------------------------- + /** @brief adds all children of other group at the end */ + group& + merge(group&& g) + { + children_.insert(children_.end(), + std::make_move_iterator(g.begin()), + std::make_move_iterator(g.end())); + return *this; + } + //----------------------------------------------------- + /** @brief adds all children of several other groups at the end */ + template<class... Groups> + group& + merge(group&& g1, group&& g2, Groups&&... gs) + { + merge(std::move(g1)); + merge(std::move(g2), std::forward<Groups>(gs)...); + return *this; + } + + + //--------------------------------------------------------------- + /** @brief indexed, nutable access to child */ + child& operator [] (size_type index) noexcept { + return children_[index]; + } + /** @brief indexed, non-nutable access to child */ + const child& operator [] (size_type index) const noexcept { + return children_[index]; + } + + //--------------------------------------------------------------- + /** @brief mutable access to first child */ + child& front() noexcept { return children_.front(); } + /** @brief non-mutable access to first child */ + const child& front() const noexcept { return children_.front(); } + //----------------------------------------------------- + /** @brief mutable access to last child */ + child& back() noexcept { return children_.back(); } + /** @brief non-mutable access to last child */ + const child& back() const noexcept { return children_.back(); } + + + //--------------------------------------------------------------- + /** @brief returns true, if group has no children, false otherwise */ + bool empty() const noexcept { return children_.empty(); } + + /** @brief returns number of children */ + size_type size() const noexcept { return children_.size(); } + + /** @brief returns number of nested levels; 1 for a flat group */ + size_type depth() const { + size_type n = 0; + for(const auto& c : children_) { + auto l = 1 + c.depth(); + if(l > n) n = l; + } + return n; + } + + + //--------------------------------------------------------------- + /** @brief returns mutating iterator to position of first element */ + iterator begin() noexcept { return children_.begin(); } + /** @brief returns non-mutating iterator to position of first element */ + const_iterator begin() const noexcept { return children_.begin(); } + /** @brief returns non-mutating iterator to position of first element */ + const_iterator cbegin() const noexcept { return children_.begin(); } + + /** @brief returns mutating iterator to position one past the last element */ + iterator end() noexcept { return children_.end(); } + /** @brief returns non-mutating iterator to position one past the last element */ + const_iterator end() const noexcept { return children_.end(); } + /** @brief returns non-mutating iterator to position one past the last element */ + const_iterator cend() const noexcept { return children_.end(); } + + + //--------------------------------------------------------------- + /** @brief returns augmented iterator for depth first searches + * @details taverser knows end of iteration and can skip over children + */ + depth_first_traverser + begin_dfs() const noexcept { + return depth_first_traverser{*this}; + } + + + //--------------------------------------------------------------- + /** @brief returns recursive parameter count */ + size_type param_count() const { + size_type c = 0; + for(const auto& n : children_) { + c += n.param_count(); + } + return c; + } + + + //--------------------------------------------------------------- + /** @brief returns range of all flags (recursive) */ + arg_list all_flags() const + { + std::vector<arg_string> all; + gather_flags(children_, all); + return all; + } + + /** @brief returns true, if no flag occurs as true + * prefix of any other flag (identical flags will be ignored) */ + bool flags_are_prefix_free() const + { + const auto fs = all_flags(); + + using std::begin; using std::end; + for(auto i = begin(fs), e = end(fs); i != e; ++i) { + if(!i->empty()) { + for(auto j = i+1; j != e; ++j) { + if(!j->empty() && *i != *j) { + if(i->find(*j) == 0) return false; + if(j->find(*i) == 0) return false; + } + } + } + } + + return true; + } + + + //--------------------------------------------------------------- + /** @brief returns longest common prefix of all flags */ + arg_string common_flag_prefix() const + { + arg_list prefixes; + gather_prefixes(children_, prefixes); + return str::longest_common_prefix(prefixes); + } + + +private: + //--------------------------------------------------------------- + static void + gather_flags(const children_store& nodes, arg_list& all) + { + for(const auto& p : nodes) { + if(p.is_group()) { + gather_flags(p.as_group().children_, all); + } + else { + const auto& pf = p.as_param().flags(); + using std::begin; + using std::end; + if(!pf.empty()) all.insert(end(all), begin(pf), end(pf)); + } + } + } + //--------------------------------------------------------------- + static void + gather_prefixes(const children_store& nodes, arg_list& all) + { + for(const auto& p : nodes) { + if(p.is_group()) { + gather_prefixes(p.as_group().children_, all); + } + else if(!p.as_param().flags().empty()) { + auto pfx = str::longest_common_prefix(p.as_param().flags()); + if(!pfx.empty()) all.push_back(std::move(pfx)); + } + } + } + + //--------------------------------------------------------------- + children_store children_; + bool exclusive_ = false; + bool joinable_ = false; + bool scoped_ = false; +}; + + + +/*************************************************************************//** + * + * @brief group or parameter + * + *****************************************************************************/ +using pattern = group::child; + + + +/*************************************************************************//** + * + * @brief makes a group of parameters and/or groups + * + *****************************************************************************/ +inline group +operator , (parameter a, parameter b) +{ + return group{std::move(a), std::move(b)}.scoped(false); +} + +//--------------------------------------------------------- +inline group +operator , (parameter a, group b) +{ + return !b.scoped() && !b.blocking() && !b.exclusive() && !b.repeatable() + && !b.joinable() && (b.doc().empty() || b.doc() == a.doc()) + ? b.push_front(std::move(a)) + : group{std::move(a), std::move(b)}.scoped(false); +} + +//--------------------------------------------------------- +inline group +operator , (group a, parameter b) +{ + return !a.scoped() && !a.blocking() && !a.exclusive() && !a.repeatable() + && !a.joinable() && (a.doc().empty() || a.doc() == b.doc()) + ? a.push_back(std::move(b)) + : group{std::move(a), std::move(b)}.scoped(false); +} + +//--------------------------------------------------------- +inline group +operator , (group a, group b) +{ + return !a.scoped() && !a.blocking() && !a.exclusive() && !a.repeatable() + && !a.joinable() && (a.doc().empty() || a.doc() == b.doc()) + ? a.push_back(std::move(b)) + : group{std::move(a), std::move(b)}.scoped(false); +} + + + +/*************************************************************************//** + * + * @brief makes a group of alternative parameters or groups + * + *****************************************************************************/ +template<class Param, class... Params> +inline group +one_of(Param param, Params... params) +{ + return group{std::move(param), std::move(params)...}.exclusive(true); +} + + +/*************************************************************************//** + * + * @brief makes a group of alternative parameters or groups + * + *****************************************************************************/ +inline group +operator | (parameter a, parameter b) +{ + return group{std::move(a), std::move(b)}.scoped(false).exclusive(true); +} + +//------------------------------------------------------------------- +inline group +operator | (parameter a, group b) +{ + return !b.scoped() && !b.blocking() && b.exclusive() && !b.repeatable() + && !b.joinable() + && (b.doc().empty() || b.doc() == a.doc()) + ? b.push_front(std::move(a)) + : group{std::move(a), std::move(b)}.scoped(false).exclusive(true); +} + +//------------------------------------------------------------------- +inline group +operator | (group a, parameter b) +{ + return !a.scoped() && a.exclusive() && !a.repeatable() && !a.joinable() + && a.blocking() == b.blocking() + && (a.doc().empty() || a.doc() == b.doc()) + ? a.push_back(std::move(b)) + : group{std::move(a), std::move(b)}.scoped(false).exclusive(true); +} + +inline group +operator | (group a, group b) +{ + return !a.scoped() && a.exclusive() &&!a.repeatable() && !a.joinable() + && a.blocking() == b.blocking() + && (a.doc().empty() || a.doc() == b.doc()) + ? a.push_back(std::move(b)) + : group{std::move(a), std::move(b)}.scoped(false).exclusive(true); +} + + + +namespace detail { + +inline void set_blocking(bool) {} + +template<class P, class... Ps> +void set_blocking(bool yes, P& p, Ps&... ps) { + p.blocking(yes); + set_blocking(yes, ps...); +} + +} // namespace detail + + +/*************************************************************************//** + * + * @brief makes a parameter/group sequence by making all input objects blocking + * + *****************************************************************************/ +template<class Param, class... Params> +inline group +in_sequence(Param param, Params... params) +{ + detail::set_blocking(true, param, params...); + return group{std::move(param), std::move(params)...}.scoped(true); +} + + +/*************************************************************************//** + * + * @brief makes a parameter/group sequence by making all input objects blocking + * + *****************************************************************************/ +inline group +operator & (parameter a, parameter b) +{ + a.blocking(true); + b.blocking(true); + return group{std::move(a), std::move(b)}.scoped(true); +} + +//--------------------------------------------------------- +inline group +operator & (parameter a, group b) +{ + a.blocking(true); + return group{std::move(a), std::move(b)}.scoped(true); +} + +//--------------------------------------------------------- +inline group +operator & (group a, parameter b) +{ + b.blocking(true); + if(a.all_blocking() && !a.exclusive() && !a.repeatable() && !a.joinable() + && (a.doc().empty() || a.doc() == b.doc())) + { + return a.push_back(std::move(b)); + } + else { + if(!a.all_blocking()) a.blocking(true); + return group{std::move(a), std::move(b)}.scoped(true); + } +} + +inline group +operator & (group a, group b) +{ + if(!b.all_blocking()) b.blocking(true); + if(a.all_blocking() && !a.exclusive() && !a.repeatable() + && !a.joinable() && (a.doc().empty() || a.doc() == b.doc())) + { + return a.push_back(std::move(b)); + } + else { + if(!a.all_blocking()) a.blocking(true); + return group{std::move(a), std::move(b)}.scoped(true); + } +} + + + +/*************************************************************************//** + * + * @brief makes a group of parameters and/or groups + * where all single char flag params ("-a", "b", ...) are joinable + * + *****************************************************************************/ +inline group& +joinable(group& param) { + return param.joinable(true); +} + +inline group&& +joinable(group&& param) { + return std::move(param.joinable(true)); +} + +//------------------------------------------------------------------- +template<class... Params> +inline group +joinable(parameter param, Params... params) +{ + return group{std::move(param), std::move(params)...}.joinable(true); +} + +template<class P2, class... Ps> +inline group +joinable(group p1, P2 p2, Ps... ps) +{ + return group{std::move(p1), std::move(p2), std::move(ps)...}.joinable(true); +} + +template<class Param, class... Params> +inline group +joinable(doc_string docstr, Param param, Params... params) +{ + return group{std::move(param), std::move(params)...} + .joinable(true).doc(std::move(docstr)); +} + + + +/*************************************************************************//** + * + * @brief makes a repeatable copy of a parameter + * + *****************************************************************************/ +inline parameter +repeatable(parameter p) { + return p.repeatable(true); +} + +/*************************************************************************//** + * + * @brief makes a repeatable copy of a group + * + *****************************************************************************/ +inline group +repeatable(group g) { + return g.repeatable(true); +} + + + +/*************************************************************************//** + * + * @brief makes a group of parameters and/or groups + * that is repeatable as a whole + * Note that a repeatable group consisting entirely of non-blocking + * children is equivalent to a non-repeatable group of + * repeatable children. + * + *****************************************************************************/ +template<class P2, class... Ps> +inline group +repeatable(parameter p1, P2 p2, Ps... ps) +{ + return group{std::move(p1), std::move(p2), + std::move(ps)...}.repeatable(true); +} + +template<class P2, class... Ps> +inline group +repeatable(group p1, P2 p2, Ps... ps) +{ + return group{std::move(p1), std::move(p2), + std::move(ps)...}.repeatable(true); +} + + + +/*************************************************************************//** + * + * @brief recursively prepends a prefix to all flags + * + *****************************************************************************/ +inline parameter&& +with_prefix(const arg_string& prefix, parameter&& p) { + return std::move(with_prefix(prefix, p)); +} + + +//------------------------------------------------------------------- +inline group& +with_prefix(const arg_string& prefix, group& params) +{ + for(auto& p : params) { + if(p.is_group()) { + with_prefix(prefix, p.as_group()); + } else { + with_prefix(prefix, p.as_param()); + } + } + return params; +} + + +inline group&& +with_prefix(const arg_string& prefix, group&& params) +{ + return std::move(with_prefix(prefix, params)); +} + + +template<class Param, class... Params> +inline group +with_prefix(arg_string prefix, Param&& param, Params&&... params) +{ + return with_prefix(prefix, group{std::forward<Param>(param), + std::forward<Params>(params)...}); +} + + + +/*************************************************************************//** + * + * @brief recursively prepends a prefix to all flags + * + * @param shortpfx : used for single-letter flags + * @param longpfx : used for flags with length > 1 + * + *****************************************************************************/ +inline parameter&& +with_prefixes_short_long(const arg_string& shortpfx, const arg_string& longpfx, + parameter&& p) +{ + return std::move(with_prefixes_short_long(shortpfx, longpfx, p)); +} + + +//------------------------------------------------------------------- +inline group& +with_prefixes_short_long(const arg_string& shortFlagPrefix, + const arg_string& longFlagPrefix, + group& params) +{ + for(auto& p : params) { + if(p.is_group()) { + with_prefixes_short_long(shortFlagPrefix, longFlagPrefix, p.as_group()); + } else { + with_prefixes_short_long(shortFlagPrefix, longFlagPrefix, p.as_param()); + } + } + return params; +} + + +inline group&& +with_prefixes_short_long(const arg_string& shortFlagPrefix, + const arg_string& longFlagPrefix, + group&& params) +{ + return std::move(with_prefixes_short_long(shortFlagPrefix, longFlagPrefix, + params)); +} + + +template<class Param, class... Params> +inline group +with_prefixes_short_long(const arg_string& shortFlagPrefix, + const arg_string& longFlagPrefix, + Param&& param, Params&&... params) +{ + return with_prefixes_short_long(shortFlagPrefix, longFlagPrefix, + group{std::forward<Param>(param), + std::forward<Params>(params)...}); +} + + + + + + + + +/*************************************************************************//** + * + * @brief parsing implementation details + * + *****************************************************************************/ +namespace detail { + + +/*************************************************************************//** + * + * @brief DFS traverser that keeps track of 'scopes' + * scope = all parameters that are either bounded by + * two blocking parameters on the same depth level + * or the beginning/end of the outermost group + * + *****************************************************************************/ +class scoped_dfs_traverser +{ +public: + using dfs_traverser = group::depth_first_traverser; + + scoped_dfs_traverser() = default; + + explicit + scoped_dfs_traverser(const group& g): + pos_{g}, lastMatch_{}, posAfterLastMatch_{}, scopes_{}, + curMatched_{false}, ignoreBlocks_{false}, + repeatGroupStarted_{false}, repeatGroupContinues_{false} + {} + + const dfs_traverser& base() const noexcept { return pos_; } + const dfs_traverser& last_match() const noexcept { return lastMatch_; } + + const group& parent() const noexcept { return pos_.parent(); } + const group* repeat_group() const noexcept { return pos_.repeat_group(); } + const group* join_group() const noexcept { return pos_.join_group(); } + + const pattern* operator ->() const noexcept { return pos_.operator->(); } + const pattern& operator *() const noexcept { return *pos_; } + + const pattern* ptr() const noexcept { return pos_.operator->(); } + + explicit operator bool() const noexcept { return bool(pos_); } + + bool joinable() const noexcept { return pos_.joinable(); } + arg_string common_flag_prefix() const { return pos_.common_flag_prefix(); } + + void ignore_blocking(bool yes) { ignoreBlocks_ = yes; } + + void invalidate() { pos_.invalidate(); curMatched_ = false; } + bool matched() const noexcept { return curMatched_; } + + bool start_of_repeat_group() const noexcept { return repeatGroupStarted_; } + + //----------------------------------------------------- + scoped_dfs_traverser& + next_sibling() { pos_.next_sibling(); return *this; } + + scoped_dfs_traverser& + next_alternative() { pos_.next_alternative(); return *this; } + + scoped_dfs_traverser& + next_after_siblings() { pos_.next_after_siblings(); return *this; } + + //----------------------------------------------------- + scoped_dfs_traverser& + operator ++ () + { + if(!pos_) return *this; + + if(pos_.is_last_in_path()) { + return_to_outermost_scope(); + return *this; + } + + //current pattern can block if it didn't match already + if(!ignoreBlocks_ && !matched()) { + //current group can block if we didn't have any match in it + if(pos_.is_last_in_group() && pos_.parent().blocking() + && (!posAfterLastMatch_ || &(posAfterLastMatch_.parent()) != &(pos_.parent()))) + { + //ascend to parent's level + ++pos_; + //skip all siblings of parent group + pos_.next_after_siblings(); + if(!pos_) return_to_outermost_scope(); + } + else if(pos_->blocking() && !pos_->is_group()) { + if(pos_.parent().exclusive()) { //is_alternative(pos_.level())) { + pos_.next_alternative(); + } else { + //no match => skip siblings of blocking param + pos_.next_after_siblings(); + } + if(!pos_) return_to_outermost_scope(); + } else { + ++pos_; + } + } else { + ++pos_; + } + check_left_scope(); + return *this; + } + + //----------------------------------------------------- + void next_after_match(scoped_dfs_traverser match) + { + if(!match || ignoreBlocks_) return; + + check_repeat_group_start(match); + + lastMatch_ = match.base(); + + if(!match->blocking() && match.base().parent().blocking()) { + match.pos_.back_to_parent(); + } + + //if match is not in current position & current position is blocking + //=> current position has to be advanced by one so that it is + //no longer reachable within current scope + //(can happen for repeatable, blocking parameters) + if(match.base() != pos_ && pos_->blocking()) pos_.next_sibling(); + + if(match->blocking()) { + if(match.pos_.is_alternative()) { + //discard other alternatives + match.pos_.skip_alternatives(); + } + + if(is_last_in_current_scope(match.pos_)) { + //if current param is not repeatable -> back to previous scope + if(!match->repeatable() && !match->is_group()) { + curMatched_ = false; + pos_ = std::move(match.pos_); + if(!scopes_.empty()) pos_.undo(scopes_.top()); + } + else { //stay at match position + curMatched_ = true; + pos_ = std::move(match.pos_); + } + } + else { //not last in current group + //if current param is not repeatable, go directly to next + if(!match->repeatable() && !match->is_group()) { + curMatched_ = false; + ++match.pos_; + } else { + curMatched_ = true; + } + + if(match.pos_.level() > pos_.level()) { + scopes_.push(pos_.undo_point()); + pos_ = std::move(match.pos_); + } + else if(match.pos_.level() < pos_.level()) { + return_to_level(match.pos_.level()); + } + else { + pos_ = std::move(match.pos_); + } + } + posAfterLastMatch_ = pos_; + } + else { + if(match.pos_.level() < pos_.level()) { + return_to_level(match.pos_.level()); + } + posAfterLastMatch_ = pos_; + } + repeatGroupContinues_ = repeat_group_continues(); + } + +private: + //----------------------------------------------------- + bool is_last_in_current_scope(const dfs_traverser& pos) + { + if(scopes_.empty()) return pos.is_last_in_path(); + //check if we would leave the current scope on ++ + auto p = pos; + ++p; + return p.level() < scopes_.top().level(); + } + + //----------------------------------------------------- + void check_repeat_group_start(const scoped_dfs_traverser& newMatch) + { + const auto newrg = newMatch.repeat_group(); + if(!newrg) { + repeatGroupStarted_ = false; + } + else if(lastMatch_.repeat_group() != newrg) { + repeatGroupStarted_ = true; + } + else if(!repeatGroupContinues_ || !newMatch.repeatGroupContinues_) { + repeatGroupStarted_ = true; + } + else { + //special case: repeat group is outermost group + //=> we can never really 'leave' and 'reenter' it + //but if the current scope is the first element, then we are + //conceptually at a position 'before' the group + repeatGroupStarted_ = scopes_.empty() || ( + newrg == pos_.root() && + scopes_.top().param() == &(*pos_.root()->begin()) ); + } + repeatGroupContinues_ = repeatGroupStarted_; + } + + //----------------------------------------------------- + bool repeat_group_continues() + { + if(!repeatGroupContinues_) return false; + const auto curRepGroup = pos_.repeat_group(); + if(!curRepGroup) return false; + if(curRepGroup != lastMatch_.repeat_group()) return false; + if(!posAfterLastMatch_) return false; + return true; + } + + //----------------------------------------------------- + void check_left_scope() + { + if(posAfterLastMatch_) { + if(pos_.level() < posAfterLastMatch_.level()) { + while(!scopes_.empty() && scopes_.top().level() >= pos_.level()) { + pos_.undo(scopes_.top()); + scopes_.pop(); + } + posAfterLastMatch_.invalidate(); + } + } + while(!scopes_.empty() && scopes_.top().level() > pos_.level()) { + pos_.undo(scopes_.top()); + scopes_.pop(); + } + repeatGroupContinues_ = repeat_group_continues(); + } + + //----------------------------------------------------- + void return_to_outermost_scope() + { + posAfterLastMatch_.invalidate(); + + if(scopes_.empty()) { + pos_.invalidate(); + repeatGroupContinues_ = false; + return; + } + + while(!scopes_.empty() && (!pos_ || pos_.level() >= 1)) { + pos_.undo(scopes_.top()); + scopes_.pop(); + } + while(!scopes_.empty()) scopes_.pop(); + + repeatGroupContinues_ = repeat_group_continues(); + } + + //----------------------------------------------------- + void return_to_level(int level) + { + if(pos_.level() <= level) return; + while(!scopes_.empty() && pos_.level() > level) { + pos_.undo(scopes_.top()); + scopes_.pop(); + } + }; + + dfs_traverser pos_; + dfs_traverser lastMatch_; + dfs_traverser posAfterLastMatch_; + std::stack<dfs_traverser::memento> scopes_; + bool curMatched_ = false; + bool ignoreBlocks_ = false; + bool repeatGroupStarted_ = false; + bool repeatGroupContinues_ = false; +}; + + + + +/***************************************************************************** + * + * some parameter property predicates + * + *****************************************************************************/ +struct select_all { + bool operator () (const parameter&) const noexcept { return true; } +}; + +struct select_flags { + bool operator () (const parameter& p) const noexcept { + return !p.flags().empty(); + } +}; + +struct select_values { + bool operator () (const parameter& p) const noexcept { + return p.flags().empty(); + } +}; + + + +/*************************************************************************//** + * + * @brief result of a matching operation + * + *****************************************************************************/ +class match_t { +public: + match_t() = default; + match_t(arg_string s, scoped_dfs_traverser p): + str_{std::move(s)}, pos_{std::move(p)} + {} + + const arg_string& str() const noexcept { return str_; } + const scoped_dfs_traverser& pos() const noexcept { return pos_; } + + explicit operator bool() const noexcept { return !str_.empty(); } + +private: + arg_string str_; + scoped_dfs_traverser pos_; +}; + + + +/*************************************************************************//** + * + * @brief finds the first parameter that matches a given string + * candidate parameters are traversed using a scoped DFS traverser + * + *****************************************************************************/ +template<class Predicate> +match_t +full_match(scoped_dfs_traverser pos, const arg_string& arg, + const Predicate& select) +{ + if(arg.empty()) return match_t{}; + + while(pos) { + if(pos->is_param()) { + const auto& param = pos->as_param(); + if(select(param)) { + const auto match = param.match(arg); + if(match && match.length() == arg.size()) { + return match_t{arg, std::move(pos)}; + } + } + } + ++pos; + } + return match_t{}; +} + + + +/*************************************************************************//** + * + * @brief finds the first parameter that matches any (non-empty) prefix + * of a given string; + * candidate parameters are traversed using a scoped DFS traverser + * + *****************************************************************************/ +template<class Predicate> +match_t +prefix_match(scoped_dfs_traverser pos, const arg_string& arg, + const Predicate& select) +{ + if(arg.empty()) return match_t{}; + + while(pos) { + if(pos->is_param()) { + const auto& param = pos->as_param(); + if(select(param)) { + const auto match = param.match(arg); + if(match.prefix()) { + if(match.length() == arg.size()) { + return match_t{arg, std::move(pos)}; + } + else { + return match_t{arg.substr(match.at(), match.length()), + std::move(pos)}; + } + } + } + } + ++pos; + } + return match_t{}; +} + + + +/*************************************************************************//** + * + * @brief finds the first parameter that partially matches a given string; + * candidate parameters are traversed using a scoped DFS traverser + * + *****************************************************************************/ +template<class Predicate> +match_t +partial_match(scoped_dfs_traverser pos, const arg_string& arg, + const Predicate& select) +{ + if(arg.empty()) return match_t{}; + + while(pos) { + if(pos->is_param()) { + const auto& param = pos->as_param(); + if(select(param)) { + const auto match = param.match(arg); + if(match) { + return match_t{arg.substr(match.at(), match.length()), + std::move(pos)}; + } + } + } + ++pos; + } + return match_t{}; +} + +} //namespace detail + + + + + + +/***************************************************************//** + * + * @brief default command line arguments parser + * + *******************************************************************/ +class parser +{ +public: + using dfs_traverser = group::depth_first_traverser; + using scoped_dfs_traverser = detail::scoped_dfs_traverser; + + + /*****************************************************//** + * @brief arg -> parameter mapping + *********************************************************/ + class arg_mapping { + public: + friend class parser; + + explicit + arg_mapping(arg_index idx, arg_string s, + const dfs_traverser& match) + : + index_{idx}, arg_{std::move(s)}, match_{match}, + repeat_{0}, startsRepeatGroup_{false}, + blocked_{false}, conflict_{false} + {} + + explicit + arg_mapping(arg_index idx, arg_string s) : + index_{idx}, arg_{std::move(s)}, match_{}, + repeat_{0}, startsRepeatGroup_{false}, + blocked_{false}, conflict_{false} + {} + + arg_index index() const noexcept { return index_; } + const arg_string& arg() const noexcept { return arg_; } + + const parameter* param() const noexcept { + return match_ && match_->is_param() + ? &(match_->as_param()) : nullptr; + } + + std::size_t repeat() const noexcept { return repeat_; } + + bool blocked() const noexcept { return blocked_; } + bool conflict() const noexcept { return conflict_; } + + bool bad_repeat() const noexcept { + if(!param()) return false; + return repeat_ > 0 && !param()->repeatable() + && !match_.repeat_group(); + } + + bool any_error() const noexcept { + return !match_ || blocked() || conflict() || bad_repeat(); + } + + private: + arg_index index_; + arg_string arg_; + dfs_traverser match_; + std::size_t repeat_; + bool startsRepeatGroup_; + bool blocked_; + bool conflict_; + }; + + /*****************************************************//** + * @brief references a non-matched, required parameter + *********************************************************/ + class missing_event { + public: + explicit + missing_event(const parameter* p, arg_index after): + param_{p}, aftIndex_{after} + {} + + const parameter* param() const noexcept { return param_; } + + arg_index after_index() const noexcept { return aftIndex_; } + + private: + const parameter* param_; + arg_index aftIndex_; + }; + + //----------------------------------------------------- + using missing_events = std::vector<missing_event>; + using arg_mappings = std::vector<arg_mapping>; + + +private: + struct miss_candidate { + miss_candidate(dfs_traverser p, arg_index idx, + bool firstInRepeatGroup = false): + pos{std::move(p)}, index{idx}, + startsRepeatGroup{firstInRepeatGroup} + {} + + dfs_traverser pos; + arg_index index; + bool startsRepeatGroup; + }; + using miss_candidates = std::vector<miss_candidate>; + + +public: + //--------------------------------------------------------------- + /** @brief initializes parser with a command line interface + * @param offset = argument index offset used for reports + * */ + explicit + parser(const group& root, arg_index offset = 0): + root_{&root}, pos_{root}, + index_{offset-1}, eaten_{0}, + args_{}, missCand_{}, blocked_{false} + { + for_each_potential_miss(dfs_traverser{root}, + [this](const dfs_traverser& p){ + missCand_.emplace_back(p, index_); + }); + } + + + //--------------------------------------------------------------- + /** @brief processes one command line argument */ + bool operator() (const arg_string& arg) + { + ++eaten_; + ++index_; + + if(!valid() || arg.empty()) return false; + + if(!blocked_ && try_match(arg)) return true; + + if(try_match_blocked(arg)) return false; + + //skipping of blocking & required patterns is not allowed + if(!blocked_ && !pos_.matched() && pos_->required() && pos_->blocking()) { + blocked_ = true; + return false; + } + + add_nomatch(arg); + return false; + } + + + //--------------------------------------------------------------- + /** @brief returns range of argument -> parameter mappings */ + const arg_mappings& args() const { + return args_; + } + + /** @brief returns list of missing events */ + missing_events missed() const { + missing_events misses; + misses.reserve(missCand_.size()); + for(auto i = missCand_.begin(); i != missCand_.end(); ++i) { + misses.emplace_back(&(i->pos->as_param()), i->index); + } + return misses; + } + + /** @brief returns number of processed command line arguments */ + arg_index parse_count() const noexcept { return eaten_; } + + /** @brief returns false if previously processed command line arguments + * lead to an invalid / inconsistent parsing result + */ + bool valid() const noexcept { return bool(pos_); } + + /** @brief returns false if previously processed command line arguments + * lead to an invalid / inconsistent parsing result + */ + explicit operator bool() const noexcept { return valid(); } + + +private: + //--------------------------------------------------------------- + using match_t = detail::match_t; + + + //--------------------------------------------------------------- + /** @brief try to match argument with unreachable parameter */ + bool try_match_blocked(const arg_string& arg) + { + //try to match ahead (using temporary parser) + if(pos_) { + auto ahead = *this; + if(try_match_blocked(std::move(ahead), arg)) return true; + } + + //try to match from the beginning (using temporary parser) + if(root_) { + parser all{*root_, index_+1}; + if(try_match_blocked(std::move(all), arg)) return true; + } + + return false; + } + + //--------------------------------------------------------------- + bool try_match_blocked(parser&& parse, const arg_string& arg) + { + const auto nold = int(parse.args_.size()); + + parse.pos_.ignore_blocking(true); + + if(!parse.try_match(arg)) return false; + + for(auto i = parse.args_.begin() + nold; i != parse.args_.end(); ++i) { + args_.push_back(*i); + args_.back().blocked_ = true; + } + return true; + } + + //--------------------------------------------------------------- + /** @brief try to find a parameter/pattern that matches 'arg' */ + bool try_match(const arg_string& arg) + { + //Note: flag-params will always take precedence over value-params + if(try_match_full(arg, detail::select_flags{})) return true; + if(try_match_joined_flags(arg)) return true; + if(try_match_joined_sequence(arg, detail::select_flags{})) return true; + if(try_match_full(arg, detail::select_values{})) return true; + if(try_match_joined_sequence(arg, detail::select_all{})) return true; + if(try_match_joined_params(arg)) return true; + return false; + } + + //--------------------------------------------------------------- + template<class Predicate> + bool try_match_full(const arg_string& arg, const Predicate& select) + { + auto match = detail::full_match(pos_, arg, select); + + if(!match) return false; + + add_match(match); + return true; + } + + //--------------------------------------------------------------- + template<class Predicate> + bool try_match_joined_sequence(arg_string arg, const Predicate& acceptFirst) + { + auto fstMatch = detail::prefix_match(pos_, arg, acceptFirst); + + if(!fstMatch) return false; + + if(fstMatch.str().size() == arg.size()) { + add_match(fstMatch); + return true; + } + + if(!fstMatch.pos()->blocking()) return false; + + auto pos = fstMatch.pos(); + pos.ignore_blocking(true); + const auto parent = &pos.parent(); + if(!pos->repeatable()) ++pos; + + arg.erase(0, fstMatch.str().size()); + std::vector<match_t> matches { std::move(fstMatch) }; + + while(!arg.empty() && pos && + pos->blocking() && pos->is_param() && + (&pos.parent() == parent)) + { + auto match = pos->as_param().match(arg); + + if(match.prefix()) { + matches.emplace_back(arg.substr(0,match.length()), pos); + arg.erase(0, match.length()); + if(!pos->repeatable()) ++pos; + } + else { + if(!pos->repeatable()) return false; + ++pos; + } + + } + + if(!arg.empty() || matches.empty()) return false; + + for(const auto& m : matches) add_match(m); + return true; + } + + //----------------------------------------------------- + bool try_match_joined_flags(const arg_string& arg) + { + return try_match_joined([&](const group& g) { + if(try_match_joined(g, arg, detail::select_flags{}, + g.common_flag_prefix()) ) + { + return true; + } + return false; + }); + } + + //--------------------------------------------------------------- + bool try_match_joined_params(const arg_string& arg) + { + return try_match_joined([&](const group& g) { + if(try_match_joined(g, arg, detail::select_all{}) ) { + return true; + } + return false; + }); + } + + //----------------------------------------------------- + template<class Predicate> + bool try_match_joined(const group& joinGroup, arg_string arg, + const Predicate& pred, + const arg_string& prefix = "") + { + parser parse {joinGroup}; + std::vector<match_t> matches; + + while(!arg.empty()) { + auto match = detail::prefix_match(parse.pos_, arg, pred); + + if(!match) return false; + + arg.erase(0, match.str().size()); + //make sure prefix is always present after the first match + //ensures that, e.g., flags "-a" and "-b" will be found in "-ab" + if(!arg.empty() && !prefix.empty() && arg.find(prefix) != 0 && + prefix != match.str()) + { + arg.insert(0,prefix); + } + + parse.add_match(match); + matches.push_back(std::move(match)); + } + + if(!arg.empty() || matches.empty()) return false; + + if(!parse.missCand_.empty()) return false; + for(const auto& a : parse.args_) if(a.any_error()) return false; + + //replay matches onto *this + for(const auto& m : matches) add_match(m); + return true; + } + + //----------------------------------------------------- + template<class Predicate> + bool try_match_joined(const Predicate& pred) + { + if(pos_ && pos_.parent().joinable()) { + const auto& g = pos_.parent(); + if(pred(g)) return true; + return false; + } + + auto pos = pos_; + while(pos) { + if(pos->is_group() && pos->as_group().joinable()) { + const auto& g = pos->as_group(); + if(pred(g)) return true; + pos.next_sibling(); + } + else { + ++pos; + } + } + return false; + } + + + //--------------------------------------------------------------- + void add_nomatch(const arg_string& arg) { + args_.emplace_back(index_, arg); + } + + + //--------------------------------------------------------------- + void add_match(const match_t& match) + { + const auto& pos = match.pos(); + if(!pos || !pos->is_param() || match.str().empty()) return; + + pos_.next_after_match(pos); + + arg_mapping newArg{index_, match.str(), pos.base()}; + newArg.repeat_ = occurrences_of(&pos->as_param()); + newArg.conflict_ = check_conflicts(pos.base()); + newArg.startsRepeatGroup_ = pos_.start_of_repeat_group(); + args_.push_back(std::move(newArg)); + + add_miss_candidates_after(pos); + clean_miss_candidates_for(pos.base()); + discard_alternative_miss_candidates(pos.base()); + + } + + //----------------------------------------------------- + bool check_conflicts(const dfs_traverser& match) + { + if(pos_.start_of_repeat_group()) return false; + bool conflict = false; + for(const auto& m : match.stack()) { + if(m.parent->exclusive()) { + for(auto i = args_.rbegin(); i != args_.rend(); ++i) { + if(!i->blocked()) { + for(const auto& c : i->match_.stack()) { + //sibling within same exclusive group => conflict + if(c.parent == m.parent && c.cur != m.cur) { + conflict = true; + i->conflict_ = true; + } + } + } + //check for conflicts only within current repeat cycle + if(i->startsRepeatGroup_) break; + } + } + } + return conflict; + } + + //----------------------------------------------------- + void clean_miss_candidates_for(const dfs_traverser& match) + { + auto i = std::find_if(missCand_.rbegin(), missCand_.rend(), + [&](const miss_candidate& m) { + return &(*m.pos) == &(*match); + }); + + if(i != missCand_.rend()) { + missCand_.erase(prev(i.base())); + } + } + + //----------------------------------------------------- + void discard_alternative_miss_candidates(const dfs_traverser& match) + { + if(missCand_.empty()) return; + //find out, if miss candidate is sibling of one of the same + //alternative groups that the current match is a member of + //if so, we can discard the miss + + //go through all exclusive groups of matching pattern + for(const auto& m : match.stack()) { + if(m.parent->exclusive()) { + for(auto i = int(missCand_.size())-1; i >= 0; --i) { + bool removed = false; + for(const auto& c : missCand_[i].pos.stack()) { + //sibling within same exclusive group => discard + if(c.parent == m.parent && c.cur != m.cur) { + missCand_.erase(missCand_.begin() + i); + if(missCand_.empty()) return; + removed = true; + break; + } + } + //remove miss candidates only within current repeat cycle + if(i > 0 && removed) { + if(missCand_[i-1].startsRepeatGroup) break; + } else { + if(missCand_[i].startsRepeatGroup) break; + } + } + } + } + } + + //----------------------------------------------------- + void add_miss_candidates_after(const scoped_dfs_traverser& match) + { + auto npos = match.base(); + if(npos.is_alternative()) npos.skip_alternatives(); + ++npos; + //need to add potential misses if: + //either new repeat group was started + const auto newRepGroup = match.repeat_group(); + if(newRepGroup) { + if(pos_.start_of_repeat_group()) { + for_each_potential_miss(std::move(npos), + [&,this](const dfs_traverser& pos) { + //only add candidates within repeat group + if(newRepGroup == pos.repeat_group()) { + missCand_.emplace_back(pos, index_, true); + } + }); + } + } + //... or an optional blocking param was hit + else if(match->blocking() && !match->required() && + npos.level() >= match.base().level()) + { + for_each_potential_miss(std::move(npos), + [&,this](const dfs_traverser& pos) { + //only add new candidates + if(std::find_if(missCand_.begin(), missCand_.end(), + [&](const miss_candidate& c){ + return &(*c.pos) == &(*pos); + }) == missCand_.end()) + { + missCand_.emplace_back(pos, index_); + } + }); + } + + } + + //----------------------------------------------------- + template<class Action> + static void + for_each_potential_miss(dfs_traverser pos, Action&& action) + { + const auto level = pos.level(); + while(pos && pos.level() >= level) { + if(pos->is_group() ) { + const auto& g = pos->as_group(); + if(g.all_optional() || (g.exclusive() && g.any_optional())) { + pos.next_sibling(); + } else { + ++pos; + } + } else { //param + if(pos->required()) { + action(pos); + ++pos; + } else if(pos->blocking()) { //optional + blocking + pos.next_after_siblings(); + } else { + ++pos; + } + } + } + } + + + //--------------------------------------------------------------- + std::size_t occurrences_of(const parameter* p) const + { + auto i = std::find_if(args_.rbegin(), args_.rend(), + [p](const arg_mapping& a){ return a.param() == p; }); + + if(i != args_.rend()) return i->repeat() + 1; + return 0; + } + + + //--------------------------------------------------------------- + const group* root_; + scoped_dfs_traverser pos_; + arg_index index_; + arg_index eaten_; + arg_mappings args_; + miss_candidates missCand_; + bool blocked_; +}; + + + + +/*************************************************************************//** + * + * @brief contains argument -> parameter mappings + * and missing parameters + * + *****************************************************************************/ +class parsing_result +{ +public: + using arg_mapping = parser::arg_mapping; + using arg_mappings = parser::arg_mappings; + using missing_event = parser::missing_event; + using missing_events = parser::missing_events; + using iterator = arg_mappings::const_iterator; + + //----------------------------------------------------- + /** @brief default: empty redult */ + parsing_result() = default; + + parsing_result(arg_mappings arg2param, missing_events misses): + arg2param_{std::move(arg2param)}, missing_{std::move(misses)} + {} + + //----------------------------------------------------- + /** @brief returns number of arguments that could not be mapped to + * a parameter + */ + arg_mappings::size_type + unmapped_args_count() const noexcept { + return std::count_if(arg2param_.begin(), arg2param_.end(), + [](const arg_mapping& a){ return !a.param(); }); + } + + /** @brief returns if any argument could only be matched by an + * unreachable parameter + */ + bool any_blocked() const noexcept { + return std::any_of(arg2param_.begin(), arg2param_.end(), + [](const arg_mapping& a){ return a.blocked(); }); + } + + /** @brief returns if any argument matched more than one parameter + * that were mutually exclusive */ + bool any_conflict() const noexcept { + return std::any_of(arg2param_.begin(), arg2param_.end(), + [](const arg_mapping& a){ return a.conflict(); }); + } + + /** @brief returns if any parameter matched repeatedly although + * it was not allowed to */ + bool any_bad_repeat() const noexcept { + return std::any_of(arg2param_.begin(), arg2param_.end(), + [](const arg_mapping& a){ return a.bad_repeat(); }); + } + + /** @brief returns true if any parsing error / violation of the + * command line interface definition occured */ + bool any_error() const noexcept { + return unmapped_args_count() > 0 || !missing().empty() || + any_blocked() || any_conflict() || any_bad_repeat(); + } + + /** @brief returns true if no parsing error / violation of the + * command line interface definition occured */ + explicit operator bool() const noexcept { return !any_error(); } + + /** @brief access to range of missing parameter match events */ + const missing_events& missing() const noexcept { return missing_; } + + /** @brief returns non-mutating iterator to position of + * first argument -> parameter mapping */ + iterator begin() const noexcept { return arg2param_.begin(); } + /** @brief returns non-mutating iterator to position one past the + * last argument -> parameter mapping */ + iterator end() const noexcept { return arg2param_.end(); } + +private: + //----------------------------------------------------- + arg_mappings arg2param_; + missing_events missing_; +}; + + + + +namespace detail { +namespace { + +/*************************************************************************//** + * + * @brief correct some common problems + * does not - and MUST NOT - change the number of arguments + * (no insertion, no deletion) + * + *****************************************************************************/ +void sanitize_args(arg_list& args) +{ + //e.g. {"-o12", ".34"} -> {"-o", "12.34"} + + if(args.empty()) return; + + for(auto i = begin(args)+1; i != end(args); ++i) { + if(i != begin(args) && i->size() > 1 && + i->find('.') == 0 && std::isdigit((*i)[1]) ) + { + //find trailing digits in previous arg + using std::prev; + auto& prv = *prev(i); + auto fstDigit = std::find_if_not(prv.rbegin(), prv.rend(), + [](arg_string::value_type c){ + return std::isdigit(c); + }).base(); + + //handle leading sign + if(fstDigit > prv.begin() && + (*prev(fstDigit) == '+' || *prev(fstDigit) == '-')) + { + --fstDigit; + } + + //prepend digits from previous arg + i->insert(begin(*i), fstDigit, end(prv)); + + //erase digits in previous arg + prv.erase(fstDigit, end(prv)); + } + } +} + + + +/*************************************************************************//** + * + * @brief executes actions based on a parsing result + * + *****************************************************************************/ +void execute_actions(const parsing_result& res) +{ + for(const auto& m : res) { + if(m.param()) { + const auto& param = *(m.param()); + + if(m.repeat() > 0) param.notify_repeated(m.index()); + if(m.blocked()) param.notify_blocked(m.index()); + if(m.conflict()) param.notify_conflict(m.index()); + //main action + if(!m.any_error()) param.execute_actions(m.arg()); + } + } + + for(auto m : res.missing()) { + if(m.param()) m.param()->notify_missing(m.after_index()); + } +} + + + +/*************************************************************************//** + * + * @brief parses input args + * + *****************************************************************************/ +static parsing_result +parse_args(const arg_list& args, const group& cli, + arg_index offset = 0) +{ + //parse args and store unrecognized arg indices + parser parse{cli, offset}; + for(const auto& arg : args) { + parse(arg); + if(!parse.valid()) break; + } + + return parsing_result{parse.args(), parse.missed()}; +} + +/*************************************************************************//** + * + * @brief parses input args & executes actions + * + *****************************************************************************/ +static parsing_result +parse_and_execute(const arg_list& args, const group& cli, + arg_index offset = 0) +{ + auto result = parse_args(args, cli, offset); + + execute_actions(result); + + return result; +} + +} //anonymous namespace +} // namespace detail + + + + +/*************************************************************************//** + * + * @brief parses vector of arg strings and executes actions + * + *****************************************************************************/ +inline parsing_result +parse(arg_list args, const group& cli) +{ + detail::sanitize_args(args); + return detail::parse_and_execute(args, cli); +} + + +/*************************************************************************//** + * + * @brief parses initializer_list of C-style arg strings and executes actions + * + *****************************************************************************/ +inline parsing_result +parse(std::initializer_list<const char*> arglist, const group& cli) +{ + arg_list args; + args.reserve(arglist.size()); + for(auto a : arglist) { + if(std::strlen(a) > 0) args.push_back(a); + } + + return parse(std::move(args), cli); +} + + +/*************************************************************************//** + * + * @brief parses range of arg strings and executes actions + * + *****************************************************************************/ +template<class InputIterator> +inline parsing_result +parse(InputIterator first, InputIterator last, const group& cli) +{ + return parse(arg_list(first,last), cli); +} + + +/*************************************************************************//** + * + * @brief parses the standard array of command line arguments; omits argv[0] + * + *****************************************************************************/ +inline parsing_result +parse(const int argc, char* argv[], const group& cli, arg_index offset = 1) +{ + arg_list args; + if(offset < argc) args.assign(argv+offset, argv+argc); + detail::sanitize_args(args); + return detail::parse_and_execute(args, cli, offset); +} + + + + + + +/*************************************************************************//** + * + * @brief filter predicate for parameters and groups; + * Can be used to limit documentation generation to parameter subsets. + * + *****************************************************************************/ +class param_filter +{ +public: + /** @brief only allow parameters with given prefix */ + param_filter& prefix(const arg_string& p) noexcept { + prefix_ = p; return *this; + } + /** @brief only allow parameters with given prefix */ + param_filter& prefix(arg_string&& p) noexcept { + prefix_ = std::move(p); return *this; + } + const arg_string& prefix() const noexcept { return prefix_; } + + /** @brief only allow parameters with given requirement status */ + param_filter& required(tri t) noexcept { required_ = t; return *this; } + tri required() const noexcept { return required_; } + + /** @brief only allow parameters with given blocking status */ + param_filter& blocking(tri t) noexcept { blocking_ = t; return *this; } + tri blocking() const noexcept { return blocking_; } + + /** @brief only allow parameters with given repeatable status */ + param_filter& repeatable(tri t) noexcept { repeatable_ = t; return *this; } + tri repeatable() const noexcept { return repeatable_; } + + /** @brief only allow parameters with given docstring status */ + param_filter& has_doc(tri t) noexcept { hasDoc_ = t; return *this; } + tri has_doc() const noexcept { return hasDoc_; } + + + /** @brief returns true, if parameter satisfies all filters */ + bool operator() (const parameter& p) const noexcept { + if(!prefix_.empty()) { + if(!std::any_of(p.flags().begin(), p.flags().end(), + [&](const arg_string& flag){ + return str::has_prefix(flag, prefix_); + })) return false; + } + if(required() != p.required()) return false; + if(blocking() != p.blocking()) return false; + if(repeatable() != p.repeatable()) return false; + if(has_doc() != !p.doc().empty()) return false; + return true; + } + +private: + arg_string prefix_; + tri required_ = tri::either; + tri blocking_ = tri::either; + tri repeatable_ = tri::either; + tri exclusive_ = tri::either; + tri hasDoc_ = tri::yes; +}; + + + + + + +/*************************************************************************//** + * + * @brief documentation formatting options + * + *****************************************************************************/ +class doc_formatting +{ +public: + using string = doc_string; + + /** @brief determines column where documentation printing starts */ + doc_formatting& start_column(int col) { startCol_ = col; return *this; } + int start_column() const noexcept { return startCol_; } + + /** @brief determines column where docstrings start */ + doc_formatting& doc_column(int col) { docCol_ = col; return *this; } + int doc_column() const noexcept { return docCol_; } + + /** @brief determines indent of documentation lines + * for children of a documented group */ + doc_formatting& indent_size(int indent) { indentSize_ = indent; return *this; } + int indent_size() const noexcept { return indentSize_; } + + /** @brief determines string to be used + * if a parameter has no flags and no label */ + doc_formatting& empty_label(const string& label) { + emptyLabel_ = label; + return *this; + } + const string& empty_label() const noexcept { return emptyLabel_; } + + /** @brief determines string for separating parameters */ + doc_formatting& param_separator(const string& sep) { + paramSep_ = sep; + return *this; + } + const string& param_separator() const noexcept { return paramSep_; } + + /** @brief determines string for separating groups (in usage lines) */ + doc_formatting& group_separator(const string& sep) { + groupSep_ = sep; + return *this; + } + const string& group_separator() const noexcept { return groupSep_; } + + /** @brief determines string for separating alternative parameters */ + doc_formatting& alternative_param_separator(const string& sep) { + altParamSep_ = sep; + return *this; + } + const string& alternative_param_separator() const noexcept { return altParamSep_; } + + /** @brief determines string for separating alternative groups */ + doc_formatting& alternative_group_separator(const string& sep) { + altGroupSep_ = sep; + return *this; + } + const string& alternative_group_separator() const noexcept { return altGroupSep_; } + + /** @brief determines string for separating flags of the same parameter */ + doc_formatting& flag_separator(const string& sep) { + flagSep_ = sep; + return *this; + } + const string& flag_separator() const noexcept { return flagSep_; } + + /** @brief determnines strings surrounding parameter labels */ + doc_formatting& + surround_labels(const string& prefix, const string& postfix) { + labelPre_ = prefix; + labelPst_ = postfix; + return *this; + } + const string& label_prefix() const noexcept { return labelPre_; } + const string& label_postfix() const noexcept { return labelPst_; } + + /** @brief determnines strings surrounding optional parameters/groups */ + doc_formatting& + surround_optional(const string& prefix, const string& postfix) { + optionPre_ = prefix; + optionPst_ = postfix; + return *this; + } + const string& optional_prefix() const noexcept { return optionPre_; } + const string& optional_postfix() const noexcept { return optionPst_; } + + /** @brief determnines strings surrounding repeatable parameters/groups */ + doc_formatting& + surround_repeat(const string& prefix, const string& postfix) { + repeatPre_ = prefix; + repeatPst_ = postfix; + return *this; + } + const string& repeat_prefix() const noexcept { return repeatPre_; } + const string& repeat_postfix() const noexcept { return repeatPst_; } + + /** @brief determnines strings surrounding exclusive groups */ + doc_formatting& + surround_alternatives(const string& prefix, const string& postfix) { + alternPre_ = prefix; + alternPst_ = postfix; + return *this; + } + const string& alternatives_prefix() const noexcept { return alternPre_; } + const string& alternatives_postfix() const noexcept { return alternPst_; } + + /** @brief determnines strings surrounding alternative flags */ + doc_formatting& + surround_alternative_flags(const string& prefix, const string& postfix) { + alternFlagPre_ = prefix; + alternFlagPst_ = postfix; + return *this; + } + const string& alternative_flags_prefix() const noexcept { return alternFlagPre_; } + const string& alternative_flags_postfix() const noexcept { return alternFlagPst_; } + + /** @brief determnines strings surrounding non-exclusive groups */ + doc_formatting& + surround_group(const string& prefix, const string& postfix) { + groupPre_ = prefix; + groupPst_ = postfix; + return *this; + } + const string& group_prefix() const noexcept { return groupPre_; } + const string& group_postfix() const noexcept { return groupPst_; } + + /** @brief determnines strings surrounding joinable groups */ + doc_formatting& + surround_joinable(const string& prefix, const string& postfix) { + joinablePre_ = prefix; + joinablePst_ = postfix; + return *this; + } + const string& joinable_prefix() const noexcept { return joinablePre_; } + const string& joinable_postfix() const noexcept { return joinablePst_; } + + /** @brief determines maximum number of flags per parameter to be printed + * in detailed parameter documentation lines */ + doc_formatting& max_flags_per_param_in_doc(int max) { + maxAltInDocs_ = max > 0 ? max : 0; + return *this; + } + int max_flags_per_param_in_doc() const noexcept { return maxAltInDocs_; } + + /** @brief determines maximum number of flags per parameter to be printed + * in usage lines */ + doc_formatting& max_flags_per_param_in_usage(int max) { + maxAltInUsage_ = max > 0 ? max : 0; + return *this; + } + int max_flags_per_param_in_usage() const noexcept { return maxAltInUsage_; } + + /** @brief determines number of empty rows after one single-line + * documentation entry */ + doc_formatting& line_spacing(int lines) { + lineSpc_ = lines > 0 ? lines : 0; + return *this; + } + int line_spacing() const noexcept { return lineSpc_; } + + /** @brief determines number of empty rows before and after a paragraph; + * a paragraph is defined by a documented group or if + * a parameter documentation entry used more than one line */ + doc_formatting& paragraph_spacing(int lines) { + paragraphSpc_ = lines > 0 ? lines : 0; + return *this; + } + int paragraph_spacing() const noexcept { return paragraphSpc_; } + + /** @brief determines if alternative flags with a common prefix should + * be printed in a merged fashion */ + doc_formatting& merge_alternative_flags_with_common_prefix(bool yes = true) { + mergeAltCommonPfx_ = yes; + return *this; + } + bool merge_alternative_flags_with_common_prefix() const noexcept { + return mergeAltCommonPfx_; + } + + /** @brief determines if joinable flags with a common prefix should + * be printed in a merged fashion */ + doc_formatting& merge_joinable_with_common_prefix(bool yes = true) { + mergeJoinableCommonPfx_ = yes; + return *this; + } + bool merge_joinable_with_common_prefix() const noexcept { + return mergeJoinableCommonPfx_; + } + + /** @brief determines if children of exclusive groups should be printed + * on individual lines if the exceed 'alternatives_min_split_size' + */ + doc_formatting& split_alternatives(bool yes = true) { + splitTopAlt_ = yes; + return *this; + } + bool split_alternatives() const noexcept { + return splitTopAlt_; + } + + /** @brief determines how many children exclusive groups can have before + * their children are printed on individual usage lines */ + doc_formatting& alternatives_min_split_size(int size) { + groupSplitSize_ = size > 0 ? size : 0; + return *this; + } + int alternatives_min_split_size() const noexcept { return groupSplitSize_; } + +private: + string paramSep_ = string(" "); + string groupSep_ = string(" "); + string altParamSep_ = string("|"); + string altGroupSep_ = string(" | "); + string flagSep_ = string(", "); + string labelPre_ = string("<"); + string labelPst_ = string(">"); + string optionPre_ = string("["); + string optionPst_ = string("]"); + string repeatPre_ = string(""); + string repeatPst_ = string("..."); + string groupPre_ = string("("); + string groupPst_ = string(")"); + string alternPre_ = string("("); + string alternPst_ = string(")"); + string alternFlagPre_ = string(""); + string alternFlagPst_ = string(""); + string joinablePre_ = string("("); + string joinablePst_ = string(")"); + string emptyLabel_ = string(""); + int startCol_ = 8; + int docCol_ = 20; + int indentSize_ = 4; + int maxAltInUsage_ = 1; + int maxAltInDocs_ = 32; + int lineSpc_ = 0; + int paragraphSpc_ = 1; + int groupSplitSize_ = 3; + bool splitTopAlt_ = true; + bool mergeAltCommonPfx_ = false; + bool mergeJoinableCommonPfx_ = true; +}; + + + + +/*************************************************************************//** + * + * @brief generates usage lines + * + * @details lazily evaluated + * + *****************************************************************************/ +class usage_lines +{ +public: + using string = doc_string; + + usage_lines(const group& params, string prefix = "", + const doc_formatting& fmt = doc_formatting{}) + : + params_(params), fmt_(fmt), prefix_(std::move(prefix)) + { + if(!prefix_.empty()) prefix_ += ' '; + if(fmt_.start_column() > 0) prefix_.insert(0, fmt.start_column(), ' '); + } + + usage_lines(const group& params, const doc_formatting& fmt): + usage_lines(params, "", fmt) + {} + + usage_lines& ommit_outermost_group_surrounders(bool yes) { + ommitOutermostSurrounders_ = yes; + return *this; + } + bool ommit_outermost_group_surrounders() const { + return ommitOutermostSurrounders_; + } + + template<class OStream> + inline friend OStream& operator << (OStream& os, const usage_lines& p) { + p.print_usage(os); + return os; + } + + string str() const { + std::ostringstream os; os << *this; return os.str(); + } + + +private: + const group& params_; + doc_formatting fmt_; + string prefix_; + bool ommitOutermostSurrounders_ = false; + + + //----------------------------------------------------- + struct context { + group::depth_first_traverser pos; + std::stack<string> separators; + std::stack<string> postfixes; + int level = 0; + const group* outermost = nullptr; + bool linestart = false; + bool useOutermost = true; + int line = 0; + + bool is_singleton() const noexcept { + return linestart && pos.is_last_in_path(); + } + bool is_alternative() const noexcept { + return pos.parent().exclusive(); + } + }; + + + /***************************************************************//** + * + * @brief writes usage text for command line parameters + * + *******************************************************************/ + template<class OStream> + void print_usage(OStream& os) const + { + context cur; + cur.pos = params_.begin_dfs(); + cur.linestart = true; + cur.level = cur.pos.level(); + cur.outermost = ¶ms_; + + print_usage(os, cur, prefix_); + } + + + /***************************************************************//** + * + * @brief writes usage text for command line parameters + * + * @param prefix all that goes in front of current things to print + * + *******************************************************************/ + template<class OStream> + void print_usage(OStream& os, context cur, string prefix) const + { + if(!cur.pos) return; + + std::ostringstream buf; + if(cur.linestart) buf << prefix; + const auto initPos = buf.tellp(); + + cur.level = cur.pos.level(); + + if(cur.useOutermost) { + //we cannot start outside of the outermost group + //so we have to treat it separately + start_group(buf, cur.pos.parent(), cur); + if(!cur.pos) { + os << buf.str(); + return; + } + } + else { + //don't visit siblings of starter node + cur.pos.skip_siblings(); + } + check_end_group(buf, cur); + + do { + if(buf.tellp() > initPos) cur.linestart = false; + if(!cur.linestart && !cur.pos.is_first_in_group()) { + buf << cur.separators.top(); + } + if(cur.pos->is_group()) { + start_group(buf, cur.pos->as_group(), cur); + if(!cur.pos) { + os << buf.str(); + return; + } + } + else { + buf << param_label(cur.pos->as_param(), cur); + ++cur.pos; + } + check_end_group(buf, cur); + } while(cur.pos); + + os << buf.str(); + } + + + /***************************************************************//** + * + * @brief handles pattern group surrounders and separators + * and alternative splitting + * + *******************************************************************/ + void start_group(std::ostringstream& os, + const group& group, context& cur) const + { + //does cur.pos already point to a member or to group itself? + //needed for special treatment of outermost group + const bool alreadyInside = &(cur.pos.parent()) == &group; + + auto lbl = joined_label(group, cur); + if(!lbl.empty()) { + os << lbl; + cur.linestart = false; + //skip over entire group as its label has already been created + if(alreadyInside) { + cur.pos.next_after_siblings(); + } else { + cur.pos.next_sibling(); + } + } + else { + const bool splitAlternatives = group.exclusive() && + fmt_.split_alternatives() && + std::any_of(group.begin(), group.end(), + [this](const pattern& p) { + return int(p.param_count()) >= fmt_.alternatives_min_split_size(); + }); + + if(splitAlternatives) { + cur.postfixes.push(""); + cur.separators.push(""); + //recursively print alternative paths in decision-DAG + //enter group? + if(!alreadyInside) ++cur.pos; + cur.linestart = true; + cur.useOutermost = false; + auto pfx = os.str(); + os.str(""); + //print paths in DAG starting at each group member + for(std::size_t i = 0; i < group.size(); ++i) { + std::stringstream buf; + cur.outermost = cur.pos->is_group() ? &(cur.pos->as_group()) : nullptr; + print_usage(buf, cur, pfx); + if(buf.tellp() > int(pfx.size())) { + os << buf.str(); + if(i < group.size()-1) { + if(cur.line > 0) { + os << string(fmt_.line_spacing(), '\n'); + } + ++cur.line; + os << '\n'; + } + } + cur.pos.next_sibling(); //do not descend into memebers + } + cur.pos.invalidate(); //signal end-of-path + return; + } + else { + //pre & postfixes, separators + auto surround = group_surrounders(group, cur); + os << surround.first; + cur.postfixes.push(std::move(surround.second)); + cur.separators.push(group_separator(group, fmt_)); + //descend into group? + if(!alreadyInside) ++cur.pos; + } + } + cur.level = cur.pos.level(); + } + + + /***************************************************************//** + * + *******************************************************************/ + void check_end_group(std::ostringstream& os, context& cur) const + { + for(; cur.level > cur.pos.level(); --cur.level) { + os << cur.postfixes.top(); + cur.postfixes.pop(); + cur.separators.pop(); + } + cur.level = cur.pos.level(); + } + + + /***************************************************************//** + * + * @brief makes usage label for one command line parameter + * + *******************************************************************/ + string param_label(const parameter& p, const context& cur) const + { + const auto& parent = cur.pos.parent(); + + const bool startsOptionalSequence = + parent.size() > 1 && p.blocking() && cur.pos.is_first_in_group(); + + const bool outermost = + ommitOutermostSurrounders_ && cur.outermost == &parent; + + const bool showopt = !cur.is_alternative() && !p.required() + && !startsOptionalSequence && !outermost; + + const bool showrep = p.repeatable() && !outermost; + + string lbl; + + if(showrep) lbl += fmt_.repeat_prefix(); + if(showopt) lbl += fmt_.optional_prefix(); + + const auto& flags = p.flags(); + if(!flags.empty()) { + const int n = std::min(fmt_.max_flags_per_param_in_usage(), + int(flags.size())); + + const bool surrAlt = n > 1 && !showopt && !cur.is_singleton(); + + if(surrAlt) lbl += fmt_.alternative_flags_prefix(); + bool sep = false; + for(int i = 0; i < n; ++i) { + if(sep) { + if(cur.is_singleton()) + lbl += fmt_.alternative_group_separator(); + else + lbl += fmt_.flag_separator(); + } + lbl += flags[i]; + sep = true; + } + if(surrAlt) lbl += fmt_.alternative_flags_postfix(); + } + else { + if(!p.label().empty()) { + lbl += fmt_.label_prefix() + + p.label() + + fmt_.label_postfix(); + } else if(!fmt_.empty_label().empty()) { + lbl += fmt_.label_prefix() + + fmt_.empty_label() + + fmt_.label_postfix(); + } else { + return ""; + } + } + + if(showopt) lbl += fmt_.optional_postfix(); + if(showrep) lbl += fmt_.repeat_postfix(); + + return lbl; + } + + + /***************************************************************//** + * + * @brief prints flags in one group in a merged fashion + * + *******************************************************************/ + string joined_label(const group& params, const context& cur) const + { + if(!fmt_.merge_alternative_flags_with_common_prefix() && + !fmt_.merge_joinable_with_common_prefix()) return ""; + + const bool flagsonly = std::all_of(params.begin(), params.end(), + [](const pattern& p){ + return p.is_param() && !p.as_param().flags().empty(); + }); + + if(!flagsonly) return ""; + + const bool showOpt = params.all_optional() && + !(ommitOutermostSurrounders_ && cur.outermost == ¶ms); + + auto pfx = params.common_flag_prefix(); + if(pfx.empty()) return ""; + + const auto n = pfx.size(); + if(params.exclusive() && + fmt_.merge_alternative_flags_with_common_prefix()) + { + string lbl; + if(showOpt) lbl += fmt_.optional_prefix(); + lbl += pfx + fmt_.alternatives_prefix(); + bool first = true; + for(const auto& p : params) { + if(p.is_param()) { + if(first) + first = false; + else + lbl += fmt_.alternative_param_separator(); + lbl += p.as_param().flags().front().substr(n); + } + } + lbl += fmt_.alternatives_postfix(); + if(showOpt) lbl += fmt_.optional_postfix(); + return lbl; + } + //no alternatives, but joinable flags + else if(params.joinable() && + fmt_.merge_joinable_with_common_prefix()) + { + const bool allSingleChar = std::all_of(params.begin(), params.end(), + [&](const pattern& p){ + return p.is_param() && + p.as_param().flags().front().substr(n).size() == 1; + }); + + if(allSingleChar) { + string lbl; + if(showOpt) lbl += fmt_.optional_prefix(); + lbl += pfx; + for(const auto& p : params) { + if(p.is_param()) + lbl += p.as_param().flags().front().substr(n); + } + if(showOpt) lbl += fmt_.optional_postfix(); + return lbl; + } + } + + return ""; + } + + + /***************************************************************//** + * + * @return symbols with which to surround a group + * + *******************************************************************/ + std::pair<string,string> + group_surrounders(const group& group, const context& cur) const + { + string prefix; + string postfix; + + const bool isOutermost = &group == cur.outermost; + if(isOutermost && ommitOutermostSurrounders_) + return {string{}, string{}}; + + if(group.exclusive()) { + if(group.all_optional()) { + prefix = fmt_.optional_prefix(); + postfix = fmt_.optional_postfix(); + if(group.all_flagless()) { + prefix += fmt_.label_prefix(); + postfix = fmt_.label_prefix() + postfix; + } + } else if(group.all_flagless()) { + prefix = fmt_.label_prefix(); + postfix = fmt_.label_postfix(); + } else if(!cur.is_singleton() || !isOutermost) { + prefix = fmt_.alternatives_prefix(); + postfix = fmt_.alternatives_postfix(); + } + } + else if(group.size() > 1 && + group.front().blocking() && !group.front().required()) + { + prefix = fmt_.optional_prefix(); + postfix = fmt_.optional_postfix(); + } + else if(group.size() > 1 && cur.is_alternative() && + &group != cur.outermost) + { + prefix = fmt_.group_prefix(); + postfix = fmt_.group_postfix(); + } + else if(!group.exclusive() && + group.joinable() && !cur.linestart) + { + prefix = fmt_.joinable_prefix(); + postfix = fmt_.joinable_postfix(); + } + + if(group.repeatable()) { + if(prefix.empty()) prefix = fmt_.group_prefix(); + prefix = fmt_.repeat_prefix() + prefix; + if(postfix.empty()) postfix = fmt_.group_postfix(); + postfix += fmt_.repeat_postfix(); + } + + return {std::move(prefix), std::move(postfix)}; + } + + + /***************************************************************//** + * + * @return symbol that separates members of a group + * + *******************************************************************/ + static string + group_separator(const group& group, const doc_formatting& fmt) + { + const bool only1ParamPerMember = std::all_of(group.begin(), group.end(), + [](const pattern& p) { return p.param_count() < 2; }); + + if(only1ParamPerMember) { + if(group.exclusive()) { + return fmt.alternative_param_separator(); + } else { + return fmt.param_separator(); + } + } + else { //there is at least one large group inside + if(group.exclusive()) { + return fmt.alternative_group_separator(); + } else { + return fmt.group_separator(); + } + } + } +}; + + + + +/*************************************************************************//** + * + * @brief generates parameter and group documentation from docstrings + * + * @details lazily evaluated + * + *****************************************************************************/ +class documentation +{ +public: + using string = doc_string; + + documentation(const group& cli, + const doc_formatting& fmt = doc_formatting{}, + const param_filter& filter = param_filter{}) + : + cli_(cli), fmt_{fmt}, usgFmt_{fmt}, filter_{filter} + { + //necessary, because we re-use "usage_lines" to generate + //labels for documented groups + usgFmt_.max_flags_per_param_in_usage( + usgFmt_.max_flags_per_param_in_doc()); + } + + documentation(const group& params, + const param_filter& filter, + const doc_formatting& fmt = doc_formatting{}) + : + documentation(params, fmt, filter) + {} + + template<class OStream> + inline friend OStream& operator << (OStream& os, const documentation& p) { + printed prn = printed::nothing; + p.print_doc(os, p.cli_, prn); + return os; + } + + string str() const { + std::ostringstream os; os << *this; return os.str(); + } + + +private: + using dfs_traverser = group::depth_first_traverser; + enum class printed { nothing, line, paragraph }; + + const group& cli_; + doc_formatting fmt_; + doc_formatting usgFmt_; + param_filter filter_; + + + /***************************************************************//** + * + * @brief writes full documentation text for command line parameters + * + *******************************************************************/ + template<class OStream> + void print_doc(OStream& os, const group& params, + printed& sofar, + int indentLvl = 0) const + { + if(params.empty()) return; + + //if group itself doesn't have docstring + if(params.doc().empty()) { + for(const auto& p : params) { + print_doc(os, p, sofar, indentLvl); + } + } + else { //group itself does have docstring + bool anyDocInside = std::any_of(params.begin(), params.end(), + [](const pattern& p){ return !p.doc().empty(); }); + + if(anyDocInside) { //group docstring as title, then child entries + if(sofar != printed::nothing) { + os << string(fmt_.paragraph_spacing() + 1, '\n'); + } + auto indent = string(fmt_.start_column(), ' '); + if(indentLvl > 0) indent += string(fmt_.indent_size() * indentLvl, ' '); + os << indent << params.doc() << '\n'; + sofar = printed::nothing; + for(const auto& p : params) { + print_doc(os, p, sofar, indentLvl + 1); + } + sofar = printed::paragraph; + } + else { //group label first then group docstring + auto lbl = usage_lines(params, usgFmt_) + .ommit_outermost_group_surrounders(true).str(); + + str::trim(lbl); + print_entry(os, lbl, params.doc(), fmt_, sofar, indentLvl); + } + } + } + + + /***************************************************************//** + * + * @brief writes documentation text for one group or parameter + * + *******************************************************************/ + template<class OStream> + void print_doc(OStream& os, const pattern& ptrn, + printed& sofar, int indentLvl) const + { + if(ptrn.is_group()) { + print_doc(os, ptrn.as_group(), sofar, indentLvl); + } + else { + const auto& p = ptrn.as_param(); + if(!filter_(p)) return; + print_entry(os, param_label(p, fmt_), p.doc(), fmt_, sofar, indentLvl); + } + } + + + /*********************************************************************//** + * + * @brief prints one entry = label + docstring + * + ************************************************************************/ + template<class OStream> + static void + print_entry(OStream& os, + const string& label, const string& docstr, + const doc_formatting& fmt, printed& sofar, int indentLvl) + { + if(label.empty()) return; + + auto indent = string(fmt.start_column(), ' '); + if(indentLvl > 0) indent += string(fmt.indent_size() * indentLvl, ' '); + + const auto len = int(indent.size() + label.size()); + const bool oneline = len < fmt.doc_column(); + + if(oneline) { + if(sofar == printed::line) + os << string(fmt.line_spacing() + 1, '\n'); + else if(sofar == printed::paragraph) + os << string(fmt.paragraph_spacing() + 1, '\n'); + } + else if(sofar != printed::nothing) { + os << string(fmt.paragraph_spacing() + 1, '\n'); + } + + sofar = oneline ? printed::line : printed::paragraph; + + os << indent << label; + + if(!docstr.empty()) { + if(oneline) { + os << string(fmt.doc_column() - len, ' '); + } else { + os << '\n' << string(fmt.doc_column(), ' '); + } + os << docstr; + } + } + + + /*********************************************************************//** + * + * @brief makes label for one parameter + * + ************************************************************************/ + static doc_string + param_label(const parameter& param, const doc_formatting& fmt) + { + doc_string lbl; + + if(param.repeatable()) lbl += fmt.repeat_prefix(); + + const auto& flags = param.flags(); + if(!flags.empty()) { + lbl += flags[0]; + const int n = std::min(fmt.max_flags_per_param_in_doc(), + int(flags.size())); + for(int i = 1; i < n; ++i) { + lbl += fmt.flag_separator() + flags[i]; + } + } + else if(!param.label().empty() || !fmt.empty_label().empty()) { + lbl += fmt.label_prefix(); + if(!param.label().empty()) { + lbl += param.label(); + } else { + lbl += fmt.empty_label(); + } + lbl += fmt.label_postfix(); + } + + if(param.repeatable()) lbl += fmt.repeat_postfix(); + + return lbl; + } + +}; + + + + +/*************************************************************************//** + * + * @brief stores strings for man page sections + * + *****************************************************************************/ +class man_page +{ +public: + //--------------------------------------------------------------- + using string = doc_string; + + //--------------------------------------------------------------- + /** @brief man page section */ + class section { + public: + using string = doc_string; + + section(string stitle, string scontent): + title_{std::move(stitle)}, content_{std::move(scontent)} + {} + + const string& title() const noexcept { return title_; } + const string& content() const noexcept { return content_; } + + private: + string title_; + string content_; + }; + +private: + using section_store = std::vector<section>; + +public: + //--------------------------------------------------------------- + using value_type = section; + using const_iterator = section_store::const_iterator; + using size_type = section_store::size_type; + + + //--------------------------------------------------------------- + man_page& + append_section(string title, string content) + { + sections_.emplace_back(std::move(title), std::move(content)); + return *this; + } + //----------------------------------------------------- + man_page& + prepend_section(string title, string content) + { + sections_.emplace(sections_.begin(), + std::move(title), std::move(content)); + return *this; + } + + + //--------------------------------------------------------------- + const section& operator [] (size_type index) const noexcept { + return sections_[index]; + } + + //--------------------------------------------------------------- + size_type size() const noexcept { return sections_.size(); } + + bool empty() const noexcept { return sections_.empty(); } + + + //--------------------------------------------------------------- + const_iterator begin() const noexcept { return sections_.begin(); } + const_iterator end() const noexcept { return sections_.end(); } + + + //--------------------------------------------------------------- + man_page& program_name(const string& n) { + progName_ = n; + return *this; + } + man_page& program_name(string&& n) { + progName_ = std::move(n); + return *this; + } + const string& program_name() const noexcept { + return progName_; + } + + + //--------------------------------------------------------------- + man_page& section_row_spacing(int rows) { + sectionSpc_ = rows > 0 ? rows : 0; + return *this; + } + int section_row_spacing() const noexcept { return sectionSpc_; } + + +private: + int sectionSpc_ = 1; + section_store sections_; + string progName_; +}; + + + +/*************************************************************************//** + * + * @brief generates man sections from command line parameters + * with sections "synopsis" and "options" + * + *****************************************************************************/ +inline man_page +make_man_page(const group& params, + doc_string progname = "", + const doc_formatting& fmt = doc_formatting{}) +{ + man_page man; + man.append_section("SYNOPSIS", usage_lines(params,progname,fmt).str()); + man.append_section("OPTIONS", documentation(params,fmt).str()); + return man; +} + + + +/*************************************************************************//** + * + * @brief generates man page based on command line parameters + * + *****************************************************************************/ +template<class OStream> +OStream& +operator << (OStream& os, const man_page& man) +{ + bool first = true; + const auto secSpc = doc_string(man.section_row_spacing() + 1, '\n'); + for(const auto& section : man) { + if(!section.content().empty()) { + if(first) first = false; else os << secSpc; + if(!section.title().empty()) os << section.title() << '\n'; + os << section.content(); + } + } + os << '\n'; + return os; +} + + + + + +/*************************************************************************//** + * + * @brief printing methods for debugging command line interfaces + * + *****************************************************************************/ +namespace debug { + + +/*************************************************************************//** + * + * @brief prints first flag or value label of a parameter + * + *****************************************************************************/ +inline doc_string doc_label(const parameter& p) +{ + if(!p.flags().empty()) return p.flags().front(); + if(!p.label().empty()) return p.label(); + return doc_string{"<?>"}; +} + +inline doc_string doc_label(const group&) +{ + return "<group>"; +} + +inline doc_string doc_label(const pattern& p) +{ + return p.is_group() ? doc_label(p.as_group()) : doc_label(p.as_param()); +} + + +/*************************************************************************//** + * + * @brief prints parsing result + * + *****************************************************************************/ +template<class OStream> +void print(OStream& os, const parsing_result& result) +{ + for(const auto& m : result) { + os << "#" << m.index() << " " << m.arg() << " -> "; + auto p = m.param(); + if(p) { + os << doc_label(*p) << " \t"; + if(m.repeat() > 0) { + os << (m.bad_repeat() ? "[bad repeat " : "[repeat ") + << m.repeat() << "]"; + } + if(m.blocked()) os << " [blocked]"; + if(m.conflict()) os << " [conflict]"; + os << '\n'; + } + else { + os << " [unmapped]\n"; + } + } + + for(const auto& m : result.missing()) { + auto p = m.param(); + if(p) { + os << doc_label(*p) << " \t"; + os << " [missing after " << m.after_index() << "]\n"; + } + } +} + + +/*************************************************************************//** + * + * @brief prints parameter label and some properties + * + *****************************************************************************/ +template<class OStream> +void print(OStream& os, const parameter& p) +{ + if(p.blocking()) os << '!'; + if(!p.required()) os << '['; + os << doc_label(p); + if(p.repeatable()) os << "..."; + if(!p.required()) os << "]"; +} + + +//------------------------------------------------------------------- +template<class OStream> +void print(OStream& os, const group& g, int level = 0); + + +/*************************************************************************//** + * + * @brief prints group or parameter; uses indentation + * + *****************************************************************************/ +template<class OStream> +void print(OStream& os, const pattern& param, int level = 0) +{ + if(param.is_group()) { + print(os, param.as_group(), level); + } + else { + os << doc_string(4*level, ' '); + print(os, param.as_param()); + } +} + + +/*************************************************************************//** + * + * @brief prints group and its contents; uses indentation + * + *****************************************************************************/ +template<class OStream> +void print(OStream& os, const group& g, int level) +{ + auto indent = doc_string(4*level, ' '); + os << indent; + if(g.blocking()) os << '!'; + if(g.joinable()) os << 'J'; + os << (g.exclusive() ? "(|\n" : "(\n"); + for(const auto& p : g) { + print(os, p, level+1); + } + os << '\n' << indent << (g.exclusive() ? "|)" : ")"); + if(g.repeatable()) os << "..."; + os << '\n'; +} + + +} // namespace debug +} //namespace clipp + +#endif + diff --git a/include/exception.h b/include/exception.h new file mode 100644 index 00000000..b630bd00 --- /dev/null +++ b/include/exception.h @@ -0,0 +1,22 @@ +#ifndef UTIL_EXCEPTION_H +#define UTIL_EXCEPTION_H + +#include <exception> +#include <string> + +namespace eic::util { + class Exception : public std::exception { + public: + Exception(std::string_view msg, std::string_view type = "exception") : msg_{msg}, type_{type} {} + + virtual const char* what() const throw() { return msg_.c_str(); } + virtual const char* type() const throw() { return type_.c_str(); } + virtual ~Exception() throw() {} + + private: + std::string msg_; + std::string type_; + }; +} // namespace eic::util + +#endif diff --git a/include/mt.h b/include/mt.h new file mode 100644 index 00000000..198050c6 --- /dev/null +++ b/include/mt.h @@ -0,0 +1,11 @@ +#ifndef MT_H +#define MT_H + +// Defines the number of threads to run within the ROOT analysis scripts. +// TODO: make this a file configured by the CI scripts so we can specify +// the number of threads (and the number of processes) at a global +// level + +constexpr const int kNumThreads = 8; + +#endif diff --git a/include/plot.h b/include/plot.h new file mode 100644 index 00000000..c1986163 --- /dev/null +++ b/include/plot.h @@ -0,0 +1,42 @@ +#ifndef PLOT_H +#define PLOT_H + +#include <TCanvas.h> +#include <TColor.h> +#include <TPad.h> +#include <TPaveText.h> +#include <TStyle.h> +#include <fmt/core.h> +#include <vector> + +namespace plot { + + const int kMpBlue = TColor::GetColor(0x1f, 0x77, 0xb4); + const int kMpOrange = TColor::GetColor(0xff, 0x7f, 0x0e); + const int kMpGreen = TColor::GetColor(0x2c, 0xa0, 0x2c); + const int kMpRed = TColor::GetColor(0xd6, 0x27, 0x28); + const int kMpPurple = TColor::GetColor(0x94, 0x67, 0xbd); + const int kMpBrown = TColor::GetColor(0x8c, 0x56, 0x4b); + const int kMpPink = TColor::GetColor(0xe3, 0x77, 0xc2); + const int kMpGrey = TColor::GetColor(0x7f, 0x7f, 0x7f); + const int kMpMoss = TColor::GetColor(0xbc, 0xbd, 0x22); + const int kMpCyan = TColor::GetColor(0x17, 0xbe, 0xcf); + + const std::vector<int> kPalette = {kMpBlue, kMpOrange, kMpGreen, kMpRed, kMpPurple, + kMpBrown, kMpPink, kMpGrey, kMpMoss, kMpCyan}; + + void draw_label(int ebeam, int pbeam, const std::string_view detector) + { + auto t = new TPaveText(.15, 0.800, .7, .925, "NB NDC"); + t->SetFillColorAlpha(kWhite, 0.4); + t->SetTextFont(43); + t->SetTextSize(25); + t->AddText(fmt::format("#bf{{{} }}SIMULATION", detector).c_str()); + t->AddText(fmt::format("{} GeV on {} GeV", ebeam, pbeam).c_str()); + t->SetTextAlign(12); + t->Draw(); + } + +} // namespace plot + +#endif diff --git a/include/util.h b/include/util.h new file mode 100644 index 00000000..6a24b293 --- /dev/null +++ b/include/util.h @@ -0,0 +1,161 @@ +#ifndef UTIL_H +#define UTIL_H + +// TODO: should probably be moved to a global benchmark utility library + +#include <algorithm> +#include <cmath> +#include <exception> +#include <fmt/core.h> +#include <limits> +#include <string> +#include <vector> + +#include <Math/Vector4D.h> + +#include "dd4pod/Geant4ParticleCollection.h" +#include "eicd/TrackParametersCollection.h" + +namespace util { + + // Exception definition for unknown particle errors + // FIXME: A utility exception base class should be included in the analysis + // utility library, so we can skip most of this boilerplate + class unknown_particle_error : public std::exception { + public: + unknown_particle_error(std::string_view particle) : m_particle{particle} {} + virtual const char* what() const throw() + { + return fmt::format("Unknown particle type: {}", m_particle).c_str(); + } + virtual const char* type() const throw() { return "unknown_particle_error"; } + + private: + const std::string m_particle; + }; + + // Simple function to return the appropriate PDG mass for the particles + // we care about for this process. + // FIXME: consider something more robust (maybe based on hepPDT) to the + // analysis utility library + inline double get_pdg_mass(std::string_view part) + { + if (part == "electron") { + return 0.0005109989461; + } else if (part == "muon") { + return .1056583745; + } else if (part == "jpsi") { + return 3.0969; + } else if (part == "upsilon") { + return 9.49630; + } else if (part == "proton"){ + return 0.938272; + } else { + throw unknown_particle_error{part}; + } + } + + // Get a vector of 4-momenta from raw tracking info, using an externally + // provided particle mass assumption. + inline auto momenta_from_tracking(const std::vector<eic::TrackParametersData>& tracks, + const double mass) + { + std::vector<ROOT::Math::PxPyPzMVector> momenta{tracks.size()}; + // transform our raw tracker info into proper 4-momenta + std::transform(tracks.begin(), tracks.end(), momenta.begin(), [mass](const auto& track) { + // make sure we don't divide by zero + if (fabs(track.qOverP) < 1e-9) { + return ROOT::Math::PxPyPzMVector{}; + } + const double p = fabs(1. / track.qOverP); + const double px = p * cos(track.phi) * sin(track.theta); + const double py = p * sin(track.phi) * sin(track.theta); + const double pz = p * cos(track.theta); + return ROOT::Math::PxPyPzMVector{px, py, pz, mass}; + }); + return momenta; + } + + // Get a vector of 4-momenta from the simulation data. + // TODO: Add PID selector (maybe using ranges?) + inline auto momenta_from_simulation(const std::vector<dd4pod::Geant4ParticleData>& parts) + { + std::vector<ROOT::Math::PxPyPzMVector> momenta{parts.size()}; + // transform our simulation particle data into 4-momenta + std::transform(parts.begin(), parts.end(), momenta.begin(), [](const auto& part) { + return ROOT::Math::PxPyPzMVector{part.psx, part.psy, part.psz, part.mass}; + }); + return momenta; + } + + // Find the decay pair candidates from a vector of particles (parts), + // with invariant mass closest to a desired value (pdg_mass) + inline std::pair<ROOT::Math::PxPyPzMVector, ROOT::Math::PxPyPzMVector> + find_decay_pair(const std::vector<ROOT::Math::PxPyPzMVector>& parts, const double pdg_mass) + { + int first = -1; + int second = -1; + double best_mass = -1; + + // go through all particle combinatorics, calculate the invariant mass + // for each combination, and remember which combination is the closest + // to the desired pdg_mass + for (size_t i = 0; i < parts.size(); ++i) { + for (size_t j = i + 1; j < parts.size(); ++j) { + const double new_mass{(parts[i] + parts[j]).mass()}; + if (fabs(new_mass - pdg_mass) < fabs(best_mass - pdg_mass)) { + first = i; + second = j; + best_mass = new_mass; + } + } + } + if (first < 0) { + return {{}, {}}; + } + return {parts[first], parts[second]}; + } + + // Calculate the magnitude of the momentum of a vector of 4-vectors + inline auto mom(const std::vector<ROOT::Math::PxPyPzMVector>& momenta) + { + std::vector<double> P(momenta.size()); + // transform our raw tracker info into proper 4-momenta + std::transform(momenta.begin(), momenta.end(), P.begin(), + [](const auto& mom) { return mom.P(); }); + return P; + } + // Calculate the transverse momentum of a vector of 4-vectors + inline auto pt(const std::vector<ROOT::Math::PxPyPzMVector>& momenta) + { + std::vector<double> pt(momenta.size()); + // transform our raw tracker info into proper 4-momenta + std::transform(momenta.begin(), momenta.end(), pt.begin(), + [](const auto& mom) { return mom.pt(); }); + return pt; + } + + // Calculate the azimuthal angle phi of a vector of 4-vectors + inline auto phi(const std::vector<ROOT::Math::PxPyPzMVector>& momenta) + { + std::vector<double> phi(momenta.size()); + // transform our raw tracker info into proper 4-momenta + std::transform(momenta.begin(), momenta.end(), phi.begin(), + [](const auto& mom) { return mom.phi(); }); + return phi; + } + // Calculate the pseudo-rapidity of a vector of particles + inline auto eta(const std::vector<ROOT::Math::PxPyPzMVector>& momenta) + { + std::vector<double> eta(momenta.size()); + // transform our raw tracker info into proper 4-momenta + std::transform(momenta.begin(), momenta.end(), eta.begin(), + [](const auto& mom) { return mom.eta(); }); + return eta; + } + + //========================================================================================================= + +} // namespace util + +#endif diff --git a/options/env.sh b/options/env.sh new file mode 100755 index 00000000..28216396 --- /dev/null +++ b/options/env.sh @@ -0,0 +1,117 @@ +#!/bin/bash + +## ============================================================================= +## Global configuration variables for the benchmark scripts +## The script defines the following environment variables that are meant to +## be overriden by the Gitlab continuous integration (CI) +## +## - JUGGLER_DETECTOR: detector package to be used for the benchmark +## - JUGGLER_N_EVENTS: #events processed by simulation/reconstruction +## - JUGGLER_INSTALL_PREFIX: location where Juggler (digi/recon) is installed +## - JUGGLER_N_THREADS: Number of threads/processes to spawn in parallel +## - JUGGLER_RNG_SEED: Random seed for the RNG +## +## It also defines the following additional variables for internal usage +## - LOCAL_PREFIX: prefix for packages installed during the benchmark +## - DETECTOR_PREFIX: prefix for the detector definitions +## - DETECTOR_PATH: actual path with the detector definitions +## +## Finally, it makes sure LOCAL_PREFIX and JUGGLER_PREFIX are added to PATH +## and LD_LIBRARY_PATH +## ============================================================================= + +echo "Setting up the Physics Benchmarks environment" + +## ============================================================================= +## Default variable definitions, normally these should be set +## by the CI. In case of local development you may want to change these +## in case you would like to modify the detector package or +## number of events to be analyzed during the benchmark + +## Detector package to be used during the benchmark process +if [ ! -n "${JUGGLER_DETECTOR}" ] ; then + export JUGGLER_DETECTOR="topside" +fi + +if [ ! -n "${JUGGLER_DETECTOR_VERSION}" ] ; then + export JUGGLER_DETECTOR_VERSION="v0.0.1" +fi + + +## Number of events that will be processed by the reconstruction +if [ ! -n "${JUGGLER_N_EVENTS}" ] ; then + export JUGGLER_N_EVENTS=100 +fi + +## Maximum number of threads or processes a single pipeline should use +## (this is not enforced, but the different pipeline scripts should use +## this to guide the number of parallel processes or threads they +## spawn). +if [ ! -n "${JUGGLER_N_THREADS}" ]; then + export JUGGLER_N_THREADS=10 +fi + +## Random seed for event generation, should typically not be changed for +## reproductability. +if [ ! -n "${JUGGLER_RNG_SEED}" ]; then + export JUGGLER_RNG_SEED=1 +fi + +## Install prefix for juggler, needed to locate the Juggler xenv files. +## Also used by the CI as install prefix for other packages where needed. +## You should not have to touch this. Note that for local usage a different +## prefix structure is automatically used. +if [ ! -n "${JUGGLER_INSTALL_PREFIX}" ] ; then + export JUGGLER_INSTALL_PREFIX="/usr/local" +fi +## Ensure the juggler prefix is an absolute path +export JUGGLER_INSTALL_PREFIX=`realpath ${JUGGLER_INSTALL_PREFIX}` + + +## Location of local data for pass data from job to job within pipeline. +## Not saved as artifacts. +if [ ! -n "${LOCAL_DATA_PATH}" ] ; then + export LOCAL_DATA_PATH="/scratch/${CI_PROJECT_NAME}_${CI_PIPELINE_ID}" +fi + +## ============================================================================= +## Other utility variables that govern how some of the dependent packages +## are built and installed. You should not have to change these. + +## local prefix to be used for local storage of packages +## downloaded/installed during the benchmark process +LOCAL_PREFIX=".local" +mkdir -p ${LOCAL_PREFIX} +export LOCAL_PREFIX=`realpath ${LOCAL_PREFIX}` + +## detector prefix: prefix for the detector definitions +export DETECTOR_PREFIX="${LOCAL_PREFIX}/detector" +mkdir -p ${DETECTOR_PREFIX} + +## detector path: actual detector definition path +export DETECTOR_PATH="${DETECTOR_PREFIX}/${JUGGLER_DETECTOR}" + +## build dir for ROOT to put its binaries etc. +export ROOT_BUILD_DIR=$LOCAL_PREFIX/root_build + +echo "JUGGLER_DETECTOR: ${JUGGLER_DETECTOR}" +echo "JUGGLER_DETECTOR_VERSION: ${JUGGLER_DETECTOR_VERSION}" +echo "JUGGLER_N_EVENTS: ${JUGGLER_N_EVENTS}" +echo "JUGGLER_N_THREADS: ${JUGGLER_N_THREADS}" +echo "JUGGLER_RNG_SEED: ${JUGGLER_RNG_SEED}" +echo "JUGGLER_INSTALL_PREFIX: ${JUGGLER_INSTALL_PREFIX}" +echo "LOCAL_DATA_PATH: ${LOCAL_DATA_PATH}" +echo "LOCAL_PREFIX: ${LOCAL_PREFIX}" +echo "DETECTOR_PREFIX: ${DETECTOR_PREFIX}" +echo "DETECTOR_PATH: ${DETECTOR_PATH}" +echo "ROOT_BUILD_DIR: ${ROOT_BUILD_DIR}" + +## ============================================================================= +## Setup PATH and LD_LIBRARY_PATH to include our prefixes +echo "Adding JUGGLER_INSTALL_PREFIX and LOCAL_PREFIX to PATH and LD_LIBRARY_PATH" +export PATH=${JUGGLER_INSTALL_PREFIX}/bin:${LOCAL_PREFIX}/bin:${PATH} +export LD_LIBRARY_PATH=${JUGGLER_INSTALL_PREFIX}/lib:${LOCAL_PREFIX}/lib:${LD_LIBRARY_PATH} + +## ============================================================================= +## That's all! +echo "Environment setup complete." diff --git a/tools/dev-shell b/tools/dev-shell new file mode 100755 index 00000000..0295f14b --- /dev/null +++ b/tools/dev-shell @@ -0,0 +1,84 @@ +#!/bin/bash + +## ============================================================================= +## Setup (if needed) and start a development shell environment on Linux or MacOS +## ============================================================================= + +## make sure we launch this script from the project root directory +PROJECT_ROOT="$( cd "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )"/.. +pushd ${PROJECT_ROOT} + +## We do not load the global development environment here, as this script is +## to be executed on a "naked" system outside of any container + +## ============================================================================= +## Step 1: Parse command line options + +## do we want to force-update the container (only affects Linux) +## default: we do not want to do this. +FORCE_UPDATE= + +function print_the_help { + echo "USAGE: ./util/start_dev_shell [-f]" + echo "OPTIONS:" + echo " -f,--force Force-update container (Only affects Linux)" + echo " -h,--help Print this message" + echo "" + echo " This script will setup and launch a containerized development + environment" + exit +} +while [ $# -gt 0 ] +do + key="$1" + case $key in + -f|--force) + FORCE_UPDATE="true" + shift # past value + ;; + -h|--help) + print_the_help + shift + ;; + *) # unknown option + echo "unknown option $1" + exit 1 + ;; + esac +done + +## get OS type +OS=`uname -s` + +## ============================================================================= +## Step 2: Update container and launch shell +echo "Launching a containerized development shell" + +case ${OS} in + Linux) + echo " - Detected OS: Linux" + ## Use the same prefix as we use for other local packages + export PREFIX=.local/lib + if [ ! -f $PREFIX/juggler_latest.sif ] || [ ! -z ${FORCE_UPDATE} ]; then + echo " - Fetching singularity image" + mkdir -p $PREFIX + wget https://eicweb.phy.anl.gov/eic/juggler/-/jobs/artifacts/master/raw/build/juggler.sif?job=singularity:latest -O $PREFIX/juggler_latest.sif + fi + echo " - Using singularity to launch shell..." + singularity exec $PREFIX/juggler_latest.sif eic-shell + ;; + Darwin) + echo " - Detector OS: MacOS" + echo " - Syncing docker container" + docker pull sly2j/juggler:latest + echo " - Using docker to launch shell..." + docker run -v /Users:/Users -w=$PWD -i -t --rm sly2j/juggler:latest eic-shell + ;; + *) + echo "ERROR: dev shell not available for this OS (${OS})" + exit 1 +esac + +## ============================================================================= +## Step 3: All done +echo "Exiting development environment..." diff --git a/tools/download.sh b/tools/download.sh new file mode 100755 index 00000000..47be238b --- /dev/null +++ b/tools/download.sh @@ -0,0 +1,73 @@ +#!/bin/bash + +## ============================================================================= +## Download generator & reconstruction artifacts for one or more physics +## processes. +## ============================================================================= + +## make sure we launch this script from the project root directory +PROJECT_ROOT="$( cd "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )"/.. +pushd ${PROJECT_ROOT} + +PROCS=() +BRANCH="master" + +function print_the_help { + echo "USAGE: -p process [-p process2] [-b git_branch]" + echo "OPTIONS:" + echo " -p,--process Physics process name (can be defined multiple + times)." + echo " -b,--branch Git branch to download artifacts from (D: + $BRANCH)" + echo " -h,--help Print this message" + echo "" + echo " This script will download the relevant generator artifacts needed" + echo " for local testing of the benchmarks." + exit +} + +while [ $# -gt 0 ] +do + key="$1" + case $key in + -p|--process) + PROCS+=("$2") + shift # past argument + shift # past value + ;; + -b|--branch) + BRANCH="$2" + shift # past argument + shift # past value + ;; + -h|--help) + print_the_help + shift + ;; + *) # unknown option + echo "unknown option: $1" + exit 1 + ;; + esac +done + +echo "Downloading generator & reconstruction artifacts for one or more physics processes" + +if [ ${#PROCS[@]} -eq 0 ]; then + echo "ERROR: need one or more processes: -p <process name> " + exit 1 +fi + +for proc in ${PROCS[@]}; do + echo "Dowloading artifacts for $proc (branch: $BRANCH)" + wget https://eicweb.phy.anl.gov/EIC/benchmarks/physics_benchmarks/-/jobs/artifacts/$BRANCH/download?job=${proc}:generate -O results_gen.zip + ## FIXME this needs to be smarter, probably through more flags... + wget https://eicweb.phy.anl.gov/EIC/benchmarks/physics_benchmarks/-/jobs/artifacts/$BRANCH/download?job=${proc}:process -O results_rec.zip + echo "Unpacking artifacts..." + unzip -u -o results_gen.zip + unzip -u -o results_rec.zip + echo "Cleaning up..." + rm results_???.zip +done +popd +echo "All done" diff --git a/trackers/dummy_test.sh b/trackers/dummy_test.sh deleted file mode 100644 index d7e83841..00000000 --- a/trackers/dummy_test.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash - -echo "Tracker Dummy Test..." -echo "passes." - - -ls -lrth - -exit 0 diff --git a/trackers/trackers_config.yml b/trackers/trackers_config.yml deleted file mode 100644 index efa664c0..00000000 --- a/trackers/trackers_config.yml +++ /dev/null @@ -1,12 +0,0 @@ -#stages: -# #- simulate -# - benchmarks -track_test_1_dummy_test: - stage: benchmarks - script: - - bash trackers/dummy_test.sh - artifact: - paths: - - results/ - allow_failure: true - diff --git a/util/build_detector.sh b/util/build_detector.sh new file mode 100755 index 00000000..bccf765b --- /dev/null +++ b/util/build_detector.sh @@ -0,0 +1,64 @@ +#!/bin/bash + +## ============================================================================= +## Build and install the JUGGLER_DETECTOR detector package into our local prefix +## ============================================================================= + +## make sure we launch this script from the project root directory +PROJECT_ROOT="$( cd "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )"/.. +pushd ${PROJECT_ROOT} + +## ============================================================================= +## Load the environment variables. To build the detector we need the following +## variables: +## +## - JUGGLER_DETECTOR: the detector package we want to use for this benchmark +## - LOCAL_PREFIX: location where local packages should be installed +## - DETECTOR_PREFIX: prefix for the detector definitions +## - DETECTOR_PATH: full path for the detector definitions +## this is the same as ${DETECTOR_PREFIX}/${JUGGLER_DETECTOR} +## +## You can read options/env.sh for more in-depth explanations of the variables +## and how they can be controlled. +source options/env.sh + +## ============================================================================= +## Step 1: download/update the detector definitions (if needed) +pushd ${DETECTOR_PREFIX} + +## We need an up-to-date copy of the detector +if [ ! -d ${JUGGLER_DETECTOR} ]; then + echo "Fetching ${JUGGLER_DETECTOR}" + git clone -b ${JUGGLER_DETECTOR_VERSION} https://eicweb.phy.anl.gov/EIC/detectors/${JUGGLER_DETECTOR}.git +else + echo "Updating ${JUGGLER_DETECTOR}" + pushd ${JUGGLER_DETECTOR} + git pull --ff-only + popd +fi +## We also need an up-to-date copy of the accelerator. For now this is done +## manually. Down the road we could maybe automize this with cmake +if [ ! -d accelerator ]; then + echo "Fetching accelerator" + git clone https://eicweb.phy.anl.gov/EIC/detectors/accelerator.git +else + echo "Updating accelerator" + pushd accelerator + git pull --ff-only + popd +fi +## Now symlink the accelerator definition into the detector definition +echo "Linking accelerator definition into detector definition" +ln -s -f ${DETECTOR_PREFIX}/accelerator/eic ${DETECTOR_PATH}/eic + +## ============================================================================= +## Step 2: Compile and install the detector definition +echo "Building and installing the ${JUGGLER_DETECTOR} package" + +mkdir -p ${DETECTOR_PREFIX}/build +pushd ${DETECTOR_PREFIX}/build +cmake ${DETECTOR_PATH} -DCMAKE_INSTALL_PREFIX=${LOCAL_PREFIX} && make -j30 install + +## ============================================================================= +## Step 3: That's all! +echo "Detector build/install complete!" diff --git a/util/collect_benchmarks.py b/util/collect_benchmarks.py new file mode 100755 index 00000000..0af7e9a1 --- /dev/null +++ b/util/collect_benchmarks.py @@ -0,0 +1,181 @@ +#!/usr/bin/env python3 + +""" +Combine the json files from the individual benchmark tests into +a final master json file combining all benchmarks. + +Benchmark results are expected to be all json files in the results +directory. +""" + +## Our master definition file, the benchmark project directory +MASTER_FILE=r'benchmarks/benchmarks.json' + +## Our results directory +RESULTS_PATH=r'results' + +## Output json file with all benchmark results +OUTPUT_FILE=r'results/summary.json' + +import argparse +import json +from pathlib import Path + +## Exceptions for this module +class Error(Exception): + '''Base class for exceptions in this module.''' + pass +class FileNotFoundError(Error): + '''File does not exist. + + Attributes: + file: the file name + message: error message + ''' + def __init__(self, file): + self.file = file + self.message = 'No such file or directory: {}'.format(file) + +class InvalidDefinitionError(Error): + '''Raised for missing keys in the definitions. + + Attributes: + key: the missing key + file: the definition file + message: error message + ''' + def __init__(self, key, file): + self.key = key + self.file = file + self.message = "key '{}' not found in '{}'".format(key, file) + +class InvalidResultError(Error): + '''Raised for invalid benchmark result value. + + Attributes: + key: the missing key + value: the invalid value + file: the benchmark definition file + message: error message + ''' + def __init__(self, key, value, file): + self.key = key + self.value = value + self.file = file + self.message = "value '{}' for key '{}' invalid in benchmark file '{}'".format( + value, key, file) + +def collect_benchmarks(): + '''Collect all benchmark results and write results to a single file.''' + print("Collecting all benchmark results") + + ## load the test definition for this benchmark + results = _load_master() + + ## collect the test results + results['benchmarks'] = _load_benchmarks() + + ## calculate aggregate test statistics + results = _aggregate_results(results) + + ## save results to output file + _save(results) + + ## Summarize results + for bm in results['benchmarks']: + _print_benchmark(bm) + _print_summary(results) + +def _load_master(): + '''Load master definition.''' + master_file = Path(MASTER_FILE) + if not master_file.exists(): + raise FileNotFoundError(master_file) + print(' --> Loading master definition from:', master_file) + results = None + with master_file.open() as f: + results = json.load(f) + ## ensure this is a valid benchmark file + for key in ('name', 'title', 'description'): + if not key in results: + raise InvalidDefinitionError('target', master_file) + return results + +def _load_benchmarks(): + '''Load all benchmark results from the results folder.''' + print(' --> Collecting all benchmarks') + rootdir = Path(RESULTS_PATH) + results = [] + for file in rootdir.glob('*.json'): + print(' --> Loading file:', file, '... ', end='') + with open(file) as f: + bm = json.load(f) + ## skip files that don't include test results + if not 'tests' in bm: + print('skipped (does not contain benchmark results).') + continue + ## check if these are valid benchmark results, + ## raise exception otherwise + for key in ('name', 'title', 'description', 'target', 'n_tests', + 'n_pass', 'n_fail', 'n_error', 'maximum', 'sum', 'value', + 'result'): + if not key in bm: + raise InvalidDefinitionError(key, file) + if bm['result'] not in ('pass', 'fail', 'error'): + raise InvalidResultError('result', bm['result'], file) + ## Append to our test results + results.append(bm) + print('done') + return results + +def _aggregate_results(results): + '''Aggregate benchmark results.''' + print(' --> Aggregating benchmark statistics') + results['n_benchmarks'] = len(results['benchmarks']) + results['n_pass'] = len([1 for t in results['benchmarks'] if t['result'] == 'pass']) + results['n_fail'] = len([1 for t in results['benchmarks'] if t['result'] == 'fail']) + results['n_error'] = len([1 for t in results['benchmarks'] if t['result'] == 'error']) + if results['n_error'] > 0: + results['result'] = 'error' + elif results['n_fail'] == 0: + results['result'] = 'pass' + else: + results['result'] = 'fail' + return results + +def _save(results): + '''Save aggregated benchmark results''' + ofile = Path(OUTPUT_FILE) + print(' --> Saving results to:', ofile) + with ofile.open('w') as f: + json.dump(results, f, indent=4) + +def _print_benchmark(bm): + '''Print benchmark summary to the terminal.''' + print('====================================================================') + print(' Summary for:', bm['title']) + print(' Pass: {}, Fail: {}, Error: {} out of {} total tests'.format( + bm['n_pass'], bm['n_fail'], bm['n_error'], + bm['n_tests'])) + print(' Weighted sum: {} / {}'.format(bm['sum'], bm['maximum'])) + print(' kBenchmark value: {} (target: {})'.format( + bm['value'], bm['target'])) + print(' ===> status:', bm['result']) + +def _print_summary(results): + '''Print master benchmark summary to the terminal.''' + print('====================================================================') + print('MASTER BENCHMARK SUMMARY FOR:', results['title'].upper()) + print('Pass: {}, Fail: {}, Error: {} out of {} total benchmarks'.format( + results['n_pass'], results['n_fail'], results['n_error'], + results['n_benchmarks'])) + print('===> status:', results['result']) + print('====================================================================') + + +if __name__ == "__main__": + try: + collect_benchmarks() + except Error as e: + print() + print('ERROR', e.message) diff --git a/util/collect_tests.py b/util/collect_tests.py new file mode 100755 index 00000000..4d860ca7 --- /dev/null +++ b/util/collect_tests.py @@ -0,0 +1,204 @@ +#!/usr/bin/env python3 + +""" +Collect the json files from individual benchmark tests into +a larger json file that combines all benchmark information, +and do additional accounting for the benchmark. + +Tests results are expected to have the following file name and directory +structure: + results/<BENCHMARK_NAME>/**/<SOME_NAME>.json +where ** implies we check recursively check all sub-directories of <BENCHMARK_NAME> + +Internally, we will look for the "tests" keyword in each of these +files to identify them as benchmark components. +""" + +## Our benchmark definition file, stored in the benchmark root directory +BENCHMARK_FILE=r'benchmarks/{}/benchmark.json' + +## Our benchmark results directory +RESULTS_PATH=r'results/{}' + +## Output json file with benchmark results +OUTPUT_FILE=r'results/{}.json' + +import argparse +import json +from pathlib import Path + +## Exceptions for this module +class Error(Exception): + '''Base class for exceptions in this module.''' + pass +class FileNotFoundError(Exception): + '''File does not exist. + + Attributes: + file: the file name + message: error message + ''' + def __init__(self, file): + self.file = file + self.message = 'No such file or directory: {}'.format(file) + +class InvalidBenchmarkDefinitionError(Exception): + '''Raised for missing keys in the benchmark definition. + + Attributes: + key: the missing key + file: the benchmark definition file + message: error message + ''' + def __init__(self, key, file): + self.key = key + self.file = file + self.message = "key '{}' not found in benchmark file '{}'".format(key, file) + +class InvalidTestDefinitionError(Exception): + '''Raised for missing keys in the test result. + + Attributes: + key: the missing key + file: the test result file + message: error message + ''' + def __init__(self, key, file): + self.key = key + self.file = file + self.message = "key '{}' not found in test file '{}'".format(key, file) +class InvalidTestResultError(Exception): + '''Raised for invalid test result value. + + Attributes: + key: the missing key + value: the invalid value + file: the benchmark definition file + message: error message + ''' + def __init__(self, key, value, file): + self.key = key + self.value = value + self.file = file + self.message = "value '{}' for key '{}' invalid in test file '{}'".format( + value, key, file) + + +parser = argparse.ArgumentParser() +parser.add_argument( + 'benchmark', + action='append', + help='One or more benchmarks for which to collect test results.') + +def collect_results(benchmark): + '''Collect benchmark tests and write results to file.''' + print("Collecting results for benchmark '{}'".format(benchmark)) + + ## load the test definition for this benchmark + results = _load_benchmark(benchmark) + + ## collect the test results + results['tests'] = _load_tests(benchmark) + + ## calculate aggregate test statistics + results = _aggregate_results(results) + + ## save results to output file + _save(benchmark, results) + + ## Summarize results + _print_summary(results) + +def _load_benchmark(benchmark): + '''Load benchmark definition.''' + benchfile = Path(BENCHMARK_FILE.format(benchmark)) + if not benchfile.exists(): + raise FileNotFoundError(benchfile) + print(' --> Loading benchmark definition from:', benchfile) + results = None + with benchfile.open() as f: + results = json.load(f) + ## ensure this is a valid benchmark file + for key in ('name', 'title', 'description', 'target'): + if not key in results: + raise InvalidBenchmarkDefinitionError('target', benchfile) + return results + +def _load_tests(benchmark): + '''Loop over all test results in benchmark folder and return results.''' + print(' --> Collecting all test results') + rootdir = Path(RESULTS_PATH.format(benchmark)) + results = [] + for file in rootdir.glob('**/*.json'): + print(' --> Loading file:', file, '... ', end='') + with open(file) as f: + new_results = json.load(f) + ## skip files that don't include test results + if not 'tests' in new_results: + print('not a test result') + continue + ## check if these are valid test results, + ## raise exception otherwise + for test in new_results['tests']: + for key in ('name', 'title', 'description', 'quantity', 'target', + 'value', 'result'): + if not key in test: + raise InvalidTestDefinitionError(key, file) + if test['result'] not in ('pass', 'fail', 'error'): + raise InvalidTestResultError('result', test['result'], file) + ## ensure 'weight' key present, defaulting to 1 in needed + if not 'weight' in test: + test['weight'] = 1. + ## Append to our test results + results.append(test) + print('done') + return results + +def _aggregate_results(results): + '''Aggregate test results for our benchmark.''' + print(' --> Aggregating benchmark statistics') + results['target'] = float(results['target']) + results['n_tests'] = len(results['tests']) + results['n_pass'] = len([1 for t in results['tests'] if t['result'] == 'pass']) + results['n_fail'] = len([1 for t in results['tests'] if t['result'] == 'fail']) + results['n_error'] = len([1 for t in results['tests'] if t['result'] == 'error']) + results['maximum'] = sum([t['weight'] for t in results['tests']]) + results['sum'] = sum([t['weight'] for t in results['tests'] if t['result'] == 'pass']) + if (results['n_tests'] > 0): + results['value'] = results['sum'] / results['maximum'] + if results['n_error'] > 0: + results['result'] = 'error' + elif results['value'] >= results['target']: + results['result'] = 'pass' + else: + results['result'] = 'fail' + else: + results['value'] = -1 + results['result'] = 'error' + return results + +def _save(benchmark, results): + '''Save benchmark results''' + ofile = Path(OUTPUT_FILE.format(benchmark)) + print(' --> Saving benchmark results to:', ofile) + with ofile.open('w') as f: + json.dump(results, f, indent=4) + +def _print_summary(results): + '''Print benchmark summary to the terminal.''' + print('====================================================================') + print('Summary for:', results['title']) + print('Pass: {}, Fail: {}, Error: {} out of {} total tests'.format( + results['n_pass'], results['n_fail'], results['n_error'], + results['n_tests'])) + print('Weighted sum: {} / {}'.format(results['sum'], results['maximum'])) + print('Benchmark value: {} (target: {})'.format( + results['value'], results['target'])) + print('===> status:', results['result']) + print('====================================================================') + + +if __name__ == "__main__": + args = parser.parse_args() + for benchmark in args.benchmark: + collect_results(benchmark) diff --git a/util/compile_analyses.py b/util/compile_analyses.py new file mode 100755 index 00000000..153f2ea2 --- /dev/null +++ b/util/compile_analyses.py @@ -0,0 +1,119 @@ +#!/usr/bin/env python3 + +""" +Compile all root analysis scripts under +benchmarks/<BENCHMARK>/analysis/*.cxx + +Doing this step here rather than during the main benchmark script has +multiple advantages: + 1. Get feedback on syntax errors early on, without wasting compute resources + 2. Avoid race conditions for large benchmarks run in parallel + 3. Make it easier to properly handle the root build directory, as + this has to exist prior to our attempt to compile, else all will + fail (this is probably an old bug in root...) + +Analysis scripts are expected to have extension 'cxx' and be located in the analysis +subdirectory +""" + +## Our analysis path and file extension for glob +ANALYSIS_PATH=r'benchmarks/{}/analysis' +ANALYSIS_EXT = r'cxx' + +import argparse +import os +from pathlib import Path + +## Exceptions for this module +class Error(Exception): + '''Base class for exceptions in this module.''' + pass + +class PathNotFoundError(Exception): + '''Path does not exist. + + Attributes: + path: the path name + message: error message + ''' + def __init__(self, path): + self.file = file + self.message = 'No such directory: {}'.format(file) +class NoAnalysesFoundError(Exception): + '''Did not find any analysis scripts to complile + + Attributes: + path: the analysis path + message: error message + ''' + def __init__(self, path): + self.file = file + self.message = 'No analysis found (extension \'{}\' in path: {}'.format(file, + ANALYSIS_EXT) + +class CompilationError(Exception): + '''Raised when we failed to compile an analysis script + + Attributes: + file: analysis file name + path: analysis path + message: error message + ''' + def __init__(self, file): + self.file = file + self.message = "Analysis '{}' failed to compile".format(file) + +parser = argparse.ArgumentParser() +parser.add_argument( + 'benchmark', + help='A benchmarks for which to compile the analysis scripts.') + +def compile_analyses(benchmark): + '''Compile all analysis scripts for a benchmark.''' + print("Compiling all analyis scripts for '{}'".format(benchmark)) + + ## Ensure our build directory exists + _init_build_dir(benchmark) + + ## Get a list of all analysis scripts + _compile_all(benchmark) + + ## All done! + print('All analyses for', benchmark, 'compiled successfully') + +def _init_build_dir(benchmark): + '''Initialize our ROOT build directory (if using one).''' + print(' --> Initializing ROOT build directory ...') + build_prefix = os.getenv('ROOT_BUILD_DIR') + if build_prefix is None: + print(' --> ROOT_BUILD_DIR not set, no action needed.') + return + ## deduce the root build directory + pwd = os.getenv('PWD') + build_dir = '{}/{}/{}'.format(build_prefix, pwd, ANALYSIS_PATH.format(benchmark)) + print(" --> Ensuring directory '{}' exists".format(build_dir)) + os.system('mkdir -p {}'.format(build_dir)) + +def _compile_all(benchmark): + '''Compile all analysis for this benchmark.''' + print(' --> Compiling analysis scripts') + anadir = Path(ANALYSIS_PATH.format(benchmark)) + if not anadir.exists(): + raise PathNotFoundError(anadir) + ana_list = [] + for file in anadir.glob('*.{}'.format(ANALYSIS_EXT)): + ana_list.append(file) + print(' --> Compiling:', file, flush=True) + err = os.system(_compile_cmd(file)) + if err: + raise CompilationError(file) + if len(ana_list) == 0: + raise NoAnalysesFoundError(anadir) + +def _compile_cmd(file): + '''Return a one-line shell command to compile an analysis script.''' + return r'bash -c "root -q -b -e \".L {}+\""'.format(file) + +if __name__ == "__main__": + args = parser.parse_args() + compile_analyses(args.benchmark) diff --git a/util/parse_cmd.sh b/util/parse_cmd.sh new file mode 100755 index 00000000..04028d89 --- /dev/null +++ b/util/parse_cmd.sh @@ -0,0 +1,127 @@ +#!/bin/bash + +## ============================================================================= +## Generic utility script to parse command line arguments for the various +## bash scripts that control the CI. This script should be source'd with +## command line arguments from a bash-like (non-POSIX) shell such as +## bash or zsh. +## +## To control some of the functionality of the script, you can set the following +## environment variables prior to calling the script: +## - REQUIRE_DECAY: require the --decay flag to be set +## ============================================================================= + +## Commented out because this should be taken care of by the +## calling script to not enforce a fixed directory structure. +## make sure we launch this script from the project root directory +#PROJECT_ROOT="$( cd "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )"/.. +#pushd ${PROJECT_ROOT} + +## ============================================================================= +## Step 1: Process the command line arguments + +function print_the_help { + echo "USAGE: --ebeam E --pbeam E --config C1 --decay D2" + echo " [--config C2 --decay D2 --decay D3 ...]" + echo "REQUIRED ARGUMENTS:" + echo " --ebeam Electron beam energy" + echo " --pbeam Ion beam energy" + echo " --config Generator configuration identifiers (at least one)" + if [ ! -z ${REQUIRE_DECAY} ]; then + echo " --decay Specific decay particle (e.g. muon)." + fi + if [ ! -z ${REQUIRE_LEADING} ]; then + echo " --leading Leading particle of interest (e.g. jpsi)." + fi + echo " -h,--help Print this message" + echo "" + echo " Generate multiple monte carlo samples for a desired process." + exit +} + +## Required variables +EBEAM= +PBEAM= +DECAYS= +CONFIG= + +while [ $# -gt 0 ] +do + key="$1" + case $key in + --config) + CONFIG="$2" + shift # past argument + shift # past value + ;; + --ebeam) + EBEAM="$2" + shift # past argument + shift # past value + ;; + --pbeam) + PBEAM="$2" + shift # past argument + shift # past value + ;; + --leading) + LEADING="$2" + shift # past argument + shift # past value + ;; + --decay) + DECAY="$2" + shift # past argument + shift # past value + ;; + -h|--help) + print_the_help + exit 0 + ;; + *) # unknown option + echo "unknown option" + exit 1 + ;; + esac +done + +if [ -z $CONFIG ]; then + echo "ERROR: CONFIG not defined: --config <config>" + print_the_help + exit 1 +elif [ -z $EBEAM ]; then + echo "ERROR: EBEAM not defined: --ebeam <energy>" + print_the_help + exit 1 +elif [ -z $PBEAM ]; then + echo "ERROR: PBEAM not defined: --pbeam <energy>" + print_the_help + exit 1 +elif [ -z $LEADING ] && [ ! -z $REQUIRE_LEADING ]; then + echo "ERROR: LEADING not defined: --leading <channel>" + print_the_help + exit 1 +elif [ ! -z $LEADING ] && [ -z $REQUIRE_LEADING ]; then + echo "ERROR: LEADING flag specified but not required" + print_the_help + exit 1 +elif [ -z $DECAY ] && [ ! -z $REQUIRE_DECAY ]; then + echo "ERROR: DECAY not defined: --decay <channel>" + print_the_help + exit 1 +elif [ ! -z $DECAY ] && [ -z $REQUIRE_DECAY ]; then + echo "ERROR: DECAY flag specified but not required" + print_the_help + exit 1 +fi + +## Export the configured variables +export CONFIG +export EBEAM +export PBEAM +if [ ! -z $REQUIRE_LEADING ]; then + export LEADING +fi +if [ ! -z $REQUIRE_DECAY ]; then + export DECAY +fi diff --git a/util/print_env.sh b/util/print_env.sh new file mode 100755 index 00000000..7a4a8935 --- /dev/null +++ b/util/print_env.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +echo "JUGGLER_TAG: ${JUGGLER_TAG}" +echo "JUGGLER_DETECTOR: ${JUGGLER_DETECTOR}" +echo "JUGGLER_DETECTOR_VERSION: ${JUGGLER_DETECTOR_VERSION}" +echo "JUGGLER_N_EVENTS: ${JUGGLER_N_EVENTS}" +echo "JUGGLER_N_THREADS: ${JUGGLER_N_THREADS}" +echo "JUGGLER_RNG_SEED: ${JUGGLER_RNG_SEED}" +echo "JUGGLER_INSTALL_PREFIX: ${JUGGLER_INSTALL_PREFIX}" +echo "LOCAL_DATA_PATH: ${LOCAL_DATA_PATH}" +echo "LOCAL_PREFIX: ${LOCAL_PREFIX}" +echo "DETECTOR_PREFIX: ${DETECTOR_PREFIX}" +echo "DETECTOR_PATH: ${DETECTOR_PATH}" diff --git a/util/run_many.py b/util/run_many.py new file mode 100755 index 00000000..ccb7e83a --- /dev/null +++ b/util/run_many.py @@ -0,0 +1,124 @@ +#!/usr/bin/env python3 + +""" +This script will run a CI generator or processing script for multiple configurations. + +Author: Sylvester Joosten <sjoosten@anl.gov> +""" + +import os +import argparse +from multiprocessing import Pool, get_context +from tempfile import NamedTemporaryFile + +class InvalidArgumentError(Exception): + pass + +parser = argparse.ArgumentParser() +parser.add_argument( + 'command', + help="Script to be launched in parallel") +parser.add_argument( + '--energy', '-e', + dest='energies', + action='append', + help='One or more beam energy pairs (e.g. 10x100)', + required=True) +parser.add_argument( + '--config', '-c', + dest='configs', + action='append', + help='One or more configurations', + required=True) +parser.add_argument( + '--leading', + dest='leads', + action='append', + help='One or more leading particles(opt.)', + required=False) +parser.add_argument( + '--decay', + dest='decays', + action='append', + help='One or more decay channels (opt.)', + required=False) +parser.add_argument( + '--nproc', + dest='nproc', + default=5, + type=int, + help='Number of processes to launch in parallel', + required=False) + +def worker(command): + '''Execute the command in a system call, with the supplied argument string.''' + ## use a temporary file to capture the terminal output, and then + ## print the terminal output once the command finishes + with NamedTemporaryFile() as f: + cmd = [command, ' 2>&1 >', f.name] + cmd = ' '.join(cmd) + print("Executing '{}'".format(cmd)) + ret = os.system(cmd) + with open(f.name) as log: + print(log.read()) + return ret + +if __name__ == '__main__': + args = parser.parse_args() + print('Launching CI script in parallel for multiple settings') + for e in args.energies: + beam_setting = e.split('x') + if not beam_setting[0].isnumeric() or not beam_setting[1].isnumeric(): + print("Error: invalid beam energy setting:", e) + raise InvalidArgumentError + + if not os.path.exists(args.command): + print("Error: Script not found:", args.command) + raise InvalidArgumentError + + if args.nproc < 1 or args.nproc > 50: + print("Error: Invalid process limit (should be 1-50):", args.nproc) + raise InvalidArgumentError + + print(' - command: {}'.format(args.command)) + print(' - energies: {}'.format(args.energies)) + print(' - config: {}'.format(args.configs)) + print(' - nproc: {}'.format(args.nproc)) + if (args.leads): + print(' - leading: {}'.format(args.leads)) + if (args.decays): + print(' - decay: {}'.format(args.decays)) + + ## Expand our command and argument list for all combinatorics + cmds = [] + decays = args.decays if args.decays else [None] + leads = args.leads if args.leads else [None] + for e in args.energies: + for c in args.configs: + for l in leads: + for d in decays: + beam_setting = e.split('x') + cmd = [args.command, + '--ebeam', beam_setting[0], + '--pbeam', beam_setting[1], + '--config', c] + if l is not None: + cmd += ['--leading', l] + if d is not None: + cmd += ['--decay', d] + cmds.append(' '.join(cmd)) + + ## create a process pool + ## note that I'm using themultiprocessing.get_context function to setup + ## a context where subprocesses are created using the new "spawn" process + ## which avoids deadlocks that sometimes happen in the default dispatch + with get_context('spawn').Pool(processes=args.nproc) as pool: + return_values = pool.map(worker, cmds) + ## check if we all exited nicely, else exit with status 1 + if not all(ret == 0 for ret in return_values): + n_fail = sum([1 for ret in return_values if ret != 0]) + print('ERROR, {} of {} jobs failed'.format(n_fail, len(cmds))) + print('Return values:', [ret for ret in return_values if ret != 0]) + exit(1) + + ## That's all! -- GitLab