diff --git a/.github/workflows/unit_tests.yaml b/.github/workflows/unit_tests.yaml new file mode 100644 index 00000000..3ed2ce5d --- /dev/null +++ b/.github/workflows/unit_tests.yaml @@ -0,0 +1,183 @@ +name: Unit Tests +run-name: Unit tests for changes made by @${{ github.actor }} +on: + push: + branches: + - '**' # Triggers on push to any branch + pull_request: + types: + - opened # When a pull request is created + - synchronize # When commits are pushed to an existing PR + - reopened +jobs: + Unit-Test-Base: + runs-on: self-hosted + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Building AmpTools + run: | + mkdir Tutorials/Dalitz/lib + mkdir Tutorials/Dalitz/bin + make + export AMPTOOLS_HOME=$(pwd) + echo "AMPTOOLS_HOME=$AMPTOOLS_HOME" >> $GITHUB_ENV + echo "AMPTOOLS=$AMPTOOLS_HOME/AmpTools" >> $GITHUB_ENV + echo "AMPPLOTTER=$AMPTOOLS_HOME/AmpPlotter" >> $GITHUB_ENV + echo "DALITZ=$AMPTOOLS_HOME/Tutorials/Dalitz" >> $GITHUB_ENV + echo "UNIT_TESTS=$AMPTOOLS_HOME/UnitTests" >> $GITHUB_ENV + - name: ConfigFileParser + if: success() || failure() + run: | + cd $UNIT_TESTS + $DALITZ/bin/generatePhaseSpace phasespace.gen.root 100000 12345 + $DALITZ/bin/toyAcceptance phasespace.gen.root phasespace.acc.root 12345 + $DALITZ/bin/generatePhysics parserTest.cfg physics.gen.root 100000 12345 + $DALITZ/bin/toyAcceptance physics.gen.root physics.acc.root 12345 + $DALITZ/bin/fitAmplitudes parserTest.cfg + ./parserTest + - name: ConfigurationInfo + if: success() || failure() + run: | + cd $UNIT_TESTS + ./configurationInfoTest + - name: AmpToolsInterface + if: success() || failure() + run: | + cd $UNIT_TESTS + ./AmpToolsInterfaceTest + - name: FitResults + if: success() || failure() + run: | + cd $UNIT_TESTS + ./fitResultsTest base + + Unit-Test-MPI: + runs-on: self-hosted + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Building AmpTools + run: | + mkdir Tutorials/Dalitz/lib + mkdir Tutorials/Dalitz/bin + make mpi + export AMPTOOLS_HOME=$(pwd) + echo "AMPTOOLS_HOME=$AMPTOOLS_HOME" >> $GITHUB_ENV + echo "AMPTOOLS=$AMPTOOLS_HOME/AmpTools" >> $GITHUB_ENV + echo "AMPPLOTTER=$AMPTOOLS_HOME/AmpPlotter" >> $GITHUB_ENV + echo "DALITZ=$AMPTOOLS_HOME/Tutorials/Dalitz" >> $GITHUB_ENV + echo "UNIT_TESTS=$AMPTOOLS_HOME/UnitTests" >> $GITHUB_ENV + - name: ConfigFileParser + if: success() || failure() + run: | + cd $UNIT_TESTS + $DALITZ/bin/generatePhaseSpace phasespace.gen.root 100000 12345 + $DALITZ/bin/toyAcceptance phasespace.gen.root phasespace.acc.root 12345 + $DALITZ/bin/generatePhysics parserTest.cfg physics.gen.root 100000 12345 + $DALITZ/bin/toyAcceptance physics.gen.root physics.acc.root 12345 + $DALITZ/bin/fitAmplitudes parserTest.cfg + ./parserTest + - name: ConfigurationInfo + if: success() || failure() + run: | + cd $UNIT_TESTS + ./configurationInfoTest + - name: AmpToolsInterface + if: success() || failure() + run: | + cd $UNIT_TESTS + mpirun -n 3 ./AmpToolsInterfaceTestMPI + - name: FitResults + if: success() || failure() + run: | + cd $UNIT_TESTS + mpirun -n 3 ./fitResultsTestMPI mpi + + Unit-Test-GPU: + runs-on: self-hosted + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Building AmpTools + run: | + mkdir Tutorials/Dalitz/lib + mkdir Tutorials/Dalitz/bin + make gpu + export AMPTOOLS_HOME=$(pwd) + echo "AMPTOOLS_HOME=$AMPTOOLS_HOME" >> $GITHUB_ENV + echo "AMPTOOLS=$AMPTOOLS_HOME/AmpTools" >> $GITHUB_ENV + echo "AMPPLOTTER=$AMPTOOLS_HOME/AmpPlotter" >> $GITHUB_ENV + echo "DALITZ=$AMPTOOLS_HOME/Tutorials/Dalitz" >> $GITHUB_ENV + echo "UNIT_TESTS=$AMPTOOLS_HOME/UnitTests" >> $GITHUB_ENV + - name: ConfigFileParser + if: success() || failure() + run: | + cd $UNIT_TESTS + $DALITZ/bin/generatePhaseSpace_GPU phasespace.gen.root 100000 12345 + $DALITZ/bin/toyAcceptance_GPU phasespace.gen.root phasespace.acc.root 12345 + $DALITZ/bin/generatePhysics_GPU parserTest.cfg physics.gen.root 100000 12345 + $DALITZ/bin/toyAcceptance_GPU physics.gen.root physics.acc.root 12345 + $DALITZ/bin/fitAmplitudes_GPU parserTest.cfg + ./parserTest_GPU + - name: ConfigurationInfo + if: success() || failure() + run: | + cd $UNIT_TESTS + ./configurationInfoTest_GPU + - name: AmpToolsInterface + if: success() || failure() + run: | + cd $UNIT_TESTS + ./AmpToolsInterfaceTest_GPU + - name: FitResults + if: success() || failure() + run: | + cd $UNIT_TESTS + ./fitResultsTest_GPU gpu + + Unit-Test-MPI-GPU: + runs-on: self-hosted + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Building AmpTools + run: | + mkdir Tutorials/Dalitz/lib + mkdir Tutorials/Dalitz/bin + make mpigpu + export AMPTOOLS_HOME=$(pwd) + echo "AMPTOOLS_HOME=$AMPTOOLS_HOME" >> $GITHUB_ENV + echo "AMPTOOLS=$AMPTOOLS_HOME/AmpTools" >> $GITHUB_ENV + echo "AMPPLOTTER=$AMPTOOLS_HOME/AmpPlotter" >> $GITHUB_ENV + echo "DALITZ=$AMPTOOLS_HOME/Tutorials/Dalitz" >> $GITHUB_ENV + echo "UNIT_TESTS=$AMPTOOLS_HOME/UnitTests" >> $GITHUB_ENV + - name: ConfigFileParser + if: success() || failure() + run: | + cd $UNIT_TESTS + $DALITZ/bin/generatePhaseSpace_GPU phasespace.gen.root 100000 12345 + $DALITZ/bin/toyAcceptance_GPU phasespace.gen.root phasespace.acc.root 12345 + $DALITZ/bin/generatePhysics_GPU parserTest.cfg physics.gen.root 100000 12345 + $DALITZ/bin/toyAcceptance_GPU physics.gen.root physics.acc.root 12345 + mpirun -n 3 $DALITZ/bin/fitAmplitudesMPI_GPU parserTest.cfg + ./parserTest_GPU + - name: ConfigurationInfo + if: success() || failure() + run: | + cd $UNIT_TESTS + ./configurationInfoTest_GPU + - name: AmpToolsInterface + if: success() || failure() + run: | + cd $UNIT_TESTS + mpirun -n 3 ./AmpToolsInterfaceTestMPI_GPU + - name: FitResults + if: success() || failure() + run: | + cd $UNIT_TESTS + mpirun -n 3 ./fitResultsTestMPI_GPU mpigpu diff --git a/.github/workflows/write_models.yaml b/.github/workflows/write_models.yaml new file mode 100644 index 00000000..c6a5748c --- /dev/null +++ b/.github/workflows/write_models.yaml @@ -0,0 +1,56 @@ +name: Write Models +run-name: Writing Models +on: [workflow_dispatch] +permissions: + contents: write +jobs: + Write-Models: + runs-on: self-hosted + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Building AmpTools + run: | + mkdir Tutorials/Dalitz/lib + mkdir Tutorials/Dalitz/bin + make + export AMPTOOLS_HOME=$(pwd) + echo "AMPTOOLS_HOME=$AMPTOOLS_HOME" >> $GITHUB_ENV + echo "AMPTOOLS=$AMPTOOLS_HOME/AmpTools" >> $GITHUB_ENV + echo "AMPPLOTTER=$AMPTOOLS_HOME/AmpPlotter" >> $GITHUB_ENV + echo "DALITZ=$AMPTOOLS_HOME/Tutorials/Dalitz" >> $GITHUB_ENV + echo "UNIT_TESTS=$AMPTOOLS_HOME/UnitTests" >> $GITHUB_ENV + - name: Generating files + run: | + cd $UNIT_TESTS + $DALITZ/bin/generatePhaseSpace phasespace.gen.root 100000 12345 + $DALITZ/bin/toyAcceptance phasespace.gen.root phasespace.acc.root 12345 + $DALITZ/bin/generatePhysics parserTest.cfg physics.gen.root 100000 12345 + $DALITZ/bin/toyAcceptance physics.gen.root physics.acc.root 12345 + $DALITZ/bin/fitAmplitudes parserTest.cfg + - name: Writing Models + run: | + cd $UNIT_TESTS + if [ ! -d "models" ]; then + mkdir -p "models" + fi + ./writeModels + - name: Commit Changes + id: commit_changes + run: | + git config --local user.email "action@github.com" + git config --local user.name "github-actions" + cd $UNIT_TESTS + git add models/ + if git diff --cached --quiet; then + echo "No changes to commit." + echo "push_required=false" >> $GITHUB_OUTPUT + else + git commit -m "Updated models" + echo "push_required=true" >> $GITHUB_OUTPUT + fi + - name: Push changes + if: steps.commit_changes.outputs.push_required == 'true' + uses: ad-m/github-push-action@master + with: + github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/Makefile b/Makefile index 0652440c..d6041da4 100644 --- a/Makefile +++ b/Makefile @@ -24,12 +24,16 @@ default: @$(MAKE) -C AmpPlotter @echo "=== Building Dalitz tutorial ===" @$(MAKE) -C Tutorials/Dalitz + @echo "=== Building UnitTests ===" + @$(MAKE) -C UnitTests mpi: default @echo "=== Building AmpTools with MPI ===" @$(MAKE) -C AmpTools MPI=1 @echo "=== Building Dalitz tutorial with MPI ===" @$(MAKE) -C Tutorials/Dalitz MPI=1 + @echo "=== Building UnitTests with MPI ===" + @$(MAKE) -C UnitTests MPI=1 gpu: @echo "=== Building AmpTools with GPU acceleration ===" @@ -38,6 +42,8 @@ gpu: @$(MAKE) -C AmpPlotter @echo "=== Building Dalitz tutorial with GPU acceleration ===" @$(MAKE) -C Tutorials/Dalitz GPU=1 + @echo "=== Building UnitTests with GPU acceleration ===" + @$(MAKE) -C UnitTests GPU=1 mpigpu: gpu @echo "=== Building AmpTools with MPI and GPU acceleration ===" @@ -46,6 +52,8 @@ mpigpu: gpu @$(MAKE) -C AmpPlotter @echo "=== Building Dalitz tutorial with MPI and GPU acceleration ===" @$(MAKE) -C Tutorials/Dalitz MPI=1 GPU=1 + @echo "=== Building UnitTests with MPI and GPU acceleration ===" + @$(MAKE) -C UnitTests MPI=1 GPU=1 gpumpi: mpigpu @@ -53,4 +61,5 @@ clean: @$(MAKE) -C AmpTools clean @$(MAKE) -C AmpPlotter clean @$(MAKE) -C Tutorials/Dalitz clean + @$(MAKE) -C UnitTests clean diff --git a/Tutorials/Dalitz/DalitzExe/generatePhaseSpace.cc b/Tutorials/Dalitz/DalitzExe/generatePhaseSpace.cc index 369e75bc..d904ceb6 100644 --- a/Tutorials/Dalitz/DalitzExe/generatePhaseSpace.cc +++ b/Tutorials/Dalitz/DalitzExe/generatePhaseSpace.cc @@ -25,8 +25,13 @@ int main(int argc, char** argv){ if (argc <= 2){ report( NOTICE, kModule ) << "Usage:" << endl << endl; report( NOTICE, kModule ) << "\tgeneratePhaseSpace " << endl << endl; + report( NOTICE, kModule ) << "\tgeneratePhaseSpace " << endl << endl; return 0; } + if (argc == 4) { + int seed = stoi(argv[3]); + srand48(seed); + } cout << endl << " *** Generate Phase Space *** " << endl << endl; diff --git a/Tutorials/Dalitz/DalitzExe/generatePhysics.cc b/Tutorials/Dalitz/DalitzExe/generatePhysics.cc index 113583cd..99378fdb 100644 --- a/Tutorials/Dalitz/DalitzExe/generatePhysics.cc +++ b/Tutorials/Dalitz/DalitzExe/generatePhysics.cc @@ -31,8 +31,13 @@ int main(int argc, char** argv){ if (argc <= 3){ report( NOTICE, kModule ) << "Usage:" << endl; report( NOTICE, kModule ) << "\tgeneratePhysics " << endl << endl; + report( NOTICE, kModule ) << "\tgeneratePhysics " << endl << endl; return 0; } + if (argc == 5) { + int seed = stoi(argv[4]); + srand48(seed); + } report( INFO, kModule ) << endl << " *** Generating Events According to Amplitudes *** " << endl << endl; diff --git a/Tutorials/Dalitz/DalitzExe/toyAcceptance.cc b/Tutorials/Dalitz/DalitzExe/toyAcceptance.cc index 743d83f7..e41e81c5 100644 --- a/Tutorials/Dalitz/DalitzExe/toyAcceptance.cc +++ b/Tutorials/Dalitz/DalitzExe/toyAcceptance.cc @@ -21,8 +21,13 @@ int main(int argc, char** argv){ if (argc <= 2){ report( NOTICE, kModule ) << "Usage:" << endl << endl; report( NOTICE, kModule ) << "\ttoyAcceptance " << endl << endl; + report( NOTICE, kModule ) << "\ttoyAcceptance " << endl << endl; return 0; } + if (argc == 4) { + int seed = stoi(argv[3]); + srand48(seed); + } report( INFO, kModule ) << endl << " *** Simulating Detector Effects *** " << endl << endl; diff --git a/Tutorials/Dalitz/run/dalitz2.cfg b/Tutorials/Dalitz/run/dalitz2.cfg index adc8bc5f..658f2906 100644 --- a/Tutorials/Dalitz/run/dalitz2.cfg +++ b/Tutorials/Dalitz/run/dalitz2.cfg @@ -52,7 +52,7 @@ fit dalitz2 reaction dalitz p1 p2 p3 genmc dalitz DalitzDataReader phasespace.gen.root -accmc dalitz DalitzDataReader phasespace.gen.root +accmc dalitz DalitzDataReader phasespace.acc.root data dalitz DalitzDataReader physics.gen.root normintfile dalitz dalitz2.ni diff --git a/UnitTests/AmpToolsInterfaceTest.cc b/UnitTests/AmpToolsInterfaceTest.cc new file mode 100644 index 00000000..c698b576 --- /dev/null +++ b/UnitTests/AmpToolsInterfaceTest.cc @@ -0,0 +1,111 @@ +#include +#include +#include +#include +#include +#include +#include +#include "IUAmpTools/ConfigFileParser.h" +#include "IUAmpTools/ConfigurationInfo.h" +#include "IUAmpTools/AmpToolsInterface.h" +#include "DalitzDataIO/DalitzDataReader.h" +#include "DalitzAmp/BreitWigner.h" +#include "DalitzAmp/Constraint.h" + +using namespace std; + + +class unitTest { + public: + bool passed = true; + vector failedTests; + vector passedTests; + void add(bool expr, string name) { + passed = passed && expr; + if (expr) { + passedTests.push_back(name); + } else { + failedTests.push_back(name); + } + } + void add(double valModel, double val, double tolerance, string name) { + bool expr = abs(valModel-val)<= tolerance; + passed = passed && expr; + if (expr) { + passedTests.push_back(name + "(diff="+to_string(abs(valModel-val)) +")"); + } else { + failedTests.push_back(name + "(diff="+to_string(abs(valModel-val)) +")"); + } + } + bool summary() { + assert(passed || failedTests.size() > 0); + if (passed) { + cout << "All unit tests passed." << endl; + for (const string& passedTest : passedTests) { + cout << "* " << passedTest << endl; + } + } else { + cout << "The following unit tests failed:" << endl; + for (const string& failedTest : failedTests) { + cout << "* " << failedTest << endl; + } + if (passedTests.size() > 0) { + cout << "The following unit tests were successful:" << endl; + for (const string& passedTest : passedTests) { + cout << "* " << passedTest << endl; + } + } + } + return passed; + } +}; + +int main() { + string ATIFile = "models/AmpToolsInterface.txt"; + string cfgname = "parserTest.cfg"; + ConfigFileParser parser(cfgname); + ConfigurationInfo* cfgInfo = parser.getConfigurationInfo(); + AmpToolsInterface::registerAmplitude(BreitWigner()); + AmpToolsInterface::registerNeg2LnLikContrib(Constraint()); + AmpToolsInterface::registerDataReader(DalitzDataReader()); + AmpToolsInterface ATI(cfgInfo); + AmpToolsInterface::setRandomSeed(12345); + cout << "________________________________________" << endl; + cout << "Testing AmpToolsInterface from ConfigurationInfo:" << endl; + cout << "________________________________________" << endl; + + unitTest unit_test; + ifstream fin; + fin.open(ATIFile); + + double neg2LL_before; + fin >> neg2LL_before; + unit_test.add(neg2LL_before, ATI.likelihood(), 1e-1, "Likelihood before fit matches model"); + + MinuitMinimizationManager* fitManager = ATI.minuitMinimizationManager(); + fitManager->setStrategy(1); + + fitManager->migradMinimization(); + + double neg2LL_after; + fin >> neg2LL_after; + unit_test.add(neg2LL_after, ATI.likelihood(), 1e-3, "Likelihood after fit matches model"); + double neg2LL_base; + fin >> neg2LL_base; + unit_test.add(neg2LL_base, ATI.likelihood("base"), 1e-3, "Likelihood of base reaction after fit matches model"); + double neg2LL_constrained; + fin >> neg2LL_constrained; + unit_test.add(neg2LL_constrained, ATI.likelihood("constrained"),1e-3, "Likelihood of constrained reaction after fit matches model"); + double neg2LL_symmetrized_implicit; + fin >> neg2LL_symmetrized_implicit; + unit_test.add(neg2LL_symmetrized_implicit,ATI.likelihood("symmetrized_implicit"),1e-4, "Likelihood of symmetrized (implicit) reaction after fit matches model"); + double neg2LL_symmetrized_explicit; + fin >> neg2LL_symmetrized_explicit; + unit_test.add(neg2LL_symmetrized_explicit, ATI.likelihood("symmetrized_explicit"), 1e-4, "Likelihood of symmetrized (explicit) reaction after fit matches model"); + bool result = unit_test.summary(); + + if (!result) { + throw runtime_error("Unit Tests Failed. See previous logs for more information."); + } + return 0; +} \ No newline at end of file diff --git a/UnitTests/AmpToolsInterfaceTestMPI.cc b/UnitTests/AmpToolsInterfaceTestMPI.cc new file mode 100644 index 00000000..007981d7 --- /dev/null +++ b/UnitTests/AmpToolsInterfaceTestMPI.cc @@ -0,0 +1,121 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "IUAmpTools/ConfigFileParser.h" +#include "IUAmpTools/ConfigurationInfo.h" +#include "IUAmpToolsMPI/AmpToolsInterfaceMPI.h" +#include "IUAmpToolsMPI/DataReaderMPI.h" +#include "DalitzDataIO/DalitzDataReader.h" +#include "DalitzAmp/BreitWigner.h" +#include "DalitzAmp/Constraint.h" + +using namespace std; + + +class unitTest { + public: + bool passed = true; + vector failedTests; + vector passedTests; + void add(bool expr, string name) { + passed = passed && expr; + if (expr) { + passedTests.push_back(name); + } else { + failedTests.push_back(name); + } + } + void add(double valModel, double val, double tolerance, string name) { + bool expr = abs(valModel-val)<= tolerance; + passed = passed && expr; + if (expr) { + passedTests.push_back(name + "(diff="+to_string(abs(valModel-val)) +")"); + } else { + failedTests.push_back(name + "(diff="+to_string(abs(valModel-val)) +")"); + } + } + bool summary() { + assert(passed || failedTests.size() > 0); + if (passed) { + cout << "All unit tests passed." << endl; + for (const string& passedTest : passedTests) { + cout << "* " << passedTest << endl; + } + } else { + cout << "The following unit tests failed:" << endl; + for (const string& failedTest : failedTests) { + cout << "* " << failedTest << endl; + } + if (passedTests.size() > 0) { + cout << "The following unit tests were successful:" << endl; + for (const string& passedTest : passedTests) { + cout << "* " << passedTest << endl; + } + } + } + return passed; + } +}; + +int main( int argc, char* argv[] ) { + string ATIFile = "models/AmpToolsInterface.txt"; + bool result; + MPI_Init( &argc, &argv ); + int rank; + int size; + MPI_Comm_rank( MPI_COMM_WORLD, &rank ); + MPI_Comm_size( MPI_COMM_WORLD, &size ); + string cfgname = "parserTest.cfg"; + ConfigFileParser parser(cfgname); + ConfigurationInfo* cfgInfo = parser.getConfigurationInfo(); + AmpToolsInterfaceMPI::registerAmplitude(BreitWigner()); + AmpToolsInterfaceMPI::registerNeg2LnLikContrib(Constraint()); + AmpToolsInterfaceMPI::registerDataReader(DataReaderMPI()); + AmpToolsInterfaceMPI ATI(cfgInfo); + AmpToolsInterfaceMPI::setRandomSeed(12345); + if (rank == 0){ + cout << "________________________________________" << endl; + cout << "Testing AmpToolsInterface from ConfigurationInfo:" << endl; + cout << "________________________________________" << endl; + + unitTest unit_test; + ifstream fin; + fin.open(ATIFile); + + double neg2LL_before; + fin >> neg2LL_before; + unit_test.add(neg2LL_before, ATI.likelihood(), 1e-1, "Likelihood before fit matches model"); + MinuitMinimizationManager* fitManager = ATI.minuitMinimizationManager(); + fitManager->setStrategy(1); + + fitManager->migradMinimization(); + + double neg2LL_after; + fin >> neg2LL_after; + unit_test.add(neg2LL_after, ATI.likelihood(), 1e-1, "Likelihood after fit matches model"); + double neg2LL_base; + fin >> neg2LL_base; + unit_test.add(neg2LL_base, ATI.likelihood("base"), 1e-3, "Likelihood of base reaction after fit matches model"); + double neg2LL_constrained; + fin >> neg2LL_constrained; + unit_test.add(neg2LL_constrained, ATI.likelihood("constrained"),1e-3, "Likelihood of constrained reaction after fit matches model"); + double neg2LL_symmetrized_implicit; + fin >> neg2LL_symmetrized_implicit; + unit_test.add(neg2LL_symmetrized_implicit,ATI.likelihood("symmetrized_implicit"),1e-4, "Likelihood of symmetrized (implicit) reaction after fit matches model"); + double neg2LL_symmetrized_explicit; + fin >> neg2LL_symmetrized_explicit; + unit_test.add(neg2LL_symmetrized_explicit, ATI.likelihood("symmetrized_explicit"), 1e-4, "Likelihood of symmetrized (explicit) reaction after fit matches model"); + result = unit_test.summary(); + if (!result) { + throw runtime_error("Unit Tests Failed. See previous logs for more information."); + } + } + ATI.exitMPI(); + MPI_Finalize(); + return 0; +} \ No newline at end of file diff --git a/UnitTests/Makefile b/UnitTests/Makefile new file mode 100644 index 00000000..73f8fbeb --- /dev/null +++ b/UnitTests/Makefile @@ -0,0 +1,97 @@ + +###### BUILDING PROJECT EXECUTABLES +###### most things below probably don't need adjustment + +ifndef AMPTOOLS_HOME +$(error Please set AMPTOOLS_HOME to point to the root of the AmpTools source tree. ) +endif + +include $(AMPTOOLS_HOME)/Makefile.settings + +# leave the binaries in this directory if the install directory hasn't been set +ifndef INSTALL_BIN +INSTALL_BIN = . +endif + +INC_DIR := -I$(AMPTOOLS_HOME)/Tutorials/Dalitz/DalitzLib -I$(AMPPLOTTER) -I$(AMPTOOLS) -I$(shell root-config --incdir) +CXX_FLAGS += $(shell root-config --cflags) + +# check if GPU build is requested +ifdef GPU +INC_DIR += -I$(CUDA_INSTALL_PATH)/include +CXX_FLAGS += -DGPU_ACCELERATION +SUFFIX := _GPU +ATSUFFIX := _GPU +endif + +# check if MPI build is requested +# * unlike AmpTools Makefile, don't modify SUFFIX because the only thing +# this affects is the executables which will be handled separately +# * however, we do need the right suffix to link MPI enabled +# AmpTools library +ifdef MPI +CXX_FLAGS += -DUSE_MPI +ATSUFFIX := $(ATSUFFIX)_MPI +endif + +# need some linking information available +LIB_DIR := -L$(shell root-config --libdir) $(CUDA_LIBS) \ + -L$(AMPTOOLS)/lib -L$(AMPPLOTTER)/lib -L$(AMPTOOLS_HOME)/Tutorials/Dalitz/lib + +LIBS := -lAmpPlotter -lAmpTools$(ATSUFFIX) -lDalitz$(SUFFIX)\ + $(EXTRA_LIBS) $(shell root-config --glibs) $(CUDA_LINK) -lstdc++ + +# any source file falls into one of these two categories -- we'll +# assume that anything that has MPI in the name is an MPI executable +MPI_SRC := $(shell ls *.cc | grep -i mpi) +NOMPI_SRC := $(shell ls *.cc | grep -vi mpi) + +TARGET_EXE := $(NOMPI_SRC:%.cc=%$(SUFFIX)) + +# optionally if MPI is called for, we will build the MPI executables +ifdef MPI +TARGET_EXE += $(MPI_SRC:%.cc=%$(SUFFIX)) +endif + +.PHONY: default bin +.PRECIOUS: .%$(SUFFIX).o .%$(SUFFIX)_mpi.o + +default: bin $(TARGET_EXE) + +bin: + $(Q)-mkdir $(INSTALL_BIN) >& /dev/null ||: + +# for the non-MPI executables we want to strip out the -DUSEMPI flag +# from the compiler arguments and also be sure we link against the +# non-MPI version of AmpTools + +$(NOMPI_SRC:%.cc=%$(SUFFIX)): %$(SUFFIX) : .%$(SUFFIX).o + $(vecho) "--> Linking $*$(SUFFIX)" + $(Q)$(CXX) $(subst -DUSEMPI,,$(CXX_FLAGS)) -o $(INSTALL_BIN)/$*$(SUFFIX) $< \ + $(INC_DIR) $(LIB_DIR) $(subst _MPI,,$(LIBS)) + +$(MPI_SRC:%.cc=%$(SUFFIX)): %$(SUFFIX) : .%$(SUFFIX)_mpi.o + $(vecho) "--> Linking $*$(SUFFIX)" + $(Q)$(MPICXX) $(CXX_FLAGS) -o $(INSTALL_BIN)/$*$(SUFFIX) $< \ + $(INC_DIR) $(LIB_DIR) $(LIBS) + +.%$(SUFFIX).o: %.cc .%$(SUFFIX).d + $(vecho) "-> Compiling $<" + $(Q)$(CXX) $(subst -DUSEMPI,,$(CXX_FLAGS)) -M -MP -MT $@ -o .$*$(SUFFIX).d $< $(INC_DIR) + $(Q)$(CXX) $(subst -DUSEMPI,,$(CXX_FLAGS)) -c -o $@ $< $(INC_DIR) + +.%$(SUFFIX)_mpi.o: %.cc .%$(SUFFIX).d + $(vecho) "-> Compiling $<" + $(Q)$(MPICXX) $(CXX_FLAGS) -M -MP -MT $@ -o .$*$(SUFFIX).d $< $(INC_DIR) + $(Q)$(MPICXX) $(CXX_FLAGS) -c -o $@ $< $(INC_DIR) + +DEPFILES := $(NOMPI_SRC:%.cc=.%$(SUFFIX).d) +ifdef MPI +DEPFILES += $(MPI_SRC:%.cc=.%$(SUFFIX).d) +endif +$(DEPFILES): + +clean: + $(Q)rm -f .*.o .*.d $(TARGET_EXE) + +-include $(DEPFILES) diff --git a/UnitTests/README b/UnitTests/README new file mode 100644 index 00000000..a3ff8dc8 --- /dev/null +++ b/UnitTests/README @@ -0,0 +1,83 @@ +Whenever anybody pushes to any branch, the action found in .github/actions/unit_tests.yaml will be run in a container hosted on gpu1. The action builds AmpTools from the latest version of the branch modified by the push that triggered the action. The following AmpTools classes are tested: + + +ConfigFileParser (All constructors): +Tested Features: +* include +* define +* parameter +* Multiple reactions +* Multiple amplitudes in same sum +* Constraining amplitudes in different reactions +* Scale +* permute +* Implicit permute (using identical particle names) +* loop +Checks: +* Number of lines in parsed file exactly matches model +* Parsed file exactly matches model + + +ConfigurationInfo (from ConfigFileParser.getConfigurationInfo()) +Tested Features: +* fitName() +* fitOutputFileName() +* userKeywords() +* reactionList() +* amplitudeList() +* coherentSumList() +* neg2LnLikContribList() +* pdfList() +* termList() +* parameterList() +Checks: +* Fit name matches model fit name +* Fit output file name matches model fit output file name +* Keywords vector size matches model vector size +* Reaction vector size matches model vector size +* Amplitude vector size matches model vector size +* Coherent sum vector size matches model vector size +* Neg2LnLikContrib vector size matches model vector size +* PDF vector size matches model vector size +* Term vector size matches model vector size +* Parameter vector size matches model vector size + + +AmpToolsInterface/AmpToolsInterfaceMPI (from ConfigurationInfo*) +Tested Features: +* AmpToolsInterface::registerAmplitude +* AmpToolsInterface::registerNeg2LnLikContrib +* AmpToolsInterface::registerDataReader +* likelihood() +* minuitMinimizationManager() +Checks (All floating point checks have a precision of 0.1): +* Likelihood before fit matches model +* Likelihood after fit matches model +* Likelihood of base reaction after fit matches model +* Likelihood of constrained reaction after fit matches model +* Likelihood of symmetrized (implicit) reaction after fit matches model +* Likelihood of symmetrized (explicit) reaction after fit matches model + + +fitResults (from .fit file and from AmpToolsInterface.fitResults()) +Tested Features: +* intensity() +* phaseDiff() +* productionParameter() +* bestMinimum() +* parNameList() +* parValueList() +Checks (all have a precision of 0.01 unless noted otherwise): +* Intensity matches model (precision of 1) +* Intensity error matches model (precision of 10) +* Phase difference matches model +* Phase difference error matches model (precision of 1) +* Real part of base reaction production parameter matches model +* Imaginary part of base reaction production parameter matches model +* Real part of constrained reaction production parameter matches model +* Imaginary part of constrained reaction production parameter matches model +* Real part of symmetrized reaction production parameter matches model +* Imaginary part of symmetrized reaction production parameter matches model +* Best minimum matches model +* Number of parameter names matches model (exact match- integer values) +* All fit parameters match model diff --git a/UnitTests/configurationInfoTest.cc b/UnitTests/configurationInfoTest.cc new file mode 100644 index 00000000..483d3bf8 --- /dev/null +++ b/UnitTests/configurationInfoTest.cc @@ -0,0 +1,138 @@ +#include +#include +#include +#include +#include +#include +#include +#include "IUAmpTools/ConfigFileParser.h" +#include "IUAmpTools/ConfigurationInfo.h" +#include "IUAmpTools/AmpToolsInterface.h" +#include "DalitzDataIO/DalitzDataReader.h" +#include "DalitzAmp/BreitWigner.h" +#include "DalitzAmp/Constraint.h" + +using namespace std; + + +class unitTest { + public: + bool passed = true; + vector failedTests; + vector passedTests; + void add(bool expr, string name) { + passed = passed && expr; + if (expr) { + passedTests.push_back(name); + } else { + failedTests.push_back(name); + } + } + void add(double valModel, double val, double tolerance, string name) { + bool expr = abs(valModel-val)<= tolerance; + passed = passed && expr; + if (expr) { + passedTests.push_back(name + "(diff="+to_string(abs(valModel-val)) +")"); + } else { + failedTests.push_back(name + "(diff="+to_string(abs(valModel-val)) +")"); + } + } + bool summary() { + assert(passed || failedTests.size() > 0); + if (passed) { + cout << "All unit tests passed." << endl; + for (const string& passedTest : passedTests) { + cout << "* " << passedTest << endl; + } + } else { + cout << "The following unit tests failed:" << endl; + for (const string& failedTest : failedTests) { + cout << "* " << failedTest << endl; + } + if (passedTests.size() > 0) { + cout << "The following unit tests were successful:" << endl; + for (const string& passedTest : passedTests) { + cout << "* " << passedTest << endl; + } + } + } + return passed; + } +}; + +bool testConfigurationInfo(ConfigurationInfo* cfgInfo, string target_name) { + + cout << "________________________________________" << endl; + cout << "Testing " << target_name << ":" << endl; + cout << "________________________________________" << endl; + + unitTest unit_test; + ifstream fin; + fin.open("models/configurationInfo.txt"); + + string model_fitname; + fin >> model_fitname; + unit_test.add(model_fitname == cfgInfo->fitName(), "Fit name matches model fit name"); + + string model_fitOutputFileName; + fin >> model_fitOutputFileName; + unit_test.add(model_fitOutputFileName== cfgInfo->fitOutputFileName(), "Fit output file name matches model fit output file name"); + + vector kwds = cfgInfo->userKeywords(); + int model_kwds_size; + fin >> model_kwds_size; + unit_test.add(kwds.size() == model_kwds_size, "Keywords vector size matches model vector size"); + vector rinfo = cfgInfo->reactionList(); + int model_reaction_size; + fin >> model_reaction_size; + unit_test.add(rinfo.size() == model_reaction_size, "Reaction vector size matches model vector size"); + + vector ainfo= cfgInfo->amplitudeList(); + int model_amplitude_size; + fin >> model_amplitude_size; + unit_test.add(ainfo.size() == model_amplitude_size, "Amplitude vector size matches model vector size"); + + vector csinfo= cfgInfo->coherentSumList(); + int model_cs_size; + fin >> model_cs_size; + unit_test.add(csinfo.size() == model_cs_size, "Coherent sum vector size matches model vector size"); + + vector llinfo= cfgInfo->neg2LnLikContribList(); + int model_ll_size; + fin >> model_ll_size; + unit_test.add(llinfo.size() == model_ll_size, "Neg2LnLikContrib vector size matches model vector size"); + + vector pdfinfo= cfgInfo->pdfList(); + int model_pdf_size; + fin >> model_pdf_size; + unit_test.add(pdfinfo.size() == model_pdf_size, "PDF vector size matches model vector size"); + + vector tinfo= cfgInfo->termList(); + int model_term_size; + fin >> model_term_size; + unit_test.add(tinfo.size() == model_term_size, "Term vector size matches model vector size"); + + vector pinfo = cfgInfo->parameterList(); + int model_parameter_size; + fin >> model_parameter_size; + unit_test.add(pinfo.size() == model_parameter_size, "Parameter vector size matches model vector size"); + + bool result = unit_test.summary(); + return result; +} + +int main() { + string cfgname = "parserTest.cfg"; + vector results; + ConfigFileParser parser(cfgname); + ConfigurationInfo* cfgInfo = parser.getConfigurationInfo(); + + results.push_back(testConfigurationInfo(cfgInfo, "configurationInfo from ConfigFileParser.getConfigurationInfo()")); + bool result; + for (const bool result : results) { + if (!result) { + throw runtime_error("Unit Tests Failed. See previous logs for more information."); + } + } + return 0; +} \ No newline at end of file diff --git a/UnitTests/fitResultsTest.cc b/UnitTests/fitResultsTest.cc new file mode 100644 index 00000000..60fe81a5 --- /dev/null +++ b/UnitTests/fitResultsTest.cc @@ -0,0 +1,156 @@ +#include +#include +#include +#include +#include +#include +#include +#include "IUAmpTools/ConfigFileParser.h" +#include "IUAmpTools/ConfigurationInfo.h" +#include "IUAmpTools/AmpToolsInterface.h" +#include "IUAmpTools/DataReader.h" +#include "IUAmpTools/FitResults.h" +#include "DalitzDataIO/DalitzDataReader.h" +#include "DalitzAmp/BreitWigner.h" +#include "DalitzAmp/Constraint.h" +#include "IUAmpTools/report.h" + +static const char* kModule = "fitResultsTest"; + +using namespace std; + +class unitTest { + public: + bool passed = true; + vector failedTests; + vector passedTests; + void add(bool expr, string name) { + passed = passed && expr; + if (expr) { + passedTests.push_back(name); + } else { + failedTests.push_back(name); + } + } + void add(double valModel, double val, double tolerance, string name) { + bool expr = abs(valModel-val)<= tolerance; + passed = passed && expr; + if (expr) { + passedTests.push_back(name + "(diff="+to_string(abs(valModel-val)) +")"); + } else { + failedTests.push_back(name + "(diff="+to_string(abs(valModel-val)) +")"); + } + } + bool summary() { + assert(passed || failedTests.size() > 0); + if (passed) { + cout << "All unit tests passed." << endl; + for (const string& passedTest : passedTests) { + cout << "* " << passedTest << endl; + } + } else { + cout << "The following unit tests failed:" << endl; + for (const string& failedTest : failedTests) { + cout << "* " << failedTest << endl; + } + if (passedTests.size() > 0) { + cout << "The following unit tests were successful:" << endl; + for (const string& passedTest : passedTests) { + cout << "* " << passedTest << endl; + } + } + } + return passed; + } +}; + +bool testFitResults(const FitResults* fitResults) +{ + string fitResultsFile = "models/fitResults.txt"; + unitTest unit_test; + ifstream fin; + fin.open(fitResultsFile); + double intensity_first; + double intensity_second; + double pd_first; + double pd_second; + double ppBase_real; + double ppBase_imag; + double ppConstrained_real; + double ppConstrained_imag; + double ppSymm_real; + double ppSymm_imag; + double bestMinimum; + int num_parameters; + fin >> intensity_first; + fin >> intensity_second; + fin >> pd_first; + fin >> pd_second; + fin >> ppBase_real; + fin >> ppBase_imag; + fin >> ppConstrained_real; + fin >> ppConstrained_imag; + fin >> ppSymm_real; + fin >> ppSymm_imag; + fin >> bestMinimum; + fin >> num_parameters; + unit_test.add(intensity_first, fitResults->intensity().first, 1e-2, "Intensity matches model"); + unit_test.add(intensity_second, fitResults->intensity().second, 1e-2, "Intensity error matches model"); + unit_test.add(pd_first, fitResults->phaseDiff("base::s1::R12", "base::s1::R13").first, 1e-4, "Phase difference between amplitudes matches model"); + unit_test.add(pd_second,fitResults->phaseDiff("base::s1::R12", "base::s1::R13").second,1e-4,"Phase difference error between amplitudes matches model"); + unit_test.add(ppBase_real,fitResults->productionParameter("base::s1::R12").real(),1e-4,"Real part of base reaction production parameter matches model"); + unit_test.add(ppBase_imag,fitResults->productionParameter("base::s1::R12").imag(),1e-4,"Imaginary part of base reaction production parameter matches model"); + unit_test.add(ppConstrained_real,fitResults->productionParameter("constrained::s2::RC12").real(),1e-4,"Real part of constrained reaction production parameter matches model"); + unit_test.add(ppConstrained_imag,fitResults->productionParameter("constrained::s2::RC12").imag(),1e-4,"Imaginary part of constrained reaction production parameter matches model"); + unit_test.add(ppSymm_real,fitResults->productionParameter("symmetrized_explicit::s4::RSE12").real(),1e-4,"Real part of symmetrized reaction production parameter matches model"); + unit_test.add(ppSymm_imag,fitResults->productionParameter("symmetrized_explicit::s4::RSE12").imag(),1e-4,"Imaginary part of symmetrized reaction production parameter matches model"); + unit_test.add(bestMinimum,fitResults->bestMinimum(),1e-3,"Best minimum matches model"); + vector parNames = fitResults->parNameList(); + int sz = parNames.size(); + unit_test.add(abs(num_parameters - sz) == 0, "Number of parameter names matches model"); + vector parVals = fitResults->parValueList(); + for (int i = 0; i < sz; i++) { + double parValModel; + fin >> parValModel; + unit_test.add(parValModel, parVals[i], 1e-4, parNames[i] + " value matches model value"); + } + return unit_test.summary(); +} + +int main() +{ + vector results; + string cfgname = "parserTest.cfg"; + ConfigFileParser parser(cfgname); + ConfigurationInfo* cfgInfo = parser.getConfigurationInfo(); + AmpToolsInterface::registerAmplitude(BreitWigner()); + AmpToolsInterface::registerNeg2LnLikContrib(Constraint()); + AmpToolsInterface::registerDataReader(DalitzDataReader()); + AmpToolsInterface ATI(cfgInfo); + AmpToolsInterface::setRandomSeed(12345); + cout << "________________________________________" << endl; + cout << "Testing FitResults from AmpToolsInterface:" << endl; + cout << "________________________________________" << endl; + + MinuitMinimizationManager* fitManager = ATI.minuitMinimizationManager(); + fitManager->setStrategy(1); + + fitManager->migradMinimization(); + ATI.finalizeFit(); + const FitResults* fitResults = ATI.fitResults(); + results.push_back(testFitResults(fitResults)); + + cout << "________________________________________" << endl; + cout << "Testing FitResults from file:" << endl; + cout << "________________________________________" << endl; + + FitResults fitResults_from_file("fitTest.fit"); + const FitResults* fr_ff = &fitResults_from_file; + results.push_back(testFitResults(fr_ff)); + for (const bool result : results) { + if (!result) { + throw runtime_error("Unit Tests Failed. See previous logs for more information."); + } + } + return 0; +} \ No newline at end of file diff --git a/UnitTests/fitResultsTestMPI.cc b/UnitTests/fitResultsTestMPI.cc new file mode 100644 index 00000000..c1bfd95b --- /dev/null +++ b/UnitTests/fitResultsTestMPI.cc @@ -0,0 +1,172 @@ +#include "DalitzAmp/BreitWigner.h" +#include "DalitzAmp/Constraint.h" +#include "DalitzDataIO/DalitzDataReader.h" +#include "IUAmpTools/ConfigFileParser.h" +#include "IUAmpTools/ConfigurationInfo.h" +#include "IUAmpTools/FitResults.h" +#include "IUAmpTools/report.h" +#include "IUAmpToolsMPI/AmpToolsInterfaceMPI.h" +#include "IUAmpToolsMPI/DataReaderMPI.h" +#include +#include +#include +#include +#include +#include +#include +#include + +static const char* kModule = "fitResultsTestMPI"; + +using namespace std; + +class unitTest { +public: + bool passed = true; + vector failedTests; + vector passedTests; + void add(bool expr, string name) + { + passed = passed && expr; + if (expr) { + passedTests.push_back(name); + } else { + failedTests.push_back(name); + } + } + void add(double valModel, double val, double tolerance, string name) + { + bool expr = abs(valModel - val) <= tolerance; + passed = passed && expr; + if (expr) { + passedTests.push_back(name + "(diff=" + to_string(abs(valModel - val)) + ")"); + } else { + failedTests.push_back(name + "(diff=" + to_string(abs(valModel - val)) + ")"); + } + } + bool summary() + { + assert(passed || failedTests.size() > 0); + if (passed) { + cout << "All unit tests passed." << endl; + for (const string& passedTest : passedTests) { + cout << "* " << passedTest << endl; + } + } else { + cout << "The following unit tests failed:" << endl; + for (const string& failedTest : failedTests) { + cout << "* " << failedTest << endl; + } + if (passedTests.size() > 0) { + cout << "The following unit tests were successful:" << endl; + for (const string& passedTest : passedTests) { + cout << "* " << passedTest << endl; + } + } + } + return passed; + } +}; + +bool testFitResults(const FitResults* fitResults) +{ + string fitResultsFile = "models/fitResults.txt"; + unitTest unit_test; + ifstream fin; + fin.open(fitResultsFile); + double intensity_first; + double intensity_second; + double pd_first; + double pd_second; + double ppBase_real; + double ppBase_imag; + double ppConstrained_real; + double ppConstrained_imag; + double ppSymm_real; + double ppSymm_imag; + double bestMinimum; + int num_parameters; + fin >> intensity_first; + fin >> intensity_second; + fin >> pd_first; + fin >> pd_second; + fin >> ppBase_real; + fin >> ppBase_imag; + fin >> ppConstrained_real; + fin >> ppConstrained_imag; + fin >> ppSymm_real; + fin >> ppSymm_imag; + fin >> bestMinimum; + fin >> num_parameters; + unit_test.add(intensity_first, fitResults->intensity().first, 1e-2, "Intensity matches model"); + unit_test.add(intensity_second, fitResults->intensity().second, 1e-2, "Intensity error matches model"); + unit_test.add(pd_first, fitResults->phaseDiff("base::s1::R12", "base::s1::R13").first, 1e-4, "Phase difference between amplitudes matches model"); + unit_test.add(pd_second, fitResults->phaseDiff("base::s1::R12", "base::s1::R13").second, 1e-4, "Phase difference error between amplitudes matches model"); + unit_test.add(ppBase_real, fitResults->productionParameter("base::s1::R12").real(), 1e-4, "Real part of base reaction production parameter matches model"); + unit_test.add(ppBase_imag, fitResults->productionParameter("base::s1::R12").imag(), 1e-4, "Imaginary part of base reaction production parameter matches model"); + unit_test.add(ppConstrained_real, fitResults->productionParameter("constrained::s2::RC12").real(), 1e-4, "Real part of constrained reaction production parameter matches model"); + unit_test.add(ppConstrained_imag, fitResults->productionParameter("constrained::s2::RC12").imag(), 1e-4, "Imaginary part of constrained reaction production parameter matches model"); + unit_test.add(ppSymm_real, fitResults->productionParameter("symmetrized_explicit::s4::RSE12").real(), 1e-4, "Real part of symmetrized reaction production parameter matches model"); + unit_test.add(ppSymm_imag, fitResults->productionParameter("symmetrized_explicit::s4::RSE12").imag(), 1e-4, "Imaginary part of symmetrized reaction production parameter matches model"); + unit_test.add(bestMinimum, fitResults->bestMinimum(), 1e-3, "Best minimum matches model"); + vector parNames = fitResults->parNameList(); + int sz = parNames.size(); + unit_test.add(abs(num_parameters - sz) == 0, "Number of parameter names matches model"); + vector parVals = fitResults->parValueList(); + for (int i = 0; i < sz; i++) { + double parValModel; + fin >> parValModel; + unit_test.add(parValModel, parVals[i], 1e-4, parNames[i] + " value matches model value"); + } + + return unit_test.summary(); +} + +int main(int argc, char* argv[]) +{ + MPI_Init(&argc, &argv); + int rank; + int size; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + MPI_Comm_size(MPI_COMM_WORLD, &size); + string cfgname = "parserTest.cfg"; + ConfigFileParser parser(cfgname); + ConfigurationInfo* cfgInfo = parser.getConfigurationInfo(); + AmpToolsInterfaceMPI::registerAmplitude(BreitWigner()); + AmpToolsInterfaceMPI::registerNeg2LnLikContrib(Constraint()); + AmpToolsInterfaceMPI::registerDataReader(DataReaderMPI()); + AmpToolsInterfaceMPI ATI(cfgInfo); + AmpToolsInterfaceMPI::setRandomSeed(12345); + if (rank == 0) { + vector results; + cout << "________________________________________" << endl; + cout << "Testing FitResults from AmpToolsInterface:" << endl; + cout << "________________________________________" << endl; + + MinuitMinimizationManager* fitManager = ATI.minuitMinimizationManager(); + fitManager->setStrategy(1); + + fitManager->migradMinimization(); + ATI.finalizeFit(); + const FitResults* fitResults = ATI.fitResults(); + results.push_back(testFitResults(fitResults)); + + cout << "________________________________________" << endl; + cout << "Testing FitResults from file:" << endl; + cout << "________________________________________" << endl; + + FitResults fitResults_from_file("fitTest.fit"); + const FitResults* fr_ff = &fitResults_from_file; + results.push_back(testFitResults(fr_ff)); + for (const bool result : results) { + if (!result) { + throw runtime_error("Unit Tests Failed. See previous logs for more information."); + } + } + } + + ATI.exitMPI(); + MPI_Finalize(); + + return 0; +} diff --git a/UnitTests/includeTest.cfg b/UnitTests/includeTest.cfg new file mode 100644 index 00000000..33d99c7c --- /dev/null +++ b/UnitTests/includeTest.cfg @@ -0,0 +1,6 @@ +reaction base p1 p2 p3 +sum base s1 +amplitude base::s1::R12 BreitWigner [M12] [G12] 1 2 +amplitude base::s1::R13 BreitWigner [M13] [G13] 1 3 +initialize base::s1::R12 cartesian 1.0 0.0 +initialize base::s1::R13 cartesian 1.0 0.0 real \ No newline at end of file diff --git a/UnitTests/models/AmpToolsInterface.txt b/UnitTests/models/AmpToolsInterface.txt new file mode 100644 index 00000000..e579f1c9 --- /dev/null +++ b/UnitTests/models/AmpToolsInterface.txt @@ -0,0 +1,6 @@ +124397 +-84.9406 +-473.64 +-1314.64 +806.42 +806.42 diff --git a/UnitTests/models/configurationInfo.txt b/UnitTests/models/configurationInfo.txt new file mode 100644 index 00000000..469095e4 --- /dev/null +++ b/UnitTests/models/configurationInfo.txt @@ -0,0 +1,10 @@ +fitTest +fitTest.fit +0 +4 +8 +5 +1 +0 +8 +6 diff --git a/UnitTests/models/fitResults.txt b/UnitTests/models/fitResults.txt new file mode 100644 index 00000000..87fc24cd --- /dev/null +++ b/UnitTests/models/fitResults.txt @@ -0,0 +1,34 @@ +20590.7 +287.663 +0.107421 +0.0433126 +13.8506 +1.49359 +13.8506 +1.49359 +15.7933 +-18.7804 +-84.9406 +22 +13.8506 +1.49359 +34.6073 +0 +13.8506 +1.49359 +34.6073 +0 +15.793 +-18.7806 +26.0313 +0 +15.7933 +-18.7804 +26.0311 +0 +1.00144 +0.204859 +1.5103 +0.170219 +3.42465 +5.28702 diff --git a/UnitTests/models/parsedConfig.txt b/UnitTests/models/parsedConfig.txt new file mode 100644 index 00000000..278ee609 --- /dev/null +++ b/UnitTests/models/parsedConfig.txt @@ -0,0 +1,126 @@ +##################################### +#### THIS IS A CONFIG FILE #### +##################################### +## +## Blank lines or lines beginning with a "#" are ignored. +## +## Double colons (::) are treated like a space. +## This is sometimes useful for grouping (for example, +## grouping strings like "reaction::sum::amplitudeName") +## +## All non-comment lines must begin with one of the following keywords. +## +## (note: means necessary +## (word) means optional) +## +## include +## define (defn1) (defn2) (defn3) ... +## loop (value3) ... +## fit +## keyword +## reaction (particle3) ... +## data (arg1) (arg2) (arg3) ... +## genmc (arg1) (arg2) (arg3) ... +## accmc (arg1) (arg2) (arg3) ... +## (optional: bkgnd (arg1) (arg2) (arg3) ... ) +## normintfile ("input") +## sum (sum2) (sum3) ... +## amplitude (arg1) (arg2) ([par]) ... +## initialize <"events"/"polar"/"cartesian"> +## ("fixed"/"real") +## scale +## constrain ... +## parameter ("fixed"/"bounded"/"gaussian") +## (lower/central) (upper/error) +## neg2LnLikContrib (arg1) (arg2) ... +## FUTURE - NOT YET SUPPORTED: +## pdf (arg1) (arg2) ([par]) ... +## pdfinitialize ("fixed") +## pdfscale +## pdfconstrain ... +## gpudevice +## DEPRECATED: +## datafile (file2) (file3) ... +## genmcfile (file2) (file3) ... +## accmcfile (file2) (file3) ... +## +##################################### + +define P12 1.000 0.200 +define P13 1.500 0.150 + +parameter A 5 +parameter BKG 2.5 +parameter M12 1.000 +parameter G12 0.200 +parameter M13 1.500 +parameter G13 0.150 + +fit fitTest + +#Base reaction +########## INCLUDE FILE includeTest.cfg ######## +reaction base p1 p2 p3 +sum base s1 +amplitude base::s1::R12 BreitWigner [M12] [G12] 1 2 +amplitude base::s1::R13 BreitWigner [M13] [G13] 1 3 +initialize base::s1::R12 cartesian 1.0 0.0 +initialize base::s1::R13 cartesian 1.0 0.0 real + +#Constrained reaction +reaction constrained p1 p2 p3 +sum constrained s2 +sum constrained s3 +amplitude constrained::s2::RC12 BreitWigner [M12] [G12] 1 2 +amplitude constrained::s3::RC13 BreitWigner [M13] [G13] 1 3 +scale constrained::s2::RC12 3 +constrain constrained::s2::RC12 base::s1::R12 +constrain constrained::s3::RC13 base::s1::R13 +initialize constrained::s2::RC12 cartesian 1.0 0.0 +initialize constrained::s3::RC13 cartesian 1.0 0.0 real + +#Symmetrized reactions +reaction symmetrized_implicit p1 p2 p2 +reaction symmetrized_explicit p1 p2 p3 +sum symmetrized_explicit s4 +sum symmetrized_implicit s5 + +amplitude symmetrized_explicit::s4::RSE12 BreitWigner P12 1 2 +amplitude symmetrized_explicit::s4::RSE13 BreitWigner P13 1 3 +permute symmetrized_explicit::s4::RSE12 0 2 1 +permute symmetrized_explicit::s4::RSE13 0 2 1 + +amplitude symmetrized_implicit::s5::RSI12 BreitWigner P12 1 2 +amplitude symmetrized_implicit::s5::RSI13 BreitWigner P13 1 3 + +initialize symmetrized_explicit::s4::RSE12 cartesian 1.0 0.0 +initialize symmetrized_explicit::s4::RSE13 cartesian 1.0 0.0 real + +initialize symmetrized_implicit::s5::RSI12 cartesian 1.0 0.0 +initialize symmetrized_implicit::s5::RSI13 cartesian 1.0 0.0 real + +loop for_each_reaction base constrained symmetrized_implicit symmetrized_explicit + +########## INSERTING A LOOP ######## +genmc for_each_reaction DalitzDataReader phasespace.gen.root +genmc for_each_reaction DalitzDataReader phasespace.gen.root +genmc for_each_reaction DalitzDataReader phasespace.gen.root +genmc for_each_reaction DalitzDataReader phasespace.gen.root +########## INSERTING A LOOP ######## +accmc for_each_reaction DalitzDataReader phasespace.acc.root +accmc for_each_reaction DalitzDataReader phasespace.acc.root +accmc for_each_reaction DalitzDataReader phasespace.acc.root +accmc for_each_reaction DalitzDataReader phasespace.acc.root +########## INSERTING A LOOP ######## +data for_each_reaction DalitzDataReader physics.acc.root +data for_each_reaction DalitzDataReader physics.acc.root +data for_each_reaction DalitzDataReader physics.acc.root +data for_each_reaction DalitzDataReader physics.acc.root +normintfile base base.ni +normintfile constrained constrained.ni +normintfile symmetrized_implicit symmetrized_implicit.ni +normintfile symmetrized_explicit symmetrized_explicit.ni + +neg2LnLikContrib Constraint [A] [M12] [G12] [BKG] + + diff --git a/UnitTests/parserTest.cc b/UnitTests/parserTest.cc new file mode 100644 index 00000000..c8abff78 --- /dev/null +++ b/UnitTests/parserTest.cc @@ -0,0 +1,135 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "IUAmpTools/ConfigFileParser.h" +#include "IUAmpTools/ConfigurationInfo.h" +#include "IUAmpTools/AmpToolsInterface.h" +#include "DalitzDataIO/DalitzDataReader.h" +#include "DalitzAmp/BreitWigner.h" +#include "DalitzAmp/Constraint.h" + +using namespace std; + +class unitTest { + public: + bool passed = true; + vector failedTests; + vector passedTests; + void add(bool expr, string name) { + passed = passed && expr; + if (expr) { + passedTests.push_back(name); + } else { + failedTests.push_back(name); + } + } + void add(double valModel, double val, double tolerance, string name) { + bool expr = abs(valModel-val)<= tolerance; + passed = passed && expr; + if (expr) { + passedTests.push_back(name + "(diff="+to_string(abs(valModel-val)) +")"); + } else { + failedTests.push_back(name + "(diff="+to_string(abs(valModel-val)) +")"); + } + } + bool summary() { + assert(passed || failedTests.size() > 0); + if (passed) { + cout << "All unit tests passed." << endl; + for (const string& passedTest : passedTests) { + cout << "* " << passedTest << endl; + } + } else { + cout << "The following unit tests failed:" << endl; + for (const string& failedTest : failedTests) { + cout << "* " << failedTest << endl; + } + if (passedTests.size() > 0) { + cout << "The following unit tests were successful:" << endl; + for (const string& passedTest : passedTests) { + cout << "* " << passedTest << endl; + } + } + } + return passed; + } +}; + +vector read_model(string model_file_name) { + ifstream fin; + fin.open(model_file_name); + vector lines; + string line; + while (getline(fin, line)) { + lines.push_back(line); + } + return lines; +} + +bool testConfigFileParser(ConfigFileParser parser, string target_name) { + cout << "________________________________________" << endl; + cout << "Testing " << target_name << ":" << endl; + cout << "________________________________________" << endl; + + unitTest unit_test; + + vector cfgLines = parser.getConfigFileLines(); + + //checking that the file was accurately parsed + + vector cfgStrings; + string cfgLine; + for (const ConfigFileLine cfgLine : cfgLines) { + cfgStrings.push_back(cfgLine.line()); + } + + + vector modelStrings = read_model("models/parsedConfig.txt"); + unit_test.add(modelStrings.size() == cfgStrings.size(), "Number of lines in parsed file matches model"); + unit_test.add(modelStrings == cfgStrings, "Parsed file exactly matches model"); + bool result = unit_test.summary(); + cout << endl; + + return result; +} + +int main() { + string cfgname = "parserTest.cfg"; + vector results; + + // Testing ConfigFileParser(const string& configFile) + ConfigFileParser parser_from_string(cfgname); + results.push_back(testConfigFileParser(parser_from_string, "ConfigFileParser(const string& configFile)")); + + // Testing ConfigFileParser(istream& input) + ifstream fin; + fin.open(cfgname); + ConfigFileParser parser_from_istream(fin); + results.push_back(testConfigFileParser(parser_from_istream, "ConfigFileParser(istream& input)")); + fin.close(); + + // Testing ConfigFileParser() and readConfigFile(const string& configFile) + ConfigFileParser default_with_read_string; + default_with_read_string.readConfigFile(cfgname); + results.push_back(testConfigFileParser(default_with_read_string, "ConfigFileParser() and readConfigFile(const string& configFile)")); + + // Testing ConfigFileParser() and readConfigFile(istream& input) + fin.open(cfgname); + ConfigFileParser default_with_read_istream; + default_with_read_istream.readConfigFile(fin); + results.push_back(testConfigFileParser(default_with_read_istream, "ConfigFileParser() and readConfigFile(istream& input)")); + fin.close(); + + bool result; + for (const bool result : results) { + if (!result) { + throw runtime_error("Unit Tests Failed. See previous logs for more information."); + } + } + return 0; +} \ No newline at end of file diff --git a/UnitTests/parserTest.cfg b/UnitTests/parserTest.cfg new file mode 100644 index 00000000..fa95ea30 --- /dev/null +++ b/UnitTests/parserTest.cfg @@ -0,0 +1,107 @@ +##################################### +#### THIS IS A CONFIG FILE #### +##################################### +## +## Blank lines or lines beginning with a "#" are ignored. +## +## Double colons (::) are treated like a space. +## This is sometimes useful for grouping (for example, +## grouping strings like "reaction::sum::amplitudeName") +## +## All non-comment lines must begin with one of the following keywords. +## +## (note: means necessary +## (word) means optional) +## +## include +## define (defn1) (defn2) (defn3) ... +## loop (value3) ... +## fit +## keyword +## reaction (particle3) ... +## data (arg1) (arg2) (arg3) ... +## genmc (arg1) (arg2) (arg3) ... +## accmc (arg1) (arg2) (arg3) ... +## (optional: bkgnd (arg1) (arg2) (arg3) ... ) +## normintfile ("input") +## sum (sum2) (sum3) ... +## amplitude (arg1) (arg2) ([par]) ... +## initialize <"events"/"polar"/"cartesian"> +## ("fixed"/"real") +## scale +## constrain ... +## parameter ("fixed"/"bounded"/"gaussian") +## (lower/central) (upper/error) +## neg2LnLikContrib (arg1) (arg2) ... +## FUTURE - NOT YET SUPPORTED: +## pdf (arg1) (arg2) ([par]) ... +## pdfinitialize ("fixed") +## pdfscale +## pdfconstrain ... +## gpudevice +## DEPRECATED: +## datafile (file2) (file3) ... +## genmcfile (file2) (file3) ... +## accmcfile (file2) (file3) ... +## +##################################### + +define P12 1.000 0.200 +define P13 1.500 0.150 + +parameter A 5 +parameter BKG 2.5 +parameter M12 1.000 +parameter G12 0.200 +parameter M13 1.500 +parameter G13 0.150 + +fit fitTest + +#Base reaction +include includeTest.cfg + +#Constrained reaction +reaction constrained p1 p2 p3 +sum constrained s2 +sum constrained s3 +amplitude constrained::s2::RC12 BreitWigner [M12] [G12] 1 2 +amplitude constrained::s3::RC13 BreitWigner [M13] [G13] 1 3 +scale constrained::s2::RC12 3 +constrain constrained::s2::RC12 base::s1::R12 +constrain constrained::s3::RC13 base::s1::R13 +initialize constrained::s2::RC12 cartesian 1.0 0.0 +initialize constrained::s3::RC13 cartesian 1.0 0.0 real + +#Symmetrized reactions +reaction symmetrized_implicit p1 p2 p2 +reaction symmetrized_explicit p1 p2 p3 +sum symmetrized_explicit s4 +sum symmetrized_implicit s5 + +amplitude symmetrized_explicit::s4::RSE12 BreitWigner P12 1 2 +amplitude symmetrized_explicit::s4::RSE13 BreitWigner P13 1 3 +permute symmetrized_explicit::s4::RSE12 0 2 1 +permute symmetrized_explicit::s4::RSE13 0 2 1 + +amplitude symmetrized_implicit::s5::RSI12 BreitWigner P12 1 2 +amplitude symmetrized_implicit::s5::RSI13 BreitWigner P13 1 3 + +initialize symmetrized_explicit::s4::RSE12 cartesian 1.0 0.0 +initialize symmetrized_explicit::s4::RSE13 cartesian 1.0 0.0 real + +initialize symmetrized_implicit::s5::RSI12 cartesian 1.0 0.0 +initialize symmetrized_implicit::s5::RSI13 cartesian 1.0 0.0 real + +loop for_each_reaction base constrained symmetrized_implicit symmetrized_explicit + +genmc for_each_reaction DalitzDataReader phasespace.gen.root +accmc for_each_reaction DalitzDataReader phasespace.acc.root +data for_each_reaction DalitzDataReader physics.acc.root +normintfile base base.ni +normintfile constrained constrained.ni +normintfile symmetrized_implicit symmetrized_implicit.ni +normintfile symmetrized_explicit symmetrized_explicit.ni + +neg2LnLikContrib Constraint [A] [M12] [G12] [BKG] + diff --git a/UnitTests/writeModels.cc b/UnitTests/writeModels.cc new file mode 100644 index 00000000..b49c5ed0 --- /dev/null +++ b/UnitTests/writeModels.cc @@ -0,0 +1,113 @@ +#include +#include +#include +#include +#include +#include +#include +#include "IUAmpTools/ConfigFileParser.h" +#include "IUAmpTools/ConfigurationInfo.h" +#include "IUAmpTools/AmpToolsInterface.h" +#include "DalitzDataIO/DalitzDataReader.h" +#include "IUAmpTools/FitResults.h" +#include "DalitzAmp/BreitWigner.h" +#include "DalitzAmp/Constraint.h" + +#include "IUAmpTools/report.h" +static const char* kModule = "writeModels"; +using namespace std; + +int main() { + ofstream fout; + string cfgname("parserTest.cfg"); + ConfigFileParser parser(cfgname); + ConfigurationInfo* cfgInfo = parser.getConfigurationInfo(); + AmpToolsInterface::registerAmplitude(BreitWigner()); + AmpToolsInterface::registerNeg2LnLikContrib(Constraint()); + AmpToolsInterface::registerDataReader(DalitzDataReader()); + AmpToolsInterface ATI(cfgInfo); + + + //ConfigFileParser + vector cfgLines = parser.getConfigFileLines(); + fout.open("models/parsedConfig.txt"); + vector cfgStrings; + for (const ConfigFileLine cfgLine : cfgLines) { + fout << cfgLine.line() << "\n"; + } + fout.close(); + + //ConfigurationInfo + fout.open("models/configurationInfo.txt"); + fout << cfgInfo->fitName() << "\n"; + fout << cfgInfo->fitOutputFileName() << "\n"; + vector kwds = cfgInfo->userKeywords(); + fout << kwds.size()<<"\n"; + vector rinfo = cfgInfo->reactionList(); + fout << rinfo.size() << "\n"; + vector ainfo= cfgInfo->amplitudeList(); + fout << ainfo.size() << "\n"; + vector csinfo= cfgInfo->coherentSumList(); + fout << csinfo.size() << "\n"; + vector llinfo= cfgInfo->neg2LnLikContribList(); + fout << llinfo.size() << "\n"; + vector pdfinfo= cfgInfo->pdfList(); + fout << pdfinfo.size() << "\n"; + vector tinfo= cfgInfo->termList(); + fout << tinfo.size() << "\n"; + vector pinfo = cfgInfo->parameterList(); + fout << pinfo.size() << "\n"; + + fout.close(); + + //AmpToolsInterface + string ATIFile = "models/AmpToolsInterface.txt"; + fout.open(ATIFile); + double neg2LL_before = ATI.likelihood(); + fout << neg2LL_before << "\n"; + + MinuitMinimizationManager* fitManager = ATI.minuitMinimizationManager(); + fitManager->setStrategy(1); + + fitManager->migradMinimization(); + + + double neg2LL_after = ATI.likelihood(); + fout << neg2LL_after << "\n"; + fout << ATI.likelihood("base") << "\n"; + fout << ATI.likelihood("constrained") << "\n"; + fout << ATI.likelihood("symmetrized_implicit") << "\n"; + fout << ATI.likelihood("symmetrized_explicit") << "\n"; + ATI.finalizeFit(); + fout.close(); + + //fitResults + + const FitResults* fitResults = ATI.fitResults(); + fout.open("models/fitResults.txt"); + pair intensity = fitResults->intensity(); + fout << intensity.first << "\n"; + fout << intensity.second << "\n"; + pair pd = fitResults->phaseDiff("base::s1::R12","base::s1::R13"); + fout << pd.first << "\n"; + fout << pd.second << "\n"; + complex ppBase = fitResults->productionParameter("base::s1::R12"); + fout << ppBase.real() << "\n"; + fout << ppBase.imag() << "\n"; + complex ppConstrained = fitResults->productionParameter("constrained::s2::RC12"); + fout << ppConstrained.real() << "\n"; + fout << ppConstrained.imag() << "\n"; + complex ppSymm = fitResults->productionParameter("symmetrized_explicit::s4::RSE12"); + fout << ppSymm.real() << "\n"; + fout << ppSymm.imag() << "\n"; + double bestMinimum = fitResults->bestMinimum(); + fout << bestMinimum << "\n"; + vector parNames = fitResults->parNameList(); + fout << parNames.size() << "\n"; + vector parVals = fitResults->parValueList(); + for (const double i : parVals) { + fout << i << "\n"; + } + fout.close(); + return 0; +} \ No newline at end of file