From 6c1e925048690b835d23c717e62d658039d90681 Mon Sep 17 00:00:00 2001 From: "E. G. Patrick Bos" Date: Mon, 7 Jun 2021 16:55:43 +0200 Subject: [PATCH 01/10] squash commit of RooFit_MultiProcess_PR branch --- .travis.yml | 2 +- CMakeLists.txt | 2 +- bindings/jupyroot/CMakeLists.txt | 11 +- bindings/pyroot/cppyy/CPyCppyy/CMakeLists.txt | 13 +- .../pyroot/cppyy/CPyCppyy/src/Pythonize.cxx | 100 +- bindings/pyroot/pythonizations/CMakeLists.txt | 15 +- core/base/src/TAttFill.cxx | 8 +- core/base/src/TAttLine.cxx | 12 +- core/base/src/TAttMarker.cxx | 12 +- core/base/src/TAttText.cxx | 34 +- core/foundation/inc/ROOT/RConfig.hxx | 4 +- core/gui/src/TBrowser.cxx | 9 + core/thread/src/TThread.cxx | 5 +- documentation/doxygen/images/v7_rbrowser.png | Bin 0 -> 122602 bytes geom/geom/src/TGeoManager.cxx | 4 +- graf2d/graf/src/TText.cxx | 16 +- gui/browserv7/src/RBrowser.cxx | 7 + gui/gui/src/TGClient.cxx | 2 +- gui/gui/src/TGCommandPlugin.cxx | 31 +- gui/gui/src/TGFileDialog.cxx | 2 + gui/gui/src/TGTextEntry.cxx | 28 +- hist/hist/src/TF2.cxx | 2 +- hist/hist/src/TF3.cxx | 2 +- .../cling/tools/plugins/clad/CMakeLists.txt | 2 +- io/io/src/TBufferJSON.cxx | 10 +- math/mathcore/inc/Math/IFunction.h | 592 +++++---- math/mathcore/src/Fitter.cxx | 2 + math/minuit2/CMakeLists.txt | 2 + .../Minuit2/AnalyticalGradientCalculator.h | 8 +- .../ExternalInternalGradientCalculator.h | 44 + math/minuit2/inc/Minuit2/FCNGradAdapter.h | 50 +- math/minuit2/inc/Minuit2/FCNGradientBase.h | 26 +- math/minuit2/inc/Minuit2/FunctionMinimizer.h | 4 +- .../inc/Minuit2/MnUserTransformation.h | 9 +- .../inc/Minuit2/SinParameterTransformation.h | 29 +- .../Minuit2/SqrtLowParameterTransformation.h | 33 +- .../Minuit2/SqrtUpParameterTransformation.h | 36 +- .../src/AnalyticalGradientCalculator.cxx | 95 +- math/minuit2/src/CMakeLists.txt | 2 + .../ExternalInternalGradientCalculator.cxx | 71 + .../minuit2/src/InitialGradientCalculator.cxx | 4 + math/minuit2/src/MnSeedGenerator.cxx | 23 +- math/minuit2/src/MnUserTransformation.cxx | 43 +- math/minuit2/src/ModularFunctionMinimizer.cxx | 19 +- .../src/SinParameterTransformation.cxx | 87 +- .../src/SqrtLowParameterTransformation.cxx | 70 +- .../src/SqrtUpParameterTransformation.cxx | 71 +- math/minuit2/test/MnTutorial/Quad1F.h | 22 +- math/minuit2/test/MnTutorial/Quad4F.h | 18 +- roofit/CMakeLists.txt | 3 + roofit/builtin_zeromq/CMakeLists.txt | 35 + roofit/multiprocess/CMakeLists.txt | 42 + roofit/multiprocess/inc/LinkDef.h | 1 + .../inc/RooFit/MultiProcess/Job.h | 130 ++ .../inc/RooFit/MultiProcess/JobManager.h | 91 ++ .../inc/RooFit/MultiProcess/Messenger.h | 208 +++ .../inc/RooFit/MultiProcess/Messenger_decl.h | 185 +++ .../inc/RooFit/MultiProcess/ProcessManager.h | 78 ++ .../inc/RooFit/MultiProcess/Queue.h | 46 + .../inc/RooFit/MultiProcess/types.h | 29 + .../inc/RooFit/MultiProcess/util.h | 34 + .../inc/RooFit/MultiProcess/worker.h | 27 + roofit/multiprocess/src/Job.cxx | 65 + roofit/multiprocess/src/JobManager.cxx | 264 ++++ roofit/multiprocess/src/Messenger.cxx | 481 +++++++ roofit/multiprocess/src/ProcessManager.cxx | 286 +++++ roofit/multiprocess/src/Queue.cxx | 224 ++++ roofit/multiprocess/src/util.cxx | 130 ++ roofit/multiprocess/src/worker.cxx | 228 ++++ roofit/multiprocess/test/CMakeLists.txt | 13 + roofit/multiprocess/test/test_Job.cxx | 252 ++++ roofit/multiprocess/test/test_JobManager.cxx | 13 + roofit/multiprocess/test/test_Messenger.cxx | 116 ++ .../multiprocess/test/test_ProcessManager.cxx | 57 + roofit/multiprocess/test/test_Queue.cxx | 13 + roofit/multiprocess/test/test_util.cxx | 13 + roofit/multiprocess/test/test_worker.cxx | 28 + roofit/multiprocess/test/utils.h | 171 +++ roofit/roofitZMQ/CMakeLists.txt | 42 + roofit/roofitZMQ/inc/LinkDef.h | 1 + roofit/roofitZMQ/inc/RooFit_ZMQ/Utility.h | 39 + .../roofitZMQ/inc/RooFit_ZMQ/ZeroMQPoller.h | 48 + roofit/roofitZMQ/inc/RooFit_ZMQ/ZeroMQSvc.h | 213 +++ roofit/roofitZMQ/inc/RooFit_ZMQ/functions.h | 111 ++ roofit/roofitZMQ/inc/RooFit_ZMQ/ppoll.h | 29 + roofit/roofitZMQ/inc/RooFit_ZMQ/zmq.hxx | 1138 +++++++++++++++++ roofit/roofitZMQ/src/ZeroMQPoller.cpp | 164 +++ roofit/roofitZMQ/src/ZeroMQSvc.cpp | 103 ++ roofit/roofitZMQ/src/functions.cpp | 9 + roofit/roofitZMQ/src/ppoll.cpp | 345 +++++ roofit/roofitZMQ/test/CMakeLists.txt | 10 + roofit/roofitZMQ/test/test_HWM.cxx | 130 ++ roofit/roofitZMQ/test/test_ZMQ.cpp | 266 ++++ .../test/test_ZMQ_load_balancing.cxx | 133 ++ roofit/roofitZMQ/test/test_polling.cxx | 149 +++ roofit/roofitcore/CMakeLists.txt | 98 +- roofit/roofitcore/inc/LinkDef.h | 6 +- .../{src => inc/MultiProcess}/BidirMMapPipe.h | 59 +- .../inc/MultiProcess/GradMinimizer.h | 71 + roofit/roofitcore/inc/MultiProcess/Job.h | 98 ++ roofit/roofitcore/inc/MultiProcess/NLLVar.h | 112 ++ .../roofitcore/inc/MultiProcess/TaskManager.h | 247 ++++ roofit/roofitcore/inc/MultiProcess/Vector.h | 182 +++ roofit/roofitcore/inc/MultiProcess/messages.h | 80 ++ roofit/roofitcore/inc/MultiProcess/util.h | 22 + .../inc/NumericalDerivatorMinuit2.h | 124 ++ roofit/roofitcore/inc/RooAbsData.h | 8 + roofit/roofitcore/inc/RooAbsMinimizerFcn.h | 130 ++ .../roofitcore/inc/RooAbsOptTestStatistic.h | 8 +- roofit/roofitcore/inc/RooAbsPdf.h | 8 +- roofit/roofitcore/inc/RooAbsReal.h | 3 +- roofit/roofitcore/inc/RooAbsTestStatistic.h | 22 +- roofit/roofitcore/inc/RooAddModel.h | 1 - roofit/roofitcore/inc/RooAddPdf.h | 1 - roofit/roofitcore/inc/RooAddition.h | 5 +- roofit/roofitcore/inc/RooCacheManager.h | 33 +- roofit/roofitcore/inc/RooDataHist.h | 7 + .../inc/{ => RooFitLegacy}/RooHashTable.h | 7 +- .../inc/{ => RooFitLegacy}/RooNameSet.h | 4 +- roofit/roofitcore/inc/RooFitResult.h | 5 +- roofit/roofitcore/inc/RooGaussMinimizer.h | 104 ++ roofit/roofitcore/inc/RooGaussMinimizerFcn.h | 112 ++ roofit/roofitcore/inc/RooGlobalFunc.h | 3 + roofit/roofitcore/inc/RooGradMinimizerFcn.h | 101 ++ roofit/roofitcore/inc/RooHelpers.h | 3 + roofit/roofitcore/inc/RooJsonListFile.h | 74 ++ roofit/roofitcore/inc/RooMinimizer.h | 350 +++-- roofit/roofitcore/inc/RooMinimizerFcn.h | 87 +- roofit/roofitcore/inc/RooNLLVar.h | 2 +- roofit/roofitcore/inc/RooNormSetCache.h | 10 +- roofit/roofitcore/inc/RooObjCacheManager.h | 2 - roofit/roofitcore/inc/RooProfileLL.h | 4 +- roofit/roofitcore/inc/RooRealIntegral.h | 6 + roofit/roofitcore/inc/RooRealMPFE.h | 51 +- roofit/roofitcore/inc/RooTaskSpec.h | 47 + roofit/roofitcore/inc/RooTimer.h | 49 + roofit/roofitcore/inc/RooTrace.h | 12 +- .../TestStatistics/LikelihoodGradientJob.h | 109 ++ .../LikelihoodGradientWrapper.h | 65 + .../inc/TestStatistics/LikelihoodJob.h | 74 ++ .../inc/TestStatistics/LikelihoodSerial.h | 53 + .../inc/TestStatistics/LikelihoodWrapper.h | 101 ++ .../inc/TestStatistics/MinuitFcnGrad.h | 168 +++ .../roofitcore/inc/TestStatistics/RooAbsL.h | 132 ++ .../inc/TestStatistics/RooBinnedL.h | 47 + .../roofitcore/inc/TestStatistics/RooRealL.h | 53 + .../inc/TestStatistics/RooSubsidiaryL.h | 52 + .../roofitcore/inc/TestStatistics/RooSumL.h | 47 + .../inc/TestStatistics/RooUnbinnedL.h | 50 + .../roofitcore/inc/TestStatistics/kahan_sum.h | 86 ++ .../inc/TestStatistics/likelihood_builders.h | 51 + .../inc/TestStatistics/optimization.h | 37 + .../TestStatistics/optional_parameter_types.h | 45 + .../src/{ => MultiProcess}/BidirMMapPipe.cxx | 178 ++- .../src/MultiProcess/GradMinimizerFcn.cxx | 232 ++++ roofit/roofitcore/src/MultiProcess/Job.cxx | 176 +++ roofit/roofitcore/src/MultiProcess/NLLVar.cxx | 225 ++++ .../src/MultiProcess/TaskManager.cxx | 622 +++++++++ .../roofitcore/src/MultiProcess/messages.cxx | 70 + roofit/roofitcore/src/MultiProcess/util.cxx | 62 + .../src/NumericalDerivatorMinuit2.cxx | 451 +++++++ roofit/roofitcore/src/RooAbsArg.cxx | 12 +- roofit/roofitcore/src/RooAbsMinimizerFcn.cxx | 539 ++++++++ .../roofitcore/src/RooAbsOptTestStatistic.cxx | 20 +- roofit/roofitcore/src/RooAbsPdf.cxx | 46 +- roofit/roofitcore/src/RooAbsReal.cxx | 31 +- roofit/roofitcore/src/RooAbsTestStatistic.cxx | 84 +- roofit/roofitcore/src/RooAddModel.cxx | 14 +- roofit/roofitcore/src/RooAddition.cxx | 4 +- roofit/roofitcore/src/RooChi2Var.cxx | 5 + .../src/{ => RooFitLegacy}/RooHashTable.cxx | 3 +- .../src/{ => RooFitLegacy}/RooNameSet.cxx | 7 +- roofit/roofitcore/src/RooGaussMinimizer.cxx | 275 ++++ .../roofitcore/src/RooGaussMinimizerFcn.cxx | 627 +++++++++ roofit/roofitcore/src/RooGlobalFunc.cxx | 8 +- roofit/roofitcore/src/RooGradMinimizerFcn.cxx | 368 ++++++ roofit/roofitcore/src/RooHelpers.cxx | 33 + roofit/roofitcore/src/RooHistFunc.cxx | 4 +- roofit/roofitcore/src/RooJsonListFile.cxx | 40 + roofit/roofitcore/src/RooLinkedList.cxx | 1 - roofit/roofitcore/src/RooMinimizer.cxx | 763 +++++------ roofit/roofitcore/src/RooMinimizerFcn.cxx | 482 +------ roofit/roofitcore/src/RooNLLVar.cxx | 1 + roofit/roofitcore/src/RooNormSetCache.cxx | 13 +- roofit/roofitcore/src/RooProdPdf.cxx | 23 +- roofit/roofitcore/src/RooProduct.cxx | 4 +- roofit/roofitcore/src/RooProjectedPdf.cxx | 21 +- roofit/roofitcore/src/RooRealIntegral.cxx | 78 +- roofit/roofitcore/src/RooRealMPFE.cxx | 1005 +++++++++++---- roofit/roofitcore/src/RooRealSumFunc.cxx | 6 +- roofit/roofitcore/src/RooRealSumPdf.cxx | 6 +- roofit/roofitcore/src/RooRealVar.cxx | 5 + roofit/roofitcore/src/RooTaskSpec.cxx | 122 ++ roofit/roofitcore/src/RooTimer.cxx | 48 + roofit/roofitcore/src/RooTrace.cxx | 29 + roofit/roofitcore/src/RooVectorDataStore.cxx | 17 +- .../TestStatistics/LikelihoodGradientJob.cxx | 446 +++++++ .../LikelihoodGradientWrapper.cxx | 43 + .../src/TestStatistics/LikelihoodJob.cxx | 254 ++++ .../src/TestStatistics/LikelihoodSerial.cxx | 129 ++ .../src/TestStatistics/LikelihoodWrapper.cxx | 170 +++ .../src/TestStatistics/MinuitFcnGrad.cxx | 339 +++++ .../roofitcore/src/TestStatistics/RooAbsL.cxx | 485 +++++++ .../src/TestStatistics/RooBinnedL.cxx | 172 +++ .../src/TestStatistics/RooRealL.cxx | 70 + .../src/TestStatistics/RooSubsidiaryL.cxx | 83 ++ .../roofitcore/src/TestStatistics/RooSumL.cxx | 91 ++ .../src/TestStatistics/RooUnbinnedL.cxx | 193 +++ .../src/TestStatistics/kahan_sum.cxx | 29 + .../TestStatistics/likelihood_builders.cxx | 315 +++++ .../src/TestStatistics/optimization.cxx | 163 +++ .../optional_parameter_types.cxx | 27 + roofit/roofitcore/test/CMakeLists.txt | 11 + .../roofitcore/test/MultiProcess/MPFEnll.cpp | 79 ++ .../test/MultiProcess/VectorGradMinimizer.cpp | 306 +++++ .../test/MultiProcess/VectorNLL.cpp | 395 ++++++ .../roofitcore/test/MultiProcess_Vector.cxx | 186 +++ roofit/roofitcore/test/RooGradMinimizer.cxx | 914 +++++++++++++ .../test/TestStatistics/RooRealL.cpp | 347 +++++ .../testLikelihoodGradientJob.cpp | 583 +++++++++ .../TestStatistics/testLikelihoodSerial.cxx | 442 +++++++ roofit/roofitcore/test/ULPdiff.h | 80 ++ roofit/roofitcore/test/testBidirMMapPipe.cxx | 793 ++++++++++++ .../roofitcore/test/testRooCacheManager.cxx | 62 + roofit/roofitcore/test/test_lib.h | 185 +++ test/Makefile.win32 | 123 +- tree/dataframe/inc/ROOT/RDFHelpers.hxx | 2 +- tree/dataframe/src/RDFUtils.cxx | 3 +- tree/dataframe/test/dataframe_helpers.cxx | 13 + tree/dataframe/test/dataframe_interface.cxx | 5 +- tree/dataframe/test/datasource_more.cxx | 1 + tree/tree/src/TTree.cxx | 4 +- tutorials/CMakeLists.txt | 1 + tutorials/image/hsumanim.C | 2 +- tutorials/math/exampleFunction.py | 6 +- 235 files changed, 23527 insertions(+), 2199 deletions(-) create mode 100644 documentation/doxygen/images/v7_rbrowser.png create mode 100644 math/minuit2/inc/Minuit2/ExternalInternalGradientCalculator.h create mode 100644 math/minuit2/src/ExternalInternalGradientCalculator.cxx create mode 100644 roofit/builtin_zeromq/CMakeLists.txt create mode 100644 roofit/multiprocess/CMakeLists.txt create mode 100644 roofit/multiprocess/inc/LinkDef.h create mode 100644 roofit/multiprocess/inc/RooFit/MultiProcess/Job.h create mode 100644 roofit/multiprocess/inc/RooFit/MultiProcess/JobManager.h create mode 100644 roofit/multiprocess/inc/RooFit/MultiProcess/Messenger.h create mode 100644 roofit/multiprocess/inc/RooFit/MultiProcess/Messenger_decl.h create mode 100644 roofit/multiprocess/inc/RooFit/MultiProcess/ProcessManager.h create mode 100644 roofit/multiprocess/inc/RooFit/MultiProcess/Queue.h create mode 100644 roofit/multiprocess/inc/RooFit/MultiProcess/types.h create mode 100644 roofit/multiprocess/inc/RooFit/MultiProcess/util.h create mode 100644 roofit/multiprocess/inc/RooFit/MultiProcess/worker.h create mode 100644 roofit/multiprocess/src/Job.cxx create mode 100644 roofit/multiprocess/src/JobManager.cxx create mode 100644 roofit/multiprocess/src/Messenger.cxx create mode 100644 roofit/multiprocess/src/ProcessManager.cxx create mode 100644 roofit/multiprocess/src/Queue.cxx create mode 100644 roofit/multiprocess/src/util.cxx create mode 100644 roofit/multiprocess/src/worker.cxx create mode 100644 roofit/multiprocess/test/CMakeLists.txt create mode 100644 roofit/multiprocess/test/test_Job.cxx create mode 100644 roofit/multiprocess/test/test_JobManager.cxx create mode 100644 roofit/multiprocess/test/test_Messenger.cxx create mode 100644 roofit/multiprocess/test/test_ProcessManager.cxx create mode 100644 roofit/multiprocess/test/test_Queue.cxx create mode 100644 roofit/multiprocess/test/test_util.cxx create mode 100644 roofit/multiprocess/test/test_worker.cxx create mode 100644 roofit/multiprocess/test/utils.h create mode 100644 roofit/roofitZMQ/CMakeLists.txt create mode 100644 roofit/roofitZMQ/inc/LinkDef.h create mode 100644 roofit/roofitZMQ/inc/RooFit_ZMQ/Utility.h create mode 100644 roofit/roofitZMQ/inc/RooFit_ZMQ/ZeroMQPoller.h create mode 100644 roofit/roofitZMQ/inc/RooFit_ZMQ/ZeroMQSvc.h create mode 100644 roofit/roofitZMQ/inc/RooFit_ZMQ/functions.h create mode 100644 roofit/roofitZMQ/inc/RooFit_ZMQ/ppoll.h create mode 100644 roofit/roofitZMQ/inc/RooFit_ZMQ/zmq.hxx create mode 100644 roofit/roofitZMQ/src/ZeroMQPoller.cpp create mode 100644 roofit/roofitZMQ/src/ZeroMQSvc.cpp create mode 100644 roofit/roofitZMQ/src/functions.cpp create mode 100644 roofit/roofitZMQ/src/ppoll.cpp create mode 100644 roofit/roofitZMQ/test/CMakeLists.txt create mode 100644 roofit/roofitZMQ/test/test_HWM.cxx create mode 100644 roofit/roofitZMQ/test/test_ZMQ.cpp create mode 100644 roofit/roofitZMQ/test/test_ZMQ_load_balancing.cxx create mode 100644 roofit/roofitZMQ/test/test_polling.cxx rename roofit/roofitcore/{src => inc/MultiProcess}/BidirMMapPipe.h (95%) create mode 100644 roofit/roofitcore/inc/MultiProcess/GradMinimizer.h create mode 100644 roofit/roofitcore/inc/MultiProcess/Job.h create mode 100644 roofit/roofitcore/inc/MultiProcess/NLLVar.h create mode 100644 roofit/roofitcore/inc/MultiProcess/TaskManager.h create mode 100644 roofit/roofitcore/inc/MultiProcess/Vector.h create mode 100644 roofit/roofitcore/inc/MultiProcess/messages.h create mode 100644 roofit/roofitcore/inc/MultiProcess/util.h create mode 100644 roofit/roofitcore/inc/NumericalDerivatorMinuit2.h create mode 100644 roofit/roofitcore/inc/RooAbsMinimizerFcn.h rename roofit/roofitcore/inc/{ => RooFitLegacy}/RooHashTable.h (96%) rename roofit/roofitcore/inc/{ => RooFitLegacy}/RooNameSet.h (94%) create mode 100644 roofit/roofitcore/inc/RooGaussMinimizer.h create mode 100644 roofit/roofitcore/inc/RooGaussMinimizerFcn.h create mode 100644 roofit/roofitcore/inc/RooGradMinimizerFcn.h create mode 100644 roofit/roofitcore/inc/RooJsonListFile.h create mode 100644 roofit/roofitcore/inc/RooTaskSpec.h create mode 100644 roofit/roofitcore/inc/RooTimer.h create mode 100644 roofit/roofitcore/inc/TestStatistics/LikelihoodGradientJob.h create mode 100644 roofit/roofitcore/inc/TestStatistics/LikelihoodGradientWrapper.h create mode 100644 roofit/roofitcore/inc/TestStatistics/LikelihoodJob.h create mode 100644 roofit/roofitcore/inc/TestStatistics/LikelihoodSerial.h create mode 100644 roofit/roofitcore/inc/TestStatistics/LikelihoodWrapper.h create mode 100644 roofit/roofitcore/inc/TestStatistics/MinuitFcnGrad.h create mode 100644 roofit/roofitcore/inc/TestStatistics/RooAbsL.h create mode 100644 roofit/roofitcore/inc/TestStatistics/RooBinnedL.h create mode 100644 roofit/roofitcore/inc/TestStatistics/RooRealL.h create mode 100644 roofit/roofitcore/inc/TestStatistics/RooSubsidiaryL.h create mode 100644 roofit/roofitcore/inc/TestStatistics/RooSumL.h create mode 100644 roofit/roofitcore/inc/TestStatistics/RooUnbinnedL.h create mode 100644 roofit/roofitcore/inc/TestStatistics/kahan_sum.h create mode 100644 roofit/roofitcore/inc/TestStatistics/likelihood_builders.h create mode 100644 roofit/roofitcore/inc/TestStatistics/optimization.h create mode 100644 roofit/roofitcore/inc/TestStatistics/optional_parameter_types.h rename roofit/roofitcore/src/{ => MultiProcess}/BidirMMapPipe.cxx (94%) create mode 100644 roofit/roofitcore/src/MultiProcess/GradMinimizerFcn.cxx create mode 100644 roofit/roofitcore/src/MultiProcess/Job.cxx create mode 100644 roofit/roofitcore/src/MultiProcess/NLLVar.cxx create mode 100644 roofit/roofitcore/src/MultiProcess/TaskManager.cxx create mode 100644 roofit/roofitcore/src/MultiProcess/messages.cxx create mode 100644 roofit/roofitcore/src/MultiProcess/util.cxx create mode 100644 roofit/roofitcore/src/NumericalDerivatorMinuit2.cxx create mode 100644 roofit/roofitcore/src/RooAbsMinimizerFcn.cxx rename roofit/roofitcore/src/{ => RooFitLegacy}/RooHashTable.cxx (99%) rename roofit/roofitcore/src/{ => RooFitLegacy}/RooNameSet.cxx (99%) create mode 100644 roofit/roofitcore/src/RooGaussMinimizer.cxx create mode 100644 roofit/roofitcore/src/RooGaussMinimizerFcn.cxx create mode 100644 roofit/roofitcore/src/RooGradMinimizerFcn.cxx create mode 100644 roofit/roofitcore/src/RooJsonListFile.cxx create mode 100644 roofit/roofitcore/src/RooTaskSpec.cxx create mode 100644 roofit/roofitcore/src/RooTimer.cxx create mode 100644 roofit/roofitcore/src/TestStatistics/LikelihoodGradientJob.cxx create mode 100644 roofit/roofitcore/src/TestStatistics/LikelihoodGradientWrapper.cxx create mode 100644 roofit/roofitcore/src/TestStatistics/LikelihoodJob.cxx create mode 100644 roofit/roofitcore/src/TestStatistics/LikelihoodSerial.cxx create mode 100644 roofit/roofitcore/src/TestStatistics/LikelihoodWrapper.cxx create mode 100644 roofit/roofitcore/src/TestStatistics/MinuitFcnGrad.cxx create mode 100644 roofit/roofitcore/src/TestStatistics/RooAbsL.cxx create mode 100644 roofit/roofitcore/src/TestStatistics/RooBinnedL.cxx create mode 100644 roofit/roofitcore/src/TestStatistics/RooRealL.cxx create mode 100644 roofit/roofitcore/src/TestStatistics/RooSubsidiaryL.cxx create mode 100644 roofit/roofitcore/src/TestStatistics/RooSumL.cxx create mode 100644 roofit/roofitcore/src/TestStatistics/RooUnbinnedL.cxx create mode 100644 roofit/roofitcore/src/TestStatistics/kahan_sum.cxx create mode 100644 roofit/roofitcore/src/TestStatistics/likelihood_builders.cxx create mode 100644 roofit/roofitcore/src/TestStatistics/optimization.cxx create mode 100644 roofit/roofitcore/src/TestStatistics/optional_parameter_types.cxx create mode 100644 roofit/roofitcore/test/MultiProcess/MPFEnll.cpp create mode 100644 roofit/roofitcore/test/MultiProcess/VectorGradMinimizer.cpp create mode 100644 roofit/roofitcore/test/MultiProcess/VectorNLL.cpp create mode 100644 roofit/roofitcore/test/MultiProcess_Vector.cxx create mode 100644 roofit/roofitcore/test/RooGradMinimizer.cxx create mode 100644 roofit/roofitcore/test/TestStatistics/RooRealL.cpp create mode 100644 roofit/roofitcore/test/TestStatistics/testLikelihoodGradientJob.cpp create mode 100644 roofit/roofitcore/test/TestStatistics/testLikelihoodSerial.cxx create mode 100644 roofit/roofitcore/test/ULPdiff.h create mode 100644 roofit/roofitcore/test/testBidirMMapPipe.cxx create mode 100644 roofit/roofitcore/test/testRooCacheManager.cxx create mode 100644 roofit/roofitcore/test/test_lib.h diff --git a/.travis.yml b/.travis.yml index 8041119e0bef9..51ca5c1dd6a24 100644 --- a/.travis.yml +++ b/.travis.yml @@ -61,5 +61,5 @@ matrix: # echo "run-clang-tidy-3.9.py -j4 -clang-tidy-binary $(which clang-tidy-3.9) -checks=-*,clang-analyzer-* $FILES_REGEX" # run-clang-tidy-3.9.py -j4 -clang-tidy-binary $(which clang-tidy-3.9) -checks=-*,clang-analyzer-* $FILES_REGEX # fi - + on_failure: echo "Showing current directory contents" && ls -la diff --git a/CMakeLists.txt b/CMakeLists.txt index 9843f76e37202..d35dbef8fb0c6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -291,7 +291,7 @@ if(cxxmodules) # including still manages to respect NDEBUG properly. CHECK_CXX_SOURCE_COMPILES("#undef NDEBUG #include - #define NDEBUG + #define NEBUG #include int main() { assert(this code is not compiled); }" CXX_SUPPORTS_MODULES) diff --git a/bindings/jupyroot/CMakeLists.txt b/bindings/jupyroot/CMakeLists.txt index 6205226680eab..8510b4db10b14 100644 --- a/bindings/jupyroot/CMakeLists.txt +++ b/bindings/jupyroot/CMakeLists.txt @@ -52,16 +52,7 @@ foreach(val RANGE ${how_many_pythons}) target_link_libraries(${libname} PUBLIC -Wl,--unresolved-symbols=ignore-all Core) endif() - target_include_directories(${libname} PRIVATE ${python_include_dir}) - - # Disables warnings originating from deprecated register keyword in Python - if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" AND CMAKE_CXX_STANDARD GREATER_EQUAL 11) - target_compile_options(${libname} PRIVATE -Wno-register) - endif() - if("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang" AND CMAKE_CXX_STANDARD GREATER_EQUAL 11) - target_compile_options(${libname} PRIVATE -Wno-register) - target_compile_options(${libname} PRIVATE -Wno-deprecated-register) - endif() + target_include_directories(${libname} SYSTEM PRIVATE ${python_include_dir}) # Compile .py files foreach(py_source ${py_sources}) diff --git a/bindings/pyroot/cppyy/CPyCppyy/CMakeLists.txt b/bindings/pyroot/cppyy/CPyCppyy/CMakeLists.txt index d8bf461350d3a..2144d97d580bb 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/CMakeLists.txt +++ b/bindings/pyroot/cppyy/CPyCppyy/CMakeLists.txt @@ -82,26 +82,19 @@ foreach(val RANGE ${how_many_pythons}) target_compile_options(${libname} PRIVATE -Wno-cast-function-type) endif() - # Disables warnings originating from deprecated register keyword in Python - if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" AND CMAKE_CXX_STANDARD GREATER_EQUAL 11) - target_compile_options(${libname} PRIVATE -Wno-register) - endif() - if("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang" AND CMAKE_CXX_STANDARD GREATER_EQUAL 11) - target_compile_options(${libname} PRIVATE -Wno-register) - target_compile_options(${libname} PRIVATE -Wno-deprecated-register) - endif() - # Disables warnings due to new field tp_vectorcall in Python 3.8 if(NOT MSVC AND ${python_version_string} VERSION_GREATER_EQUAL "3.8") target_compile_options(${libname} PRIVATE -Wno-missing-field-initializers) endif() + target_include_directories(${libname} + SYSTEM PUBLIC ${python_include_dir}) + target_include_directories(${libname} PRIVATE ${CMAKE_SOURCE_DIR}/core/foundation/inc # needed for string_view backport ${CMAKE_BINARY_DIR}/ginclude PUBLIC - ${python_include_dir} $ $ ) diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/Pythonize.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/Pythonize.cxx index 92c48f01286a3..f587f41907db3 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/Pythonize.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/Pythonize.cxx @@ -653,19 +653,38 @@ PyObject* MapContains(PyObject* self, PyObject* obj) return result; } + //- STL container iterator support -------------------------------------------- +static const ptrdiff_t PS_END_ADDR = 7; // non-aligned address, so no clash +static const ptrdiff_t PS_FLAG_ADDR = 11; // id. +static const ptrdiff_t PS_COLL_ADDR = 13; // id. + PyObject* StlSequenceIter(PyObject* self) { // Implement python's __iter__ for std::iterator<>s PyObject* iter = PyObject_CallMethodObjArgs(self, PyStrings::gBegin, nullptr); if (iter) { PyObject* end = PyObject_CallMethodObjArgs(self, PyStrings::gEnd, nullptr); - if (end) - PyObject_SetAttr(iter, PyStrings::gEnd, end); - Py_XDECREF(end); - - // add iterated collection as attribute so its refcount stays >= 1 while it's being iterated over - PyObject_SetAttr(iter, CPyCppyy_PyText_FromString("_collection"), self); + if (end) { + if (CPPInstance_Check(iter)) { + // use the data member cache to store extra state on the iterator object, + // without it being visible on the Python side + auto& dmc = ((CPPInstance*)iter)->GetDatamemberCache(); + dmc.push_back(std::make_pair(PS_END_ADDR, end)); + + // set a flag, indicating first iteration (reset in __next__) + Py_INCREF(Py_False); + dmc.push_back(std::make_pair(PS_FLAG_ADDR, Py_False)); + + // make sure the iterated over collection remains alive for the duration + Py_INCREF(self); + dmc.push_back(std::make_pair(PS_COLL_ADDR, self)); + } else { + // could store "end" on the object's dictionary anyway, but if end() returns + // a user-customized object, then its __next__ is probably custom, too + Py_DECREF(end); + } + } } return iter; } @@ -856,44 +875,47 @@ Py_hash_t StlStringHash(PyObject* self) PyObject* StlIterNext(PyObject* self) { // Python iterator protocol __next__ for STL forward iterators. - PyObject* next = nullptr; - PyObject* last = PyObject_GetAttr(self, PyStrings::gEnd); + bool mustIncrement = true; + PyObject* last = nullptr; + if (CPPInstance_Check(self)) { + auto& dmc = ((CPPInstance*)self)->GetDatamemberCache(); + for (auto& p: dmc) { + if (p.first == PS_END_ADDR) { + last = p.second; + Py_INCREF(last); + } else if (p.first == PS_FLAG_ADDR) { + mustIncrement = p.second == Py_True; + if (!mustIncrement) { + Py_DECREF(p.second); + Py_INCREF(Py_True); + p.second = Py_True; + } + } + } + } + PyObject* next = nullptr; if (last) { // handle special case of empty container (i.e. self is end) - if (PyObject_RichCompareBool(last, self, Py_EQ) == 0) { - // first, get next from the _current_ iterator as internal state may change - // when call post or pre increment - next = PyObject_CallMethodObjArgs(self, PyStrings::gDeref, nullptr); - if (!next) PyErr_Clear(); - - // use postinc, even as the C++11 range-based for loops prefer preinc b/c - // that allows the current value from the iterator to be had from __deref__, - // an issue that does not come up in C++ - static PyObject* dummy = PyInt_FromLong(1l); - PyObject* iter = PyObject_CallMethodObjArgs(self, PyStrings::gPostInc, dummy, nullptr); - if (!iter) { - // allow preinc, as in that case likely __deref__ is not defined and it - // is the iterator rather that is returned in the loop - PyErr_Clear(); - iter = PyObject_CallMethodObjArgs(self, PyStrings::gPreInc, nullptr); - } - if (iter) { - // prefer != as per C++11 range-based for - int isNotEnd = PyObject_RichCompareBool(last, iter, Py_NE); - if (isNotEnd && !next) { - // if no dereference, continue iterating over the iterator - Py_INCREF(iter); - next = iter; + if (!PyObject_RichCompareBool(last, self, Py_EQ)) { + bool iter_valid = true; + if (mustIncrement) { + // prefer preinc, but allow post-inc; in both cases, it is "self" that has + // the updated state to dereference + PyObject* iter = PyObject_CallMethodObjArgs(self, PyStrings::gPreInc, nullptr); + if (!iter) { + PyErr_Clear(); + static PyObject* dummy = PyInt_FromLong(1l); + iter = PyObject_CallMethodObjArgs(self, PyStrings::gPostInc, dummy, nullptr); } - Py_DECREF(iter); - } else { - // fail current next, even if available - Py_XDECREF(next); - next = nullptr; + iter_valid = iter && PyObject_RichCompareBool(last, self, Py_NE); + Py_XDECREF(iter); + } + + if (iter_valid) { + next = PyObject_CallMethodObjArgs(self, PyStrings::gDeref, nullptr); + if (!next) PyErr_Clear(); } - } else { - PyErr_SetString(PyExc_StopIteration, ""); } Py_DECREF(last); } diff --git a/bindings/pyroot/pythonizations/CMakeLists.txt b/bindings/pyroot/pythonizations/CMakeLists.txt index 1f5874a16ec4a..94db56b215835 100644 --- a/bindings/pyroot/pythonizations/CMakeLists.txt +++ b/bindings/pyroot/pythonizations/CMakeLists.txt @@ -111,23 +111,16 @@ foreach(val RANGE ${how_many_pythons}) endif() target_include_directories(${libname} - PRIVATE ${python_include_dir} - PUBLIC $) + SYSTEM PRIVATE ${python_include_dir}) + + target_include_directories(${libname} + PUBLIC $) # Disables warnings caused by Py_RETURN_TRUE/Py_RETURN_FALSE if(NOT MSVC) target_compile_options(${libname} PRIVATE -Wno-strict-aliasing) endif() - # Disables warnings originating from deprecated register keyword in Python - if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" AND CMAKE_CXX_STANDARD GREATER_EQUAL 11) - target_compile_options(${libname} PRIVATE -Wno-register) - endif() - if("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang" AND CMAKE_CXX_STANDARD GREATER_EQUAL 11) - target_compile_options(${libname} PRIVATE -Wno-register) - target_compile_options(${libname} PRIVATE -Wno-deprecated-register) - endif() - # Compile .py files foreach(py_source ${py_sources}) install(CODE "execute_process(COMMAND ${python_executable} -m py_compile ${localruntimedir}/${py_source})") diff --git a/core/base/src/TAttFill.cxx b/core/base/src/TAttFill.cxx index 57cdfbeff70aa..dfdd2e004684d 100644 --- a/core/base/src/TAttFill.cxx +++ b/core/base/src/TAttFill.cxx @@ -32,10 +32,10 @@ attributes. ## Fill Area attributes Fill Area attributes are: - - [Fill Area color](\ref F1) - - [Fill Area style](\ref F2) + - [Fill Area color](\ref ATTFILL1) + - [Fill Area style](\ref ATTFILL2) -\anchor F1 +\anchor ATTFILL1 ## Fill Area color The fill area color is a color index (integer) pointing in the ROOT color table. @@ -104,7 +104,7 @@ If the current style fill area color is set to 0, then ROOT will force a black&white output for all objects with a fill area defined and independently of the object fill style. -\anchor F2 +\anchor ATTFILL2 ## Fill Area style The fill area style defines the pattern used to fill a polygon. The fill area style of any class inheriting from `TAttFill` can diff --git a/core/base/src/TAttLine.cxx b/core/base/src/TAttLine.cxx index ea8fd7b872ae6..2a5cd3e8dd5ac 100644 --- a/core/base/src/TAttLine.cxx +++ b/core/base/src/TAttLine.cxx @@ -33,11 +33,11 @@ by many other classes (graphics, histograms). It holds all the line attributes. ## Line attributes Line attributes are: - - [Line Color](\ref L1) - - [Line Width](\ref L2) - - [Line Style](\ref L3) + - [Line Color](\ref ATTLINE1) + - [Line Width](\ref ATTLINE2) + - [Line Style](\ref ATTLINE3) -\anchor L1 +\anchor ATTLINE1 ## Line Color The line color is a color index (integer) pointing in the ROOT color table. @@ -69,7 +69,7 @@ in `$ROOTSYS/etc/system.rootrc`, or on Mac with the Cocoa backend. On the file o it is visible with PDF, PNG, Gif, JPEG, SVG, TeX ... but not PostScript. -\anchor L2 +\anchor ATTLINE2 ## Line Width The line width is expressed in pixel units. The line width of any class inheriting from `TAttLine` can @@ -93,7 +93,7 @@ Begin_Macro } End_Macro -\anchor L3 +\anchor ATTLINE3 ## Line Style Line styles are identified via integer numbers. The line style of any class inheriting from `TAttLine` can be changed using the method diff --git a/core/base/src/TAttMarker.cxx b/core/base/src/TAttMarker.cxx index 50a99968e1397..481cb6c63e467 100644 --- a/core/base/src/TAttMarker.cxx +++ b/core/base/src/TAttMarker.cxx @@ -33,12 +33,12 @@ attributes. ## Marker attributes The marker attributes are: - - [Marker color](\ref M1) - - [Marker style](\ref M2) - - [Marker line width](\ref M21) + - [Marker color](\ref ATTMARKER1) + - [Marker style](\ref ATTMARKER2) + - [Marker line width](\ref ATTMARKER21) - [Marker size](\ref M3) -\anchor M1 +\anchor ATTMARKER1 ## Marker color The marker color is a color index (integer) pointing in the ROOT color table. @@ -70,7 +70,7 @@ The transparency is available on all platforms when the flag `OpenGL.CanvasPrefe in `$ROOTSYS/etc/system.rootrc`, or on Mac with the Cocoa backend. On the file output it is visible with PDF, PNG, Gif, JPEG, SVG, TeX ... but not PostScript. -\anchor M2 +\anchor ATTMARKER2 ## Marker style The Marker style defines the markers' shape. @@ -134,7 +134,7 @@ Begin_Macro } End_Macro -\anchor M21 +\anchor ATTMARKER21 ### Marker line width The line width of a marker is not actually a marker attribute since it does diff --git a/core/base/src/TAttText.cxx b/core/base/src/TAttText.cxx index 57829460bdb75..9f2ab52d95ba7 100644 --- a/core/base/src/TAttText.cxx +++ b/core/base/src/TAttText.cxx @@ -34,16 +34,16 @@ by many other classes (graphics, histograms). It holds all the text attributes. ## Text attributes Text attributes are: - - [Text Alignment](\ref T1) - - [Text Angle](\ref T2) - - [Text Color](\ref T3) - - [Text Size](\ref T4) - - [Text Font and Precision](\ref T5) - - [Font quality and speed](\ref T51) - - [How to use True Type Fonts](\ref T52) - - [List of the currently supported fonts](\ref T53) - -\anchor T1 + - [Text Alignment](\ref ATTTEXT1) + - [Text Angle](\ref ATTTEXT2) + - [Text Color](\ref ATTTEXT3) + - [Text Size](\ref ATTTEXT4) + - [Text Font and Precision](\ref ATTTEXT5) + - [Font quality and speed](\ref ATTTEXT51) + - [How to use True Type Fonts](\ref ATTTEXT52) + - [List of the currently supported fonts](\ref ATTTEXT53) + +\anchor ATTTEXT1 ## Text Alignment The text alignment is an integer number (`align`) allowing to control @@ -93,7 +93,7 @@ They allow to write: object->SetTextAlign(kHAlignLeft+kVAlignTop); ~~~ -\anchor T2 +\anchor ATTTEXT2 ## Text Angle Text angle in degrees. @@ -106,7 +106,7 @@ Begin_Macro(source) textangle.C End_Macro -\anchor T3 +\anchor ATTTEXT3 ## Text Color The text color is a color index (integer) pointing in the ROOT @@ -138,7 +138,7 @@ The transparency is available on all platforms when the flag `OpenGL.CanvasPrefe in `$ROOTSYS/etc/system.rootrc`, or on Mac with the Cocoa backend. On the file output it is visible with PDF, PNG, Gif, JPEG, SVG, TeX ... but not PostScript. -\anchor T4 +\anchor ATTTEXT4 ## Text Size If the text precision (see next paragraph) is smaller than 3, the text @@ -169,7 +169,7 @@ The text size of any class inheriting from `TAttText` can be changed using the method `SetTextSize` and retrieved using the method `GetTextSize`. -\anchor T5 +\anchor ATTTEXT5 ## Text Font and Precision The text font code is combination of the font number and the precision. @@ -190,7 +190,7 @@ The text font and precision of any class inheriting from `TAttText` can be changed using the method `SetTextFont` and retrieved using the method `GetTextFont`. -\anchor T51 +\anchor ATTTEXT51 ### Font quality and speed When precision 0 is used, only the original non-scaled system fonts are @@ -201,7 +201,7 @@ Precision 1 and 2 fonts have a different behaviour depending if the True Type Fonts (TTF) are used or not. If TTF are used, you always get very good quality scalable and rotatable fonts. However TTF are slow. -\anchor T52 +\anchor ATTTEXT52 ### How to use True Type Fonts One can activate the TTF by adding (or activating) the following line @@ -225,7 +225,7 @@ printout given by this command: Unix.*.Root.UseTTFonts: true [Global] ~~~ -\anchor T53 +\anchor ATTTEXT53 ### List of the currently supported fonts ~~~ {.cpp} diff --git a/core/foundation/inc/ROOT/RConfig.hxx b/core/foundation/inc/ROOT/RConfig.hxx index 3ae236eba00ed..484eea701ffb5 100644 --- a/core/foundation/inc/ROOT/RConfig.hxx +++ b/core/foundation/inc/ROOT/RConfig.hxx @@ -531,9 +531,9 @@ /*---- misc ------------------------------------------------------------------*/ #ifdef R__GNU -# define SafeDelete(p) { if (p) { delete p; p = 0; } } +# define SafeDelete(p) { if (p) { delete p; p = nullptr; } } #else -# define SafeDelete(p) { delete p; p = 0; } +# define SafeDelete(p) { delete p; p = nullptr; } #endif #ifdef __FAST_MATH__ diff --git a/core/gui/src/TBrowser.cxx b/core/gui/src/TBrowser.cxx index 11ca682f4f6fc..9a1d4af5d9c39 100644 --- a/core/gui/src/TBrowser.cxx +++ b/core/gui/src/TBrowser.cxx @@ -20,6 +20,15 @@ will place all browsable objects in a new list and draws the contents of the selected class in the icon-box. And so on.... \image html base_browser.png + +\since **ROOT version 6.24/00** + +TBrowser invokes by default the Web-based %ROOT file browser RBrowser +To change this behaviour, and invoke the standard TBrowser, one should put +the following directive in the `.rootrc` file: +``` +Browser.Name: TRootBrowser +``` */ #include "TBrowser.h" diff --git a/core/thread/src/TThread.cxx b/core/thread/src/TThread.cxx index b8c7d090c2732..42e7c1d3cd012 100644 --- a/core/thread/src/TThread.cxx +++ b/core/thread/src/TThread.cxx @@ -947,7 +947,10 @@ void TThread::Printf(const char *va_(fmt), ...) void *arr[2]; arr[1] = (void*) buf; - if (XARequest("PRTF", 2, arr, 0)) return; + if (XARequest("PRTF", 2, arr, 0)) { + delete [] buf; + return; + } printf("%s\n", buf); fflush(stdout); diff --git a/documentation/doxygen/images/v7_rbrowser.png b/documentation/doxygen/images/v7_rbrowser.png new file mode 100644 index 0000000000000000000000000000000000000000..93c81d8612d049f35d64d062d03e3f099109392b GIT binary patch literal 122602 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

XuCCEg z3LHALABhC^FMq)4pkpr`##BohvBU(^5%Cn@4Kf>2WqKojJ#bn*Heha#XD>7cCX>11 z2Ca)sPf=86!xVYQZ#i_6ofi*(FqQtP-<@X$~lAjPrvAwn6du6 z*XT6ps|%@*oOkHERtk}0q*mqfE3|mc;DJ}P)N|h`@B0)g|L$V@5qJ zG830>+K>%xv$LpN$)OLzHhqnrnC~i-I~>SPS6F`L^7iH4K#(Kd{JEv|Q@h8%uZ_k{JpFjvbL&^lGX2_mQN3 zI#}+#$9HawEjY@h`wZ!gdyKauM0E^V15_b8Z->gY{Ggj$r`6o^IL7#u!dS}OqI@Kr z9{%NepPjwK?+Wgn`T9-5ev!A4YiA14?!YP{g>+Yx8_DzJmFwbSr4?SNzN3YIqdtS`QPZZ*Zg7ao80l zLlK58b2ALi>KSfI`4tUDeV`wu;+@b`Axouv>}c8usZRP9kT3?kv8ofS?PD}XGz;ab z?)&>XAzYLHu`YmD#(*72-3Z)R;B@V&4$lP*ar8xa@+seIz~ z&;v04)amwOtxEt_Asc(!DMW2G{;Bg8O$~dC>#TB)l%5*=qi*Mr|KWdKdIMlgL?WwH zLuNys+DSHGaFW3KJQ}eUUV}3{!1CLqI#F3Brl!0^2ol|AxOjtFVfAo<3>(lZbN0y| z2=|FcmE*cUYcqQ5?T+A18oin4m)Eb%2?OyTEmxc-cBiHLj|athpTzhxdG)SjbICQj zEXe8>E1$NW2H@SixceR9AQ#^Cf@@~qZ>)XhX^mH*odmm(>>5ud1b+@#xF{i4^=e$% z2{FEoz3@jtXbB@#r~-{`d?-(V5=3uXt@l)PG<*$ZBwZdL=WN_ez*ZF2Uu(|vspqPuMPXa}`)t5zx8B)dltj;j8=0fPsnjg5Ar#p7Se)<^6eGTz zLw#wBzsAa0GKbE)cz=Q0_1FhYyH{mVg&vpt?>OO(%i~C0&EG-C`bnfK8J8)F)-$c+ zIn?Uk=Zy%nvncDT7D;1V$)on{h4x=eqJS%`sOFo9{YHuM;LQS|r@7#*;4V68Vl9x+ zJK*>`VM43?SmPZ1pSZ;k3`wg?bt`|{SYsXF9ED6i1-xN9Z)5IiTzwk8nv%t;vO9al ziX+j$ONT`l+~wK=TH)Qks)mR-%?Be4OgR&$zf!6dDg&k3W7N{wGW0Z^oi=_v__1s& zfRC=+$mpnd=`(kc&VNH(#j8sj-QV@V9xv?2??>*1Ui$0fYI#w?H=cH5fsz)#iwTd9 zKn=;d5@3QS_kj_Pu7`j&&HV~*-vdl-L0FbSGMFtlb_^MwZ>$n-pFS`<@bmEhnJEj{Jbse!iMwX@oZV}UcE{hKZR>v(F!o+w zD+@wb>n_+*97A#PUz;|t*`Y`D$!yX4AFsbbW!IH;-_nw0NpF8-%ikYZEhQ=?MG zi-TvP#0nJ>fm5(1_|*y^Q`lgZz)u0h)Jd;oP)rQMND!auKUB|2O&yCFS?Uy+X_>QS z<6;{DOOnWR9DY#ng?$*@evFD5bH?aV_iaI$gMUFlIrbcjCY82+k9Kv2KV7W9ZrlN2SFS|u0&%@5~_QK zb|hhJGVo&pcgThlFzq}}u(OG$fv&nSeWMk+Bh5FP8O9<{C1*iD_;hcHE+p-FK}IOG z@T71CryV*|Fba-Qn?SZUd2FL48w-r2*EZiOJC)s>BV0#TtMza zn^WJM6>`^{f0h%~Xb)|=?)nzBB~cIyVDhqR(%SUd`fot^^mW={AO+$kteE zd|jg{?61NJN-LmPfv_|MoPos|9W2F4S*hhqFmAk#92fO)SCn&aaA5-1{a-~vC8jdc ztDMDRG!2VfzFaoar1CQ-I-<<@QWb*#o{VMn(FGrQ3k@@!!3rW!BSMpDxDU`45rrs^ zlenIoog;hHC;tG64e;XD>=;uVa~fkak1hoXN^uk4e>iG**`7T(e;W2>@~;QJZl#ds z2SqP}H4n!9nx3*j0cIESO{c&XcpV2iX0lpgX$J(0bW?JYal)1ceX5O{d{Vs}B_u6J zGC4w85mu)2ral=tRaIip;nM4_7h8ZVLwMhxLO(pm*ws129#=U-n=}xpA!^5Or`Tj( zk$@kdxl>7zHQ@$6_Q+;ph?***R(jMd?VMuJkvQ?KdTSBHM~6<&d>O0-s$3%bv9{D| zZp^`k@gb@HK3Tn$LEsj<t4EoRBLJwjC=iwm zPyA`=47WjGZ9dKjG(JMPHh%UI>`AB@#|s1CvvvwZtcU8Xk>-F1@e{cx44R7iC>)u50SjeWWlDPu-i)4*l(hrjC2Yxm0c?(H% znGlo%1xK1)nF`hdyr)@6OOh{BMkGF!Mh5ejpY9$Ek`ZaKfSuC*RiZ&xQbwDIR#W{> zfLPoah}IuEfAwCyRyx+hqodennMv9zV4z$P*A`Z1%t?a?+CqVoEu}dno{{k`M4Q}B zch;xNMp{rzA-;a0A*lkUFofy?Cv5AoZ50cdC*u+kUn!x3n;lxrEGmz(~q z#7epF%0{iiTWkY&%>E}gVV|NL&RmM#iSH1j_Zl_^ti;}ikgLw;oA#cOLx1l&#~(Yt zXZyliircq-s*=L(9)$EAT9!B33KCLx*8C$!r>9Qj<8om`m$TpQf%0x_X+Hlc@RTsy zvI<<04y*(VC!ZB5`0@%}e^xlisQ^0Q9^VNsG#)DJPgQU*eRse5GjaFm?W?NqKCNakF76YNmNsE*a+h*T#f_;1BHOdw+W1f^qrdQs3|K35b3DlRzbmBkOW)DHMAU zg%+j7I7;>#<#Ald4d8N1k}x$KAY{uz-aD7{8!=9T^ww6 zUu}%489I(U`zBS!oD7bA6D{o-oRSrn7m+F{lp&Zde8*62Dw`=T^7N}LQ$L!m65VN>!ks_};;a?~?d1UN6cw$&{}u6ag9OvHut&MAhX~8J5}Ebv+Qlb}C9Y zOFuZpgs@af?3Z8jN>IaKwRzpE^9kQ5K_S=p2#p&4Djxq~ z<6QyHFk!b+?F1=$p_ROaZvEi<*mx2@g9w36--M-gxLUZp`U@9tKiRrgyVVzaKqwsB zo9?I5CQsKPFgUu;#WA+xm}ou>o}epMu;#-tT4ka2`yll`WBc0=QrA7&3zrJ|HFuyV z)PC?Bo**tLjt^se%JYrI|Br7wRFtARB|}lT3^cncu&ERbRZHqtXe2zkB}o989t;ue z!7jw@HhpYWk}O%+UCFw%lj%vMO4 z^4i4?s6_+Bb)-a@?bQUAC}Fs?F0Cnj9bxP{9Bw7}2Bp^-bN;gf{=~z8@qQniyZ(qV zSjN(7NYJEsb6_HPYB2tu{;9R~`~B{ur@Z8UIp*QD93KV1F&QsY2A%LK46kWAT-}rg z*OUR`eoD0}$)IjHIiW`gAV4B*a#>^DpI?IqP(#TH_b<^;brCkn>Q7&GBV{OPpL!|w z1#UK}qrOv6&H~P`nesGd7&~?qjJ}TLm-*y_!6whY*6ZvX+o+$sH>{7|*sWrJ|MTOv zh{F2}lqIYtdO44@VZSb$~m+Xk{tIbIYk->p26rjvK0h=|Z85VPE zUEcjKKg0T7jJ!Gd^{#_9PW%PLhDV~K;iX1YuOcA0z5`z_Bw7K;1MCT>>V_?p>(DNU=uXo!e! z{!rh~&=TdsvM;DyMYyR3+9@*uT&QGC7B+ief?e&iwtHRbKGNoDL=P9Ie@Tc~9gb@~ zHO{@O(95}ZJGYZ(j>50738}}$q=OR}z5_HvoqhnXKsny<$(qW5YB~`7Chw-8vF0Z zVGW^Nju5VtCg%!to5#mR^_^h9zI54p<|C;Ciiyv}36|J@Lf`x8nY*qd2X%a=o4e+v zWUk~U=M(o1wPV{IRusC4{T zK;~q3A!!BF^;)9VWs@NJm86QG@oA*hJKiU$q# z)d!rD&vBpJ|FRf%KOf##kiwx=eyNX;@v+g!*$>Qt*vCYOo*xq^F~Hdr zke6l1`L|B}S1=$j<77NS1&XRpQtcq`L85}Wx8l<}=!@Z{X`Y2Iy~);j&L%*a11?xa z)LgjehF^aW`7PMR!6%%@7-!{HE z-hqLT1A*_Ief;Am28V>ShFjEAQ#TTTLh6kazSo})0}f|=(FAn!Ra!rDP+c7OGvy$q zccLE0JZrkm8Er!O_#&5j2A?^%FQeSr4^LrJR;#-ln8q-2!k2Lyg>9)4ZzBU|M9lF$ zZh1F|t|x9k6Ly$nC5mw`Mgu}?T8G&0wZ!!bEY(IJI2yPA?Thnc@Ktz#_1jszlvI~z zgsEs~u_`k0Bs^>Yxa7?W3>IZR3rdngqw)316Pg(wH!XS_T3yS2vm0HRI@6mBNWnB* zpKg?a*36iHk@+C(N`^$W^?L%8dM7GzAm-;-5LC*Y6#1k>hQp$3(zf}&AnxLy-g9+P z;%rH}`{wtO=~j#RjdnUs!Aa=(m|#BZkeQ(? z)?Yjb$GMl;Tr*B<=5;={$(|#8-beelnqXL}6EQ(e8o0NY1~5ojK>GpCvlX6{YFs7f zpa0spA^1{HtSjGzo-4quw}0-%eaSAV$H#PE-0h@Vd!K><-sPxD3Z&=RdX#6l~1!oI`6(I~l#q1AxuDN3r- zHBC+UsFLc8|5&#AU4m!)=0Ot(R-JNH)jRIbt^X92EaBajFN@srLIK>A(Q;5qxMOcr zQObeD_!7sh6d?Tlr1|4p+idjg*K~h|>B&D|y^3N(H-iyf8d{8253x1npsjp{p%E5A zx=@4Q)Kg*x4`mFJb^3&Y|I6%<|J{oIpHU+J-~UY~+G{QIR_5#?6yKghe*^sO4%)M` JYWvYk{|kg?ZLRDelete(); SafeDelete(fMaterials);} SafeDelete(fElementTable); if (fMedia) {fMedia->Delete(); SafeDelete(fMedia);} - if (fHashVolumes) fHashVolumes->Clear("nodelete"); SafeDelete(fHashVolumes); - if (fHashGVolumes) fHashGVolumes->Clear("nodelete"); SafeDelete(fHashGVolumes); + if (fHashVolumes) { fHashVolumes->Clear("nodelete"); SafeDelete(fHashVolumes); } + if (fHashGVolumes) { fHashGVolumes->Clear("nodelete"); SafeDelete(fHashGVolumes); } if (fHashPNE) {fHashPNE->Delete(); SafeDelete(fHashPNE);} if (fArrayPNE) {delete fArrayPNE;} if (fVolumes) {fVolumes->Delete(); SafeDelete(fVolumes);} diff --git a/graf2d/graf/src/TText.cxx b/graf2d/graf/src/TText.cxx index a7c73eff1afc1..8fdf60386424b 100644 --- a/graf2d/graf/src/TText.cxx +++ b/graf2d/graf/src/TText.cxx @@ -110,15 +110,15 @@ void TText::Copy(TObject &obj) const ((TText&)obj).fY = fY; TNamed::Copy(obj); TAttText::Copy(((TText&)obj)); - if (((TText&)obj).fWcsTitle != NULL) { - if (fWcsTitle != NULL) { - *reinterpret_cast(&((TText&)obj).fWcsTitle) = *reinterpret_cast(&fWcsTitle); + if (((TText&)obj).fWcsTitle) { + if (fWcsTitle) { + *reinterpret_cast(((TText&)obj).fWcsTitle) = *reinterpret_cast(fWcsTitle); } else { - delete reinterpret_cast(&((TText&)obj).fWcsTitle); - ((TText&)obj).fWcsTitle = NULL; + delete reinterpret_cast(((TText&)obj).fWcsTitle); + ((TText&)obj).fWcsTitle = nullptr; } } else { - if (fWcsTitle != NULL) { + if (fWcsTitle) { ((TText&)(obj)).fWcsTitle = new std::wstring(*reinterpret_cast(fWcsTitle)); } } @@ -129,10 +129,10 @@ void TText::Copy(TObject &obj) const const void *TText::GetWcsTitle(void) const { - if (fWcsTitle != NULL) { + if (fWcsTitle) { return reinterpret_cast(fWcsTitle)->c_str(); } else { - return NULL; + return nullptr; } } diff --git a/gui/browserv7/src/RBrowser.cxx b/gui/browserv7/src/RBrowser.cxx index 1f083325eb78b..66bfb39d67709 100644 --- a/gui/browserv7/src/RBrowser.cxx +++ b/gui/browserv7/src/RBrowser.cxx @@ -120,6 +120,13 @@ class RBrowserEditorWidget : public RBrowserWidget { /** \class ROOT::Experimental::RBrowser \ingroup rbrowser \brief Web-based %ROOT file browser + +RBrowser requires one of the supported web browsers: + - Google Chrome (preferable) + - Mozilla Firefox + +\image html v7_rbrowser.png + */ ////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/gui/gui/src/TGClient.cxx b/gui/gui/src/TGClient.cxx index 65a563c324167..ec6b7ff1d46a9 100644 --- a/gui/gui/src/TGClient.cxx +++ b/gui/gui/src/TGClient.cxx @@ -407,7 +407,7 @@ Bool_t TGClient::GetColorByName(const char *name, Pixel_t &pixel) const status = kFALSE; } else if (!gVirtualX->AllocColor(attributes.fColormap, color)) { Warning("GetColorByName", "couldn't retrieve color %s.\n" - "Please close any other application, like netscape, " + "Please close any other application, like web browsers, " "that might exhaust\nthe colormap and start ROOT again", name); status = kFALSE; } diff --git a/gui/gui/src/TGCommandPlugin.cxx b/gui/gui/src/TGCommandPlugin.cxx index 7f61a25c82242..34f054ebaf1d1 100644 --- a/gui/gui/src/TGCommandPlugin.cxx +++ b/gui/gui/src/TGCommandPlugin.cxx @@ -228,20 +228,33 @@ void TGCommandPlugin::HandleCommand() void TGCommandPlugin::HandleTab() { - std::string prompt = gInterpreter->GetPrompt(); std::string line = fCommandBuf->GetString(); - if (prompt.find("root") == std::string::npos) - prompt = "root []"; - prompt += " "; - prompt += line; - fStatus->AddLine(prompt.c_str()); - fStatus->ShowBottom(); std::vector result; size_t cur = line.length(); gInterpreter->CodeComplete(line, cur, result); - for (auto& res : result) { - fStatus->AddLine(res.c_str()); + if (result.size() == 1) { + // when there is only one result, complete the command line input + std::string found = result[0]; + std::string what = line; + size_t colon = line.find_last_of("::"); + if (colon != std::string::npos) + what = line.substr(colon+2); + size_t pos = found.find(what) + what.length(); + std::string suffix = found.substr(pos); + fCommand->AppendText(suffix.c_str()); + } else { + // otherwise print all results + std::string prompt = gInterpreter->GetPrompt(); + if (prompt.find("root") == std::string::npos) + prompt = "root []"; + prompt += " "; + prompt += line; + fStatus->AddLine(prompt.c_str()); fStatus->ShowBottom(); + for (auto& res : result) { + fStatus->AddLine(res.c_str()); + fStatus->ShowBottom(); + } } } diff --git a/gui/gui/src/TGFileDialog.cxx b/gui/gui/src/TGFileDialog.cxx index c1664be727eb3..373a3d55b56e3 100644 --- a/gui/gui/src/TGFileDialog.cxx +++ b/gui/gui/src/TGFileDialog.cxx @@ -479,6 +479,8 @@ Bool_t TGFileDialog::ProcessMessage(Long_t msg, Long_t parm1, Long_t) case kIDF_CANCEL: fFileInfo->SetFilename(nullptr); + if (fDlgType == kDOpen || fDlgType == kDSave) + fFileInfo->SetIniDir(nullptr); if (fFc->GetDisplayStat()) fFc->SetDisplayStat(kFALSE); fFileInfo->DeleteFileNamesList(); diff --git a/gui/gui/src/TGTextEntry.cxx b/gui/gui/src/TGTextEntry.cxx index c0820a7a12309..1de112b614c0d 100644 --- a/gui/gui/src/TGTextEntry.cxx +++ b/gui/gui/src/TGTextEntry.cxx @@ -34,7 +34,7 @@ Hitting the tab key will generate: kC_TEXTENTRY, kTE_TAB, widget id, 0. This widget has the behaviour e.g. of the "Location" field in -netscape. That includes handling Control/Shift key modifiers and +web browsers. That includes handling Control/Shift key modifiers and scrolling the text. enum TGTextEntry::EEchoMode @@ -1456,19 +1456,19 @@ Bool_t TGTextEntry::HandleFocusChange(Event_t *event) if (!IsEnabled()) return kTRUE; // check this when porting to Win32 - if (event->fType == kFocusIn) { - fCursorOn = kTRUE; - if (!fCurBlink) fCurBlink = new TBlinkTimer(this, 500); - fCurBlink->Reset(); - gBlinkingEntry = this; - gSystem->AddTimer(fCurBlink); - } else { - fCursorOn = kFALSE; - // fSelectionOn = kFALSE; // "netscape location behavior" - if (fCurBlink) fCurBlink->Remove(); - gBlinkingEntry = 0; - } - fClient->NeedRedraw(this); + if (event->fType == kFocusIn) { + fCursorOn = kTRUE; + if (!fCurBlink) fCurBlink = new TBlinkTimer(this, 500); + fCurBlink->Reset(); + gBlinkingEntry = this; + gSystem->AddTimer(fCurBlink); + } else { + fCursorOn = kFALSE; + // fSelectionOn = kFALSE; // "web browser location behavior" + if (fCurBlink) fCurBlink->Remove(); + gBlinkingEntry = 0; + } + fClient->NeedRedraw(this); return kTRUE; } diff --git a/hist/hist/src/TF2.cxx b/hist/hist/src/TF2.cxx index b5108c4dfff33..aabe092909e5e 100644 --- a/hist/hist/src/TF2.cxx +++ b/hist/hist/src/TF2.cxx @@ -371,7 +371,7 @@ Double_t TF2::FindMinMax(Double_t *x, Bool_t findmax) const else { xxmin = x[0]; yymin = x[1]; - zzmin = function(xx); + zzmin = function(x); } xx[0] = xxmin; xx[1] = yymin; diff --git a/hist/hist/src/TF3.cxx b/hist/hist/src/TF3.cxx index 3ab98b1e4652c..d496fa82920fc 100644 --- a/hist/hist/src/TF3.cxx +++ b/hist/hist/src/TF3.cxx @@ -246,7 +246,7 @@ Double_t TF3::FindMinMax(Double_t *x, Bool_t findmax) const xxmin = x[0]; yymin = x[1]; zzmin = x[2]; - zzmin = function(xx); + zzmin = function(x); } xx[0] = xxmin; xx[1] = yymin; diff --git a/interpreter/cling/tools/plugins/clad/CMakeLists.txt b/interpreter/cling/tools/plugins/clad/CMakeLists.txt index d7e3a5d93b0e2..0237b4a0f6b9c 100644 --- a/interpreter/cling/tools/plugins/clad/CMakeLists.txt +++ b/interpreter/cling/tools/plugins/clad/CMakeLists.txt @@ -69,7 +69,7 @@ endif() ExternalProject_Add( clad GIT_REPOSITORY https://github.com/vgvassilev/clad.git - GIT_TAG v0.7 + GIT_TAG v0.8 UPDATE_COMMAND "" CMAKE_ARGS -G ${CMAKE_GENERATOR} -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} diff --git a/io/io/src/TBufferJSON.cxx b/io/io/src/TBufferJSON.cxx index 755afd86e0a9b..7be95121f9f53 100644 --- a/io/io/src/TBufferJSON.cxx +++ b/io/io/src/TBufferJSON.cxx @@ -1218,8 +1218,14 @@ void TBufferJSON::JsonStartElement(const TStreamerElement *elem, const TClass *b switch (special_kind) { case 0: - if (!base_class) - elem_name = elem->GetName(); + if (base_class) return; + elem_name = elem->GetName(); + if (strcmp(elem_name,"fLineStyle") == 0) + if ((strcmp(elem->GetTypeName(),"TString") == 0) && (strcmp(elem->GetFullName(),"fLineStyle[30]") == 0)) { + auto st1 = fStack.at(fStack.size() - 2).get(); + if (st1->IsStreamerInfo() && st1->fInfo && (strcmp(st1->fInfo->GetName(),"TStyle") == 0)) + elem_name = "fLineStyles"; + } break; case TClassEdit::kVector: elem_name = "fVector"; break; case TClassEdit::kList: elem_name = "fList"; break; diff --git a/math/mathcore/inc/Math/IFunction.h b/math/mathcore/inc/Math/IFunction.h index afd88f14b193d..c8cd52f0865d7 100644 --- a/math/mathcore/inc/Math/IFunction.h +++ b/math/mathcore/inc/Math/IFunction.h @@ -1,9 +1,10 @@ // @(#)root/mathcore:$Id$ -// Authors: L. Moneta 11/2006 +// Authors: L. Moneta, E.G.P. Bos 2006-2017 /********************************************************************** * * * Copyright (c) 2006 , LCG ROOT MathLib Team * + * Copyright (c) 2017 Patrick Bos, Netherlands eScience Center * * * * * **********************************************************************/ @@ -34,68 +35,69 @@ //typedefs and tags definitions #include "Math/IFunctionfwd.h" +#include // runtime_error namespace ROOT { - namespace Math { + namespace Math { - /** - @defgroup GenFunc Generic Function Evaluation Interfaces - Interface classes for evaluation of function object classes in one or multi-dimensions. - @ingroup CppFunctions - */ + /** + @defgroup GenFunc Generic Function Evaluation Interfaces + Interface classes for evaluation of function object classes in one or multi-dimensions. + @ingroup CppFunctions + */ //___________________________________________________________________________________ - /** - Documentation for the abstract class IBaseFunctionMultiDim. - Interface (abstract class) for generic functions objects of multi-dimension - Provides a method to evaluate the function given a vector of coordinate values, - by implementing operator() (const double *). - In addition it defines the interface for copying functions via the pure virtual method Clone() - and the interface for getting the function dimension via the NDim() method. - Derived classes must implement the pure private virtual method DoEval(const double *) for the - function evaluation in addition to NDim() and Clone(). - - @ingroup GenFunc - */ + /** + Documentation for the abstract class IBaseFunctionMultiDim. + Interface (abstract class) for generic functions objects of multi-dimension + Provides a method to evaluate the function given a vector of coordinate values, + by implementing operator() (const double *). + In addition it defines the interface for copying functions via the pure virtual method Clone() + and the interface for getting the function dimension via the NDim() method. + Derived classes must implement the pure private virtual method DoEval(const double *) for the + function evaluation in addition to NDim() and Clone(). - template - class IBaseFunctionMultiDimTempl { + @ingroup GenFunc + */ - public: + template + class IBaseFunctionMultiDimTempl { - typedef T BackendType; - typedef IBaseFunctionMultiDimTempl BaseFunc; + public: + typedef T BackendType; + typedef IBaseFunctionMultiDimTempl BaseFunc; - IBaseFunctionMultiDimTempl() {} - /** - virtual destructor - */ - virtual ~IBaseFunctionMultiDimTempl() {} + IBaseFunctionMultiDimTempl() {} - /** - Clone a function. - Each derived class must implement their version of the Clone method - */ - virtual IBaseFunctionMultiDimTempl *Clone() const = 0; + /** + virtual destructor + */ + virtual ~IBaseFunctionMultiDimTempl() {} - /** - Retrieve the dimension of the function - */ - virtual unsigned int NDim() const = 0; + /** + Clone a function. + Each derived class must implement their version of the Clone method + */ + virtual IBaseFunctionMultiDimTempl *Clone() const = 0; - /** - Evaluate the function at a point x[]. - Use the pure virtual private method DoEval which must be implemented by the sub-classes - */ - T operator()(const T *x) const - { - return DoEval(x); - } + /** + Retrieve the dimension of the function + */ + virtual unsigned int NDim() const = 0; + + /** + Evaluate the function at a point x[]. + Use the pure virtual private method DoEval which must be implemented by the sub-classes + */ + T operator()(const T *x) const + { + return DoEval(x); + } #ifdef LATER - /** + /** Template method to eveluate the function using the begin of an iterator User is responsible to provide correct size for the iterator */ @@ -107,314 +109,364 @@ namespace ROOT { #endif - private: + private: - /** - Implementation of the evaluation function. Must be implemented by derived classes - */ - virtual T DoEval(const T *x) const = 0; + /** + Implementation of the evaluation function. Must be implemented by derived classes + */ + virtual T DoEval(const T *x) const = 0; - }; + }; //___________________________________________________________________________________ - /** - Interface (abstract class) for generic functions objects of one-dimension - Provides a method to evaluate the function given a value (simple double) - by implementing operator() (const double ). - In addition it defines the interface for copying functions via the pure virtual method Clone(). - Derived classes must implement the pure virtual private method DoEval(double ) for the - function evaluation in addition to Clone(). - An interface for evaluating the function passing a vector (like for multidim functions) is also - provided - - @ingroup GenFunc - */ - class IBaseFunctionOneDim { + /** + Interface (abstract class) for generic functions objects of one-dimension + Provides a method to evaluate the function given a value (simple double) + by implementing operator() (const double ). + In addition it defines the interface for copying functions via the pure virtual method Clone(). + Derived classes must implement the pure virtual private method DoEval(double ) for the + function evaluation in addition to Clone(). + An interface for evaluating the function passing a vector (like for multidim functions) is also + provided - public: + @ingroup GenFunc + */ + class IBaseFunctionOneDim { - typedef IBaseFunctionOneDim BaseFunc; + public: - IBaseFunctionOneDim() {} + typedef IBaseFunctionOneDim BaseFunc; - /** - virtual destructor - */ - virtual ~IBaseFunctionOneDim() {} + IBaseFunctionOneDim() {} - /** - Clone a function. - Each derived class will implement their version of the provate DoClone method - */ - virtual IBaseFunctionOneDim *Clone() const = 0; + /** + virtual destructor + */ + virtual ~IBaseFunctionOneDim() {} - /** - Evaluate the function at a point x - Use the a pure virtual private method DoEval which must be implemented by sub-classes - */ - double operator()(double x) const - { - return DoEval(x); - } + /** + Clone a function. + Each derived class will implement their version of the provate DoClone method + */ + virtual IBaseFunctionOneDim *Clone() const = 0; - /** - Evaluate the function at a point x[]. - Compatible method with multi-dimensional functions - */ - double operator()(const double *x) const - { - return DoEval(*x); - } + /** + Evaluate the function at a point x + Use the a pure virtual private method DoEval which must be implemented by sub-classes + */ + double operator()(double x) const + { + return DoEval(x); + } + /** + Evaluate the function at a point x[]. + Compatible method with multi-dimensional functions + */ + double operator()(const double *x) const + { + return DoEval(*x); + } - private: - // use private virtual inheritance + private: - /// implementation of the evaluation function. Must be implemented by derived classes - virtual double DoEval(double x) const = 0; + // use private virtual inheritance - }; + /// implementation of the evaluation function. Must be implemented by derived classes + virtual double DoEval(double x) const = 0; + + }; //-------- GRAD functions--------------------------- //___________________________________________________________________________________ - /** - Gradient interface (abstract class) defining the signature for calculating the gradient of a - multi-dimensional function. - Three methods are provided: - - Gradient(const double *x, double * grad) evaluate the full gradient vector at the vector value x - - Derivative(const double * x, int icoord) evaluate the partial derivative for the icoord coordinate - - FdF(const double *x, double &f, double * g) evaluate at the same time gradient and function/ + /** + Gradient interface (abstract class) defining the signature for calculating the gradient of a + multi-dimensional function. + Three methods are provided: + - Gradient(const double *x, double * grad) evaluate the full gradient vector at the vector value x + - Derivative(const double * x, int icoord) evaluate the partial derivative for the icoord coordinate + - FdF(const double *x, double &f, double * g) evaluate at the same time gradient and function/ - Concrete classes should derive from ROOT::Math::IGradientFunctionMultiDim and not from this class. + Concrete classes should derive from ROOT::Math::IGradientFunctionMultiDim and not from this class. - @ingroup GenFunc - */ + @ingroup GenFunc + */ - template - class IGradientMultiDimTempl { + template + class IGradientMultiDimTempl { - public: + public: - /// virual destructor - virtual ~IGradientMultiDimTempl() {} + /// virual destructor + virtual ~IGradientMultiDimTempl() {} - /** - Evaluate all the vector of function derivatives (gradient) at a point x. - Derived classes must re-implement if it is more efficient than evaluting one at a time - */ - virtual void Gradient(const T *x, T *grad) const = 0; + /** + Evaluate all the vector of function derivatives (gradient) at a point x. + Derived classes must re-implement if it is more efficient than evaluting one at a time + */ + virtual void Gradient(const T *x, T *grad) const = 0; - /** - Return the partial derivative with respect to the passed coordinate - */ - T Derivative(const T *x, unsigned int icoord = 0) const { return DoDerivative(x, icoord); } + /** + Return the partial derivative with respect to the passed coordinate + */ + T Derivative(const T *x, unsigned int icoord = 0) const { return DoDerivative(x, icoord); } + + T SecondDerivative(const T *x, unsigned int icoord) const { return DoSecondDerivative(x, icoord); } + T StepSize(const T *x, unsigned int icoord) const { return DoStepSize(x, icoord); } + /** + Optimized method to evaluate at the same time the function value and derivative at a point x. + Often both value and derivatives are needed and it is often more efficient to compute them at the same time. + Derived class should implement this method if performances play an important role and if it is faster to + evaluate value and derivative at the same time - /** - Optimized method to evaluate at the same time the function value and derivative at a point x. - Often both value and derivatives are needed and it is often more efficient to compute them at the same time. - Derived class should implement this method if performances play an important role and if it is faster to - evaluate value and derivative at the same time + */ + virtual void FdF(const T *x, T &f, T *df) const = 0; - */ - virtual void FdF(const T *x, T &f, T *df) const = 0; + private: - private: + /** + function to evaluate the derivative with respect each coordinate. To be implemented by the derived class + */ + virtual T DoDerivative(const T *x, unsigned int icoord) const = 0; - /** - function to evaluate the derivative with respect each coordinate. To be implemented by the derived class - */ - virtual T DoDerivative(const T *x, unsigned int icoord) const = 0; + /** + function to evaluate the second derivative with respect to each coordinate. + Optionally override in the derived class if you want to use it. If not, it will simply throw. + This because this function was added retroactively. Perhaps a cleaner setup would be to define + a separate subclass that can do second derivatives (and step sizes, see DoStepSize), but this + was chosen because of easier integration into the existing Minuit2 / Fitter framework. + */ + virtual T DoSecondDerivative(const T */*x*/, unsigned int /*icoord*/) const { + throw std::runtime_error("IGradientMultiDimTempl::DoSecondDerivative not defined!"); }; -//___________________________________________________________________________________ /** - Specialized Gradient interface(abstract class) for one dimensional functions - It provides a method to evaluate the derivative of the function, Derivative and a - method to evaluate at the same time the function and the derivative FdF + function to evaluate the step size for each coordinate. + Optionally override in the derived class if you want to use it. If not, it will simply throw. + This because this function was added retroactively. Perhaps a cleaner setup would be to define + a separate subclass that can do step sizes (and second derivatives, see DoSecondDerivative), but this + was chosen because of easier integration into the existing Minuit2 / Fitter framework. + */ + virtual T DoStepSize(const T */*x*/, unsigned int /*icoord*/) const { + throw std::runtime_error("IGradientMultiDimTempl::DoStepSize not defined!"); + }; - Concrete classes should derive from ROOT::Math::IGradientFunctionOneDim and not from this class. + }; - @ingroup GenFunc - */ - class IGradientOneDim { +//___________________________________________________________________________________ + /** + Specialized Gradient interface(abstract class) for one dimensional functions + It provides a method to evaluate the derivative of the function, Derivative and a + method to evaluate at the same time the function and the derivative FdF - public: + Concrete classes should derive from ROOT::Math::IGradientFunctionOneDim and not from this class. - /// virtual destructor - virtual ~IGradientOneDim() {} + @ingroup GenFunc + */ + class IGradientOneDim { - /** - Return the derivative of the function at a point x - Use the private method DoDerivative - */ - double Derivative(double x) const - { - return DoDerivative(x); - } + public: + /// virtual destructor + virtual ~IGradientOneDim() {} - /** - Optimized method to evaluate at the same time the function value and derivative at a point x. - Often both value and derivatives are needed and it is often more efficient to compute them at the same time. - Derived class should implement this method if performances play an important role and if it is faster to - evaluate value and derivative at the same time + /** + Return the derivative of the function at a point x + Use the private method DoDerivative + */ + double Derivative(double x) const + { + return DoDerivative(x); + } - */ - virtual void FdF(double x, double &f, double &df) const = 0; + /** + Optimized method to evaluate at the same time the function value and derivative at a point x. + Often both value and derivatives are needed and it is often more efficient to compute them at the same time. + Derived class should implement this method if performances play an important role and if it is faster to + evaluate value and derivative at the same time - /** - Compatibility method with multi-dimensional interface for partial derivative - */ - double Derivative(const double *x) const - { - return DoDerivative(*x); - } + */ + virtual void FdF(double x, double &f, double &df) const = 0; - /** - Compatibility method with multi-dimensional interface for Gradient - */ - void Gradient(const double *x, double *g) const - { - g[0] = DoDerivative(*x); - } - /** - Compatibility method with multi-dimensional interface for Gradient and function evaluation - */ - void FdF(const double *x, double &f, double *df) const - { - FdF(*x, f, *df); - } + /** + Compatibility method with multi-dimensional interface for partial derivative + */ + double Derivative(const double *x) const + { + return DoDerivative(*x); + } + /** + Compatibility method with multi-dimensional interface for Gradient + */ + void Gradient(const double *x, double *g) const + { + g[0] = DoDerivative(*x); + } + /** + Compatibility method with multi-dimensional interface for Gradient and function evaluation + */ + void FdF(const double *x, double &f, double *df) const + { + FdF(*x, f, *df); + } - private: - /** - function to evaluate the derivative with respect each coordinate. To be implemented by the derived class - */ - virtual double DoDerivative(double x) const = 0; + private: - }; -//___________________________________________________________________________________ /** - Interface (abstract class) for multi-dimensional functions providing a gradient calculation. - It implements both the ROOT::Math::IBaseFunctionMultiDimTempl and - ROOT::Math::IGradientMultiDimTempl interfaces. - The method ROOT::Math::IFunction::Gradient calculates the full gradient vector, - ROOT::Math::IFunction::Derivative calculates the partial derivative for each coordinate and - ROOT::Math::Fdf calculates the gradient and the function value at the same time. - The pure private virtual method DoDerivative() must be implemented by the derived classes, while - Gradient and FdF are by default implemented using DoDerivative, butthey can be overloaded by the - derived classes to improve the efficiency in the derivative calculation. - - @ingroup GenFunc + function to evaluate the derivative with respect each coordinate. To be implemented by the derived class */ + virtual double DoDerivative(double x) const = 0; - template - class IGradientFunctionMultiDimTempl : virtual public IBaseFunctionMultiDimTempl, - public IGradientMultiDimTempl { + }; - public: - typedef IBaseFunctionMultiDimTempl BaseFunc; - typedef IGradientMultiDimTempl BaseGrad; - - /** - Virtual Destructor (no operations) - */ - virtual ~IGradientFunctionMultiDimTempl() {} - - /** - Evaluate all the vector of function derivatives (gradient) at a point x. - Derived classes must re-implement it if more efficient than evaluting one at a time - */ - virtual void Gradient(const T *x, T *grad) const - { - unsigned int ndim = NDim(); - for (unsigned int icoord = 0; icoord < ndim; ++icoord) - grad[icoord] = BaseGrad::Derivative(x, icoord); - } +//___________________________________________________________________________________ + /** + Interface (abstract class) for multi-dimensional functions providing a gradient calculation. + It implements both the ROOT::Math::IBaseFunctionMultiDimTempl and + ROOT::Math::IGradientMultiDimTempl interfaces. + The method ROOT::Math::IFunction::Gradient calculates the full gradient vector, + ROOT::Math::IFunction::Derivative calculates the partial derivative for each coordinate and + ROOT::Math::Fdf calculates the gradient and the function value at the same time. + The pure private virtual method DoDerivative() must be implemented by the derived classes, while + Gradient and FdF are by default implemented using DoDerivative, butthey can be overloaded by the + derived classes to improve the efficiency in the derivative calculation. + + @ingroup GenFunc + */ + + template + class IGradientFunctionMultiDimTempl : virtual public IBaseFunctionMultiDimTempl, + public IGradientMultiDimTempl { + + public: + typedef IBaseFunctionMultiDimTempl BaseFunc; + typedef IGradientMultiDimTempl BaseGrad; - using BaseFunc::NDim; + /** + Virtual Destructor (no operations) + */ + virtual ~IGradientFunctionMultiDimTempl() {} - /** - Optimized method to evaluate at the same time the function value and derivative at a point x. - Often both value and derivatives are needed and it is often more efficient to compute them at the same time. - Derived class should implement this method if performances play an important role and if it is faster to - evaluate value and derivative at the same time - */ - virtual void FdF(const T *x, T &f, T *df) const - { - f = BaseFunc::operator()(x); - Gradient(x, df); - } + /** + Evaluate all the vector of function derivatives (gradient) at a point x. + Derived classes must re-implement it if more efficient than evaluting one at a time + */ + virtual void Gradient(const T *x, T *grad) const + { + unsigned int ndim = NDim(); + for (unsigned int icoord = 0; icoord < ndim; ++icoord) + grad[icoord] = BaseGrad::Derivative(x, icoord); + } + using BaseFunc::NDim; - }; + /** + Optimized method to evaluate at the same time the function value and derivative at a point x. + Often both value and derivatives are needed and it is often more efficient to compute them at the same time. + Derived class should implement this method if performances play an important role and if it is faster to + evaluate value and derivative at the same time + */ + virtual void FdF(const T *x, T &f, T *df) const + { + f = BaseFunc::operator()(x); + Gradient(x, df); + } + + virtual void G2ndDerivative(const T *x, T *g2) const { + unsigned int ndim = NDim(); + for (unsigned int icoord = 0; icoord < ndim; ++icoord) { + g2[icoord] = BaseGrad::SecondDerivative(x, icoord); + } + } + + virtual void GStepSize(const T *x, T *gstep) const { + unsigned int ndim = NDim(); + for (unsigned int icoord = 0; icoord < ndim; ++icoord) { + gstep[icoord] = BaseGrad::StepSize(x, icoord); + } + } + + virtual bool hasG2ndDerivative() const { + return false; + } + + virtual bool hasGStepSize() const { + return false; + } + + virtual bool returnsInMinuit2ParameterSpace() const { + return false; + } + + }; //___________________________________________________________________________________ - /** - Interface (abstract class) for one-dimensional functions providing a gradient calculation. - It implements both the ROOT::Math::IBaseFunctionOneDim and - ROOT::Math::IGradientOneDim interfaces. - The method ROOT::Math::IFunction::Derivative calculates the derivative and - ROOT::Math::Fdf calculates the derivative and the function values at the same time. - The pure private virtual method DoDerivative() must be implemented by the derived classes, while - FdF is by default implemented using DoDerivative, but it can be overloaded by the - derived classes to improve the efficiency in the derivative calculation. + /** + Interface (abstract class) for one-dimensional functions providing a gradient calculation. + It implements both the ROOT::Math::IBaseFunctionOneDim and + ROOT::Math::IGradientOneDim interfaces. + The method ROOT::Math::IFunction::Derivative calculates the derivative and + ROOT::Math::Fdf calculates the derivative and the function values at the same time. + The pure private virtual method DoDerivative() must be implemented by the derived classes, while + FdF is by default implemented using DoDerivative, but it can be overloaded by the + derived classes to improve the efficiency in the derivative calculation. - @ingroup GenFunc - */ - //template <> - class IGradientFunctionOneDim : - virtual public IBaseFunctionOneDim , - public IGradientOneDim { + @ingroup GenFunc + */ + //template <> + class IGradientFunctionOneDim : + virtual public IBaseFunctionOneDim , + public IGradientOneDim { - public: + public: - typedef IBaseFunctionOneDim BaseFunc; - typedef IGradientOneDim BaseGrad; + typedef IBaseFunctionOneDim BaseFunc; + typedef IGradientOneDim BaseGrad; - /** - Virtual Destructor (no operations) - */ - virtual ~IGradientFunctionOneDim() {} + /** + Virtual Destructor (no operations) + */ + virtual ~IGradientFunctionOneDim() {} - /** - Optimized method to evaluate at the same time the function value and derivative at a point x. - Often both value and derivatives are needed and it is often more efficient to compute them at the same time. - Derived class should implement this method if performances play an important role and if it is faster to - evaluate value and derivative at the same time + /** + Optimized method to evaluate at the same time the function value and derivative at a point x. + Often both value and derivatives are needed and it is often more efficient to compute them at the same time. + Derived class should implement this method if performances play an important role and if it is faster to + evaluate value and derivative at the same time - */ - virtual void FdF(double x, double &f, double &df) const - { - f = operator()(x); - df = Derivative(x); - } + */ + virtual void FdF(double x, double &f, double &df) const + { + f = operator()(x); + df = Derivative(x); + } - }; + }; - } // namespace Math + } // namespace Math } // namespace ROOT #endif /* ROOT_Math_IFunction */ diff --git a/math/mathcore/src/Fitter.cxx b/math/mathcore/src/Fitter.cxx index 3a0d478a0f991..145b124d80268 100644 --- a/math/mathcore/src/Fitter.cxx +++ b/math/mathcore/src/Fitter.cxx @@ -32,6 +32,8 @@ #include "Math/MultiDimParamFunctionAdapter.h" +#include + // #include "TMatrixDSym.h" // for debugging //#include "TMatrixD.h" diff --git a/math/minuit2/CMakeLists.txt b/math/minuit2/CMakeLists.txt index fac5ca399789e..962ee4c78f71e 100644 --- a/math/minuit2/CMakeLists.txt +++ b/math/minuit2/CMakeLists.txt @@ -28,6 +28,7 @@ if(CMAKE_PROJECT_NAME STREQUAL ROOT) Minuit2/CombinedMinimumBuilder.h Minuit2/ContoursError.h Minuit2/DavidonErrorUpdator.h + Minuit2/ExternalInternalGradientCalculator.h Minuit2/FCNAdapter.h Minuit2/FCNBase.h Minuit2/FCNGradAdapter.h @@ -128,6 +129,7 @@ if(CMAKE_PROJECT_NAME STREQUAL ROOT) src/CMakeLists.txt src/CombinedMinimumBuilder.cxx src/DavidonErrorUpdator.cxx + src/ExternalInternalGradientCalculator.cxx src/FitterUtil.h src/FumiliBuilder.cxx src/FumiliErrorUpdator.cxx diff --git a/math/minuit2/inc/Minuit2/AnalyticalGradientCalculator.h b/math/minuit2/inc/Minuit2/AnalyticalGradientCalculator.h index 1f9e0f0fcda8f..a682515b04640 100644 --- a/math/minuit2/inc/Minuit2/AnalyticalGradientCalculator.h +++ b/math/minuit2/inc/Minuit2/AnalyticalGradientCalculator.h @@ -16,10 +16,10 @@ namespace ROOT { namespace Minuit2 { -class FCNGradientBase; -class MnUserTransformation; + class FCNGradientBase; + class MnUserTransformation; -class AnalyticalGradientCalculator : public GradientCalculator { + class AnalyticalGradientCalculator : public GradientCalculator { public: AnalyticalGradientCalculator(const FCNGradientBase &fcn, const MnUserTransformation &state) @@ -35,7 +35,7 @@ class AnalyticalGradientCalculator : public GradientCalculator { virtual bool CheckGradient() const; -private: +protected: const FCNGradientBase &fGradCalc; const MnUserTransformation &fTransformation; }; diff --git a/math/minuit2/inc/Minuit2/ExternalInternalGradientCalculator.h b/math/minuit2/inc/Minuit2/ExternalInternalGradientCalculator.h new file mode 100644 index 0000000000000..3147817785ffd --- /dev/null +++ b/math/minuit2/inc/Minuit2/ExternalInternalGradientCalculator.h @@ -0,0 +1,44 @@ +// @(#)root/minuit2:$Id$ +// Authors: M. Winkler, F. James, L. Moneta, A. Zsenei, E.G.P. Bos 2003-2017 + +/********************************************************************** + * * + * Copyright (c) 2005 LCG ROOT Math team, CERN/PH-SFT * + * Copyright (c) 2017 Patrick Bos, Netherlands eScience Center * + * * + **********************************************************************/ + +#ifndef ROOT_Minuit2_ExternalInternalGradientCalculator +#define ROOT_Minuit2_ExternalInternalGradientCalculator + +#include "Minuit2/AnalyticalGradientCalculator.h" + +namespace ROOT { + + namespace Minuit2 { + + class FCNGradientBase; + class MnUserTransformation; + + class ExternalInternalGradientCalculator : public AnalyticalGradientCalculator { + + public: + + ExternalInternalGradientCalculator(const FCNGradientBase &fcn, const MnUserTransformation &state) : + AnalyticalGradientCalculator(fcn, state) + {} + + ~ExternalInternalGradientCalculator() {} + + virtual FunctionGradient operator()(const MinimumParameters &) const; + + virtual FunctionGradient operator()(const MinimumParameters &, + const FunctionGradient &) const; + + }; + + } // namespace Minuit2 + +} // namespace ROOT + +#endif // ROOT_Minuit2_ExternalInternalGradientCalculator diff --git a/math/minuit2/inc/Minuit2/FCNGradAdapter.h b/math/minuit2/inc/Minuit2/FCNGradAdapter.h index c33103902653c..8ed690f1da2ec 100644 --- a/math/minuit2/inc/Minuit2/FCNGradAdapter.h +++ b/math/minuit2/inc/Minuit2/FCNGradAdapter.h @@ -1,9 +1,10 @@ // @(#)root/minuit2:$Id$ -// Author: L. Moneta 10/2006 +// Authors: L. Moneta, E.G.P. Bos 2006-2017 /********************************************************************** * * * Copyright (c) 2006 ROOT Foundation, CERN/PH-SFT * + * Copyright (c) 2017 Patrick Bos, Netherlands eScience Center * * * **********************************************************************/ @@ -21,29 +22,32 @@ namespace Minuit2 { /** - template wrapped class for adapting to FCNBase signature a IGradFunction -@author Lorenzo Moneta +@author Lorenzo Moneta, Patrick Bos @ingroup Minuit */ + template class FCNGradAdapter : public FCNGradientBase { public: - FCNGradAdapter(const Function &f, double up = 1.) : fFunc(f), fUp(up), fGrad(std::vector(fFunc.NDim())) {} + FCNGradAdapter(const Function &f, double up = 1.) : fFunc(f), fUp(up), fGrad(std::vector(fFunc.NDim())), + fG2(fFunc.hasG2ndDerivative() ? std::vector(fFunc.NDim()) : std::vector(0)), + fGStep(fFunc.hasGStepSize() ? std::vector(fFunc.NDim()) : std::vector(0)) {} + +~FCNGradAdapter() {} - ~FCNGradAdapter() {} - double operator()(const std::vector &v) const { return fFunc.operator()(&v[0]); } + double operator()(const std::vector &v) const override { return fFunc.operator()(&v[0]); } double operator()(const double *v) const { return fFunc.operator()(v); } - double Up() const { return fUp; } + double Up() const override { return fUp; } - std::vector Gradient(const std::vector &v) const + std::vector Gradient(const std::vector &v) const override { fFunc.Gradient(&v[0], &fGrad[0]); @@ -56,12 +60,40 @@ class FCNGradAdapter : public FCNGradientBase { } // forward interface // virtual double operator()(int npar, double* params,int iflag = 4) const; - bool CheckGradient() const { return false; } + bool CheckGradient() const override { return false; } + + std::vector G2ndDerivative(const std::vector& v) const override { + fFunc.G2ndDerivative(v.data(), fG2.data()); + return fG2; + }; + + std::vector GStepSize(const std::vector& v) const override { + fFunc.GStepSize(v.data(), fGStep.data()); + return fGStep; + }; + + bool hasG2ndDerivative() const override { + return fFunc.hasG2ndDerivative(); + } + + bool hasGStepSize() const override { + return fFunc.hasGStepSize(); + } + + GradientParameterSpace gradParameterSpace() const override { + if (fFunc.returnsInMinuit2ParameterSpace()) { + return GradientParameterSpace::Internal; + } else { + return GradientParameterSpace::External; + } + } private: const Function &fFunc; double fUp; mutable std::vector fGrad; + mutable std::vector fG2; + mutable std::vector fGStep; }; } // end namespace Minuit2 diff --git a/math/minuit2/inc/Minuit2/FCNGradientBase.h b/math/minuit2/inc/Minuit2/FCNGradientBase.h index 2fd6abebf83ac..1fdbe6fda616d 100644 --- a/math/minuit2/inc/Minuit2/FCNGradientBase.h +++ b/math/minuit2/inc/Minuit2/FCNGradientBase.h @@ -1,9 +1,10 @@ // @(#)root/minuit2:$Id$ -// Authors: M. Winkler, F. James, L. Moneta, A. Zsenei 2003-2005 +// Authors: M. Winkler, F. James, L. Moneta, A. Zsenei, E.G.P. Bos 2003-2017 /********************************************************************** * * * Copyright (c) 2005 LCG ROOT Math team, CERN/PH-SFT * + * Copyright (c) 2017 Patrick Bos, Netherlands eScience Center * * * **********************************************************************/ @@ -29,7 +30,11 @@ namespace Minuit2 { Minuit does a check of the user Gradient at the beginning, if this is not wanted the method "CheckGradient()" has to be overridden to return "false". - */ + */ + +enum class GradientParameterSpace { + External, Internal +}; class FCNGradientBase : public FCNBase { @@ -38,7 +43,24 @@ class FCNGradientBase : public FCNBase { virtual std::vector Gradient(const std::vector &) const = 0; + virtual std::vector G2ndDerivative(const std::vector &) const = 0; + + virtual std::vector GStepSize(const std::vector &) const = 0; + + virtual bool hasG2ndDerivative() const { + return false; + } + + virtual bool hasGStepSize() const { + return false; + } + virtual bool CheckGradient() const { return true; } + + virtual GradientParameterSpace gradParameterSpace() const { + return GradientParameterSpace::External; + }; + }; } // namespace Minuit2 diff --git a/math/minuit2/inc/Minuit2/FunctionMinimizer.h b/math/minuit2/inc/Minuit2/FunctionMinimizer.h index 8c3d430cccc7d..5525184951955 100644 --- a/math/minuit2/inc/Minuit2/FunctionMinimizer.h +++ b/math/minuit2/inc/Minuit2/FunctionMinimizer.h @@ -1,9 +1,10 @@ // @(#)root/minuit2:$Id$ -// Authors: M. Winkler, F. James, L. Moneta, A. Zsenei 2003-2005 +// Authors: M. Winkler, F. James, L. Moneta, A. Zsenei, E.G.P. Bos 2003-2017 /********************************************************************** * * * Copyright (c) 2005 LCG ROOT Math team, CERN/PH-SFT * + * Copyright (c) 2017 Patrick Bos, Netherlands eScience Center * * * **********************************************************************/ @@ -11,6 +12,7 @@ #define ROOT_Minuit2_FunctionMinimizer #include "Minuit2/MnConfig.h" +#include "Minuit2/FCNGradientBase.h" #include namespace ROOT { diff --git a/math/minuit2/inc/Minuit2/MnUserTransformation.h b/math/minuit2/inc/Minuit2/MnUserTransformation.h index 37b3f93b62b37..d85a6a7033c98 100644 --- a/math/minuit2/inc/Minuit2/MnUserTransformation.h +++ b/math/minuit2/inc/Minuit2/MnUserTransformation.h @@ -1,9 +1,10 @@ // @(#)root/minuit2:$Id$ -// Authors: M. Winkler, F. James, L. Moneta, A. Zsenei 2003-2005 +// Authors: M. Winkler, F. James, L. Moneta, A. Zsenei, E.G.P. Bos 2003-2017 /********************************************************************** * * * Copyright (c) 2005 LCG ROOT Math team, CERN/PH-SFT * + * Copyright (c) 2017 Patrick Bos, Netherlands eScience Center * * * **********************************************************************/ @@ -26,7 +27,7 @@ namespace ROOT { namespace Minuit2 { -class MnUserCovariance; + class MnUserCovariance; // class MnMachinePrecision; @@ -91,6 +92,8 @@ class MnUserTransformation { // Index = internal Parameter double DInt2Ext(unsigned int, double) const; + double D2Int2Ext(unsigned int, double) const; + double GStepInt2Ext(unsigned int, double) const; // // Index = external Parameter // double dExt2Int(unsigned int, double) const; @@ -112,7 +115,7 @@ class MnUserTransformation { // return initial parameter values (useful especially to get fixed parameter values) const std::vector &InitialParValues() const { return fCache; } - /** forwarded interface */ + /** forwarded interface */ const MnMachinePrecision &Precision() const { return fPrecision; } void SetPrecision(double eps) { fPrecision.SetPrecision(eps); } diff --git a/math/minuit2/inc/Minuit2/SinParameterTransformation.h b/math/minuit2/inc/Minuit2/SinParameterTransformation.h index 97c5c96620d9c..7724412c7d626 100644 --- a/math/minuit2/inc/Minuit2/SinParameterTransformation.h +++ b/math/minuit2/inc/Minuit2/SinParameterTransformation.h @@ -1,9 +1,10 @@ // @(#)root/minuit2:$Id$ -// Authors: M. Winkler, F. James, L. Moneta, A. Zsenei 2003-2005 +// Authors: M. Winkler, F. James, L. Moneta, A. Zsenei, E.G.P. Bos 2003-2017 /********************************************************************** * * * Copyright (c) 2005 LCG ROOT Math team, CERN/PH-SFT * + * Copyright (c) 2017 Patrick Bos, Netherlands eScience Center * * * **********************************************************************/ @@ -14,26 +15,32 @@ namespace ROOT { namespace Minuit2 { -class MnMachinePrecision; + class MnMachinePrecision; /** class for the transformation for double-limited parameter Using a sin function one goes from a double-limited parameter range to an unlimited one */ -class SinParameterTransformation { + class SinParameterTransformation { -public: - SinParameterTransformation() {} + public: - ~SinParameterTransformation() {} + SinParameterTransformation() {} - double Int2ext(double Value, double Upper, double Lower) const; - double Ext2int(double Value, double Upper, double Lower, const MnMachinePrecision &) const; - double DInt2Ext(double Value, double Upper, double Lower) const; + ~SinParameterTransformation() {} -private: -}; + long double Int2ext(long double Value, long double Upper, long double Lower) const; + long double Ext2int(long double Value, long double Upper, long double Lower, + const MnMachinePrecision&) const; + long double DInt2Ext(long double Value, long double Upper, long double Lower) const; + + long double D2Int2Ext(long double Value, long double Upper, long double Lower) const; + long double GStepInt2Ext(long double Value, long double Upper, long double Lower) const; + + private: + + }; } // namespace Minuit2 diff --git a/math/minuit2/inc/Minuit2/SqrtLowParameterTransformation.h b/math/minuit2/inc/Minuit2/SqrtLowParameterTransformation.h index e38558520cdc3..af1e290b0c39c 100644 --- a/math/minuit2/inc/Minuit2/SqrtLowParameterTransformation.h +++ b/math/minuit2/inc/Minuit2/SqrtLowParameterTransformation.h @@ -1,9 +1,10 @@ // @(#)root/minuit2:$Id$ -// Authors: M. Winkler, F. James, L. Moneta, A. Zsenei 2003-2005 +// Authors: M. Winkler, F. James, L. Moneta, A. Zsenei, E.G.P. Bos 2003-2017 /********************************************************************** * * * Copyright (c) 2005 LCG ROOT Math team, CERN/PH-SFT * + * Copyright (c) 2017 Patrick Bos, Netherlands eScience Center * * * **********************************************************************/ @@ -19,7 +20,7 @@ namespace ROOT { namespace Minuit2 { -class MnMachinePrecision; + class MnMachinePrecision; /** * Transformation from external to internal Parameter based on sqrt(1 + x**2) @@ -27,24 +28,28 @@ class MnMachinePrecision; * This transformation applies for the case of single side Lower Parameter limits */ -class SqrtLowParameterTransformation /* : public ParameterTransformation */ { + class SqrtLowParameterTransformation /* : public ParameterTransformation */ { -public: - SqrtLowParameterTransformation() {} + public: - ~SqrtLowParameterTransformation() {} + SqrtLowParameterTransformation() {} - // transformation from internal to external - double Int2ext(double Value, double Lower) const; + ~SqrtLowParameterTransformation() {} - // transformation from external to internal - double Ext2int(double Value, double Lower, const MnMachinePrecision &) const; + // transformation from internal to external + long double Int2ext(long double Value, long double Lower) const; - // derivative of transformation from internal to external - double DInt2Ext(double Value, double Lower) const; + // transformation from external to internal + long double Ext2int(long double Value, long double Lower, const MnMachinePrecision&) const; -private: -}; + // derivative of transformation from internal to external + long double DInt2Ext(long double Value, long double Lower) const; + + long double D2Int2Ext(long double Value, long double Lower) const; + long double GStepInt2Ext(long double Value, long double Lower) const; + + private: + }; } // namespace Minuit2 diff --git a/math/minuit2/inc/Minuit2/SqrtUpParameterTransformation.h b/math/minuit2/inc/Minuit2/SqrtUpParameterTransformation.h index 7e744306d98b0..ba37999fb5457 100644 --- a/math/minuit2/inc/Minuit2/SqrtUpParameterTransformation.h +++ b/math/minuit2/inc/Minuit2/SqrtUpParameterTransformation.h @@ -1,9 +1,10 @@ // @(#)root/minuit2:$Id$ -// Authors: M. Winkler, F. James, L. Moneta, A. Zsenei 2003-2005 +// Authors: M. Winkler, F. James, L. Moneta, A. Zsenei, E.G.P. Bos 2003-2017 /********************************************************************** * * * Copyright (c) 2005 LCG ROOT Math team, CERN/PH-SFT * + * Copyright (c) 2017 Patrick Bos, Netherlands eScience Center * * * **********************************************************************/ @@ -19,7 +20,7 @@ namespace ROOT { namespace Minuit2 { -class MnMachinePrecision; + class MnMachinePrecision; /** * Transformation from external to internal Parameter based on sqrt(1 + x**2) @@ -27,25 +28,30 @@ class MnMachinePrecision; * This transformation applies for the case of single side Upper Parameter limits */ -class SqrtUpParameterTransformation /* : public ParameterTransformation */ { + class SqrtUpParameterTransformation /* : public ParameterTransformation */ { -public: - // create with user defined precision - SqrtUpParameterTransformation() {} + public: - ~SqrtUpParameterTransformation() {} + // create with user defined precision + SqrtUpParameterTransformation() {} - // transformation from internal to external - double Int2ext(double Value, double Upper) const; + ~SqrtUpParameterTransformation() {} - // transformation from external to internal - double Ext2int(double Value, double Upper, const MnMachinePrecision &) const; + // transformation from internal to external + long double Int2ext(long double Value, long double Upper) const; - // derivative of transformation from internal to external - double DInt2Ext(double Value, double Upper) const; + // transformation from external to internal + long double Ext2int(long double Value, long double Upper, const MnMachinePrecision&) const; -private: -}; + // derivative of transformation from internal to external + long double DInt2Ext(long double Value, long double Upper) const; + + long double D2Int2Ext(long double Value, long double Upper) const; + long double GStepInt2Ext(long double Value, long double Upper) const; + + private: + + }; } // namespace Minuit2 diff --git a/math/minuit2/src/AnalyticalGradientCalculator.cxx b/math/minuit2/src/AnalyticalGradientCalculator.cxx index 689e9477b729e..78a82d023d332 100644 --- a/math/minuit2/src/AnalyticalGradientCalculator.cxx +++ b/math/minuit2/src/AnalyticalGradientCalculator.cxx @@ -1,9 +1,10 @@ // @(#)root/minuit2:$Id$ -// Authors: M. Winkler, F. James, L. Moneta, A. Zsenei 2003-2005 +// Authors: M. Winkler, F. James, L. Moneta, A. Zsenei, E.G.P. Bos 2003-2017 /********************************************************************** * * * Copyright (c) 2005 LCG ROOT Math team, CERN/PH-SFT * + * Copyright (c) 2017 Patrick Bos, Netherlands eScience Center * * * **********************************************************************/ @@ -18,47 +19,69 @@ namespace ROOT { namespace Minuit2 { -FunctionGradient AnalyticalGradientCalculator::operator()(const MinimumParameters &par) const -{ - // evaluate analytical gradient. take care of parameter transformations - std::vector grad = fGradCalc.Gradient(fTransformation(par.Vec())); - assert(grad.size() == fTransformation.Parameters().size()); + FunctionGradient AnalyticalGradientCalculator::operator()(const MinimumParameters& par) const { + // evaluate analytical gradient. take care of parameter transformations - MnAlgebraicVector v(par.Vec().size()); - for (unsigned int i = 0; i < par.Vec().size(); i++) { - unsigned int ext = fTransformation.ExtOfInt(i); - if (fTransformation.Parameter(ext).HasLimits()) { - // double dd = (fTransformation.Parameter(ext).Upper() - - // fTransformation.Parameter(ext).Lower())*0.5*cos(par.Vec()(i)); - // const ParameterTransformation * pt = fTransformation.transformation(ext); - // double dd = pt->dInt2ext(par.Vec()(i), fTransformation.Parameter(ext).Lower(), - // fTransformation.Parameter(ext).Upper() ); - double dd = fTransformation.DInt2Ext(i, par.Vec()(i)); - v(i) = dd * grad[ext]; - } else { - v(i) = grad[ext]; + std::vector grad = fGradCalc.Gradient(fTransformation(par.Vec())); + assert(grad.size() == fTransformation.Parameters().size()); + + MnAlgebraicVector v(par.Vec().size()); + for(unsigned int i = 0; i < par.Vec().size(); i++) { + unsigned int ext = fTransformation.ExtOfInt(i); + if(fTransformation.Parameter(ext).HasLimits()) { + //double dd = (fTransformation.Parameter(ext).Upper() - fTransformation.Parameter(ext).Lower())*0.5*cos(par.Vec()(i)); + // const ParameterTransformation * pt = fTransformation.transformation(ext); + // double dd = pt->dInt2ext(par.Vec()(i), fTransformation.Parameter(ext).Lower(), fTransformation.Parameter(ext).Upper() ); + double dd = fTransformation.DInt2Ext(i, par.Vec()(i)); + v(i) = dd*grad[ext]; + } else { + v(i) = grad[ext]; + } } - } - MnPrint print("AnalyticalGradientCalculator"); - print.Debug("User given gradient in Minuit2", v); + MnPrint print("AnalyticalGradientCalculator"); + print.Debug("User given gradient in Minuit2", v); + + // check for 2nd derivative and step-size from the external gradient + // function and use them if present + // N.B.: for the time being we only allow both at the same time, since + // FunctionGradient only has ctors for two cases: 1. gradient only, + // 2. grad, g2 & gstep. + if (fGradCalc.hasG2ndDerivative() && fGradCalc.hasGStepSize()) { + std::vector g2 = fGradCalc.G2ndDerivative(fTransformation(par.Vec())); + std::vector gstep = fGradCalc.GStepSize(fTransformation(par.Vec())); - return FunctionGradient(v); -} + MnAlgebraicVector vg2(par.Vec().size()); + MnAlgebraicVector vgstep(par.Vec().size()); + for(unsigned int i = 0; i < par.Vec().size(); i++) { + unsigned int ext = fTransformation.ExtOfInt(i); + if(fTransformation.Parameter(ext).HasLimits()) { + double int2ext_g2 = fTransformation.D2Int2Ext(i, par.Vec()(i)); + double int2ext_gstep = fTransformation.GStepInt2Ext(i, par.Vec()(i)); + vg2(i) = int2ext_g2 * g2[ext]; + vgstep(i) = int2ext_gstep * gstep[ext]; + } else { + vg2(i) = g2[ext]; + vgstep(i) = gstep[ext]; + } + } -FunctionGradient AnalyticalGradientCalculator::operator()(const MinimumParameters &par, const FunctionGradient &) const -{ - // needed from base class - return (*this)(par); -} + return FunctionGradient(v, vg2, vgstep); + } else { + return FunctionGradient(v); + } + } -bool AnalyticalGradientCalculator::CheckGradient() const -{ - // check to be sure FCN implements analytical gradient - return fGradCalc.CheckGradient(); -} + FunctionGradient AnalyticalGradientCalculator::operator()(const MinimumParameters& par, const FunctionGradient&) const { + // needed from base class + return (*this)(par); + } -} // namespace Minuit2 + bool AnalyticalGradientCalculator::CheckGradient() const { + // check to be sure FCN implements analytical gradient + return fGradCalc.CheckGradient(); + } -} // namespace ROOT + } // namespace Minuit2 +} // namespace ROOT diff --git a/math/minuit2/src/CMakeLists.txt b/math/minuit2/src/CMakeLists.txt index 33020a0c421ca..957f53ceda4f2 100644 --- a/math/minuit2/src/CMakeLists.txt +++ b/math/minuit2/src/CMakeLists.txt @@ -17,6 +17,7 @@ set(MINUIT2_HEADERS CombinedMinimumBuilder.h ContoursError.h DavidonErrorUpdator.h + ExternalInternalGradientCalculator.h FCNAdapter.h FCNBase.h FCNGradAdapter.h @@ -117,6 +118,7 @@ set(MINUIT2_SOURCES BFGSErrorUpdator.cxx CombinedMinimumBuilder.cxx DavidonErrorUpdator.cxx + ExternalInternalGradientCalculator.cxx FumiliBuilder.cxx FumiliErrorUpdator.cxx FumiliGradientCalculator.cxx diff --git a/math/minuit2/src/ExternalInternalGradientCalculator.cxx b/math/minuit2/src/ExternalInternalGradientCalculator.cxx new file mode 100644 index 0000000000000..4f0ae78b095ef --- /dev/null +++ b/math/minuit2/src/ExternalInternalGradientCalculator.cxx @@ -0,0 +1,71 @@ +// @(#)root/minuit2:$Id$ +// Authors: M. Winkler, F. James, L. Moneta, A. Zsenei, E.G.P. Bos 2003-2017 + +/********************************************************************** + * * + * Copyright (c) 2005 LCG ROOT Math team, CERN/PH-SFT * + * Copyright (c) 2017 Patrick Bos, Netherlands eScience Center * + * * + **********************************************************************/ + +#include +#include "Minuit2/ExternalInternalGradientCalculator.h" +#include "Minuit2/FCNGradientBase.h" +#include "Minuit2/MnUserTransformation.h" +#include "Minuit2/FunctionGradient.h" +#include "Minuit2/MinimumParameters.h" + +namespace ROOT { + namespace Minuit2 { + + FunctionGradient ExternalInternalGradientCalculator::operator()(const MinimumParameters& par) const { + // evaluate analytical gradient. take care of parameter transformations + std::vector par_vec; + par_vec.resize(par.Vec().size()); + for (std::size_t ix = 0; ix < par.Vec().size(); ++ix) { + par_vec[ix] = par.Vec()(ix); + } + + std::vector grad = fGradCalc.Gradient(par_vec); + assert(grad.size() == fTransformation.Parameters().size()); + + MnAlgebraicVector v(par.Vec().size()); + for(unsigned int i = 0; i < par.Vec().size(); i++) { + unsigned int ext = fTransformation.ExtOfInt(i); + v(i) = grad[ext]; + } + +#ifdef DEBUG + std::cout << "User given gradient in Minuit2" << v << std::endl; +#endif + + // check for 2nd derivative and step-size from the external gradient + // function and use them if present + // N.B.: for the time being we only allow both at the same time, since + // FunctionGradient only has ctors for two cases: 1. gradient only, + // 2. grad, g2 & gstep. + if (fGradCalc.hasG2ndDerivative() && fGradCalc.hasGStepSize()) { + std::vector g2 = fGradCalc.G2ndDerivative(par_vec); + std::vector gstep = fGradCalc.GStepSize(par_vec); + + MnAlgebraicVector vg2(par.Vec().size()); + MnAlgebraicVector vgstep(par.Vec().size()); + for(unsigned int i = 0; i < par.Vec().size(); i++) { + unsigned int ext = fTransformation.ExtOfInt(i); + vg2(i) = g2[ext]; + vgstep(i) = gstep[ext]; + } + + return FunctionGradient(v, vg2, vgstep); + } else { + return FunctionGradient(v); + } + } + + FunctionGradient ExternalInternalGradientCalculator::operator()(const MinimumParameters& par, const FunctionGradient&) const { + // needed from base class + return (*this)(par); + } + + } // namespace Minuit2 +} // namespace ROOT diff --git a/math/minuit2/src/InitialGradientCalculator.cxx b/math/minuit2/src/InitialGradientCalculator.cxx index 06f4547705092..36af236968ba3 100644 --- a/math/minuit2/src/InitialGradientCalculator.cxx +++ b/math/minuit2/src/InitialGradientCalculator.cxx @@ -26,6 +26,8 @@ FunctionGradient InitialGradientCalculator::operator()(const MinimumParameters & { // initial rough estimate of the gradient using the parameter step size +// std::cout << "########### InitialGradientCalculator::operator()" < Trafo().Parameter(exOfIn).UpperLimit()) diff --git a/math/minuit2/src/MnSeedGenerator.cxx b/math/minuit2/src/MnSeedGenerator.cxx index 0aad44cea8d18..bc8f542368f40 100644 --- a/math/minuit2/src/MnSeedGenerator.cxx +++ b/math/minuit2/src/MnSeedGenerator.cxx @@ -1,9 +1,10 @@ // @(#)root/minuit2:$Id$ -// Authors: M. Winkler, F. James, L. Moneta, A. Zsenei 2003-2005 +// Authors: M. Winkler, F. James, L. Moneta, A. Zsenei, E.G.P. Bos 2003-2017 /********************************************************************** * * * Copyright (c) 2005 LCG ROOT Math team, CERN/PH-SFT * + * Copyright (c) 2017 Patrick Bos, Netherlands eScience Center * * * **********************************************************************/ @@ -94,10 +95,10 @@ operator()(const MnFcn &fcn, const GradientCalculator &gc, const MnUserParameter print.Info("run Hesse - new state:", tmp); - return MinimumSeed(tmp, st.Trafo()); + return MinimumSeed(tmp, st.Trafo()); } - return MinimumSeed(state, st.Trafo()); + return MinimumSeed(state, st.Trafo()); } MinimumSeed MnSeedGenerator::operator()(const MnFcn &fcn, const AnalyticalGradientCalculator &gc, @@ -109,17 +110,17 @@ MinimumSeed MnSeedGenerator::operator()(const MnFcn &fcn, const AnalyticalGradie unsigned int n = st.VariableParameters(); const MnMachinePrecision &prec = st.Precision(); - // initial starting values + // initial starting values MnAlgebraicVector x(n); for (unsigned int i = 0; i < n; i++) x(i) = st.IntParameters()[i]; double fcnmin = fcn(x); MinimumParameters pa(x, fcnmin); - InitialGradientCalculator igc(fcn, st.Trafo(), stra); - FunctionGradient tmp = igc(pa); FunctionGradient grd = gc(pa); - FunctionGradient dgrad(grd.Grad(), tmp.G2(), tmp.Gstep()); + +// FunctionGradient dgrad(grd.Grad(), tmp.G2(), tmp.Gstep()); + FunctionGradient dgrad(grd.Grad(), grd.G2(), grd.Gstep()); if (gc.CheckGradient()) { bool good = true; @@ -159,14 +160,16 @@ MinimumSeed MnSeedGenerator::operator()(const MnFcn &fcn, const AnalyticalGradie MinimumState state(pa, err, dgrad, edm, fcn.NumOfCalls()); NegativeG2LineSearch ng2ls; - if (ng2ls.HasNegativeG2(dgrad, prec)) { - Numerical2PGradientCalculator ngc(fcn, st.Trafo(), stra); - state = ng2ls(fcn, state, ngc, prec); + if(ng2ls.HasNegativeG2(dgrad, prec)) { +// Numerical2PGradientCalculator ngc(fcn, st.Trafo(), stra); +// state = ng2ls(fcn, state, ngc, prec); + state = ng2ls(fcn, state, gc, prec); } if (stra.Strategy() == 2 && !st.HasCovariance()) { // calculate full 2nd derivative MinimumState tmpState = MnHesse(stra)(fcn, state, st.Trafo()); + return MinimumSeed(tmpState, st.Trafo()); } diff --git a/math/minuit2/src/MnUserTransformation.cxx b/math/minuit2/src/MnUserTransformation.cxx index 155d821657f4e..b51394af8784b 100644 --- a/math/minuit2/src/MnUserTransformation.cxx +++ b/math/minuit2/src/MnUserTransformation.cxx @@ -1,9 +1,10 @@ // @(#)root/minuit2:$Id$ -// Authors: M. Winkler, F. James, L. Moneta, A. Zsenei 2003-2005 +// Authors: M. Winkler, F. James, L. Moneta, A. Zsenei, E.G.P. Bos 2003-2017 /********************************************************************** * * * Copyright (c) 2005 LCG ROOT Math team, CERN/PH-SFT * + * Copyright (c) 2017 Patrick Bos, Netherlands eScience Center * * * **********************************************************************/ @@ -16,6 +17,8 @@ #include #include +#include + namespace ROOT { namespace Minuit2 { @@ -208,6 +211,44 @@ double MnUserTransformation::DInt2Ext(unsigned int i, double val) const return dd; } + + double MnUserTransformation::D2Int2Ext(unsigned int i, double val) const { + // return the 2nd derivative of the int->ext transformation: d^2{Pext(i)} / d{Pint(i)}^2 + // for the parameter i with value val + + double dd = 1.; + if(fParameters[fExtOfInt[i]].HasLimits()) { + if (fParameters[fExtOfInt[i]].HasUpperLimit() && fParameters[fExtOfInt[i]].HasLowerLimit()) { + dd = fDoubleLimTrafo.D2Int2Ext(val, fParameters[fExtOfInt[i]].UpperLimit(), + fParameters[fExtOfInt[i]].LowerLimit()); + } else if (fParameters[fExtOfInt[i]].HasUpperLimit() && !fParameters[fExtOfInt[i]].HasLowerLimit()) { + dd = fUpperLimTrafo.D2Int2Ext(val, fParameters[fExtOfInt[i]].UpperLimit()); + } else { + dd = fLowerLimTrafo.D2Int2Ext(val, fParameters[fExtOfInt[i]].LowerLimit()); + } + } + + return dd; + } + + double MnUserTransformation::GStepInt2Ext(unsigned int i, double val) const { + // return the conversion factor of the int->ext transformation for the step size + // for the parameter i with value val + + double dd = 1.; + if(fParameters[fExtOfInt[i]].HasLimits()) { + if(fParameters[fExtOfInt[i]].HasUpperLimit() && fParameters[fExtOfInt[i]].HasLowerLimit()) { + dd = fDoubleLimTrafo.GStepInt2Ext(val, fParameters[fExtOfInt[i]].UpperLimit(), fParameters[fExtOfInt[i]].LowerLimit()); + } else if(fParameters[fExtOfInt[i]].HasUpperLimit() && !fParameters[fExtOfInt[i]].HasLowerLimit()) { + dd = fUpperLimTrafo.GStepInt2Ext(val, fParameters[fExtOfInt[i]].UpperLimit()); + } else { + dd = fLowerLimTrafo.GStepInt2Ext(val, fParameters[fExtOfInt[i]].LowerLimit()); + } + } + + return dd; + } + /* double MnUserTransformation::dExt2Int(unsigned int, double) const { double dd = 1.; diff --git a/math/minuit2/src/ModularFunctionMinimizer.cxx b/math/minuit2/src/ModularFunctionMinimizer.cxx index b14d18c3b84da..bfe1529d9ae19 100644 --- a/math/minuit2/src/ModularFunctionMinimizer.cxx +++ b/math/minuit2/src/ModularFunctionMinimizer.cxx @@ -1,15 +1,17 @@ // @(#)root/minuit2:$Id$ -// Authors: M. Winkler, F. James, L. Moneta, A. Zsenei 2003-2005 +// Authors: M. Winkler, F. James, L. Moneta, A. Zsenei, E.G.P. Bos 2003-2017 /********************************************************************** * * * Copyright (c) 2005 LCG ROOT Math team, CERN/PH-SFT * + * Copyright (c) 2017 Patrick Bos, Netherlands eScience Center * * * **********************************************************************/ #include "Minuit2/ModularFunctionMinimizer.h" #include "Minuit2/MinimumSeedGenerator.h" #include "Minuit2/AnalyticalGradientCalculator.h" +#include "Minuit2/ExternalInternalGradientCalculator.h" #include "Minuit2/Numerical2PGradientCalculator.h" #include "Minuit2/MinimumBuilder.h" #include "Minuit2/MinimumSeed.h" @@ -148,7 +150,14 @@ FunctionMinimum ModularFunctionMinimizer::Minimize(const FCNGradientBase &fcn, c // Create the minuit FCN wrapper (MnUserFcn) containing the trasformation (int<->ext) MnUserFcn mfcn(fcn, st.Trafo()); - AnalyticalGradientCalculator gc(fcn, st.Trafo()); + AnalyticalGradientCalculator *gc; + if (fcn.gradParameterSpace() == GradientParameterSpace::Internal) { + // std::cout << "-- ModularFunctionMinimizer::Minimize: Internal parameter space" << std::endl; + gc = new ExternalInternalGradientCalculator(fcn, st.Trafo()); + } else { + // std::cout << "-- ModularFunctionMinimizer::Minimize: External parameter space" << std::endl; + gc = new AnalyticalGradientCalculator(fcn, st.Trafo()); + } unsigned int npar = st.VariableParameters(); if (maxfcn == 0) @@ -158,7 +167,11 @@ FunctionMinimum ModularFunctionMinimizer::Minimize(const FCNGradientBase &fcn, c Numerical2PGradientCalculator numgc(mfcn, st.Trafo(), strategy); MinimumSeed mnseeds = SeedGenerator()(mfcn, numgc, st, strategy); - return Minimize(mfcn, gc, mnseeds, strategy, maxfcn, toler); + auto minimum = Minimize(mfcn, *gc, mnseeds, strategy, maxfcn, toler); + + delete gc; + + return minimum; } FunctionMinimum ModularFunctionMinimizer::Minimize(const MnFcn &mfcn, const GradientCalculator &gc, diff --git a/math/minuit2/src/SinParameterTransformation.cxx b/math/minuit2/src/SinParameterTransformation.cxx index 03355a4ccb8b8..0f401c532ebad 100644 --- a/math/minuit2/src/SinParameterTransformation.cxx +++ b/math/minuit2/src/SinParameterTransformation.cxx @@ -1,9 +1,10 @@ // @(#)root/minuit2:$Id$ -// Authors: M. Winkler, F. James, L. Moneta, A. Zsenei 2003-2005 +// Authors: M. Winkler, F. James, L. Moneta, A. Zsenei, E.G.P. Bos 2003-2017 /********************************************************************** * * * Copyright (c) 2005 LCG ROOT Math team, CERN/PH-SFT * + * Copyright (c) 2017 Patrick Bos, Netherlands eScience Center * * * **********************************************************************/ @@ -14,49 +15,55 @@ namespace ROOT { -namespace Minuit2 { - -double SinParameterTransformation::Int2ext(double Value, double Upper, double Lower) const -{ - // transformation from to internal (unlimited) to external values (limited by Lower/Upper ) - return Lower + 0.5 * (Upper - Lower) * (std::sin(Value) + 1.); -} - -double -SinParameterTransformation::Ext2int(double Value, double Upper, double Lower, const MnMachinePrecision &prec) const -{ - // transformation from external (limited by Lower/Upper ) to internal (unlimited) values given the lower/upper - // limits - - double piby2 = 2. * std::atan(1.); - double distnn = 8. * std::sqrt(prec.Eps2()); - double vlimhi = piby2 - distnn; - double vlimlo = -piby2 + distnn; - - double yy = 2. * (Value - Lower) / (Upper - Lower) - 1.; - double yy2 = yy * yy; - if (yy2 > (1. - prec.Eps2())) { - if (yy < 0.) { - // Lower limit - // std::cout<<"SinParameterTransformation warning: is at its Lower allowed limit. "< (1. - prec.Eps2())) { + if(yy < 0.) { + // Lower limit + // std::cout<<"SinParameterTransformation warning: is at its Lower allowed limit. "< &par) const + double operator()(const std::vector &par) const override { double x = par[0]; @@ -30,7 +30,7 @@ class Quad1F : public FCNGradientBase { return (x * x); } - std::vector Gradient(const std::vector &par) const + std::vector Gradient(const std::vector &par) const override { double x = par[0]; @@ -38,9 +38,21 @@ class Quad1F : public FCNGradientBase { return (std::vector(1, 2. * x)); } - void SetErrorDef(double up) { fErrorDef = up; } - - double Up() const { return fErrorDef; } + // G2ndDerivative and GStepSize will not be used since the default hasG2ndDerivative + // and hasGStepSize functions that return false are not overridden, but these have to + // be defined, since they are pure virtual functions in FCNGradientBase + std::vector G2ndDerivative(const std::vector&) const override { + std::vector g(0); + return g; + } + std::vector GStepSize(const std::vector&) const override { + std::vector g(0); + return g; + } + + void SetErrorDef(double up) override { fErrorDef = up; } + + double Up() const override { return fErrorDef; } const FCNBase *Base() const { return this; } diff --git a/math/minuit2/test/MnTutorial/Quad4F.h b/math/minuit2/test/MnTutorial/Quad4F.h index 1089184ca7a69..eedb150ede9c4 100644 --- a/math/minuit2/test/MnTutorial/Quad4F.h +++ b/math/minuit2/test/MnTutorial/Quad4F.h @@ -46,7 +46,7 @@ class Quad4FGrad : public FCNGradientBase { ~Quad4FGrad() {} - double operator()(const std::vector &par) const + double operator()(const std::vector &par) const override { double x = par[0]; @@ -57,7 +57,7 @@ class Quad4FGrad : public FCNGradientBase { return ((1. / 70.) * (21 * x * x + 20 * y * y + 19 * z * z - 14 * x * z - 20 * y * z) + w * w); } - std::vector Gradient(const std::vector &par) const + std::vector Gradient(const std::vector &par) const override { double x = par[0]; @@ -73,7 +73,19 @@ class Quad4FGrad : public FCNGradientBase { return g; } - double Up() const { return 1.; } + // G2ndDerivative and GStepSize will not be used since the default hasG2ndDerivative + // and hasGStepSize functions that return false are not overridden, but these have to + // be defined, since they are pure virtual functions in FCNGradientBase + std::vector G2ndDerivative(const std::vector&) const override { + std::vector g(0); + return g; + } + std::vector GStepSize(const std::vector&) const override { + std::vector g(0); + return g; + } + + double Up() const override { return 1.; } private: }; diff --git a/roofit/CMakeLists.txt b/roofit/CMakeLists.txt index 890df0340dde2..db5a2bf03f1f6 100644 --- a/roofit/CMakeLists.txt +++ b/roofit/CMakeLists.txt @@ -5,6 +5,9 @@ # For the list of contributors see $ROOTSYS/README/CREDITS. add_subdirectory(batchcompute) +add_subdirectory(builtin_zeromq) +add_subdirectory(roofitZMQ) +add_subdirectory(multiprocess) add_subdirectory(roofitcore) add_subdirectory(roofit) if(mathmore) diff --git a/roofit/builtin_zeromq/CMakeLists.txt b/roofit/builtin_zeromq/CMakeLists.txt new file mode 100644 index 0000000000000..8407e20d1deae --- /dev/null +++ b/roofit/builtin_zeromq/CMakeLists.txt @@ -0,0 +1,35 @@ +include(ExternalProject) + +set(ZeroMQ_PREFIX ${CMAKE_CURRENT_BINARY_DIR}/BUILTIN_ZeroMQ-prefix) +set(ZeroMQ_LIBNAME ${CMAKE_STATIC_LIBRARY_PREFIX}zmq${CMAKE_STATIC_LIBRARY_SUFFIX}) + +set(ZeroMQ_LIBRARY ${ZeroMQ_PREFIX}/lib/${ZeroMQ_LIBNAME} CACHE INTERNAL "" FORCE) +set(ZeroMQ_LIBRARIES ${ZeroMQ_LIBRARY} CACHE INTERNAL "" FORCE) + +ExternalProject_Add(BUILTIN_ZeroMQ + URL https://github.com/zeromq/libzmq/archive/refs/tags/v4.3.2.tar.gz + CMAKE_ARGS + -DCMAKE_INSTALL_PREFIX= + -DWITH_PERF_TOOL=OFF + -DZMQ_BUILD_TESTS=OFF + -DPOLLER=select + BUILD_BYPRODUCTS ${ZeroMQ_LIBRARIES} + ) + +set(ZeroMQ_FOUND TRUE CACHE BOOL "" FORCE) + +set(ZeroMQ_INCLUDE_DIR ${ZeroMQ_PREFIX}/include CACHE INTERNAL "" FORCE) +set(ZeroMQ_INCLUDE_DIRS ${ZeroMQ_PREFIX}/include CACHE INTERNAL "" FORCE) + +add_library(ZeroMQ INTERFACE) +target_include_directories(ZeroMQ INTERFACE $) +target_link_libraries(ZeroMQ INTERFACE $) +add_dependencies(ZeroMQ BUILTIN_ZeroMQ) + +add_library(ZeroMQ::ZeroMQ ALIAS ZeroMQ) + +set(ZeroMQ_DIR ${ZeroMQ_PREFIX}/share/cmake/ZeroMQ CACHE INTERNAL "" FORCE) + +# also need source and build directories for ppoll in RooFitZMQ +set(ZeroMQ_SOURCE_DIR ${ZeroMQ_PREFIX}/src/BUILTIN_ZeroMQ/src CACHE INTERNAL "" FORCE) +set(ZeroMQ_BUILD_DIR ${ZeroMQ_PREFIX}/src/BUILTIN_ZeroMQ-build CACHE INTERNAL "" FORCE) \ No newline at end of file diff --git a/roofit/multiprocess/CMakeLists.txt b/roofit/multiprocess/CMakeLists.txt new file mode 100644 index 0000000000000..2ec60366ce3db --- /dev/null +++ b/roofit/multiprocess/CMakeLists.txt @@ -0,0 +1,42 @@ +############################################################################ +# CMakeLists.txt file for building ROOT RooFitMultiProcess package +# @author Patrick Bos, Netherlands eScience Center +############################################################################ + +#FIND_PACKAGE(ZeroMQ REQUIRED) + +ROOT_STANDARD_LIBRARY_PACKAGE(RooFitMultiProcess + HEADERS + RooFit/MultiProcess/worker.h + RooFit/MultiProcess/Messenger_decl.h + RooFit/MultiProcess/Messenger.h + RooFit/MultiProcess/ProcessManager.h + RooFit/MultiProcess/JobManager.h + RooFit/MultiProcess/util.h + RooFit/MultiProcess/Queue.h + RooFit/MultiProcess/types.h + RooFit/MultiProcess/Job.h +# GradMinimizer.h +# NLLVar.h +# Vector.h + SOURCES + src/worker.cxx + src/Messenger.cxx + src/ProcessManager.cxx + src/util.cxx + src/Queue.cxx + src/JobManager.cxx + src/Job.cxx +# src/GradMinimizerFcn.cxx +# src/NLLVar.cxx + DICTIONARY_OPTIONS + "-writeEmptyRootPCM" + DEPENDENCIES + RooFitZMQ + LIBRARIES + ${ZeroMQ_LIBRARY} +) + +if(testing) + ROOT_ADD_TEST_SUBDIRECTORY(test) +endif() diff --git a/roofit/multiprocess/inc/LinkDef.h b/roofit/multiprocess/inc/LinkDef.h new file mode 100644 index 0000000000000..6a9ddbee0696a --- /dev/null +++ b/roofit/multiprocess/inc/LinkDef.h @@ -0,0 +1 @@ +// ceci n'est pas un fichier \ No newline at end of file diff --git a/roofit/multiprocess/inc/RooFit/MultiProcess/Job.h b/roofit/multiprocess/inc/RooFit/MultiProcess/Job.h new file mode 100644 index 0000000000000..84987fa354265 --- /dev/null +++ b/roofit/multiprocess/inc/RooFit/MultiProcess/Job.h @@ -0,0 +1,130 @@ +/* + * Project: RooFit + * Authors: + * PB, Patrick Bos, Netherlands eScience Center, p.bos@esciencecenter.nl + * IP, Inti Pelupessy, Netherlands eScience Center, i.pelupessy@esciencecenter.nl + * + * Copyright (c) 2016-2019, Netherlands eScience Center + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +#ifndef ROOT_ROOFIT_MultiProcess_Job_decl +#define ROOT_ROOFIT_MultiProcess_Job_decl + +#include +#include "RooFit_ZMQ/zmq.hxx" + +namespace RooFit { +namespace MultiProcess { + +// forward declaration +class JobManager; + +/* + * @brief interface class for defining the actual work that must be done + * + * Think of "job" as in "employment", e.g. the job of a baker, which + * involves *tasks* like baking and selling bread. The Job must define the + * tasks through its execution (evaluate_task), based on a task index argument. + * + * Classes inheriting from Job must implement the pure virtual methods: + * - void evaluate_task(std::size_t task) + * - void update_real(std::size_t ix, double val, bool is_constant) + * - void update_bool(std::size_t ix, bool value) + * - void receive_task_result_on_queue(std::size_t task, std::size_t worker_id) + * - void send_back_results_from_queue_to_master() + * - void clear_results() + * - void receive_results_on_master() + * - void send_back_task_result_from_worker(std::size_t task) + * + * The latter five deal with sending results back from workers to master. + * update_real is used to sync parameters that changed on master to the workers + * before the next set of tasks is queued; it must have a part that updates the + * actual parameters on the worker process, but can optionally also be used to + * send them from the master to the queue (Q2W is handled by the Queue::loop). + * An example implementation: + + + + * + * The type of result from each task is strongly dependent on the Job at hand + * and so Job does not provide a default results vector or anything like this. + * It is up to the inheriting class to implement this. We would have liked a + * template parameter task_result_t, so that we could also provide a default + * "boilerplate" calculate function to show a typical Job use-case of all the + * above infrastructure. This is not trivial, because the JobManager has to + * keep a list of Job pointers, so if there would be different template + * instantiations of Jobs, this would complicate this list. As an example, + * a boilerplate calculation could look something like this: + * +return_type CertainJob::evaluate() { + if (get_manager()->process_manager().is_master()) { + // update parameters that changed since last calculation (or creation if first time) + for (size_t ix = 0; ix < changed_parameters.size(); ++ix) { // changed_parameters would be a member of CertainJob + auto msg = RooFit::MultiProcess::M2Q::update_real; + get_manager()->messenger().send_from_master_to_queue(msg, id, ix, + changed_parameters[ix].getVal(), + changed_parameters[ix].isConstant()); + } + + // master fills queue with tasks + for (std::size_t ix = 0; ix < N_tasks; ++ix) { // N_tasks would also be a member of CertainJob + JobTask job_task(id, ix); + get_manager()->queue().add(job_task); + } + + // wait for task results back from workers to master + gather_worker_results(); + + // possibly do some final manipulation of the results that were gathered + } + return result; // or not, it could be stored and accessed later; also here, result would be a member of CertainJob +} + * + * Child classes should refrain from direct access to the JobManager instance + * (through JobManager::instance), but rather use the here provided + * Job::get_manager(). This function starts the worker_loop on the worker when + * first called, meaning that the workers will not + */ +class Job { +public: + explicit Job(); + Job(const Job &other); + + ~Job(); + + virtual void evaluate_task(std::size_t task) = 0; + virtual void update_real(std::size_t ix, double val, bool is_constant) = 0; + virtual void update_bool(std::size_t ix, bool value) = 0; + virtual void update_state(); + + virtual void send_back_task_result_from_worker(std::size_t task) = 0; + virtual void receive_task_result_on_queue(std::size_t task, std::size_t worker_id) = 0; + virtual void send_back_results_from_queue_to_master() = 0; + // after results have been retrieved, they may need to be cleared to ensure + // they won't be retrieved the next time again, e.g. when using a map to + // collect results; if not needed it can just be left empty + virtual void clear_results() = 0; + virtual void receive_results_on_master() = 0; + virtual bool receive_task_result_on_master(const zmq::message_t & message) = 0; + + void gather_worker_results(); + +protected: + JobManager *get_manager(); + + std::size_t id; + +private: + // do not use _manager directly, it must first be initialized! use get_manager() + JobManager *_manager = nullptr; +}; + +} // namespace MultiProcess +} // namespace RooFit + +#endif // ROOT_ROOFIT_MultiProcess_Job_decl diff --git a/roofit/multiprocess/inc/RooFit/MultiProcess/JobManager.h b/roofit/multiprocess/inc/RooFit/MultiProcess/JobManager.h new file mode 100644 index 0000000000000..c3524138ae50a --- /dev/null +++ b/roofit/multiprocess/inc/RooFit/MultiProcess/JobManager.h @@ -0,0 +1,91 @@ +/* + * Project: RooFit + * Authors: + * PB, Patrick Bos, Netherlands eScience Center, p.bos@esciencecenter.nl + * IP, Inti Pelupessy, Netherlands eScience Center, i.pelupessy@esciencecenter.nl + * + * Copyright (c) 2016-2019, Netherlands eScience Center + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +#ifndef ROOT_ROOFIT_MultiProcess_JobManager +#define ROOT_ROOFIT_MultiProcess_JobManager + +#include // unique_ptr +#include + +#include "RooFit/MultiProcess/types.h" + +namespace RooFit { +namespace MultiProcess { + +// forward definitions +class ProcessManager; +class Messenger; +class Queue; +class Job; + +/* + * @brief Main point of access for all MultiProcess infrastructure + * + * This class mainly serves as the access point to the multi-process infrastructure + * for Jobs. It is meant to be used as a singleton that holds and connects the other + * infrastructural classes: the messenger, process manager, worker and queue loops. + * + * It is important that the user of this class, particularly the one that calls + * instance() first, calls activate_loops() soon after, because everything that is + * done in between instance() and activate_loops() will be executed on all processes. + * This may be useful in some cases, but in general, one will probably want to always + * use the JobManager in its full capacity, including the queue and worker loops. + * This is the way the Job class uses this class, see + * + * The default number of processes is set using std::thread::hardware_concurrency(). + * To change it, simply set JobManager::default_N_workers to a different value + * before creation of a new instance. + */ + +class JobManager { +public: + static JobManager *instance(); + static bool is_instantiated(); + + ~JobManager(); + + static std::size_t add_job_object(Job *job_object); + static Job *get_job_object(std::size_t job_object_id); + static bool remove_job_object(std::size_t job_object_id); + + ProcessManager & process_manager() const; + Messenger & messenger() const; + Queue & queue() const; + + void retrieve(std::size_t requesting_job_id); + void results_from_queue_to_master(); + + void activate(); + bool is_activated() const; + +private: + explicit JobManager(std::size_t N_workers); + + std::unique_ptr process_manager_ptr; + std::unique_ptr messenger_ptr; + std::unique_ptr queue_ptr; + bool activated = false; + + static std::map job_objects; + static std::size_t job_counter; + static std::unique_ptr _instance; + +public: + static unsigned int default_N_workers; // no need for getters/setters, just public +}; + +} // namespace MultiProcess +} // namespace RooFit + +#endif // ROOT_ROOFIT_MultiProcess_JobManager diff --git a/roofit/multiprocess/inc/RooFit/MultiProcess/Messenger.h b/roofit/multiprocess/inc/RooFit/MultiProcess/Messenger.h new file mode 100644 index 0000000000000..69163933b0db0 --- /dev/null +++ b/roofit/multiprocess/inc/RooFit/MultiProcess/Messenger.h @@ -0,0 +1,208 @@ +/* + * Project: RooFit + * Authors: + * PB, Patrick Bos, Netherlands eScience Center, p.bos@esciencecenter.nl + * IP, Inti Pelupessy, Netherlands eScience Center, i.pelupessy@esciencecenter.nl + * + * Copyright (c) 2016-2019, Netherlands eScience Center + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +#ifndef ROOT_ROOFIT_MultiProcess_Messenger +#define ROOT_ROOFIT_MultiProcess_Messenger + +#include "RooFit/MultiProcess/Messenger_decl.h" + +namespace RooFit { +namespace MultiProcess { + +// -- WORKER - QUEUE COMMUNICATION -- + +template +void Messenger::send_from_worker_to_queue(T item, Ts... items) +{ + #ifndef NDEBUG + std::stringstream ss; + ss << "PID " << getpid() << " sends W2Q " << item; + debug_print(ss.str()); + #endif + + zmqSvc().send(*this_worker_qw_push, item, send_flag); + // if (sizeof...(items) > 0) { // this will only work with if constexpr, c++17 + send_from_worker_to_queue(items...); +} + +template +value_t Messenger::receive_from_worker_on_queue(std::size_t this_worker_id) +{ + qw_pull_poller[this_worker_id].ppoll(-1, &ppoll_sigmask); + auto value = zmqSvc().receive(*qw_pull[this_worker_id], ZMQ_DONTWAIT); + + #ifndef NDEBUG + std::stringstream ss; + ss << "PID " << getpid() << " receives W(" << this_worker_id << ")2Q " << value; + debug_print(ss.str()); + #endif + + return value; +} + +template +void Messenger::send_from_queue_to_worker(std::size_t this_worker_id, T item, Ts... items) +{ +#ifndef NDEBUG + std::stringstream ss; + ss << "PID " << getpid() << " sends Q2W(" << this_worker_id << ") " << item; + debug_print(ss.str()); +#endif + + zmqSvc().send(*qw_push[this_worker_id], item, send_flag); + // if (sizeof...(items) > 0) { // this will only work with if constexpr, c++17 + send_from_queue_to_worker(this_worker_id, items...); +} + +template +value_t Messenger::receive_from_queue_on_worker() +{ + qw_pull_poller[0].ppoll(-1, &ppoll_sigmask); + auto value = zmqSvc().receive(*this_worker_qw_pull, ZMQ_DONTWAIT); + + #ifndef NDEBUG + std::stringstream ss; + ss << "PID " << getpid() << " receives Q2W " << value; + debug_print(ss.str()); + #endif + + return value; +} + + +// -- QUEUE - MASTER COMMUNICATION -- + +template +void Messenger::send_from_queue_to_master(T item, Ts... items) +{ + #ifndef NDEBUG + std::stringstream ss; + ss << "PID " << getpid() << " sends Q2M " << item; + debug_print(ss.str()); + #endif + + zmqSvc().send(*mq_push, item, send_flag); + // if (sizeof...(items) > 0) { // this will only work with if constexpr, c++17 + send_from_queue_to_master(items...); +} + +template +value_t Messenger::receive_from_queue_on_master() +{ + mq_pull_poller.ppoll(-1, &ppoll_sigmask); + auto value = zmqSvc().receive(*mq_pull, ZMQ_DONTWAIT); + + #ifndef NDEBUG + std::stringstream ss; + ss << "PID " << getpid() << " receives Q2M " << value; + debug_print(ss.str()); + #endif + + return value; +} + +template +void Messenger::send_from_master_to_queue(T item, Ts... items) +{ + #ifndef NDEBUG + std::stringstream ss; + ss << "PID " << getpid() << " sends M2Q " << item; + debug_print(ss.str()); + #endif + + zmqSvc().send(*mq_push, item, send_flag); + // if (sizeof...(items) > 0) { // this will only work with if constexpr, c++17 + send_from_master_to_queue(items...); +} + +template +value_t Messenger::receive_from_master_on_queue() +{ + mq_pull_poller.ppoll(-1, &ppoll_sigmask); + auto value = zmqSvc().receive(*mq_pull, ZMQ_DONTWAIT); + + #ifndef NDEBUG + std::stringstream ss; + ss << "PID " << getpid() << " receives M2Q " << value; + debug_print(ss.str()); + #endif + + return value; +} + + +// -- MASTER - WORKER COMMUNICATION -- + +template +void Messenger::publish_from_master_to_workers(T item, Ts... items) +{ +#ifndef NDEBUG + std::stringstream ss; + ss << "PID " << getpid() << " sends M2W " << item; + debug_print(ss.str()); +#endif + + zmqSvc().send(*mw_pub, item, send_flag); + // if (sizeof...(items) > 0) { // this will only work with if constexpr, c++17 + publish_from_master_to_workers(items...); +} + +template +value_t Messenger::receive_from_master_on_worker() +{ + mw_sub_poller.ppoll(-1, &ppoll_sigmask); + auto value = zmqSvc().receive(*mw_sub, ZMQ_DONTWAIT); + +#ifndef NDEBUG + std::stringstream ss; + ss << "PID " << getpid() << " receives M2W " << value; + debug_print(ss.str()); +#endif + + return value; +} + +template +void Messenger::send_from_worker_to_master(T item, Ts... items) +{ +#ifndef NDEBUG + std::stringstream ss; + ss << "PID " << getpid() << " sends M2W " << item; + debug_print(ss.str()); +#endif + + zmqSvc().send(*wm_push, item, send_flag); + // if (sizeof...(items) > 0) { // this will only work with if constexpr, c++17 + send_from_worker_to_master(items...); +} + +template +value_t Messenger::receive_from_worker_on_master() +{ + wm_pull_poller.ppoll(-1, &ppoll_sigmask); + auto value = zmqSvc().receive(*wm_pull, ZMQ_DONTWAIT); + +#ifndef NDEBUG + std::stringstream ss; + ss << "PID " << getpid() << " receives M2W " << value; + debug_print(ss.str()); +#endif + + return value; +} + +} // namespace MultiProcess +} // namespace RooFit + +#endif // ROOT_ROOFIT_MultiProcess_Messenger diff --git a/roofit/multiprocess/inc/RooFit/MultiProcess/Messenger_decl.h b/roofit/multiprocess/inc/RooFit/MultiProcess/Messenger_decl.h new file mode 100644 index 0000000000000..80d690658b241 --- /dev/null +++ b/roofit/multiprocess/inc/RooFit/MultiProcess/Messenger_decl.h @@ -0,0 +1,185 @@ +/* + * Project: RooFit + * Authors: + * PB, Patrick Bos, Netherlands eScience Center, p.bos@esciencecenter.nl + * IP, Inti Pelupessy, Netherlands eScience Center, i.pelupessy@esciencecenter.nl + * + * Copyright (c) 2016-2019, Netherlands eScience Center + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +#ifndef ROOT_ROOFIT_MultiProcess_Messenger_decl +#define ROOT_ROOFIT_MultiProcess_Messenger_decl + +#include +#include +#include // sigprocmask, sigset_t, etc + +#include "RooFit_ZMQ/ZeroMQSvc.h" +#include "RooFit_ZMQ/ZeroMQPoller.h" +#include "RooFit/MultiProcess/ProcessManager.h" + +namespace RooFit { +namespace MultiProcess { + +void set_socket_immediate(ZmqLingeringSocketPtr<> &socket); + +// test messages +enum class X2X : int { + ping = -1, + pong = -2, + initial_value = 0 +}; + +class Messenger { +public: + explicit Messenger(const ProcessManager &process_manager); + ~Messenger(); + + void test_connections(const ProcessManager &process_manager); + + enum class test_snd_pipes { + M2Q, + Q2M, + Q2W, + W2Q + }; + + enum class test_rcv_pipes { + fromQonM, + fromMonQ, + fromWonQ, + fromQonW, + }; + + std::pair create_queue_poller(); + std::pair create_worker_poller(); + + // -- WORKER - QUEUE COMMUNICATION -- + + void send_from_worker_to_queue(); + template + void send_from_worker_to_queue(T item, Ts... items); + template + value_t receive_from_worker_on_queue(std::size_t this_worker_id); + void send_from_queue_to_worker(std::size_t this_worker_id); + template + void send_from_queue_to_worker(std::size_t this_worker_id, T item, Ts... items); + template + value_t receive_from_queue_on_worker(); + + // -- QUEUE - MASTER COMMUNICATION -- + + void send_from_queue_to_master(); + + template + void send_from_queue_to_master(T item, Ts... items); + template + value_t receive_from_queue_on_master(); + void send_from_master_to_queue(); + + template + void send_from_master_to_queue(T item, Ts... items); + template + value_t receive_from_master_on_queue(); + + // -- MASTER - WORKER COMMUNICATION -- + + void publish_from_master_to_workers(); + template + void publish_from_master_to_workers(T item, Ts... items); + template + value_t receive_from_master_on_worker(); + + void send_from_worker_to_master(); + template + void send_from_worker_to_master(T item, Ts... items); + template + value_t receive_from_worker_on_master(); + + void test_receive(X2X expected_ping_value, test_rcv_pipes rcv_pipe, std::size_t worker_id); + void test_send(X2X ping_value, test_snd_pipes snd_pipe, std::size_t worker_id); + + sigset_t ppoll_sigmask; +// std::size_t N_available_polled_results = 0; + + void set_send_flag(int flag); + +private: + void debug_print(std::string s); + + // push + std::vector> qw_push; + ZmqLingeringSocketPtr<> this_worker_qw_push; + ZmqLingeringSocketPtr<> mq_push; + // pollers for all push sockets + std::vector qw_push_poller; + ZeroMQPoller mq_push_poller; + // pull + std::vector> qw_pull; + ZmqLingeringSocketPtr<> this_worker_qw_pull; + ZmqLingeringSocketPtr<> mq_pull; + // pollers for all pull sockets + std::vector qw_pull_poller; + ZeroMQPoller mq_pull_poller; + + // test to circumvent queue for parameter updating + ZmqLingeringSocketPtr<> mw_pub; + ZmqLingeringSocketPtr<> mw_sub; + ZeroMQPoller mw_sub_poller; + // test to circumvent queue for result retrieving + ZmqLingeringSocketPtr<> wm_push; + ZmqLingeringSocketPtr<> wm_pull; + ZeroMQPoller wm_pull_poller; + + // destruction flags to distinguish between different process-type setups: + bool close_MQ_on_destruct_ = false; + bool close_this_QW_on_destruct_ = false; + bool close_QW_container_on_destruct_ = false; + + int send_flag = 0; +}; + + +// Messages from master to queue +enum class M2Q : int { + terminate = 100, + enqueue = 10, + retrieve = 11, + update_real = 12, + // update_cat = 13, + update_bool = 14, +}; + +// Messages from queue to master +enum class Q2M : int { retrieve_rejected = 20, retrieve_accepted = 21, retrieve_later = 22 }; + +// Messages from worker to queue +enum class W2Q : int { dequeue = 30, send_result = 31 }; + +// Messages from queue to worker +enum class Q2W : int { + terminate = 400, + dequeue_rejected = 40, + dequeue_accepted = 41, + result_received = 43, + update_real = 44, + // update_cat = 45 + update_bool = 46, +}; + +// stream output operators for debugging +std::ostream &operator<<(std::ostream &out, const M2Q value); +std::ostream &operator<<(std::ostream &out, const Q2M value); +std::ostream &operator<<(std::ostream &out, const Q2W value); +std::ostream &operator<<(std::ostream &out, const W2Q value); +std::ostream &operator<<(std::ostream &out, const X2X value); + +} // namespace MultiProcess +} // namespace RooFit + +#endif // ROOT_ROOFIT_MultiProcess_Messenger_decl diff --git a/roofit/multiprocess/inc/RooFit/MultiProcess/ProcessManager.h b/roofit/multiprocess/inc/RooFit/MultiProcess/ProcessManager.h new file mode 100644 index 0000000000000..7c83546edecf0 --- /dev/null +++ b/roofit/multiprocess/inc/RooFit/MultiProcess/ProcessManager.h @@ -0,0 +1,78 @@ +/* + * Project: RooFit + * Authors: + * PB, Patrick Bos, Netherlands eScience Center, p.bos@esciencecenter.nl + * IP, Inti Pelupessy, Netherlands eScience Center, i.pelupessy@esciencecenter.nl + * + * Copyright (c) 2016-2019, Netherlands eScience Center + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +#ifndef ROOT_ROOFIT_MultiProcess_ProcessManager +#define ROOT_ROOFIT_MultiProcess_ProcessManager + +#include // pid_t +#include // sig_atomic_t and for sigterm handling on child processes (in ProcessManager.cxx) +#include + +// forward declaration +class Queue; + +namespace RooFit { +namespace MultiProcess { + +class ProcessManager { + friend Queue; + +public: + explicit ProcessManager(std::size_t N_workers); + ~ProcessManager(); + + bool is_initialized() const; + + void terminate() noexcept; +// void terminate_workers(); + void wait_for_sigterm_then_exit(); + + bool is_master() const; + bool is_queue() const; + bool is_worker() const; + std::size_t worker_id() const; + std::size_t N_workers() const; + + void identify_processes() const; + + static void handle_sigterm(int signum); + static bool sigterm_received(); + + // for debugging/testing: + pid_t get_queue_pid() {return queue_pid;} + std::vector get_worker_pids() {return worker_pids;} + +private: + void initialize_processes(bool cpu_pinning = true); + void shutdown_processes(); + + bool _is_master = false; + bool _is_queue = false; + bool _is_worker = false; + std::size_t _worker_id; + std::size_t _N_workers; + + // master must wait for workers after completion, for which it needs their PIDs + std::vector worker_pids; + pid_t queue_pid; + + bool initialized = false; + + static volatile sig_atomic_t _sigterm_received; +}; + +} // namespace MultiProcess +} // namespace RooFit + +#endif // ROOT_ROOFIT_MultiProcess_ProcessManager diff --git a/roofit/multiprocess/inc/RooFit/MultiProcess/Queue.h b/roofit/multiprocess/inc/RooFit/MultiProcess/Queue.h new file mode 100644 index 0000000000000..bd44ac1c36a96 --- /dev/null +++ b/roofit/multiprocess/inc/RooFit/MultiProcess/Queue.h @@ -0,0 +1,46 @@ +/* + * Project: RooFit + * Authors: + * PB, Patrick Bos, Netherlands eScience Center, p.bos@esciencecenter.nl + * IP, Inti Pelupessy, Netherlands eScience Center, i.pelupessy@esciencecenter.nl + * + * Copyright (c) 2016-2019, Netherlands eScience Center + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +#ifndef ROOT_ROOFIT_MultiProcess_Queue +#define ROOT_ROOFIT_MultiProcess_Queue + +#include + +#include "RooFit/MultiProcess/types.h" +#include "RooFit/MultiProcess/Messenger.h" + +namespace RooFit { +namespace MultiProcess { + +class Queue { +public: + bool pop(JobTask &job_task); + void add(JobTask job_task); + + void loop(); + + bool process_master_message(M2Q message); + void process_worker_message(std::size_t this_worker_id, W2Q message); + +private: + std::queue _queue; + std::size_t N_tasks = 0; // total number of received tasks + std::size_t N_tasks_completed = 0; + std::size_t N_tasks_at_workers = 0; +}; + +} // namespace MultiProcess +} // namespace RooFit + +#endif // ROOT_ROOFIT_MultiProcess_Queue diff --git a/roofit/multiprocess/inc/RooFit/MultiProcess/types.h b/roofit/multiprocess/inc/RooFit/MultiProcess/types.h new file mode 100644 index 0000000000000..6b0f0cab87ece --- /dev/null +++ b/roofit/multiprocess/inc/RooFit/MultiProcess/types.h @@ -0,0 +1,29 @@ +/* + * Project: RooFit + * Authors: + * PB, Patrick Bos, Netherlands eScience Center, p.bos@esciencecenter.nl + * + * Copyright (c) 2016-2019, Netherlands eScience Center + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +#ifndef ROOT_ROOFIT_MultiProcess_types +#define ROOT_ROOFIT_MultiProcess_types + +#include // pair + +namespace RooFit { +namespace MultiProcess { + +// some helper types +using Task = std::size_t; +using JobTask = std::pair; // combined job_object and task identifier type + +} // namespace MultiProcess +} // namespace RooFit + +#endif // ROOT_ROOFIT_MultiProcess_types diff --git a/roofit/multiprocess/inc/RooFit/MultiProcess/util.h b/roofit/multiprocess/inc/RooFit/MultiProcess/util.h new file mode 100644 index 0000000000000..4f5f5f287857a --- /dev/null +++ b/roofit/multiprocess/inc/RooFit/MultiProcess/util.h @@ -0,0 +1,34 @@ +/* + * Project: RooFit + * Authors: + * PB, Patrick Bos, Netherlands eScience Center, p.bos@esciencecenter.nl + * + * Copyright (c) 2016-2019, Netherlands eScience Center + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ + +#ifndef ROOT_ROOFIT_MultiProcess_util +#define ROOT_ROOFIT_MultiProcess_util + +#include // getpid, pid_t + +#include // for ZMQ::ppoll_error_t +#include + +namespace RooFit { +namespace MultiProcess { + +int wait_for_child(pid_t child_pid, bool may_throw, int retries_before_killing); + +enum class zmq_ppoll_error_response { abort, unknown_eintr, retry }; +zmq_ppoll_error_response handle_zmq_ppoll_error(ZMQ::ppoll_error_t &e); +std::tuple>, bool> careful_ppoll(ZeroMQPoller &poller, const sigset_t &ppoll_sigmask, std::size_t max_tries = 2); + +} // namespace MultiProcess +} // namespace RooFit +#endif // ROOT_ROOFIT_MultiProcess_util diff --git a/roofit/multiprocess/inc/RooFit/MultiProcess/worker.h b/roofit/multiprocess/inc/RooFit/MultiProcess/worker.h new file mode 100644 index 0000000000000..2fc2eaa7ef5ae --- /dev/null +++ b/roofit/multiprocess/inc/RooFit/MultiProcess/worker.h @@ -0,0 +1,27 @@ +/* + * Project: RooFit + * Authors: + * PB, Patrick Bos, Netherlands eScience Center, p.bos@esciencecenter.nl + * IP, Inti Pelupessy, Netherlands eScience Center, i.pelupessy@esciencecenter.nl + * + * Copyright (c) 2016-2019, Netherlands eScience Center + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +#ifndef ROOT_ROOFIT_MultiProcess_worker +#define ROOT_ROOFIT_MultiProcess_worker + +namespace RooFit { +namespace MultiProcess { + +void worker_loop(); +bool is_worker_loop_running(); + +} // namespace MultiProcess +} // namespace RooFit + +#endif // ROOT_ROOFIT_MultiProcess_worker diff --git a/roofit/multiprocess/src/Job.cxx b/roofit/multiprocess/src/Job.cxx new file mode 100644 index 0000000000000..9f3cd7aacf1a1 --- /dev/null +++ b/roofit/multiprocess/src/Job.cxx @@ -0,0 +1,65 @@ +/* + * Project: RooFit + * Authors: + * PB, Patrick Bos, Netherlands eScience Center, p.bos@esciencecenter.nl + * IP, Inti Pelupessy, Netherlands eScience Center, i.pelupessy@esciencecenter.nl + * + * Copyright (c) 2016-2019, Netherlands eScience Center + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ + +#include "RooFit/MultiProcess/JobManager.h" +#include "RooFit/MultiProcess/Messenger.h" +#include "RooFit/MultiProcess/worker.h" +#include "RooFit/MultiProcess/Job.h" +#include "RooFit/MultiProcess/Queue.h" + +namespace RooFit { +namespace MultiProcess { + +Job::Job() +{ + id = JobManager::add_job_object(this); +} + +Job::Job(const Job &other) + : _manager(other._manager) +{ + id = JobManager::add_job_object(this); +} + +Job::~Job() +{ + JobManager::remove_job_object(id); +} + +// This function is necessary here, because the Job knows about the number +// of workers, so only from the Job can the JobManager be instantiated. +JobManager *Job::get_manager() +{ + if (!_manager) { + _manager = JobManager::instance(); + } + + if (!_manager->is_activated()) { + _manager->activate(); + } + + return _manager; +} + +void Job::gather_worker_results() +{ + get_manager()->retrieve(id); +} + +void Job::update_state() {} + + +} // namespace MultiProcess +} // namespace RooFit diff --git a/roofit/multiprocess/src/JobManager.cxx b/roofit/multiprocess/src/JobManager.cxx new file mode 100644 index 0000000000000..d65a821d39c66 --- /dev/null +++ b/roofit/multiprocess/src/JobManager.cxx @@ -0,0 +1,264 @@ +/* + * Project: RooFit + * Authors: + * PB, Patrick Bos, Netherlands eScience Center, p.bos@esciencecenter.nl + * IP, Inti Pelupessy, Netherlands eScience Center, i.pelupessy@esciencecenter.nl + * + * Copyright (c) 2016-2019, Netherlands eScience Center + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +#include // getpid + +#include // std::thread::hardware_concurrency() + +#include "ROOT/RMakeUnique.hxx" // make_unique in C++11 + +#include "RooFit/MultiProcess/ProcessManager.h" +#include "RooFit/MultiProcess/Messenger.h" +#include "RooFit/MultiProcess/JobManager.h" +#include "RooFit/MultiProcess/Job.h" +#include "RooFit/MultiProcess/Queue.h" // complete type for JobManager::queue() +#include "RooFit/MultiProcess/worker.h" +#include "RooFit/MultiProcess/util.h" + + +namespace RooFit { +namespace MultiProcess { + +// static function +JobManager* JobManager::instance() { + if (!JobManager::is_instantiated()) { + assert(default_N_workers != 0); + _instance.reset(new JobManager(default_N_workers)); // can't use make_unique, because ctor is private + _instance->messenger().test_connections(_instance->process_manager()); + // set send to non blocking on all processes after checking the connections are working: + _instance->messenger().set_send_flag(ZMQ_DONTWAIT); + } + return _instance.get(); +} + + +// static function +bool JobManager::is_instantiated() { + return static_cast(_instance); +} + +// (private) constructor +// Don't construct JobManager objects manually, use the static instance if +// you need to run multiple jobs. +JobManager::JobManager(std::size_t N_workers) { + queue_ptr = std::make_unique(); + process_manager_ptr = std::make_unique(N_workers); + messenger_ptr = std::make_unique(*process_manager_ptr); +} + +JobManager::~JobManager() { + // The instance gets created by some Job. Once all Jobs are gone, the + // JM will get destroyed. In this case, the job_objects map should have + // been emptied. This check makes sure: + assert(JobManager::job_objects.empty()); + messenger_ptr.reset(nullptr); + process_manager_ptr.reset(nullptr); + queue_ptr.reset(nullptr); +} + + +// static function +// returns job_id for added job_object +std::size_t JobManager::add_job_object(Job *job_object) { + if (JobManager::is_instantiated()) { + if (_instance->process_manager().is_initialized()) { + std::stringstream ss; + ss << "Cannot add Job to JobManager instantiation, forking has already taken place! Instance object at raw ptr " << _instance.get(); + throw std::logic_error("Cannot add Job to JobManager instantiation, forking has already taken place! Call terminate() on the instance before adding new Jobs."); + } + } + std::size_t job_id = job_counter++; + job_objects[job_id] = job_object; + return job_id; +} + +// static function +Job* JobManager::get_job_object(std::size_t job_object_id) { + return job_objects[job_object_id]; +} + +// static function +bool JobManager::remove_job_object(std::size_t job_object_id) { + bool removed_succesfully = job_objects.erase(job_object_id) == 1; + if (job_objects.empty()) { + _instance.reset(nullptr); + } + return removed_succesfully; +} + + +ProcessManager & JobManager::process_manager() const { + return *process_manager_ptr; +} + + +Messenger & JobManager::messenger() const { + return *messenger_ptr; +} + + +Queue & JobManager::queue() const { + return *queue_ptr; +} + + +//void JobManager::retrieve() { +// if (process_manager().is_master()) { +// bool carry_on = true; +// while (carry_on) { +// messenger().send_from_master_to_queue(M2Q::retrieve); +// try { +// auto handshake = messenger().receive_from_queue_on_master(); +// switch (handshake) { +// case Q2M::retrieve_accepted: { +// carry_on = false; +// auto N_jobs = messenger().receive_from_queue_on_master(); +// for (std::size_t job_ix = 0; job_ix < N_jobs; ++job_ix) { +// auto job_object_id = messenger().receive_from_queue_on_master(); +// JobManager::get_job_object(job_object_id)->receive_results_on_master(); +// } +// } break; +// case Q2M::retrieve_later: { +// carry_on = true; +// } break; +// case Q2M::retrieve_rejected: { +// carry_on = false; +// throw std::logic_error( +// "Master sent M2Q::retrieve, but queue had no tasks yet: Q2M::retrieve_rejected. Aborting!"); +// } break; +// } +// } catch (ZMQ::ppoll_error_t &e) { +// zmq_ppoll_error_response response; +// try { +// response = handle_zmq_ppoll_error(e); +// } catch (std::logic_error& e) { +// printf("JobManager::retrieve got unhandleable ZMQ::ppoll_error_t\n"); +// throw; +// } +// if (response == zmq_ppoll_error_response::abort) { +// throw std::logic_error("in JobManager::retrieve: master received a SIGTERM, aborting"); +// } else if (response == zmq_ppoll_error_response::unknown_eintr) { +// printf("EINTR in JobManager::retrieve, continuing\n"); +// continue; +// } else if (response == zmq_ppoll_error_response::retry) { +// printf("EAGAIN from ppoll in JobManager::retrieve, continuing\n"); +// continue; +// } +// } catch (zmq::error_t& e) { +// printf("unhandled zmq::error_t (not a ppoll_error_t) in JobManager::retrieve with errno %d: %s\n", e.num(), e.what()); +// throw; +// } +// +// } +// } +//} + + +void JobManager::retrieve(std::size_t requesting_job_id) { + if (process_manager().is_master()) { +// auto get_time = []() { +// return std::chrono::duration_cast( +// std::chrono::high_resolution_clock::now().time_since_epoch()) +// .count(); +// }; +// decltype(get_time()) t1, t2; + + bool job_fully_retrieved = false; + while (not job_fully_retrieved) { + try { +// t1 = get_time(); + auto task_result_message = messenger().receive_from_worker_on_master(); + auto job_object_id = *reinterpret_cast(task_result_message.data()); // job_id must always be the first element of the result message! + bool this_job_fully_retrieved = JobManager::get_job_object(job_object_id)->receive_task_result_on_master(task_result_message); + if (requesting_job_id == job_object_id) { + job_fully_retrieved = this_job_fully_retrieved; + } +// t2 = get_time(); +// printf("wallclock [master] retrieve got task %lu: %f\n", *(reinterpret_cast(task_result_message.data()) + 1), (t2 - t1) / 1.e9); + } catch (ZMQ::ppoll_error_t &e) { + zmq_ppoll_error_response response; + try { + response = handle_zmq_ppoll_error(e); + } catch (std::logic_error& e) { + printf("JobManager::retrieve got unhandleable ZMQ::ppoll_error_t\n"); + throw; + } + if (response == zmq_ppoll_error_response::abort) { + throw std::logic_error("in JobManager::retrieve: master received a SIGTERM, aborting"); + } else if (response == zmq_ppoll_error_response::unknown_eintr) { + printf("EINTR in JobManager::retrieve, continuing\n"); + continue; + } else if (response == zmq_ppoll_error_response::retry) { + printf("EAGAIN from ppoll in JobManager::retrieve, continuing\n"); + continue; + } + } catch (zmq::error_t& e) { + printf("unhandled zmq::error_t (not a ppoll_error_t) in JobManager::retrieve with errno %d: %s\n", e.num(), e.what()); + throw; + } + } + } +} + + +void JobManager::results_from_queue_to_master() +{ + assert(process_manager().is_queue()); + messenger().send_from_queue_to_master(Q2M::retrieve_accepted, JobManager::job_objects.size()); + for (auto job_tuple : JobManager::job_objects) { + messenger().send_from_queue_to_master(job_tuple.first); // job id + job_tuple.second->send_back_results_from_queue_to_master(); // N_job_tasks, task_ids and results + job_tuple.second->clear_results(); + } +} + + +void JobManager::activate() +{ + // This function exists purely because activation from the constructor is + // impossible; the constructor must return a constructed instance, which it + // can't do if it's stuck in an infinite loop. This means the Job that first + // creates the JobManager instance must also activate it (or any other user + // of this class). + // This should be called soon after creation of instance, because everything + // between construction and activation gets executed both on the master + // process and on the slaves. + + activated = true; + + if (process_manager().is_queue()) { + queue().loop(); + std::_Exit(0); + } + + if (!is_worker_loop_running() && process_manager().is_worker()) { + RooFit::MultiProcess::worker_loop(); + std::_Exit(0); + } +} + + +bool JobManager::is_activated() const +{ + return activated; +} + +// initialize static members +std::map JobManager::job_objects; +std::size_t JobManager::job_counter = 0; +std::unique_ptr JobManager::_instance {nullptr}; +unsigned int JobManager::default_N_workers = std::thread::hardware_concurrency(); + +} // namespace MultiProcess +} // namespace RooFit diff --git a/roofit/multiprocess/src/Messenger.cxx b/roofit/multiprocess/src/Messenger.cxx new file mode 100644 index 0000000000000..911535506034d --- /dev/null +++ b/roofit/multiprocess/src/Messenger.cxx @@ -0,0 +1,481 @@ +/* + * Project: RooFit + * Authors: + * PB, Patrick Bos, Netherlands eScience Center, p.bos@esciencecenter.nl + * IP, Inti Pelupessy, Netherlands eScience Center, i.pelupessy@esciencecenter.nl + * + * Copyright (c) 2016-2019, Netherlands eScience Center + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ + +#include // sigprocmask etc +#include // sprintf + +#include +#include "RooFit/MultiProcess/Messenger.h" + +namespace RooFit { +namespace MultiProcess { + +void set_socket_immediate(ZmqLingeringSocketPtr<> &socket) +{ + int optval = 1; + socket->setsockopt(ZMQ_IMMEDIATE, &optval, sizeof(optval)); +} + +Messenger::Messenger(const ProcessManager &process_manager) +{ + sigemptyset(&ppoll_sigmask); + + // high water mark for master-queue sending, which can be quite a busy channel, especially at the start of a run + int hwm = 0; + // create zmq connections (zmq context is automatically created in the ZeroMQSvc class and maintained as singleton) + // and pollers where necessary + try { + if (process_manager.is_master()) { + mq_push.reset(zmqSvc().socket_ptr(zmq::PUSH)); + auto rc = zmq_setsockopt (*mq_push, ZMQ_SNDHWM, &hwm, sizeof hwm); + assert (rc == 0); + mq_push->bind("ipc:///tmp/roofitMP_from_master_to_queue"); + + mq_push_poller.register_socket(*mq_push, zmq::POLLOUT); + + mq_pull.reset(zmqSvc().socket_ptr(zmq::PULL)); + rc = zmq_setsockopt (*mq_pull, ZMQ_RCVHWM, &hwm, sizeof hwm); + assert (rc == 0); + mq_pull->bind("ipc:///tmp/roofitMP_from_queue_to_master"); + + mq_pull_poller.register_socket(*mq_pull, zmq::POLLIN); + + mw_pub.reset(zmqSvc().socket_ptr(zmq::PUB)); + rc = zmq_setsockopt (*mw_pub, ZMQ_SNDHWM, &hwm, sizeof hwm); + assert (rc == 0); + mw_pub->bind("ipc:///tmp/roofitMP_from_master_to_workers"); + + wm_pull.reset(zmqSvc().socket_ptr(zmq::PULL)); + rc = zmq_setsockopt (*wm_pull, ZMQ_RCVHWM, &hwm, sizeof hwm); + assert (rc == 0); + wm_pull->bind("ipc:///tmp/roofitMP_from_workers_to_master"); + wm_pull_poller.register_socket(*wm_pull, zmq::POLLIN); + + close_MQ_on_destruct_ = true; + + // make sure all subscribers are connected + ZmqLingeringSocketPtr<> subscriber_ping_socket {zmqSvc().socket_ptr(zmq::REP)}; + subscriber_ping_socket->bind("ipc:///tmp/roofitMP_subscriber_ping_socket"); + ZeroMQPoller subscriber_ping_poller; + subscriber_ping_poller.register_socket(*subscriber_ping_socket, zmq::POLLIN); + std::size_t N_subscribers_confirmed = 0; + while (N_subscribers_confirmed < process_manager.N_workers()) { + zmqSvc().send(*mw_pub, false); + auto poll_results = subscriber_ping_poller.poll(0); +// for (auto& poll_result : poll_results) { + for (std::size_t ix = 0; ix < poll_results.size(); ++ix) { + auto request = zmqSvc().receive(*subscriber_ping_socket, zmq::DONTWAIT); + assert(request == "present"); + zmqSvc().send(*subscriber_ping_socket, "roger"); + ++N_subscribers_confirmed; + } + } + zmqSvc().send(*mw_pub, true); + + } else if (process_manager.is_queue()) { + // first the queue-worker sockets + // do resize instead of reserve so that the unique_ptrs are initialized + // (to nullptr) so that we can do reset below, alternatively you can do + // push/emplace_back with move or something + qw_push.resize(process_manager.N_workers()); + qw_pull.resize(process_manager.N_workers()); + qw_push_poller.resize(process_manager.N_workers()); + qw_pull_poller.resize(process_manager.N_workers()); + for (std::size_t ix = 0; ix < process_manager.N_workers(); ++ix) { + std::stringstream push_name, pull_name; + // push + qw_push[ix].reset(zmqSvc().socket_ptr(zmq::PUSH)); + push_name << "ipc:///tmp/roofitMP_from_queue_to_worker_" << ix; + qw_push[ix]->bind(push_name.str()); + + qw_push_poller[ix].register_socket(*qw_push[ix], zmq::POLLOUT); + + // pull + qw_pull[ix].reset(zmqSvc().socket_ptr(zmq::PULL)); + pull_name << "ipc:///tmp/roofitMP_from_worker_" << ix << "_to_queue"; + qw_pull[ix]->bind(pull_name.str()); + + qw_pull_poller[ix].register_socket(*qw_pull[ix], zmq::POLLIN); + } + + // then the master-queue sockets + mq_push.reset(zmqSvc().socket_ptr(zmq::PUSH)); + auto rc = zmq_setsockopt (*mq_push, ZMQ_SNDHWM, &hwm, sizeof hwm); + assert (rc == 0); + mq_push->connect("ipc:///tmp/roofitMP_from_queue_to_master"); + + mq_push_poller.register_socket(*mq_push, zmq::POLLOUT); + + mq_pull.reset(zmqSvc().socket_ptr(zmq::PULL)); + rc = zmq_setsockopt (*mq_pull, ZMQ_RCVHWM, &hwm, sizeof hwm); + assert (rc == 0); + mq_pull->connect("ipc:///tmp/roofitMP_from_master_to_queue"); + + mq_pull_poller.register_socket(*mq_pull, zmq::POLLIN); + + close_MQ_on_destruct_ = true; + close_QW_container_on_destruct_ = true; + } else if (process_manager.is_worker()) { + // we only need one queue-worker pipe on the worker + qw_push_poller.resize(1); + qw_pull_poller.resize(1); + + std::stringstream push_name, pull_name; + // push + this_worker_qw_push.reset(zmqSvc().socket_ptr(zmq::PUSH)); + push_name << "ipc:///tmp/roofitMP_from_worker_" << process_manager.worker_id() << "_to_queue"; + this_worker_qw_push->connect(push_name.str()); + + qw_push_poller[0].register_socket(*this_worker_qw_push, zmq::POLLOUT); + + // pull + this_worker_qw_pull.reset(zmqSvc().socket_ptr(zmq::PULL)); + pull_name << "ipc:///tmp/roofitMP_from_queue_to_worker_" << process_manager.worker_id(); + this_worker_qw_pull->connect(pull_name.str()); + + qw_pull_poller[0].register_socket(*this_worker_qw_pull, zmq::POLLIN); + + mw_sub.reset(zmqSvc().socket_ptr(zmq::SUB)); + auto rc = zmq_setsockopt (*mw_sub, ZMQ_RCVHWM, &hwm, sizeof hwm); + assert (rc == 0); + rc = zmq_setsockopt(*mw_sub, ZMQ_SUBSCRIBE, "", 0); + assert (rc == 0); + mw_sub->connect("ipc:///tmp/roofitMP_from_master_to_workers"); + mw_sub_poller.register_socket(*mw_sub, zmq::POLLIN); + + wm_push.reset(zmqSvc().socket_ptr(zmq::PUSH)); + rc = zmq_setsockopt (*wm_push, ZMQ_SNDHWM, &hwm, sizeof hwm); + assert (rc == 0); + wm_push->connect("ipc:///tmp/roofitMP_from_workers_to_master"); + + // check publisher connection and then wait until all subscribers are connected + ZmqLingeringSocketPtr<> subscriber_ping_socket {zmqSvc().socket_ptr(zmq::REQ)}; + subscriber_ping_socket->connect("ipc:///tmp/roofitMP_subscriber_ping_socket"); + auto all_connected = zmqSvc().receive(*mw_sub); + zmqSvc().send(*subscriber_ping_socket, "present"); + auto reply = zmqSvc().receive(*subscriber_ping_socket); + assert(reply == "roger"); + + while (!all_connected) { + all_connected = zmqSvc().receive(*mw_sub); + } + + close_this_QW_on_destruct_ = true; + } else { + // should never get here + throw std::runtime_error("Messenger ctor: I'm neither master, nor queue, nor a worker"); + } + } catch (zmq::error_t &e) { + std::cerr << e.what() << " -- errnum: " << e.num() << std::endl; + throw; + }; +} + +Messenger::~Messenger() { + printf("Messenger dtor on PID %d\n", getpid()); + if (close_MQ_on_destruct_) { + try { + mq_push.reset(nullptr); + mq_pull.reset(nullptr); + mw_pub.reset(nullptr); + wm_pull.reset(nullptr); + } catch (const std::exception& e) { + std::cerr << "WARNING: something in Messenger dtor threw an exception! Original exception message:\n" << e.what() << std::endl; + } + } + if (close_this_QW_on_destruct_) { + this_worker_qw_push.reset(nullptr); + this_worker_qw_pull.reset(nullptr); + mw_sub.reset(nullptr); + wm_push.reset(nullptr); + } + if (close_QW_container_on_destruct_) { + for (auto& socket : qw_push) { + socket.reset(nullptr); + } + for (auto& socket : qw_pull) { + socket.reset(nullptr); + } + } + zmqSvc().close_context(); +} + + +void Messenger::test_send(X2X ping_value, test_snd_pipes snd_pipe, std::size_t worker_id) { + try { + switch (snd_pipe) { + case test_snd_pipes::M2Q: { + send_from_master_to_queue(ping_value); + break; + } + case test_snd_pipes::Q2M : { + send_from_queue_to_master(ping_value); + break; + } + case test_snd_pipes::Q2W: { + send_from_queue_to_worker(worker_id, ping_value); + break; + } + case test_snd_pipes::W2Q: { + send_from_worker_to_queue(ping_value); + break; + } + } + } catch (zmq::error_t &e) { + if (e.num() == EAGAIN) { + throw std::runtime_error("Messenger::test_connections: SEND over master-queue connection timed out!"); + } else { + throw; + } + } +} + + +void Messenger::test_receive(X2X expected_ping_value, test_rcv_pipes rcv_pipe, std::size_t worker_id) { + X2X handshake = X2X::initial_value; + + std::size_t max_tries = 3, tries = 0; + bool carry_on = true; + while (carry_on && (tries++ < max_tries)) { + try { + switch (rcv_pipe) { + case test_rcv_pipes::fromMonQ: { + handshake = receive_from_master_on_queue(); + break; + } + case test_rcv_pipes::fromQonM: { + handshake = receive_from_queue_on_master(); + break; + } + case test_rcv_pipes::fromQonW: { + handshake = receive_from_queue_on_worker(); + break; + } + case test_rcv_pipes::fromWonQ: { + handshake = receive_from_worker_on_queue(worker_id); + break; + } + } + carry_on = false; + } catch (ZMQ::ppoll_error_t &e) { + auto response = handle_zmq_ppoll_error(e); + if (response == zmq_ppoll_error_response::abort) { + throw std::runtime_error("EINTR in test_receive and SIGTERM received, aborting\n"); + } else if (response == zmq_ppoll_error_response::unknown_eintr) { + printf("EINTR in test_receive but no SIGTERM received, try %lu\n", tries); + continue; + } else if (response == zmq_ppoll_error_response::retry) { + printf("EAGAIN in test_receive, try %lu\n", tries); + continue; + } + } catch (zmq::error_t &e) { + if (e.num() == EAGAIN) { + throw std::runtime_error("Messenger::test_connections: RECEIVE over master-queue connection timed out!"); + } else { + printf("unhandled zmq::error_t (not a ppoll_error_t) in Messenger::test_receive with errno %d: %s\n", e.num(), e.what()); + throw; + } + } + } + + if (handshake != expected_ping_value) { + throw std::runtime_error("Messenger::test_connections: RECEIVE over master-queue connection failed, did not receive pong!"); + } +} + + +void Messenger::test_connections(const ProcessManager &process_manager) { + // Before blocking SIGTERM, set the signal handler, so we can also check after blocking whether a signal occurred + // In our case, we already set it in the ProcessManager after forking to the queue and worker processes. + sigset_t sigmask; + sigemptyset(&sigmask); + sigaddset(&sigmask, SIGTERM); + sigprocmask(SIG_BLOCK, &sigmask, &ppoll_sigmask); + + if (process_manager.is_master()) { + test_send(X2X::ping, test_snd_pipes::M2Q, -1); + test_receive(X2X::pong, test_rcv_pipes::fromQonM, -1); + test_receive(X2X::ping, test_rcv_pipes::fromQonM, -1); + test_send(X2X::pong, test_snd_pipes::M2Q, -1); + } else if (process_manager.is_queue()) { + ZeroMQPoller poller; + std::size_t mq_index; + std::tie(poller, mq_index) = create_queue_poller(); + + for (std::size_t ix = 0; ix < process_manager.N_workers(); ++ix) { + test_send(X2X::ping, test_snd_pipes::Q2W, ix); + } + + while (!process_manager.sigterm_received() && (poller.size() > 0)) { + // poll: wait until status change (-1: infinite timeout) + std::vector> poll_result; + bool abort; + std::tie(poll_result, abort) = careful_ppoll(poller, ppoll_sigmask); + if (abort) break; + + // then process incoming messages from sockets + for (auto readable_socket : poll_result) { + // message comes from the master/queue socket (first element): + if (readable_socket.first == mq_index) { + test_receive(X2X::ping, test_rcv_pipes::fromMonQ, -1); + test_send(X2X::pong, test_snd_pipes::Q2M, -1); + test_send(X2X::ping, test_snd_pipes::Q2M, -1); + test_receive(X2X::pong, test_rcv_pipes::fromMonQ, -1); + poller.unregister_socket(*mq_pull); + } else { // from a worker socket + // TODO: dangerous assumption for this_worker_id, may become invalid if we allow multiple queue_loops on the same process! + auto this_worker_id = readable_socket.first - 1; // TODO: replace with a more reliable lookup + + test_receive(X2X::pong, test_rcv_pipes::fromWonQ, this_worker_id); + test_receive(X2X::ping, test_rcv_pipes::fromWonQ, this_worker_id); + test_send(X2X::pong, test_snd_pipes::Q2W, this_worker_id); + + poller.unregister_socket(*qw_pull[this_worker_id]); + } + } + } + + } else if (process_manager.is_worker()) { + test_receive(X2X::ping, test_rcv_pipes::fromQonW, -1); + test_send(X2X::pong, test_snd_pipes::W2Q, -1); + test_send(X2X::ping, test_snd_pipes::W2Q, -1); + test_receive(X2X::pong, test_rcv_pipes::fromQonW, -1); + } else { + // should never get here + throw std::runtime_error("Messenger::test_connections: I'm neither master, nor queue, nor a worker"); + } + + // clean up signal management modifications + sigprocmask(SIG_SETMASK, &ppoll_sigmask, nullptr); + printf("done with test_connections on PID %d\n", getpid()); +} + + +std::pair Messenger::create_queue_poller() { + ZeroMQPoller poller; + std::size_t mq_index = poller.register_socket(*mq_pull, zmq::POLLIN); + for (auto &s : qw_pull) { + poller.register_socket(*s, zmq::POLLIN); + } + return {std::move(poller), mq_index}; +} + + +std::pair Messenger::create_worker_poller() { + ZeroMQPoller poller; + poller.register_socket(*this_worker_qw_pull, zmq::POLLIN); + std::size_t mw_sub_index = poller.register_socket(*mw_sub, zmq::POLLIN); + return {std::move(poller), mw_sub_index}; +} + + +// -- WORKER - QUEUE COMMUNICATION -- + +void Messenger::send_from_worker_to_queue() {} + +void Messenger::send_from_queue_to_worker(std::size_t /*this_worker_id*/) {} + +// -- QUEUE - MASTER COMMUNICATION -- + +void Messenger::send_from_queue_to_master() {} + +void Messenger::send_from_master_to_queue() {} + +void Messenger::set_send_flag(int flag) { + if (flag == 0 || flag == ZMQ_DONTWAIT || flag == ZMQ_SNDMORE || flag == (ZMQ_DONTWAIT | ZMQ_SNDMORE)) { + send_flag = flag; + } else { + throw std::runtime_error("in Messenger::set_send_flag: trying to set illegal flag, see zmq_send API for allowed flags"); + } +} + +// -- MASTER - WORKER COMMUNICATION -- + +void Messenger::publish_from_master_to_workers() {} + +void Messenger::send_from_worker_to_master() {} + + +// for debugging +#define PROCESS_VAL(p) case(p): s = #p; break; + +std::ostream& operator<<(std::ostream& out, const M2Q value){ + std::string s; + switch(value){ + PROCESS_VAL(M2Q::terminate); + PROCESS_VAL(M2Q::enqueue); + PROCESS_VAL(M2Q::retrieve); + PROCESS_VAL(M2Q::update_real); + PROCESS_VAL(M2Q::update_bool); + default: s = std::to_string(static_cast(value)); + } + return out << s; +} + +std::ostream& operator<<(std::ostream& out, const Q2M value){ + std::string s; + switch(value){ + PROCESS_VAL(Q2M::retrieve_rejected); + PROCESS_VAL(Q2M::retrieve_accepted); + PROCESS_VAL(Q2M::retrieve_later); + default: s = std::to_string(static_cast(value)); + } + return out << s; +} + +std::ostream& operator<<(std::ostream& out, const W2Q value){ + std::string s; + switch(value){ + PROCESS_VAL(W2Q::dequeue); + PROCESS_VAL(W2Q::send_result); + default: s = std::to_string(static_cast(value)); + } + return out << s; +} + +std::ostream& operator<<(std::ostream& out, const Q2W value){ + std::string s; + switch(value){ + PROCESS_VAL(Q2W::terminate); + PROCESS_VAL(Q2W::dequeue_rejected); + PROCESS_VAL(Q2W::dequeue_accepted); + PROCESS_VAL(Q2W::update_real); + PROCESS_VAL(Q2W::result_received); + PROCESS_VAL(Q2W::update_bool); + default: s = std::to_string(static_cast(value)); + } + return out << s; +} + +std::ostream& operator<<(std::ostream& out, const X2X value){ + std::string s; + switch(value){ + PROCESS_VAL(X2X::ping); + PROCESS_VAL(X2X::pong); + default: s = std::to_string(static_cast(value)); + } + return out << s; +} + +#undef PROCESS_VAL + +void Messenger::debug_print(std::string /*s*/) +{ +// printf("%s\n", s.c_str()); +// std::cerr << s << std::endl; +} + +} // namespace MultiProcess +} // namespace RooFit diff --git a/roofit/multiprocess/src/ProcessManager.cxx b/roofit/multiprocess/src/ProcessManager.cxx new file mode 100644 index 0000000000000..6720443b9dddd --- /dev/null +++ b/roofit/multiprocess/src/ProcessManager.cxx @@ -0,0 +1,286 @@ +/* + * Project: RooFit + * Authors: + * PB, Patrick Bos, Netherlands eScience Center, p.bos@esciencecenter.nl + * IP, Inti Pelupessy, Netherlands eScience Center, i.pelupessy@esciencecenter.nl + * + * Copyright (c) 2016-2019, Netherlands eScience Center + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ + +#include // for strsignal +#include // for wait +#include +#include + +#include "RooFit/MultiProcess/util.h" +#include "RooFit/MultiProcess/JobManager.h" +#include "RooFit/MultiProcess/Messenger.h" +#include "RooFit/MultiProcess/ProcessManager.h" + +namespace RooFit { +namespace MultiProcess { + + +ProcessManager::ProcessManager(std::size_t N_workers) : _N_workers(N_workers) { + // This class defines three types of processes: + // 1. master: the initial main process. It defines and enqueues tasks + // and processes results. + // 2. workers: a pool of processes that will try to take tasks from the + // queue. These are forked from master. + // 3. queue: communication between the other types (necessarily) goes + // through this process. This process runs the queue_loop and + // maintains the queue of tasks. + + initialize_processes(); +} + +ProcessManager::~ProcessManager() +{ + if (is_master()) { + terminate(); + } else { + wait_for_sigterm_then_exit(); + } +} + + +// static member initialization +volatile sig_atomic_t ProcessManager::_sigterm_received = 0; + +// static function +void ProcessManager::handle_sigterm(int signum) { + // We need this to tell the children to die, because we can't talk + // to them anymore during JobManager destruction, because that kills + // the Messenger first. We do that with SIGTERMs. The sigterm_received() + // should be checked in message loops to stop them when it's true. + _sigterm_received = 1; + printf("handled %s on PID %d\n", strsignal(signum), getpid()); +} + +// static function +bool ProcessManager::sigterm_received() { + if (_sigterm_received > 0) { + return true; + } else { + return false; + } +} + +void ProcessManager::initialize_processes(bool cpu_pinning) { + // Initialize processes; + // ... first workers: + worker_pids.resize(_N_workers); + pid_t child_pid {}; + for (std::size_t ix = 0; ix < _N_workers; ++ix) { + child_pid = fork(); + if (!child_pid) { // we're on the worker + _is_worker = true; + _worker_id = ix; + break; + } else { // we're on master + worker_pids[ix] = child_pid; + } + } + + // ... then queue: + if (child_pid) { // we're on master + queue_pid = fork(); + if (!queue_pid) { // we're now on queue + _is_queue = true; + } else { + _is_master = true; + } + } + +// printf("ATTACH NOOOOOOW PID %d\n", getpid()); +// sleep(30); + + // set the sigterm handler on the child processes + if (!_is_master) { + struct sigaction sa; + memset (&sa, '\0', sizeof(sa)); + sa.sa_handler = ProcessManager::handle_sigterm; + + if (sigaction(SIGTERM, &sa, NULL) < 0) { + std::perror("sigaction failed"); + std::exit(1); + } + } + + if (cpu_pinning) { +#if defined(__APPLE__) + #ifndef NDEBUG + static bool affinity_warned = false; + if (is_master() & !affinity_warned) { + std::cout << "CPU affinity cannot be set on macOS" << std::endl; + affinity_warned = true; + } + #endif // NDEBUG +#elif defined(_WIN32) + #ifndef NDEBUG + if (is_master()) std::cerr << "WARNING: CPU affinity setting not implemented on Windows, continuing..." << std::endl; + #endif // NDEBUG +#else + cpu_set_t mask; + // zero all bits in mask + CPU_ZERO(&mask); + // set correct bit + std::size_t set_cpu; + if (is_master()) { + set_cpu = _N_workers + 1; + } else if (is_queue()) { + set_cpu = _N_workers; + } else { + set_cpu = _worker_id; + } + CPU_SET(set_cpu, &mask); + #ifndef NDEBUG + // sched_setaffinity returns 0 on success + if (sched_setaffinity(0, sizeof(mask), &mask) == -1) { + std::cerr << "WARNING: Could not set CPU affinity, continuing..." << std::endl; + } else { + std::cerr << "CPU affinity set to cpu " << set_cpu << " in process " << getpid() << std::endl; + } + #endif // NDEBUG +#endif + } + +#ifndef NDEBUG + identify_processes(); +#endif // NDEBUG + + initialized = true; +} + +bool ProcessManager::is_initialized() const { + return initialized; +} + + +void ProcessManager::terminate() noexcept { + try { + if (is_master() && is_initialized()) { +// JobManager::instance()->messenger().send_from_master_to_queue(M2Q::terminate); + shutdown_processes(); + } + } catch (const std::exception& e) { + std::cerr << "WARNING: something in ProcessManager::terminate threw an exception! Original exception message:\n" << e.what() << std::endl; + } +} + + +void ProcessManager::wait_for_sigterm_then_exit() { + if (!is_master()) { + while(!sigterm_received()) {} + std::_Exit(0); + } +} + + +//void ProcessManager::terminate_workers() { +// if (is_queue()) { +// for(std::size_t worker_ix = 0; worker_ix < _N_workers; ++worker_ix) { +// JobManager::instance()->messenger().send_from_queue_to_worker(worker_ix, Q2W::terminate); +// } +// JobManager::instance()->messenger().close_queue_worker_connections(); +// } +//} + + +int chill_wait() { + int status = 0; + pid_t pid; + do { + pid = wait(&status); + } while (-1 == pid && EINTR == errno); // retry on interrupted system call + + if (0 != status) { + if (WIFEXITED(status)) { + printf("exited, status=%d\n", WEXITSTATUS(status)); + } else if (WIFSIGNALED(status)) { + printf("killed by signal %d\n", WTERMSIG(status)); + } else if (WIFSTOPPED(status)) { + printf("stopped by signal %d\n", WSTOPSIG(status)); + } else if (WIFCONTINUED(status)) { + printf("continued\n"); + } + } + + if (-1 == pid) { + if (errno == ECHILD) { + printf("chill_wait: no children (got ECHILD error code from wait call), done\n"); + } else { + throw std::runtime_error(std::string("chill_wait: error in wait call: ") + strerror(errno) + std::string(", errno ") + std::to_string(errno)); + } + } + + return pid; +} + +void ProcessManager::shutdown_processes() { + if (is_master()) { + // terminate all children + std::unordered_set children; + children.insert(queue_pid); + kill(queue_pid, SIGTERM); + for (auto pid : worker_pids) { + kill(pid, SIGTERM); + children.insert(pid); + } + // then wait for them to actually die and clean out the zombies + while (!children.empty()) { + pid_t pid = chill_wait(); + children.erase(pid); + } + } + + initialized = false; +} + + +// Getters + +bool ProcessManager::is_master() const { + return _is_master; +} + +bool ProcessManager::is_queue() const { + return _is_queue; +} + +bool ProcessManager::is_worker() const { + return _is_worker; +} + +std::size_t ProcessManager::worker_id() const { + return _worker_id; +} + +std::size_t ProcessManager::N_workers() const { + return _N_workers; +} + + +// Debugging + +void ProcessManager::identify_processes() const { + // identify yourselves (for debugging) + if (_is_worker) { + printf("I'm a worker, PID %d\n", getpid()); + } else if (_is_master) { + printf("I'm master, PID %d\n", getpid()); + } else if (_is_queue) { + printf("I'm queue, PID %d\n", getpid()); + } else { + printf("I'm not master, queue or worker, weird! PID %d\n", getpid()); + } +} + +} // namespace MultiProcess +} // namespace RooFit diff --git a/roofit/multiprocess/src/Queue.cxx b/roofit/multiprocess/src/Queue.cxx new file mode 100644 index 0000000000000..5ea167812de59 --- /dev/null +++ b/roofit/multiprocess/src/Queue.cxx @@ -0,0 +1,224 @@ +/* + * Project: RooFit + * Authors: + * PB, Patrick Bos, Netherlands eScience Center, p.bos@esciencecenter.nl + * IP, Inti Pelupessy, Netherlands eScience Center, i.pelupessy@esciencecenter.nl + * + * Copyright (c) 2016-2019, Netherlands eScience Center + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ + +#include "RooFit/MultiProcess/JobManager.h" +#include "RooFit/MultiProcess/ProcessManager.h" +#include "RooFit/MultiProcess/Job.h" // complete Job object for JobManager::get_job_object() +#include "RooFit/MultiProcess/Queue.h" +#include "RooFit/MultiProcess/util.h" + +namespace RooFit { +namespace MultiProcess { + +// Have a worker ask for a task-message from the queue +bool Queue::pop(JobTask &job_task) +{ + if (_queue.empty()) { + return false; + } else { + job_task = _queue.front(); + _queue.pop(); + return true; + } +} + +// Enqueue a task +void Queue::add(JobTask job_task) +{ + if (JobManager::instance()->process_manager().is_master()) { +// not necessary, the job adds tasks, so it is already activated there; TODO: remove commented out lines +// if (!JobManager::instance()->is_activated()) { +// JobManager::instance()->activate(); +// } + JobManager::instance()->messenger().send_from_master_to_queue(M2Q::enqueue, job_task.first, job_task.second); + } else if (JobManager::instance()->process_manager().is_queue()) { + _queue.push(job_task); + } else { + throw std::logic_error("calling Communicator::to_master_queue from slave process"); + } +} + + +bool Queue::process_master_message(M2Q message) +{ + bool carry_on = true; + + switch (message) { + case M2Q::terminate: { + carry_on = false; + break; + } + + case M2Q::enqueue: { + // enqueue task + auto job_object_id = JobManager::instance()->messenger().receive_from_master_on_queue(); + auto task = JobManager::instance()->messenger().receive_from_master_on_queue(); + JobTask job_task(job_object_id, task); + add(job_task); + N_tasks++; + break; + } + + case M2Q::retrieve: { + // retrieve task results after queue is empty and all + // tasks have been completed + if (_queue.empty() && N_tasks_at_workers == 0 && N_tasks_completed == 0) { + JobManager::instance()->messenger().send_from_queue_to_master(Q2M::retrieve_rejected); // handshake message: no tasks enqueued, premature retrieve! + } else if (_queue.empty() && N_tasks_completed == N_tasks) { + JobManager::instance()->results_from_queue_to_master(); + // reset number of received and completed tasks + N_tasks = 0; + N_tasks_completed = 0; + } else { + JobManager::instance()->messenger().send_from_queue_to_master(Q2M::retrieve_later); // handshake message: tasks not done yet, try again + } + break; + } + + case M2Q::update_real: { +// auto get_time = []() { +// return std::chrono::duration_cast( +// std::chrono::high_resolution_clock::now().time_since_epoch()) +// .count(); +// }; +// auto t1 = get_time(); + auto job_id = JobManager::instance()->messenger().receive_from_master_on_queue(); + auto ix = JobManager::instance()->messenger().receive_from_master_on_queue(); + auto val = JobManager::instance()->messenger().receive_from_master_on_queue(); + auto is_constant = JobManager::instance()->messenger().receive_from_master_on_queue(); +// std::cout << "\ngot ix = " << ix << ", job_id = " << job_id << ", val = " << val << ", is_constant = " << is_constant << " in queue loop from M2Q::update_real" << std::endl; + for (std::size_t worker_ix = 0; worker_ix < JobManager::instance()->process_manager().N_workers(); ++worker_ix) { + JobManager::instance()->messenger().send_from_queue_to_worker(worker_ix, Q2W::update_real, job_id, ix, val, is_constant); + } +// auto t2 = get_time(); +// std::cout << "update_real on queue: " << (t2 - t1) / 1.e9 << "s (from " << t1 << " to " << t2 << "ns)" << std::endl; + break; + } + + case M2Q::update_bool: { + auto job_id = JobManager::instance()->messenger().receive_from_master_on_queue(); + auto ix = JobManager::instance()->messenger().receive_from_master_on_queue(); + auto value = JobManager::instance()->messenger().receive_from_master_on_queue(); + for (std::size_t worker_ix = 0; worker_ix < JobManager::instance()->process_manager().N_workers(); ++worker_ix) { + JobManager::instance()->messenger().send_from_queue_to_worker(worker_ix, Q2W::update_bool, job_id, ix, value); + } + } + } + + return carry_on; +} + + +void Queue::process_worker_message(std::size_t this_worker_id, W2Q message) +{ + switch (message) { + case W2Q::dequeue: { + // dequeue task + JobTask job_task; + bool popped = pop(job_task); + if (popped) { + // Note: below two commands should be run atomically for thread safety (if that ever becomes an issue) + JobManager::instance()->messenger().send_from_queue_to_worker(this_worker_id, Q2W::dequeue_accepted, job_task.first, job_task.second); + ++N_tasks_at_workers; + } else { + JobManager::instance()->messenger().send_from_queue_to_worker(this_worker_id, Q2W::dequeue_rejected); + } + break; + } + + case W2Q::send_result: { + // receive back task result + auto job_object_id = JobManager::instance()->messenger().receive_from_worker_on_queue(this_worker_id); + auto task = JobManager::instance()->messenger().receive_from_worker_on_queue(this_worker_id); + JobManager::get_job_object(job_object_id)->receive_task_result_on_queue(task, this_worker_id); + JobManager::instance()->messenger().send_from_queue_to_worker(this_worker_id, Q2W::result_received); + N_tasks_completed++; + --N_tasks_at_workers; + break; + } + } +} + + +void Queue::loop() +{ + assert(JobManager::instance()->process_manager().is_queue()); + bool carry_on = true; + ZeroMQPoller poller; + std::size_t mq_index; + std::tie(poller, mq_index) = JobManager::instance()->messenger().create_queue_poller(); + + // Before blocking SIGTERM, set the signal handler, so we can also check after blocking whether a signal occurred + // In our case, we already set it in the ProcessManager after forking to the queue and worker processes. + + sigset_t sigmask; + sigemptyset(&sigmask); + sigaddset(&sigmask, SIGTERM); + sigprocmask(SIG_BLOCK, &sigmask, &JobManager::instance()->messenger().ppoll_sigmask); + + // Before doing anything, check whether we have received a terminate signal while blocking signals! + // In this case, we also do that in the while condition. + while (!ProcessManager::sigterm_received() && carry_on) { + try { // watch for zmq_error from ppoll caused by SIGTERM from master + // poll: wait until status change (-1: infinite timeout) + auto poll_result = poller.ppoll(-1, &JobManager::instance()->messenger().ppoll_sigmask); +// JobManager::instance()->messenger().N_available_polled_results = poll_result.size(); + // then process incoming messages from sockets + for (auto readable_socket : poll_result) { + // message comes from the master/queue socket (first element): + if (readable_socket.first == mq_index) { + auto message = JobManager::instance()->messenger().receive_from_master_on_queue(); + carry_on = process_master_message(message); + // on terminate, also stop for-loop, no need to check other + // sockets anymore: + if (!carry_on) { + break; + } + } else { // from a worker socket + // TODO: dangerous assumption for this_worker_id, may become invalid if we allow multiple queue_loops on the same process! + auto this_worker_id = readable_socket.first - 1; // TODO: replace with a more reliable lookup + auto message = JobManager::instance()->messenger().receive_from_worker_on_queue(this_worker_id); + process_worker_message(this_worker_id, message); + } + } + } catch (ZMQ::ppoll_error_t& e) { + zmq_ppoll_error_response response; + try { + response = handle_zmq_ppoll_error(e); + } catch (std::logic_error& e) { + printf("queue loop got unhandleable ZMQ::ppoll_error_t\n"); + throw; + } + if (response == zmq_ppoll_error_response::abort) { + break; + } else if (response == zmq_ppoll_error_response::unknown_eintr) { + printf("EINTR in queue loop but no SIGTERM received, continuing\n"); + continue; + } else if (response == zmq_ppoll_error_response::retry) { + printf("EAGAIN from ppoll in queue loop, continuing\n"); + continue; + } + } catch (zmq::error_t& e) { + printf("unhandled zmq::error_t (not a ppoll_error_t) in queue loop with errno %d: %s\n", e.num(), e.what()); + throw; + } + } + + // clean up signal management modifications + sigprocmask(SIG_SETMASK, &JobManager::instance()->messenger().ppoll_sigmask, nullptr); +} + +} // namespace MultiProcess +} // namespace RooFit diff --git a/roofit/multiprocess/src/util.cxx b/roofit/multiprocess/src/util.cxx new file mode 100644 index 0000000000000..ff9aa14120eac --- /dev/null +++ b/roofit/multiprocess/src/util.cxx @@ -0,0 +1,130 @@ +/* + * Project: RooFit + * Authors: + * PB, Patrick Bos, Netherlands eScience Center, p.bos@esciencecenter.nl + * + * Copyright (c) 2016-2019, Netherlands eScience Center + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ + +#include // kill, SIGKILL +#include // cerr, and indirectly WNOHANG, EINTR, W* macros +#include // runtime_error +#include // waitpid +#include + +#include +#include + +namespace RooFit { +namespace MultiProcess { + +int wait_for_child(pid_t child_pid, bool may_throw, int retries_before_killing) +{ + int status = 0; + int patience = retries_before_killing; + pid_t tmp; + do { + if (patience-- < 1) { + ::kill(child_pid, SIGKILL); + } + tmp = waitpid(child_pid, &status, WNOHANG); + } while (tmp == 0 // child has not yet changed state, try again + || (-1 == tmp && EINTR == errno) // retry on interrupted system call + ); + + if (patience < 1) { + std::cout << "Had to send PID " << child_pid << " " << (-patience + 1) << " SIGKILLs"; + } + + if (0 != status) { + if (WIFEXITED(status)) { + printf("exited, status=%d\n", WEXITSTATUS(status)); + } else if (WIFSIGNALED(status)) { + printf("killed by signal %d\n", WTERMSIG(status)); + } else if (WIFSTOPPED(status)) { + printf("stopped by signal %d\n", WSTOPSIG(status)); + } else if (WIFCONTINUED(status)) { + printf("continued\n"); + } + } + + if (-1 == tmp && may_throw) + throw std::runtime_error(std::string("waitpid, errno ") + std::to_string(errno)); + + return status; +} + +zmq_ppoll_error_response handle_zmq_ppoll_error(ZMQ::ppoll_error_t &e) +{ + if ((e.num() == EINTR) && (ProcessManager::sigterm_received())) { + // valid EINTR, because we want to exit and kill the processes on SIGTERM + return zmq_ppoll_error_response::abort; + } else if (e.num() == EINTR) { + // on other EINTRs, we retry (mostly this happens in debuggers) + // printf("got EINTR but no SIGTERM received"); + return zmq_ppoll_error_response::unknown_eintr; + } else if (e.num() == EAGAIN) { + // This can happen from recv if ppoll initially gets a read-ready signal for a socket, + // but the received data does not pass the checksum test, so the socket becomes unreadable + // again or from non-blocking send if the socket becomes unwritable either due to the HWM + // being reached or the socket not being connected (anymore). The latter case usually means + // the connection has been severed from the other side, meaning it has probably been killed + // and in that case the next ppoll call will probably also receive a SIGTERM, ending the + // loop. In case something else is wrong, this message will print multiple times, which + // should be taken as a cue for writing a bug report :) + // TODO: handle this more rigorously + // printf("EAGAIN (from either send or receive), try %d\n", tries); + return zmq_ppoll_error_response::retry; + } else { + char buffer[512]; + snprintf(buffer, 512, "handle_zmq_ppoll_error is out of options to handle exception, caught ZMQ::ppoll_error_t had errno %d and text: %s\n", + e.num(), e.what()); + throw std::logic_error(buffer); + } +} + +// returns a tuple containing first the poll result and second a boolean flag that tells the caller whether it should +// abort the enclosing loop +std::tuple>, bool> +careful_ppoll(ZeroMQPoller &poller, const sigset_t &ppoll_sigmask, std::size_t max_tries) +{ + std::size_t tries = 0; + std::vector> poll_result; + bool abort = true; + bool carry_on = true; + while (carry_on && (tries++ < max_tries)) { + if (tries > 1) { + printf("careful_ppoll try %lu\n", tries); + } + try { // watch for zmq_error from ppoll caused by SIGTERM from master + poll_result = poller.ppoll(-1, &ppoll_sigmask); + abort = false; + carry_on = false; + } catch (ZMQ::ppoll_error_t &e) { + auto response = handle_zmq_ppoll_error(e); + if (response == zmq_ppoll_error_response::abort) { + break; + } else if (response == zmq_ppoll_error_response::unknown_eintr) { + printf("EINTR in careful_ppoll but no SIGTERM received, try %lu\n", tries); + continue; + } else if (response == zmq_ppoll_error_response::retry) { + printf("EAGAIN in careful_ppoll (from either send or receive), try %lu\n", tries); + continue; + } + } + } + + if (tries == max_tries) { + printf("careful_ppoll reached maximum number of tries, %lu, please report as a bug\n", tries); + } + return {poll_result, abort}; +} + +} // namespace MultiProcess +} // namespace RooFit \ No newline at end of file diff --git a/roofit/multiprocess/src/worker.cxx b/roofit/multiprocess/src/worker.cxx new file mode 100644 index 0000000000000..86191dfd694a4 --- /dev/null +++ b/roofit/multiprocess/src/worker.cxx @@ -0,0 +1,228 @@ +/* + * Project: RooFit + * Authors: + * PB, Patrick Bos, Netherlands eScience Center, p.bos@esciencecenter.nl + * IP, Inti Pelupessy, Netherlands eScience Center, i.pelupessy@esciencecenter.nl + * + * Copyright (c) 2016-2019, Netherlands eScience Center + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ + +#include // getpid, pid_t +#include // EINTR +#include +#include // sigprocmask etc + +#include "RooFit/MultiProcess/JobManager.h" +#include "RooFit/MultiProcess/types.h" +#include "RooFit/MultiProcess/Messenger.h" +#include "RooFit/MultiProcess/Job.h" +#include "RooFit/MultiProcess/util.h" + +#include "RooFit/MultiProcess/worker.h" + +namespace RooFit { +namespace MultiProcess { + +static bool worker_loop_running = false; + +bool is_worker_loop_running() +{ + return worker_loop_running; +} + +bool process_queue_message(Q2W message_q2w, bool &dequeue_acknowledged) +{ + bool carry_on = true; + + auto get_time = []() { + return std::chrono::duration_cast( + std::chrono::high_resolution_clock::now().time_since_epoch()) + .count(); + }; + decltype(get_time()) t1 = 0, t2 = 0; + + + switch (message_q2w) { + case Q2W::terminate: { + carry_on = false; + break; + } + + case Q2W::dequeue_rejected: { + t2 = get_time(); +// std::cout +// << "no work: worker " << JobManager::instance()->process_manager().worker_id() << " asked at " << t1 +// << " and got rejected at " << t2 << std::endl; + + dequeue_acknowledged = true; + break; + } + case Q2W::dequeue_accepted: { + dequeue_acknowledged = true; + auto job_id = JobManager::instance()->messenger().receive_from_queue_on_worker(); + auto task = JobManager::instance()->messenger().receive_from_queue_on_worker(); + + JobManager::get_job_object(job_id)->evaluate_task(task); + + // std::cout +// << "task done: worker " << JobManager::instance()->process_manager().worker_id() << " asked at " << t1 << ", started at " +// << t2 << " and finished at " << t3 << std::endl; + +// { +// // old +// JobManager::instance()->messenger().send_from_worker_to_queue(W2Q::send_result, job_id, task); +// JobManager::get_job_object(job_id)->send_back_task_result_from_worker(task); +// +// message_q2w = JobManager::instance()->messenger().receive_from_queue_on_worker(); +// if (message_q2w != Q2W::result_received) { +// std::cerr << "worker " << getpid() +// << " sent result, but did not receive Q2W::result_received handshake! Got " << message_q2w +// << " instead." << std::endl; +// throw std::runtime_error(""); +// } + +// } + { + // new +// JobManager::instance()->messenger().send_from_worker_to_master(job_id, task); + JobManager::get_job_object(job_id)->send_back_task_result_from_worker(task); + } + + // std::cout << "task done: worker " << JobManager::instance()->process_manager().worker_id() << " sent back results" << std::endl; + +// std::cout << "task done: worker " << JobManager::instance()->process_manager().worker_id() << " finished dequeue_accepted" << std::endl; + break; + } + + // previously this was non-work mode + + case Q2W::update_real: { + t1 = get_time(); + + auto job_id = JobManager::instance()->messenger().receive_from_queue_on_worker(); + auto ix = JobManager::instance()->messenger().receive_from_queue_on_worker(); + auto val = JobManager::instance()->messenger().receive_from_queue_on_worker(); + bool is_constant = JobManager::instance()->messenger().receive_from_queue_on_worker(); + JobManager::get_job_object(job_id)->update_real(ix, val, is_constant); + + t2 = get_time(); +// std::cout +// << "update_real on worker " << JobManager::instance()->process_manager().worker_id() << ": " << (t2 - t1) / 1.e9 +// << "s (from " << t1 << " to " << t2 << "ns)" << std::endl; + + break; + } + + case Q2W::update_bool: { + auto job_id = JobManager::instance()->messenger().receive_from_queue_on_worker(); + auto ix = JobManager::instance()->messenger().receive_from_queue_on_worker(); + auto value = JobManager::instance()->messenger().receive_from_queue_on_worker(); + JobManager::get_job_object(job_id)->update_bool(ix, value); + + break; + } + + case Q2W::result_received: { + std::cerr << "In worker_loop: " << message_q2w + << " message received, but should only be received as handshake!" << std::endl; + break; + } + } + + return carry_on; +} + +void worker_loop() +{ + assert(JobManager::instance()->process_manager().is_worker()); + worker_loop_running = true; + bool carry_on = true; +// Task task; + std::size_t job_id; + Q2W message_q2w; + + // use a flag to not ask twice + bool dequeue_acknowledged = true; + + ZeroMQPoller poller; + std::size_t mw_sub_index; + + std::tie(poller, mw_sub_index) = JobManager::instance()->messenger().create_worker_poller(); + + // Before blocking SIGTERM, set the signal handler, so we can also check after blocking whether a signal occurred + // In our case, we already set it in the ProcessManager after forking to the queue and worker processes. + + sigset_t sigmask; + sigemptyset(&sigmask); + sigaddset(&sigmask, SIGTERM); + sigprocmask(SIG_BLOCK, &sigmask, &JobManager::instance()->messenger().ppoll_sigmask); + + // Before doing anything, check whether we have received a terminate signal while blocking signals! + // In this case, we also do that in the while condition. + while (!ProcessManager::sigterm_received() && carry_on) { + try { // watch for error from ppoll (which is called inside receive functions) caused by SIGTERM from master + + // try to dequeue a task + if (dequeue_acknowledged) { // don't ask twice + JobManager::instance()->messenger().send_from_worker_to_queue(W2Q::dequeue); + dequeue_acknowledged = false; + } + +// // receive handshake +// message_q2w = JobManager::instance()->messenger().receive_from_queue_on_worker(); + // receive handshake, other message or update from sub socket + auto poll_result = poller.ppoll(-1, &JobManager::instance()->messenger().ppoll_sigmask); +// JobManager::instance()->messenger().N_available_polled_results = poll_result.size(); + // then process incoming messages from sockets + for (auto readable_socket : poll_result) { + // message comes from the master/queue socket (first element): + if (readable_socket.first == mw_sub_index) { + job_id = JobManager::instance()->messenger().receive_from_master_on_worker(); + JobManager::get_job_object(job_id)->update_state(); + } else { // from queue socket + message_q2w = JobManager::instance()->messenger().receive_from_queue_on_worker(); + carry_on = process_queue_message(message_q2w, dequeue_acknowledged); + // on terminate, also stop for-loop, no need to check other + // sockets anymore: + if (!carry_on) { + break; + } + } + } + + } catch (ZMQ::ppoll_error_t& e) { + zmq_ppoll_error_response response; + try { + response = handle_zmq_ppoll_error(e); + } catch (std::logic_error& e) { + printf("worker loop at PID %d got unhandleable ZMQ::ppoll_error_t\n", getpid()); + throw; + } + if (response == zmq_ppoll_error_response::abort) { + break; + } else if (response == zmq_ppoll_error_response::unknown_eintr) { + printf("EINTR in worker loop at PID %d but no SIGTERM received, continuing\n", getpid()); + continue; + } else if (response == zmq_ppoll_error_response::retry) { + printf("EAGAIN from ppoll in worker loop at PID %d, continuing\n", getpid()); + continue; + } + } catch (zmq::error_t& e) { + printf("unhandled zmq::error_t (not a ppoll_error_t) in worker loop at PID %d with errno %d: %s\n", getpid(), e.num(), e.what()); + throw; + } + } + // clean up signal management modifications + sigprocmask(SIG_SETMASK, &JobManager::instance()->messenger().ppoll_sigmask, nullptr); + + worker_loop_running = false; +} + +} // namespace MultiProcess +} // namespace RooFit diff --git a/roofit/multiprocess/test/CMakeLists.txt b/roofit/multiprocess/test/CMakeLists.txt new file mode 100644 index 0000000000000..f71718cfe7fdf --- /dev/null +++ b/roofit/multiprocess/test/CMakeLists.txt @@ -0,0 +1,13 @@ +# @author Patrick Bos, NL eScience Center, 2019 + +#find_package(ZeroMQ REQUIRED) + +add_library(RooFit_multiprocess_testing_utils INTERFACE) +target_link_libraries(RooFit_multiprocess_testing_utils INTERFACE RooFitCore RooBatchCompute) + +ROOT_ADD_GTEST(test_RooFitMultiProcess_Messenger test_Messenger.cxx LIBRARIES RooFitMultiProcess ${ZeroMQ_LIBRARY}) +ROOT_ADD_GTEST(test_RooFitMultiProcess_ProcessManager test_ProcessManager.cxx LIBRARIES RooFitMultiProcess ${ZeroMQ_LIBRARY}) +ROOT_ADD_GTEST(test_RooFitMultiProcess_Job test_Job.cxx LIBRARIES RooFitMultiProcess ${ZeroMQ_LIBRARY} RooFit_multiprocess_testing_utils) + +#target_include_directories(test_RooFitMultiProcess_Job PUBLIC ${ZeroMQ_INCLUDE_DIR}) +#target_include_directories(test_RooFitMultiProcess_Messenger PUBLIC ${ZeroMQ_INCLUDE_DIR}) diff --git a/roofit/multiprocess/test/test_Job.cxx b/roofit/multiprocess/test/test_Job.cxx new file mode 100644 index 0000000000000..6b71f7e3369fa --- /dev/null +++ b/roofit/multiprocess/test/test_Job.cxx @@ -0,0 +1,252 @@ +/***************************************************************************** + * Project: RooFit * + * Package: RooFitCore * + * @(#)root/roofitcore:$Id$ + * Authors: * + * PB, Patrick Bos, NL eScience Center, p.bos@esciencecenter.nl * + * IP, Inti Pelupessy, NL eScience Center, i.pelupessy@esciencecenter.nl * + * VC, Vince Croft, DIANA / NYU, vincent.croft@cern.ch * + * * + * Redistribution and use in source and binary forms, * + * with or without modification, are permitted according to the terms * + * listed in LICENSE (http://roofit.sourceforge.net/license.txt) * + *****************************************************************************/ + +#include +#include + +#include +#include // JobTask +// needed to complete type returned from... +#include // ... Job::get_manager() +#include // ... JobManager::process_manager() +#include // ... JobManager::queue() + +#include "gtest/gtest.h" +#include "utils.h" + +class xSquaredPlusBVectorSerial { +public: + xSquaredPlusBVectorSerial(double b, std::vector x_init) : _b(b), x(std::move(x_init)), result(x.size()) {} + + void evaluate() + { + // call evaluate_task for each task + for (std::size_t ix = 0; ix < x.size(); ++ix) { + result[ix] = std::pow(x[ix], 2) + _b; + } + } + + std::vector get_result() + { + evaluate(); + return result; + } + + // for simplicity of the example (avoiding getters/setters) we make data members public as well + double _b; + std::vector x; + std::vector result; +}; + +class xSquaredPlusBVectorParallel : public RooFit::MultiProcess::Job { +public: + xSquaredPlusBVectorParallel(xSquaredPlusBVectorSerial* serial) + : serial(serial) + { + } + + void evaluate() + { + if (get_manager()->process_manager().is_master()) { + // master fills queue with tasks + for (std::size_t task_id = 0; task_id < serial->x.size(); ++task_id) { + RooFit::MultiProcess::JobTask job_task(id, task_id); + get_manager()->queue().add(job_task); + } + + // wait for task results back from workers to master + gather_worker_results(); + } + } + + std::vector get_result() + { + evaluate(); + return serial->result; + } + + void update_real(std::size_t /*ix*/, double val, bool /*is_constant*/) override { + if (get_manager()->process_manager().is_worker()) { + serial->_b = val; + } + } + + void update_bool(std::size_t /*ix*/, bool /*value*/) override { + // pass + } + + // -- BEGIN plumbing -- + + void receive_task_result_on_queue(std::size_t task, std::size_t worker_id) override { + double result = get_manager()->messenger().template receive_from_worker_on_queue(worker_id); + serial->result[task] = result; + } + + void send_back_task_result_from_worker(std::size_t task) override { + get_manager()->messenger().template send_from_worker_to_queue(serial->result[task]); + } + + void send_back_results_from_queue_to_master() override { +// get_manager()->messenger().send_from_queue_to_master(serial->result.size()); + for (std::size_t task_ix = 0ul; task_ix < serial->result.size(); ++task_ix) { + get_manager()->messenger().send_from_queue_to_master(task_ix, serial->result[task_ix]); + } + } + + void clear_results() override { + // no need to clear any results cache since we just reuse the result vector on the queue + } + + void receive_results_on_master() override { +// std::size_t N_job_tasks = get_manager()->messenger().template receive_from_queue_on_master(); +// for (std::size_t task_ix = 0ul; task_ix < N_job_tasks; ++task_ix) { + for (std::size_t task_ix = 0ul; task_ix < serial->result.size(); ++task_ix) { + std::size_t task_id = get_manager()->messenger().template receive_from_queue_on_master(); + serial->result[task_id] = get_manager()->messenger().template receive_from_queue_on_master(); + } + } + + bool receive_task_result_on_master(const zmq::message_t & /*message*/) override { + // TODO: implement; this no-op placeholder is just to make everything compile first so I can check whether merge was successful + return true; + } + + // -- END plumbing -- + + +private: + void evaluate_task(std::size_t task) override + { + assert(get_manager()->process_manager().is_worker()); + serial->result[task] = std::pow(serial->x[task], 2) + serial->_b; + } + + xSquaredPlusBVectorSerial* serial; +}; + +class TestMPJob : public ::testing::TestWithParam { + // You can implement all the usual fixture class members here. + // To access the test parameter, call GetParam() from class + // TestWithParam. +}; + +TEST_P(TestMPJob, singleJobGetResult) +{ + // Simple test case: calculate x^2 + b, where x is a vector. This case does + // both a simple calculation (squaring the input vector x) and represents + // handling of state updates in b. + std::vector x{0, 1, 2, 3}; + double b_initial = 3.; + + // start serial test + + xSquaredPlusBVectorSerial x_sq_plus_b(b_initial, x); + + auto y = x_sq_plus_b.get_result(); + std::vector y_expected{3, 4, 7, 12}; + + EXPECT_EQ(Hex(y[0]), Hex(y_expected[0])); + EXPECT_EQ(Hex(y[1]), Hex(y_expected[1])); + EXPECT_EQ(Hex(y[2]), Hex(y_expected[2])); + EXPECT_EQ(Hex(y[3]), Hex(y_expected[3])); + + std::size_t NumCPU = GetParam(); + + // start parallel test + + xSquaredPlusBVectorParallel x_sq_plus_b_parallel(&x_sq_plus_b); + RooFit::MultiProcess::JobManager::default_N_workers = NumCPU; + + auto y_parallel = x_sq_plus_b_parallel.get_result(); + + EXPECT_EQ(Hex(y_parallel[0]), Hex(y_expected[0])); + EXPECT_EQ(Hex(y_parallel[1]), Hex(y_expected[1])); + EXPECT_EQ(Hex(y_parallel[2]), Hex(y_expected[2])); + EXPECT_EQ(Hex(y_parallel[3]), Hex(y_expected[3])); +} + + +TEST_P(TestMPJob, multiJobGetResult) +{ + // Simple test case: calculate x^2 + b, where x is a vector. This case does + // both a simple calculation (squaring the input vector x) and represents + // handling of state updates in b. + std::vector x{0, 1, 2, 3}; + double b_initial = 3.; + + std::vector y_expected{3, 4, 7, 12}; + + std::size_t NumCPU = GetParam(); + + // define jobs + xSquaredPlusBVectorSerial x_sq_plus_b(b_initial, x); + xSquaredPlusBVectorSerial x_sq_plus_b2(b_initial + 1, x); + xSquaredPlusBVectorParallel x_sq_plus_b_parallel(&x_sq_plus_b); + xSquaredPlusBVectorParallel x_sq_plus_b_parallel2(&x_sq_plus_b2); + RooFit::MultiProcess::JobManager::default_N_workers = NumCPU; + + // do stuff + auto y_parallel = x_sq_plus_b_parallel.get_result(); + auto y_parallel2 = x_sq_plus_b_parallel2.get_result(); + + EXPECT_EQ(Hex(y_parallel[0]), Hex(y_expected[0])); + EXPECT_EQ(Hex(y_parallel[1]), Hex(y_expected[1])); + EXPECT_EQ(Hex(y_parallel[2]), Hex(y_expected[2])); + EXPECT_EQ(Hex(y_parallel[3]), Hex(y_expected[3])); + + EXPECT_EQ(Hex(y_parallel2[0]), Hex(y_expected[0] + 1)); + EXPECT_EQ(Hex(y_parallel2[1]), Hex(y_expected[1] + 1)); + EXPECT_EQ(Hex(y_parallel2[2]), Hex(y_expected[2] + 1)); + EXPECT_EQ(Hex(y_parallel2[3]), Hex(y_expected[3] + 1)); +} + + +// TODO: implement update_real test! + +//TEST_P(TestMPJob, singleJobUpdateReal) +//{ +//// Simple test case: calculate x^2 + b, where x is a vector. This case does +//// both a simple calculation (squaring the input vector x) and represents +//// handling of state updates in b. +//std::vector x{0, 1, 2, 3}; +//double b_initial = 3.; +// +//// start serial test +// +//xSquaredPlusBVectorSerial x_sq_plus_b(b_initial, x); +// +//auto y = x_sq_plus_b.get_result(); +//std::vector y_expected{3, 4, 7, 12}; +// +//EXPECT_EQ(Hex(y[0]), Hex(y_expected[0])); +//EXPECT_EQ(Hex(y[1]), Hex(y_expected[1])); +//EXPECT_EQ(Hex(y[2]), Hex(y_expected[2])); +//EXPECT_EQ(Hex(y[3]), Hex(y_expected[3])); +// +//std::size_t NumCPU = GetParam(); +// +//// start parallel test +// +//xSquaredPlusBVectorParallel x_sq_plus_b_parallel(NumCPU, &x_sq_plus_b); +// +//auto y_parallel = x_sq_plus_b_parallel.get_result(); +// +//EXPECT_EQ(Hex(y_parallel[0]), Hex(y_expected[0])); +//EXPECT_EQ(Hex(y_parallel[1]), Hex(y_expected[1])); +//EXPECT_EQ(Hex(y_parallel[2]), Hex(y_expected[2])); +//EXPECT_EQ(Hex(y_parallel[3]), Hex(y_expected[3])); +//} + + +INSTANTIATE_TEST_SUITE_P(NumberOfWorkerProcesses, TestMPJob, ::testing::Values(1, 2, 3)); diff --git a/roofit/multiprocess/test/test_JobManager.cxx b/roofit/multiprocess/test/test_JobManager.cxx new file mode 100644 index 0000000000000..a9f4e7ff844ac --- /dev/null +++ b/roofit/multiprocess/test/test_JobManager.cxx @@ -0,0 +1,13 @@ +/* + * Project: RooFit + * Authors: + * PB, Patrick Bos, Netherlands eScience Center, p.bos@esciencecenter.nl + * + * Copyright (c) 2016-2019, Netherlands eScience Center + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ diff --git a/roofit/multiprocess/test/test_Messenger.cxx b/roofit/multiprocess/test/test_Messenger.cxx new file mode 100644 index 0000000000000..49b9936f4e30f --- /dev/null +++ b/roofit/multiprocess/test/test_Messenger.cxx @@ -0,0 +1,116 @@ +/* + * Project: RooFit + * Authors: + * PB, Patrick Bos, Netherlands eScience Center, p.bos@esciencecenter.nl + * + * Copyright (c) 2016-2019, Netherlands eScience Center + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ + +#include "RooFit/MultiProcess/Messenger.h" +#include // ... JobManager::process_manager() + +#include "gtest/gtest.h" + +TEST(TestMPMessenger, Connections) +{ + RooFit::MultiProcess::ProcessManager pm(2); + RooFit::MultiProcess::Messenger messenger(pm); + messenger.test_connections(pm); +} + +TEST(TestMPMessenger, ConnectionsManualExit) +{ + // the point of this test is to see whether clean-up of ZeroMQ resources is done properly without calling any + // destructors (which is what happens when you call _Exit() instead of regularly ending the program by reaching the + // end of main()). + RooFit::MultiProcess::ProcessManager pm( 2); + RooFit::MultiProcess::Messenger messenger(pm); + messenger.test_connections(pm); + if (!pm.is_master()) { + std::_Exit(0); + } +} + + +#include + +TEST(TestMPMessenger, SigStop) +{ + // For some reason, I cannot get my debugger (lldb) to play nicely with ZeroMQ; every time I pause a process, it + // says it crashed because of SIGSTOP, but SIGSTOP should just pause it... let's see whether we can reproduce this + // behavior here and, if possible, fix it. + RooFit::MultiProcess::ProcessManager pm(2); + RooFit::MultiProcess::Messenger messenger(pm); +// messenger.test_connections(pm); + + // all this works fine without a sigmask, maybe the sigmask is the issue? let's try: + + + if (pm.is_master()) { + printf("first send message to queue...\n"); + messenger.send_from_master_to_queue(1); + printf("sleep for 2 seconds...\n"); + sleep(2); + printf("SIGSTOPping all children...\n"); + kill(pm.get_queue_pid(), SIGSTOP); + kill(pm.get_worker_pids()[0], SIGSTOP); + kill(pm.get_worker_pids()[1], SIGSTOP); + printf("send another message to queue...\n"); + messenger.send_from_master_to_queue(2); + printf("sleep for 2 seconds...\n"); + sleep(2); + printf("SIGCONT queue and worker 1...\n"); + kill(pm.get_queue_pid(), SIGCONT); + kill(pm.get_worker_pids()[0], SIGCONT); + printf("sleep for 2 seconds...\n"); + sleep(2); + printf("SIGCONT worker 2...\n"); + kill(pm.get_worker_pids()[1], SIGCONT); + EXPECT_EQ(messenger.receive_from_queue_on_master(), 3); + } + + if (pm.is_queue()) { + auto number = messenger.receive_from_master_on_queue(); + printf("received %d on queue\n", number); + number = messenger.receive_from_master_on_queue(); + printf("received %d on queue\n", number); + messenger.send_from_queue_to_master(3); + } + + if (!pm.is_master()) { + while (!pm.sigterm_received()) {} + } +} + +TEST(TestMPMessenger, StressPubSub) +{ + std::size_t N_workers = 64; + RooFit::MultiProcess::ProcessManager pm(N_workers); + RooFit::MultiProcess::Messenger messenger(pm); + + if (pm.is_master()) { + messenger.publish_from_master_to_workers("bert"); + std::size_t ernies = 0; + for (; ernies < N_workers; ++ernies) { + auto receipt = messenger.receive_from_worker_on_master(); + if (receipt != "ernie") { + printf("whoops, got %s instead of ernie!\n", receipt.c_str()); + FAIL(); + } + } + EXPECT_EQ(ernies, N_workers); + } else if (pm.is_worker()) { + auto receipt = messenger.receive_from_master_on_worker(); + if (receipt == "bert") { + messenger.send_from_worker_to_master("ernie"); + } else { + printf("no bert on worker %lu\n", pm.worker_id()); + } + } +} diff --git a/roofit/multiprocess/test/test_ProcessManager.cxx b/roofit/multiprocess/test/test_ProcessManager.cxx new file mode 100644 index 0000000000000..4660657e4c720 --- /dev/null +++ b/roofit/multiprocess/test/test_ProcessManager.cxx @@ -0,0 +1,57 @@ +/* + * Project: RooFit + * Authors: + * PB, Patrick Bos, Netherlands eScience Center, p.bos@esciencecenter.nl + * + * Copyright (c) 2016-2019, Netherlands eScience Center + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ + +#include // ... JobManager::process_manager() + +#include "gtest/gtest.h" + +TEST(TestMPProcessManager, birthAndDeath) +{ + auto pm = RooFit::MultiProcess::ProcessManager(2); +} + +// disabled: it is not working anymore (16 March 2021), due to changes introduced to fix Messenger::test_connections +TEST(TestMPProcessManager, DISABLED_multiBirth) +{ + // This test doesn't actually represent a current usecase in RooFit, but let's + // showcase the possibility anyway for future reference. + + // first create a regular pm like in the birthAndDeath test + auto pm = RooFit::MultiProcess::ProcessManager(2); + // then from each forked node, spin up another set of workers+queue! + auto pm2 = RooFit::MultiProcess::ProcessManager(2); +} + + +TEST(TestMPProcessManager, checkState) +{ + std::size_t N_workers = 2; + auto master_pid = getpid(); + auto pm = RooFit::MultiProcess::ProcessManager(N_workers); + ASSERT_TRUE(pm.is_initialized()); + ASSERT_EQ(pm.N_workers(), N_workers); + + if (pm.is_master()) { + EXPECT_EQ(getpid(), master_pid); + EXPECT_FALSE(pm.is_queue()); + EXPECT_FALSE(pm.is_worker()); + } else if (pm.is_queue()) { + EXPECT_FALSE(pm.is_master()); + EXPECT_FALSE(pm.is_worker()); + } else if (pm.is_worker()) { + EXPECT_FALSE(pm.is_master()); + EXPECT_FALSE(pm.is_queue()); + EXPECT_TRUE(pm.worker_id() == 0 || pm.worker_id() == 1); + } +} \ No newline at end of file diff --git a/roofit/multiprocess/test/test_Queue.cxx b/roofit/multiprocess/test/test_Queue.cxx new file mode 100644 index 0000000000000..a9f4e7ff844ac --- /dev/null +++ b/roofit/multiprocess/test/test_Queue.cxx @@ -0,0 +1,13 @@ +/* + * Project: RooFit + * Authors: + * PB, Patrick Bos, Netherlands eScience Center, p.bos@esciencecenter.nl + * + * Copyright (c) 2016-2019, Netherlands eScience Center + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ diff --git a/roofit/multiprocess/test/test_util.cxx b/roofit/multiprocess/test/test_util.cxx new file mode 100644 index 0000000000000..a9f4e7ff844ac --- /dev/null +++ b/roofit/multiprocess/test/test_util.cxx @@ -0,0 +1,13 @@ +/* + * Project: RooFit + * Authors: + * PB, Patrick Bos, Netherlands eScience Center, p.bos@esciencecenter.nl + * + * Copyright (c) 2016-2019, Netherlands eScience Center + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ diff --git a/roofit/multiprocess/test/test_worker.cxx b/roofit/multiprocess/test/test_worker.cxx new file mode 100644 index 0000000000000..505c5ef27a610 --- /dev/null +++ b/roofit/multiprocess/test/test_worker.cxx @@ -0,0 +1,28 @@ +/* + * Project: RooFit + * Authors: + * PB, Patrick Bos, Netherlands eScience Center, p.bos@esciencecenter.nl + * + * Copyright (c) 2016-2019, Netherlands eScience Center + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ + +#include "RooFit/MultiProcess/worker.h" + +#include "gtest/gtest.h" + +//TEST(worker, loop) +//{ +// do_fork(); +// if (on_master()) { +// wait(); +// terminate_worker(); +// } else if (on_worker()) { +// worker_loop(); +// } +//} diff --git a/roofit/multiprocess/test/utils.h b/roofit/multiprocess/test/utils.h new file mode 100644 index 0000000000000..f4f72976e7226 --- /dev/null +++ b/roofit/multiprocess/test/utils.h @@ -0,0 +1,171 @@ +/***************************************************************************** + * Project: RooFit * + * Package: RooFitCore * + * @(#)root/roofitcore:$Id$ + * Authors: * + * PB, Patrick Bos, NL eScience Center, p.bos@esciencecenter.nl * + * * + * Redistribution and use in source and binary forms, * + * with or without modification, are permitted according to the terms * + * listed in LICENSE (http://roofit.sourceforge.net/license.txt) * + *****************************************************************************/ + +#ifndef ROOT_ROOFIT_MultiProcess_tests_utils_h +#define ROOT_ROOFIT_MultiProcess_tests_utils_h + +#include + +#include // make_unique + +#include "RooWorkspace.h" +#include "RooRandom.h" +#include "RooAddPdf.h" +#include "RooDataSet.h" +#include "RooRealVar.h" // for the dynamic cast to have a complete type + + +std::tuple, std::unique_ptr> +generate_1D_gaussian_pdf_nll(RooWorkspace &w, unsigned long N_events) { + w.factory("Gaussian::g(x[-5,5],mu[0,-3,3],sigma[1])"); + + RooAbsPdf *pdf = w.pdf("g"); + RooRealVar *mu = w.var("mu"); + + RooDataSet *data = pdf->generate(RooArgSet(*w.var("x")), N_events); + mu->setVal(-2.9); + + std::unique_ptr nll {pdf->createNLL(*data)}; + + // save initial values for the start of all minimizations + std::unique_ptr values = std::make_unique(*mu, *pdf, *nll, "values"); + + return std::make_tuple(std::move(nll), std::move(values)); +} + +// return two unique_ptrs, the first because nll is a pointer, +// the second because RooArgSet doesn't have a move ctor +std::tuple, std::unique_ptr> +generate_ND_gaussian_pdf_nll(RooWorkspace &w, unsigned int n, unsigned long N_events) { + RooArgSet obs_set; + + // create gaussian parameters + double mean[n], sigma[n]; + for (unsigned ix = 0; ix < n; ++ix) { + mean[ix] = RooRandom::randomGenerator()->Gaus(0, 2); + sigma[ix] = 0.1 + abs(RooRandom::randomGenerator()->Gaus(0, 2)); + } + + // create gaussians and also the observables and parameters they depend on + for (unsigned ix = 0; ix < n; ++ix) { + std::ostringstream os; + os << "Gaussian::g" << ix + << "(x" << ix << "[-10,10]," + << "m" << ix << "[" << mean[ix] << ",-10,10]," + << "s" << ix << "[" << sigma[ix] << ",0.1,10])"; + w.factory(os.str().c_str()); + } + + // create uniform background signals on each observable + for (unsigned ix = 0; ix < n; ++ix) { + { + std::ostringstream os; + os << "Uniform::u" << ix << "(x" << ix << ")"; + w.factory(os.str().c_str()); + } + + // gather the observables in a list for data generation below + { + std::ostringstream os; + os << "x" << ix; + obs_set.add(*w.arg(os.str().c_str())); + } + } + + RooArgSet pdf_set = w.allPdfs(); + + // create event counts for all pdfs + RooArgSet count_set; + + // ... for the gaussians + for (unsigned ix = 0; ix < n; ++ix) { + std::stringstream os, os2; + os << "Nsig" << ix; + os2 << "#signal events comp " << ix; + RooRealVar a(os.str().c_str(), os2.str().c_str(), N_events/10, 0., 10*N_events); + w.import(a); + // gather in count_set + count_set.add(*w.arg(os.str().c_str())); + } + // ... and for the uniform background components + for (unsigned ix = 0; ix < n; ++ix) { + std::stringstream os, os2; + os << "Nbkg" << ix; + os2 << "#background events comp " << ix; + RooRealVar a(os.str().c_str(), os2.str().c_str(), N_events/10, 0., 10*N_events); + w.import(a); + // gather in count_set + count_set.add(*w.arg(os.str().c_str())); + } + + RooAddPdf* sum = new RooAddPdf("sum", "gaussians+uniforms", pdf_set, count_set); + w.import(*sum); // keep sum around after returning + + // --- Generate a toyMC sample from composite PDF --- + RooDataSet *data = sum->generate(obs_set, N_events); + + std::unique_ptr nll {sum->createNLL(*data)}; + + // set values randomly so that they actually need to do some fitting + for (unsigned ix = 0; ix < n; ++ix) { + { + std::ostringstream os; + os << "m" << ix; + dynamic_cast(w.arg(os.str().c_str()))->setVal(RooRandom::randomGenerator()->Gaus(0, 2)); + } + { + std::ostringstream os; + os << "s" << ix; + dynamic_cast(w.arg(os.str().c_str()))->setVal(0.1 + abs(RooRandom::randomGenerator()->Gaus(0, 2))); + } + } + + // gather all values of parameters, pdfs and nll here for easy + // saving and restoring + std::unique_ptr all_values = std::make_unique(pdf_set, count_set, "all_values"); + all_values->add(*nll); + all_values->add(*sum); + for (unsigned ix = 0; ix < n; ++ix) { + { + std::ostringstream os; + os << "m" << ix; + all_values->add(*w.arg(os.str().c_str())); + } + { + std::ostringstream os; + os << "s" << ix; + all_values->add(*w.arg(os.str().c_str())); + } + } + + return std::make_tuple(std::move(nll), std::move(all_values)); +} + + +class Hex { +public: + explicit Hex(double n) : number_(n) {} + operator double() const { return number_; } + bool operator==(const Hex& other) { + return double(*this) == double(other); + } + +private: + double number_; +}; + +::std::ostream& operator<<(::std::ostream& os, const Hex& hex) { + return os << std::hexfloat << double(hex) << std::defaultfloat; // whatever needed to print bar to os +} + + +#endif //ROOT_ROOFIT_MultiProcess_tests_utils_h diff --git a/roofit/roofitZMQ/CMakeLists.txt b/roofit/roofitZMQ/CMakeLists.txt new file mode 100644 index 0000000000000..0491fcf172079 --- /dev/null +++ b/roofit/roofitZMQ/CMakeLists.txt @@ -0,0 +1,42 @@ +############################################################################ +# CMakeLists.txt file for building ROOT roofitcore/ZMQ package +# @author Patrick Bos, NL eScience Center +############################################################################ + +#if(NOT ZeroMQ_FOUND) +# find_package(ZeroMQ REQUIRED) +#endif() + +ROOT_STANDARD_LIBRARY_PACKAGE(RooFitZMQ + HEADERS + RooFit_ZMQ/zmq.hxx + RooFit_ZMQ/Utility.h + RooFit_ZMQ/ZeroMQSvc.h + RooFit_ZMQ/functions.h + RooFit_ZMQ/ZeroMQPoller.h + RooFit_ZMQ/ppoll.h + SOURCES + src/ZeroMQSvc.cpp + src/ZeroMQPoller.cpp + src/functions.cpp + src/ppoll.cpp + DICTIONARY_OPTIONS +# -I${ZeroMQ_INCLUDE_DIR} + -writeEmptyRootPCM + DEPENDENCIES + Core # TVersionCheck, RegisterModule, these are always called from G__RooFitZMQ.cxx.o +# RIO # TBufferFile, TBufferIO + LIBRARIES +# ${ZeroMQ_LIBRARY} + ZeroMQ + ) + + +target_link_libraries(RooFitZMQ PRIVATE ZeroMQ) +target_include_directories(RooFitZMQ PUBLIC $) +target_include_directories(RooFitZMQ PUBLIC $) +target_include_directories(RooFitZMQ PUBLIC $) + +if(testing) + ROOT_ADD_TEST_SUBDIRECTORY(test) +endif() diff --git a/roofit/roofitZMQ/inc/LinkDef.h b/roofit/roofitZMQ/inc/LinkDef.h new file mode 100644 index 0000000000000..5495493c90b99 --- /dev/null +++ b/roofit/roofitZMQ/inc/LinkDef.h @@ -0,0 +1 @@ +// something \ No newline at end of file diff --git a/roofit/roofitZMQ/inc/RooFit_ZMQ/Utility.h b/roofit/roofitZMQ/inc/RooFit_ZMQ/Utility.h new file mode 100644 index 0000000000000..a329805967df6 --- /dev/null +++ b/roofit/roofitZMQ/inc/RooFit_ZMQ/Utility.h @@ -0,0 +1,39 @@ +#ifndef SERIALIZE_UTILITY_H +#define SERIALIZE_UTILITY_H 1 + +#if defined(__clang__) && (__clang_major__ > 3 || (__clang_major__ == 3 && __clang_minor__ >= 10)) +#define HAVE_TRIVIALLY_COPYABLE 1 +#elif defined(__GNUC__) && __GNUC__ >= 5 +#define HAVE_TRIVIALLY_COPYABLE 1 +#else +#undef HAVE_TRIVIALLY_COPYABLE +#endif + +#include + +namespace ZMQ { + namespace Detail { + +#if defined(HAVE_TRIVIALLY_COPYABLE) + template + using simple_object = std::is_trivially_copyable; +#else + template + using simple_object = std::is_pod; +#endif + +// is trivial + template + struct is_trivial : std::conditional< + simple_object< + typename std::decay::type + >::value, + std::true_type, + std::false_type + >::type { + }; + + } +} + +#endif // SERIALIZE_UTILITY_H diff --git a/roofit/roofitZMQ/inc/RooFit_ZMQ/ZeroMQPoller.h b/roofit/roofitZMQ/inc/RooFit_ZMQ/ZeroMQPoller.h new file mode 100644 index 0000000000000..4d14444c01553 --- /dev/null +++ b/roofit/roofitZMQ/inc/RooFit_ZMQ/ZeroMQPoller.h @@ -0,0 +1,48 @@ +#ifndef ZEROMQPOLLER_H +#define ZEROMQPOLLER_H 1 +#include +#include +#include +#include + +#include "RooFit_ZMQ/ZeroMQSvc.h" +#include "RooFit_ZMQ/functions.h" + +class ZeroMQPoller { +public: + + using entry_t = std::tuple; + // The key is what zmq::socket_t stores inside, and what goes into + // pollitem_t through zmq::socket_t's conversion to void* operator + using sockets_t = std::unordered_map; + + using fd_entry_t = std::tuple; + using fds_t = std::unordered_map; + + using free_t = std::deque; + + ZeroMQPoller() = default; + + std::vector> poll(int timeo = -1); + std::vector> ppoll(int timeo, const sigset_t * sigmask_); + + size_t size() const; + + size_t register_socket(zmq::socket_t& socket, zmq::PollType type); + size_t register_socket(int fd, zmq::PollType type); + + size_t unregister_socket(zmq::socket_t& socket); + size_t unregister_socket(int fd); + +private: + + // Vector of (socket, flags) + std::vector m_items; + sockets_t m_sockets; + fds_t m_fds; + + // free slots in items + free_t m_free; +}; + +#endif // ZEROMQPOLLER_H diff --git a/roofit/roofitZMQ/inc/RooFit_ZMQ/ZeroMQSvc.h b/roofit/roofitZMQ/inc/RooFit_ZMQ/ZeroMQSvc.h new file mode 100644 index 0000000000000..d86a9d9eb067a --- /dev/null +++ b/roofit/roofitZMQ/inc/RooFit_ZMQ/ZeroMQSvc.h @@ -0,0 +1,213 @@ +#ifndef ZEROMQ_IZEROMQSVC_H +#define ZEROMQ_IZEROMQSVC_H 1 + +// Include files +// from STL +#include +#include +#include +#include +#include + +// ZeroMQ +#include "RooFit_ZMQ/zmq.hxx" +#include "RooFit_ZMQ/Utility.h" +#include "RooFit_ZMQ/functions.h" + +// debugging +#include // getpid + +namespace ZMQ { + +struct TimeOutException : std::exception { + TimeOutException() = default; + TimeOutException(const TimeOutException&) = default; + ~TimeOutException() = default; + TimeOutException& operator=(const TimeOutException&) = default; +}; + +struct MoreException : std::exception { + MoreException() = default; + MoreException(const MoreException&) = default; + ~MoreException() = default; + MoreException& operator=(const MoreException&) = default; +}; + +} + +template +struct ZmqLingeringSocketPtrDeleter { + void operator()(zmq::socket_t* socket) { + auto period = PERIOD; + + int tries = 0; + int max_tries = 3; + while(true) { + try { + // the actual work this function should do, plus the delete socket below: + if (socket) socket->setsockopt(ZMQ_LINGER, &period, sizeof(period)); + break; + } catch (zmq::error_t& e) { + if (++tries == max_tries + || e.num() == EINVAL || e.num() == ETERM || e.num() == ENOTSOCK // not recoverable from here + ) { + std::cerr << "ERROR in ZeroMQSvc::socket: " << e.what() << " (errno: " << e.num() << ")\n"; + throw; + } + std::cerr << "RETRY " << tries << "/" << (max_tries - 1) << " in ZmqLingeringSocketPtrDeleter: call interrupted (errno: " << e.num() << ")\n"; + } + } + + delete socket; + } +}; + +template +using ZmqLingeringSocketPtr = std::unique_ptr>; + + +// We retry send and receive only on EINTR, all other errors are either fatal, or can only +// be handled at the caller. +template +auto retry_send(zmq::socket_t& socket, int max_tries, args_t ...args) -> decltype(socket.send(args...)) { + int tries = 0; + while(true) { + try { + // the actual work this function should do, all the rest is error handling: + return socket.send(args...); + } catch (zmq::error_t& e) { + if (++tries == max_tries + || e.num() != EINTR // only recoverable error + ) { +// std::cerr << "ERROR in ZeroMQSvc::send (retry_send) on pid " << getpid() << ": " << e.what() << " (errno: " << e.num() << ")\n"; + throw; + } + std::cerr << "RETRY " << tries << "/" << (max_tries - 1) << " in ZeroMQSvc::send (retry_send) on pid " << getpid() << ": " << e.what() << ")\n"; + } + } +} + +template +auto retry_recv(zmq::socket_t& socket, int max_tries, args_t ...args) -> decltype(socket.recv(args...)) { + int tries = 0; + while(true) { + try { + // the actual work this function should do, all the rest is error handling: + return socket.recv(args...); + } catch (zmq::error_t& e) { + if (++tries == max_tries + || e.num() != EINTR // only recoverable error + ) { +// std::cerr << "ERROR in ZeroMQSvc::recv (retry_recv) on pid " << getpid() << ": " << e.what() << " (errno: " << e.num() << ")\n"; + throw; + } + std::cerr << "RETRY " << tries << "/" << (max_tries - 1) << " in ZeroMQSvc::recv (retry_recv) on pid " << getpid() << ": " << e.what() << ")\n"; + } + } +} + + +/** @class IZeroMQSvc IZeroMQSvc.h ZeroMQ/IZeroMQSvc.h + * + * + * @author + * @date 2015-06-22 + */ +class ZeroMQSvc { + // Note on error handling: + // Creating message_t can throw, but only when memory ran out (errno ENOMEM), + // and that is something only the caller can fix, so we don't catch it here. + +public: + + enum Encoding { + Text = 0, + Binary + }; + + Encoding encoding() const; + void setEncoding(const Encoding& e); + zmq::context_t& context() const; + zmq::socket_t socket(int type) const; + zmq::socket_t* socket_ptr(int type) const; + void close_context() const; + + // decode message with ZMQ, POD version + template ::value + && ZMQ::Detail::is_trivial::value, T>::type* = nullptr> + T decode(const zmq::message_t& msg) const { + T object; + memcpy(&object, msg.data(), msg.size()); + return object; + } + + // decode ZMQ message, string version + template ::value, T>::type* = nullptr> + std::string decode(const zmq::message_t& msg) const { + std::string r(msg.size() + 1, char{}); + r.assign(static_cast(msg.data()), msg.size()); + return r; + } + + // receive message with ZMQ, general version + // FIXME: what to do with flags=0.... more is a pointer, that might prevent conversion + template ::value), T>::type* = nullptr> + T receive(zmq::socket_t& socket, int flags = 0, bool* more = nullptr) const { + // receive message + zmq::message_t msg; + auto nbytes = retry_recv(socket, 2, &msg, flags); + if (0 == nbytes) { + throw ZMQ::TimeOutException{}; + } + if (more) *more = msg.more(); + + // decode message + return decode(msg); + } + + // receive message with ZMQ + template ::value, T>::type* = nullptr> + T receive(zmq::socket_t& socket, int flags = 0, bool* more = nullptr) const { + // receive message + zmq::message_t msg; + auto nbytes = retry_recv(socket, 2, &msg, flags); + if (0 == nbytes) { + throw ZMQ::TimeOutException{}; + } + if (more) *more = msg.more(); + return msg; + } + + // encode message to ZMQ + template ::value + && ZMQ::Detail::is_trivial::value, T>::type* = nullptr> + zmq::message_t encode(const T& item, std::function sizeFun = ZMQ::defaultSizeOf) const { + size_t s = sizeFun(item); + zmq::message_t msg{s}; + memcpy((void *)msg.data(), &item, s); + return msg; + } + + zmq::message_t encode(const char* item) const; + zmq::message_t encode(const std::string& item) const; + + // Send message with ZMQ + template ::value, T>::type* = nullptr> + bool send(zmq::socket_t& socket, const T& item, int flags = 0) const { + return retry_send(socket, 1, encode(item), flags); + } + + bool send(zmq::socket_t& socket, const char* item, int flags = 0) const; + bool send(zmq::socket_t& socket, zmq::message_t& msg, int flags = 0) const; + bool send(zmq::socket_t& socket, zmq::message_t&& msg, int flags = 0) const; + +private: + + Encoding m_enc = Text; + mutable zmq::context_t* m_context = nullptr; + +}; + +ZeroMQSvc& zmqSvc(); + +#endif // ZEROMQ_IZEROMQSVC_H diff --git a/roofit/roofitZMQ/inc/RooFit_ZMQ/functions.h b/roofit/roofitZMQ/inc/RooFit_ZMQ/functions.h new file mode 100644 index 0000000000000..1933efbbe2d6d --- /dev/null +++ b/roofit/roofitZMQ/inc/RooFit_ZMQ/functions.h @@ -0,0 +1,111 @@ +#ifndef ZEROMQ_FUNCTIONS_H +#define ZEROMQ_FUNCTIONS_H 1 + +#include "zmq.hxx" + +namespace zmq { + + enum SocketTypes { + PAIR = ZMQ_PAIR, + PUB = ZMQ_PUB, + SUB = ZMQ_SUB, + REQ = ZMQ_REQ, + REP = ZMQ_REP, + DEALER = ZMQ_DEALER, + ROUTER = ZMQ_ROUTER, + PULL = ZMQ_PULL, + PUSH = ZMQ_PUSH, + XPUB = ZMQ_XPUB, + XSUB = ZMQ_XSUB, + STREAM = ZMQ_STREAM + }; + + enum PollType : short { + POLLIN = ZMQ_POLLIN, + POLLOUT = ZMQ_POLLOUT + }; + + enum SocketOptions { + AFFINITY = ZMQ_AFFINITY, + IDENTITY = ZMQ_IDENTITY, + SUBSCRIBE = ZMQ_SUBSCRIBE, + UNSUBSCRIBE = ZMQ_UNSUBSCRIBE, + RATE = ZMQ_RATE, + RECOVERY_IVL = ZMQ_RECOVERY_IVL, + SNDBUF = ZMQ_SNDBUF, + RCVBUF = ZMQ_RCVBUF, + RCVMORE = ZMQ_RCVMORE, + FD = ZMQ_FD, + EVENTS = ZMQ_EVENTS, + TYPE = ZMQ_TYPE, + LINGER = ZMQ_LINGER, + RECONNECT_IVL = ZMQ_RECONNECT_IVL, + BACKLOG = ZMQ_BACKLOG, + RECONNECT_IVL_MAX = ZMQ_RECONNECT_IVL_MAX, + MAXMSGSIZE = ZMQ_MAXMSGSIZE, + SNDHWM = ZMQ_SNDHWM, + RCVHWM = ZMQ_RCVHWM, + MULTICAST_HOPS = ZMQ_MULTICAST_HOPS, + RCVTIMEO = ZMQ_RCVTIMEO, + SNDTIMEO = ZMQ_SNDTIMEO, + LAST_ENDPOINT = ZMQ_LAST_ENDPOINT, + ROUTER_MANDATORY = ZMQ_ROUTER_MANDATORY, + TCP_KEEPALIVE = ZMQ_TCP_KEEPALIVE, + TCP_KEEPALIVE_CNT = ZMQ_TCP_KEEPALIVE_CNT, + TCP_KEEPALIVE_IDLE = ZMQ_TCP_KEEPALIVE_IDLE, + TCP_KEEPALIVE_INTVL = ZMQ_TCP_KEEPALIVE_INTVL, + IMMEDIATE = ZMQ_IMMEDIATE, + XPUB_VERBOSE = ZMQ_XPUB_VERBOSE, + ROUTER_RAW = ZMQ_ROUTER_RAW, + IPV6 = ZMQ_IPV6, + MECHANISM = ZMQ_MECHANISM, + PLAIN_SERVER = ZMQ_PLAIN_SERVER, + PLAIN_USERNAME = ZMQ_PLAIN_USERNAME, + PLAIN_PASSWORD = ZMQ_PLAIN_PASSWORD, + CURVE_SERVER = ZMQ_CURVE_SERVER, + CURVE_PUBLICKEY = ZMQ_CURVE_PUBLICKEY, + CURVE_SECRETKEY = ZMQ_CURVE_SECRETKEY, + CURVE_SERVERKEY = ZMQ_CURVE_SERVERKEY, + PROBE_ROUTER = ZMQ_PROBE_ROUTER, + REQ_CORRELATE = ZMQ_REQ_CORRELATE, + REQ_RELAXED = ZMQ_REQ_RELAXED, + CONFLATE = ZMQ_CONFLATE, + ZAP_DOMAIN = ZMQ_ZAP_DOMAIN, + ROUTER_HANDOVER = ZMQ_ROUTER_HANDOVER, + TOS = ZMQ_TOS, + CONNECT_RID = ZMQ_CONNECT_RID, + GSSAPI_SERVER = ZMQ_GSSAPI_SERVER, + GSSAPI_PRINCIPAL = ZMQ_GSSAPI_PRINCIPAL, + GSSAPI_SERVICE_PRINCIPAL = ZMQ_GSSAPI_SERVICE_PRINCIPAL, + GSSAPI_PLAINTEXT = ZMQ_GSSAPI_PLAINTEXT, + HANDSHAKE_IVL = ZMQ_HANDSHAKE_IVL, + SOCKS_PROXY = ZMQ_SOCKS_PROXY, + XPUB_NODROP = ZMQ_XPUB_NODROP + }; + + // Message options + enum MessageOptions { + MORE = ZMQ_MORE, + SRCFD = ZMQ_SRCFD, + SHARED = ZMQ_SHARED + }; + + // Send/recv options. + enum SendRecvOptions { + DONTWAIT = ZMQ_DONTWAIT, + SNDMORE = ZMQ_SNDMORE + }; +} + +namespace ZMQ { + +template +size_t defaultSizeOf(const T&) { + return sizeof(T); +} + +size_t stringLength(const char& cs); + +} + +#endif // ZEROMQ_FUNCTIONS_H diff --git a/roofit/roofitZMQ/inc/RooFit_ZMQ/ppoll.h b/roofit/roofitZMQ/inc/RooFit_ZMQ/ppoll.h new file mode 100644 index 0000000000000..3bfe5b2ce6ee3 --- /dev/null +++ b/roofit/roofitZMQ/inc/RooFit_ZMQ/ppoll.h @@ -0,0 +1,29 @@ +/* + * Project: RooFit + * Authors: + * PB, Patrick Bos, Netherlands eScience Center, p.bos@esciencecenter.nl + * + * Copyright (c) 2016-2019, Netherlands eScience Center + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +#ifndef ROOT_ROOFIT_ZMQ_ppoll +#define ROOT_ROOFIT_ZMQ_ppoll + +#include +#include "zmq.hxx" + +namespace ZMQ { + +int zmq_ppoll (zmq_pollitem_t *items_, int nitems_, long timeout_, const sigset_t * sigmask_); +int ppoll(zmq_pollitem_t *items_, size_t nitems_, long timeout_, const sigset_t * sigmask_); +int ppoll(std::vector &items, long timeout_, const sigset_t * sigmask_); +class ppoll_error_t : public zmq::error_t {}; + +} + +#endif // ROOT_ROOFIT_ZMQ_ppoll diff --git a/roofit/roofitZMQ/inc/RooFit_ZMQ/zmq.hxx b/roofit/roofitZMQ/inc/RooFit_ZMQ/zmq.hxx new file mode 100644 index 0000000000000..30896aa097a69 --- /dev/null +++ b/roofit/roofitZMQ/inc/RooFit_ZMQ/zmq.hxx @@ -0,0 +1,1138 @@ +/* + Copyright (c) 2016-2017 ZeroMQ community + Copyright (c) 2009-2011 250bpm s.r.o. + Copyright (c) 2011 Botond Ballo + Copyright (c) 2007-2009 iMatix Corporation + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to + deal in the Software without restriction, including without limitation the + rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + sell copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. +*/ + +#ifndef __ZMQ_HPP_INCLUDED__ +#define __ZMQ_HPP_INCLUDED__ + +#if (__cplusplus >= 201402L) +#define ZMQ_DEPRECATED(msg) [[deprecated(msg)]] +#elif defined(_MSC_VER) +#define ZMQ_DEPRECATED(msg) __declspec(deprecated(msg)) +#elif defined(__GNUC__) +#define ZMQ_DEPRECATED(msg) __attribute__((deprecated(msg))) +#endif + +#if (__cplusplus >= 201103L) || (defined(_MSC_VER) && (_MSC_VER >= 1900)) +#define ZMQ_CPP11 +#define ZMQ_NOTHROW noexcept +#define ZMQ_EXPLICIT explicit +#define ZMQ_OVERRIDE override +#else +#define ZMQ_CPP03 +#define ZMQ_NOTHROW +#define ZMQ_EXPLICIT +#define ZMQ_OVERRIDE +#endif + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include //debugging + +/* Version macros for compile-time API version detection */ +#define CPPZMQ_VERSION_MAJOR 4 +#define CPPZMQ_VERSION_MINOR 3 +#define CPPZMQ_VERSION_PATCH 1 + +#define CPPZMQ_VERSION \ + ZMQ_MAKE_VERSION(CPPZMQ_VERSION_MAJOR, CPPZMQ_VERSION_MINOR, \ + CPPZMQ_VERSION_PATCH) + +#ifdef ZMQ_CPP11 +#include +#include +#include +#include +#include +#endif + +// Detect whether the compiler supports C++11 rvalue references. +#if (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ > 2)) \ + && defined(__GXX_EXPERIMENTAL_CXX0X__)) +#define ZMQ_HAS_RVALUE_REFS +#define ZMQ_DELETED_FUNCTION = delete +#elif defined(__clang__) +#if __has_feature(cxx_rvalue_references) +#define ZMQ_HAS_RVALUE_REFS +#endif + +#if __has_feature(cxx_deleted_functions) +#define ZMQ_DELETED_FUNCTION = delete +#else +#define ZMQ_DELETED_FUNCTION +#endif +#elif defined(_MSC_VER) && (_MSC_VER >= 1900) +#define ZMQ_HAS_RVALUE_REFS +#define ZMQ_DELETED_FUNCTION = delete +#elif defined(_MSC_VER) && (_MSC_VER >= 1600) +#define ZMQ_HAS_RVALUE_REFS +#define ZMQ_DELETED_FUNCTION +#else +#define ZMQ_DELETED_FUNCTION +#endif + +#if ZMQ_VERSION >= ZMQ_MAKE_VERSION(3, 3, 0) +#define ZMQ_NEW_MONITOR_EVENT_LAYOUT +#endif + +#if ZMQ_VERSION >= ZMQ_MAKE_VERSION(4, 1, 0) +#define ZMQ_HAS_PROXY_STEERABLE +/* Socket event data */ +typedef struct +{ + uint16_t event; // id of the event as bitfield + int32_t value; // value is either error code, fd or reconnect interval +} zmq_event_t; +#endif + +// Avoid using deprecated message receive function when possible +#if ZMQ_VERSION < ZMQ_MAKE_VERSION(3, 2, 0) +#define zmq_msg_recv(msg, socket, flags) zmq_recvmsg(socket, msg, flags) +#endif + + +// In order to prevent unused variable warnings when building in non-debug +// mode use this macro to make assertions. +#ifndef NDEBUG +#define ZMQ_ASSERT(expression) assert(expression) +#else +#define ZMQ_ASSERT(expression) (void) (expression) +#endif + +namespace zmq +{ +typedef zmq_free_fn free_fn; +typedef zmq_pollitem_t pollitem_t; + +class error_t : public std::exception +{ + public: + error_t() : errnum(zmq_errno()) {} +#ifdef ZMQ_CPP11 + virtual const char *what() const noexcept { return zmq_strerror(errnum); } +#else + virtual const char *what() const throw() { return zmq_strerror(errnum); } +#endif + int num() const { return errnum; } + + private: + int errnum; +}; + +inline int poll(zmq_pollitem_t const *items_, size_t nitems_, long timeout_ = -1) +{ + int rc = zmq_poll(const_cast(items_), + static_cast(nitems_), timeout_); + if (rc < 0) + throw error_t(); + return rc; +} + +#ifdef ZMQ_CPP11 +inline int +poll(zmq_pollitem_t const *items, size_t nitems, std::chrono::milliseconds timeout) +{ + return poll(items, nitems, static_cast(timeout.count())); +} + +inline int poll(std::vector const &items, + std::chrono::milliseconds timeout) +{ + return poll(items.data(), items.size(), static_cast(timeout.count())); +} + +inline int poll(std::vector const &items, long timeout_ = -1) +{ + return poll(items.data(), items.size(), timeout_); +} +#endif + + +inline void proxy(void *frontend, void *backend, void *capture) +{ + int rc = zmq_proxy(frontend, backend, capture); + if (rc != 0) + throw error_t(); +} + +#ifdef ZMQ_HAS_PROXY_STEERABLE +inline void +proxy_steerable(void *frontend, void *backend, void *capture, void *control) +{ + int rc = zmq_proxy_steerable(frontend, backend, capture, control); + if (rc != 0) + throw error_t(); +} +#endif + +inline void version(int *major_, int *minor_, int *patch_) +{ + zmq_version(major_, minor_, patch_); +} + +#ifdef ZMQ_CPP11 +inline std::tuple version() +{ + std::tuple v; + zmq_version(&std::get<0>(v), &std::get<1>(v), &std::get<2>(v)); + return v; +} +#endif + +class message_t +{ + friend class socket_t; + + public: + inline message_t() + { + int rc = zmq_msg_init(&msg); + if (rc != 0) + throw error_t(); + } + + inline explicit message_t(size_t size_) + { + int rc = zmq_msg_init_size(&msg, size_); + if (rc != 0) + throw error_t(); + } + + template message_t(T first, T last) : msg() + { + typedef typename std::iterator_traits::difference_type size_type; + typedef typename std::iterator_traits::value_type value_t; + + size_type const size_ = std::distance(first, last) * sizeof(value_t); + int const rc = zmq_msg_init_size(&msg, size_); + if (rc != 0) + throw error_t(); + value_t *dest = data(); + while (first != last) { + *dest = *first; + ++dest; + ++first; + } + } + + inline message_t(const void *data_, size_t size_) + { + int rc = zmq_msg_init_size(&msg, size_); + if (rc != 0) + throw error_t(); + memcpy(data(), data_, size_); + } + + inline message_t(void *data_, size_t size_, free_fn *ffn_, void *hint_ = NULL) + { + int rc = zmq_msg_init_data(&msg, data_, size_, ffn_, hint_); + if (rc != 0) + throw error_t(); + } + +#if defined(ZMQ_BUILD_DRAFT_API) && defined(ZMQ_CPP11) + template + explicit message_t(const T &msg_) : message_t(std::begin(msg_), std::end(msg_)) + { + } +#endif + +#ifdef ZMQ_HAS_RVALUE_REFS + inline message_t(message_t &&rhs) : msg(rhs.msg) + { + int rc = zmq_msg_init(&rhs.msg); + if (rc != 0) + throw error_t(); + } + + inline message_t &operator=(message_t &&rhs) ZMQ_NOTHROW + { + std::swap(msg, rhs.msg); + return *this; + } +#endif + + inline ~message_t() ZMQ_NOTHROW + { + int rc = zmq_msg_close(&msg); + ZMQ_ASSERT(rc == 0); + } + + inline void rebuild() + { + int rc = zmq_msg_close(&msg); + if (rc != 0) + throw error_t(); + rc = zmq_msg_init(&msg); + if (rc != 0) + throw error_t(); + } + + inline void rebuild(size_t size_) + { + int rc = zmq_msg_close(&msg); + if (rc != 0) + throw error_t(); + rc = zmq_msg_init_size(&msg, size_); + if (rc != 0) + throw error_t(); + } + + inline void rebuild(const void *data_, size_t size_) + { + int rc = zmq_msg_close(&msg); + if (rc != 0) + throw error_t(); + rc = zmq_msg_init_size(&msg, size_); + if (rc != 0) + throw error_t(); + memcpy(data(), data_, size_); + } + + inline void rebuild(void *data_, size_t size_, free_fn *ffn_, void *hint_ = NULL) + { + int rc = zmq_msg_close(&msg); + if (rc != 0) + throw error_t(); + rc = zmq_msg_init_data(&msg, data_, size_, ffn_, hint_); + if (rc != 0) + throw error_t(); + } + + inline void move(message_t const *msg_) + { + int rc = zmq_msg_move(&msg, const_cast(&(msg_->msg))); + if (rc != 0) + throw error_t(); + } + + inline void copy(message_t const *msg_) + { + int rc = zmq_msg_copy(&msg, const_cast(&(msg_->msg))); + if (rc != 0) + throw error_t(); + } + + inline bool more() const ZMQ_NOTHROW + { + int rc = zmq_msg_more(const_cast(&msg)); + return rc != 0; + } + + inline void *data() ZMQ_NOTHROW { return zmq_msg_data(&msg); } + + inline const void *data() const ZMQ_NOTHROW + { + return zmq_msg_data(const_cast(&msg)); + } + + inline size_t size() const ZMQ_NOTHROW + { + return zmq_msg_size(const_cast(&msg)); + } + + template T *data() ZMQ_NOTHROW { return static_cast(data()); } + + template T const *data() const ZMQ_NOTHROW + { + return static_cast(data()); + } + + ZMQ_DEPRECATED("from 4.3.0, use operator== instead") + inline bool equal(const message_t *other) const ZMQ_NOTHROW + { + return *this == *other; + } + + inline bool operator==(const message_t &other) const ZMQ_NOTHROW + { + const size_t my_size = size(); + return my_size == other.size() && 0 == memcmp(data(), other.data(), my_size); + } + + inline bool operator!=(const message_t &other) const ZMQ_NOTHROW + { + return !(*this == other); + } + +#if ZMQ_VERSION >= ZMQ_MAKE_VERSION(4, 1, 0) + inline const char *gets(const char *property_) + { + const char *value = zmq_msg_gets(&msg, property_); + if (value == NULL) + throw error_t(); + return value; + } +#endif + +#if defined(ZMQ_BUILD_DRAFT_API) && ZMQ_VERSION >= ZMQ_MAKE_VERSION(4, 2, 0) + inline uint32_t routing_id() const + { + return zmq_msg_routing_id(const_cast(&msg)); + } + + inline void set_routing_id(uint32_t routing_id) + { + int rc = zmq_msg_set_routing_id(&msg, routing_id); + if (rc != 0) + throw error_t(); + } + + inline const char* group() const + { + return zmq_msg_group(const_cast(&msg)); + } + + inline void set_group(const char* group) + { + int rc = zmq_msg_set_group(&msg, group); + if (rc != 0) + throw error_t(); + } +#endif + + /** Dump content to string. Ascii chars are readable, the rest is printed as hex. + * Probably ridiculously slow. + */ + inline std::string str() const + { + // Partly mutuated from the same method in zmq::multipart_t + std::stringstream os; + + const unsigned char *msg_data = this->data(); + unsigned char byte; + size_t size = this->size(); + int is_ascii[2] = {0, 0}; + + os << "zmq::message_t [size " << std::dec << std::setw(3) + << std::setfill('0') << size << "] ("; + // Totally arbitrary + if (size >= 1000) { + os << "... too big to print)"; + } else { + while (size--) { + byte = *msg_data++; + + is_ascii[1] = (byte >= 33 && byte < 127); + if (is_ascii[1] != is_ascii[0]) + os << " "; // Separate text/non text + + if (is_ascii[1]) { + os << byte; + } else { + os << std::hex << std::uppercase << std::setw(2) + << std::setfill('0') << static_cast(byte); + } + is_ascii[0] = is_ascii[1]; + } + os << ")"; + } + return os.str(); + } + + private: + // The underlying message + zmq_msg_t msg; + + // Disable implicit message copying, so that users won't use shared + // messages (less efficient) without being aware of the fact. + message_t(const message_t &) ZMQ_DELETED_FUNCTION; + void operator=(const message_t &) ZMQ_DELETED_FUNCTION; +}; + +class context_t +{ + friend class socket_t; + + public: + inline context_t() + { + ptr = zmq_ctx_new(); + if (ptr == NULL) + throw error_t(); + } + + + inline explicit context_t(int io_threads_, + int max_sockets_ = ZMQ_MAX_SOCKETS_DFLT) + { + ptr = zmq_ctx_new(); + if (ptr == NULL) + throw error_t(); + + int rc = zmq_ctx_set(ptr, ZMQ_IO_THREADS, io_threads_); + ZMQ_ASSERT(rc == 0); + + rc = zmq_ctx_set(ptr, ZMQ_MAX_SOCKETS, max_sockets_); + ZMQ_ASSERT(rc == 0); + } + +#ifdef ZMQ_HAS_RVALUE_REFS + inline context_t(context_t &&rhs) ZMQ_NOTHROW : ptr(rhs.ptr) { rhs.ptr = NULL; } + inline context_t &operator=(context_t &&rhs) ZMQ_NOTHROW + { + std::swap(ptr, rhs.ptr); + return *this; + } +#endif + + inline int setctxopt(int option_, int optval_) + { + int rc = zmq_ctx_set(ptr, option_, optval_); + ZMQ_ASSERT(rc == 0); + return rc; + } + + inline int getctxopt(int option_) { return zmq_ctx_get(ptr, option_); } + + inline ~context_t() ZMQ_NOTHROW { close(); } + + inline void close() ZMQ_NOTHROW + { + if (ptr == NULL) + return; + + int rc; + do { + rc = zmq_ctx_destroy(ptr); + } while (rc == -1 && errno == EINTR); + + ZMQ_ASSERT(rc == 0); + ptr = NULL; + } + + // Be careful with this, it's probably only useful for + // using the C api together with an existing C++ api. + // Normally you should never need to use this. + inline ZMQ_EXPLICIT operator void *() ZMQ_NOTHROW { return ptr; } + + inline ZMQ_EXPLICIT operator void const *() const ZMQ_NOTHROW { return ptr; } + + inline operator bool() const ZMQ_NOTHROW { return ptr != NULL; } + + private: + void *ptr; + + context_t(const context_t &) ZMQ_DELETED_FUNCTION; + void operator=(const context_t &) ZMQ_DELETED_FUNCTION; +}; + +#ifdef ZMQ_CPP11 +enum class socket_type : int +{ + req = ZMQ_REQ, + rep = ZMQ_REP, + dealer = ZMQ_DEALER, + router = ZMQ_ROUTER, + pub = ZMQ_PUB, + sub = ZMQ_SUB, + xpub = ZMQ_XPUB, + xsub = ZMQ_XSUB, + push = ZMQ_PUSH, + pull = ZMQ_PULL, +#if defined(ZMQ_BUILD_DRAFT_API) && ZMQ_VERSION >= ZMQ_MAKE_VERSION(4, 2, 0) + server = ZMQ_SERVER, + client = ZMQ_CLIENT, + radio = ZMQ_RADIO, + dish = ZMQ_DISH, +#endif +#if ZMQ_VERSION_MAJOR >= 4 + stream = ZMQ_STREAM, +#endif + pair = ZMQ_PAIR +}; +#endif + +class socket_t +{ + friend class monitor_t; + + public: + inline socket_t(context_t &context_, int type_) { init(context_, type_); } + +#ifdef ZMQ_CPP11 + inline socket_t(context_t &context_, socket_type type_) + { + init(context_, static_cast(type_)); + } +#endif + +#ifdef ZMQ_HAS_RVALUE_REFS + inline socket_t(socket_t &&rhs) ZMQ_NOTHROW : ptr(rhs.ptr), ctxptr(rhs.ctxptr) + { + rhs.ptr = NULL; + rhs.ctxptr = NULL; + } + inline socket_t &operator=(socket_t &&rhs) ZMQ_NOTHROW + { + std::swap(ptr, rhs.ptr); + return *this; + } +#endif + + inline ~socket_t() ZMQ_NOTHROW { close(); } + + inline operator void *() ZMQ_NOTHROW { return ptr; } + + inline operator void const *() const ZMQ_NOTHROW { return ptr; } + + inline void close() ZMQ_NOTHROW + { + if (ptr == NULL) + // already closed + return; + int rc = zmq_close(ptr); + ZMQ_ASSERT(rc == 0); + ptr = 0; + } + + template void setsockopt(int option_, T const &optval) + { + setsockopt(option_, &optval, sizeof(T)); + } + + inline void setsockopt(int option_, const void *optval_, size_t optvallen_) + { + int rc = zmq_setsockopt(ptr, option_, optval_, optvallen_); + if (rc != 0) + throw error_t(); + } + + inline void getsockopt(int option_, void *optval_, size_t *optvallen_) const + { + int rc = zmq_getsockopt(ptr, option_, optval_, optvallen_); + if (rc != 0) + throw error_t(); + } + + template T getsockopt(int option_) const + { + T optval; + size_t optlen = sizeof(T); + getsockopt(option_, &optval, &optlen); + return optval; + } + + inline void bind(std::string const &addr) { bind(addr.c_str()); } + + inline void bind(const char *addr_) + { + int rc = zmq_bind(ptr, addr_); + if (rc != 0) + throw error_t(); + } + + inline void unbind(std::string const &addr) { unbind(addr.c_str()); } + + inline void unbind(const char *addr_) + { + int rc = zmq_unbind(ptr, addr_); + if (rc != 0) + throw error_t(); + } + + inline void connect(std::string const &addr) { connect(addr.c_str()); } + + inline void connect(const char *addr_) + { + int rc = zmq_connect(ptr, addr_); + if (rc != 0) + throw error_t(); + } + + inline void disconnect(std::string const &addr) { disconnect(addr.c_str()); } + + inline void disconnect(const char *addr_) + { + int rc = zmq_disconnect(ptr, addr_); + if (rc != 0) + throw error_t(); + } + + inline bool connected() const ZMQ_NOTHROW { return (ptr != NULL); } + + inline size_t send(const void *buf_, size_t len_, int flags_ = 0) + { + int nbytes = zmq_send(ptr, buf_, len_, flags_); + if (nbytes >= 0) + return (size_t) nbytes; + if (zmq_errno() == EAGAIN) + return 0; + throw error_t(); + } + + inline bool send(message_t &msg_, int flags_ = 0) + { + int nbytes = zmq_msg_send(&(msg_.msg), ptr, flags_); + if (nbytes >= 0) + return true; + if (zmq_errno() == EAGAIN) + return false; + throw error_t(); + } + + template bool send(T first, T last, int flags_ = 0) + { + zmq::message_t msg(first, last); + return send(msg, flags_); + } + +#ifdef ZMQ_HAS_RVALUE_REFS + inline bool send(message_t &&msg_, int flags_ = 0) { return send(msg_, flags_); } +#endif + + inline size_t recv(void *buf_, size_t len_, int flags_ = 0) + { + int nbytes = zmq_recv(ptr, buf_, len_, flags_); + if (nbytes >= 0) + return (size_t) nbytes; + if (zmq_errno() == EAGAIN) + return 0; + throw error_t(); + } + + inline bool recv(message_t *msg_, int flags_ = 0) + { + int nbytes = zmq_msg_recv(&(msg_->msg), ptr, flags_); + if (nbytes >= 0) + return true; + if (zmq_errno() == EAGAIN) + return false; + throw error_t(); + } + +#if defined(ZMQ_BUILD_DRAFT_API) && ZMQ_VERSION >= ZMQ_MAKE_VERSION(4, 2, 0) + inline void join(const char* group) + { + int rc = zmq_join(ptr, group); + if (rc != 0) + throw error_t(); + } + + inline void leave(const char* group) + { + int rc = zmq_leave(ptr, group); + if (rc != 0) + throw error_t(); + } +#endif + + private: + inline void init(context_t &context_, int type_) + { + ctxptr = context_.ptr; + ptr = zmq_socket(context_.ptr, type_); + if (ptr == NULL) + throw error_t(); + } + + void *ptr; + void *ctxptr; + + socket_t(const socket_t &) ZMQ_DELETED_FUNCTION; + void operator=(const socket_t &) ZMQ_DELETED_FUNCTION; +}; + +class monitor_t +{ + public: + monitor_t() : socketPtr(NULL), monitor_socket(NULL) {} + + virtual ~monitor_t() + { + if (socketPtr) + zmq_socket_monitor(socketPtr, NULL, 0); + + if (monitor_socket) + zmq_close(monitor_socket); + } + + +#ifdef ZMQ_HAS_RVALUE_REFS + monitor_t(monitor_t &&rhs) ZMQ_NOTHROW : socketPtr(rhs.socketPtr), + monitor_socket(rhs.monitor_socket) + { + rhs.socketPtr = NULL; + rhs.monitor_socket = NULL; + } + + socket_t &operator=(socket_t &&rhs) ZMQ_DELETED_FUNCTION; +#endif + + + void + monitor(socket_t &socket, std::string const &addr, int events = ZMQ_EVENT_ALL) + { + monitor(socket, addr.c_str(), events); + } + + void monitor(socket_t &socket, const char *addr_, int events = ZMQ_EVENT_ALL) + { + init(socket, addr_, events); + while (true) { + check_event(-1); + } + } + + void init(socket_t &socket, std::string const &addr, int events = ZMQ_EVENT_ALL) + { + init(socket, addr.c_str(), events); + } + + void init(socket_t &socket, const char *addr_, int events = ZMQ_EVENT_ALL) + { + int rc = zmq_socket_monitor(socket.ptr, addr_, events); + if (rc != 0) + throw error_t(); + + socketPtr = socket.ptr; + monitor_socket = zmq_socket(socket.ctxptr, ZMQ_PAIR); + assert(monitor_socket); + + rc = zmq_connect(monitor_socket, addr_); + assert(rc == 0); + + on_monitor_started(); + } + + bool check_event(int timeout = 0) + { + assert(monitor_socket); + + zmq_msg_t eventMsg; + zmq_msg_init(&eventMsg); + + zmq::pollitem_t items[] = { + {monitor_socket, 0, ZMQ_POLLIN, 0}, + }; + + zmq::poll(&items[0], 1, timeout); + + if (items[0].revents & ZMQ_POLLIN) { + int rc = zmq_msg_recv(&eventMsg, monitor_socket, 0); + if (rc == -1 && zmq_errno() == ETERM) + return false; + assert(rc != -1); + + } else { + zmq_msg_close(&eventMsg); + return false; + } + +#if ZMQ_VERSION_MAJOR >= 4 + const char *data = static_cast(zmq_msg_data(&eventMsg)); + zmq_event_t msgEvent; + memcpy(&msgEvent.event, data, sizeof(uint16_t)); + data += sizeof(uint16_t); + memcpy(&msgEvent.value, data, sizeof(int32_t)); + zmq_event_t *event = &msgEvent; +#else + zmq_event_t *event = static_cast(zmq_msg_data(&eventMsg)); +#endif + +#ifdef ZMQ_NEW_MONITOR_EVENT_LAYOUT + zmq_msg_t addrMsg; + zmq_msg_init(&addrMsg); + int rc = zmq_msg_recv(&addrMsg, monitor_socket, 0); + if (rc == -1 && zmq_errno() == ETERM) { + zmq_msg_close(&eventMsg); + return false; + } + + assert(rc != -1); + const char *str = static_cast(zmq_msg_data(&addrMsg)); + std::string address(str, str + zmq_msg_size(&addrMsg)); + zmq_msg_close(&addrMsg); +#else + // Bit of a hack, but all events in the zmq_event_t union have the same layout so this will work for all event types. + std::string address = event->data.connected.addr; +#endif + +#ifdef ZMQ_EVENT_MONITOR_STOPPED + if (event->event == ZMQ_EVENT_MONITOR_STOPPED) { + zmq_msg_close(&eventMsg); + return false; + } + +#endif + + switch (event->event) { + case ZMQ_EVENT_CONNECTED: + on_event_connected(*event, address.c_str()); + break; + case ZMQ_EVENT_CONNECT_DELAYED: + on_event_connect_delayed(*event, address.c_str()); + break; + case ZMQ_EVENT_CONNECT_RETRIED: + on_event_connect_retried(*event, address.c_str()); + break; + case ZMQ_EVENT_LISTENING: + on_event_listening(*event, address.c_str()); + break; + case ZMQ_EVENT_BIND_FAILED: + on_event_bind_failed(*event, address.c_str()); + break; + case ZMQ_EVENT_ACCEPTED: + on_event_accepted(*event, address.c_str()); + break; + case ZMQ_EVENT_ACCEPT_FAILED: + on_event_accept_failed(*event, address.c_str()); + break; + case ZMQ_EVENT_CLOSED: + on_event_closed(*event, address.c_str()); + break; + case ZMQ_EVENT_CLOSE_FAILED: + on_event_close_failed(*event, address.c_str()); + break; + case ZMQ_EVENT_DISCONNECTED: + on_event_disconnected(*event, address.c_str()); + break; +#ifdef ZMQ_BUILD_DRAFT_API +#if ZMQ_VERSION >= ZMQ_MAKE_VERSION(4, 2, 3) + case ZMQ_EVENT_HANDSHAKE_FAILED_NO_DETAIL: + on_event_handshake_failed_no_detail(*event, address.c_str()); + break; + case ZMQ_EVENT_HANDSHAKE_FAILED_PROTOCOL: + on_event_handshake_failed_protocol(*event, address.c_str()); + break; + case ZMQ_EVENT_HANDSHAKE_FAILED_AUTH: + on_event_handshake_failed_auth(*event, address.c_str()); + break; + case ZMQ_EVENT_HANDSHAKE_SUCCEEDED: + on_event_handshake_succeeded(*event, address.c_str()); + break; +#elif ZMQ_VERSION >= ZMQ_MAKE_VERSION(4, 2, 1) + case ZMQ_EVENT_HANDSHAKE_FAILED: + on_event_handshake_failed(*event, address.c_str()); + break; + case ZMQ_EVENT_HANDSHAKE_SUCCEED: + on_event_handshake_succeed(*event, address.c_str()); + break; +#endif +#endif + default: + on_event_unknown(*event, address.c_str()); + break; + } + zmq_msg_close(&eventMsg); + + return true; + } + +#ifdef ZMQ_EVENT_MONITOR_STOPPED + void abort() + { + if (socketPtr) + zmq_socket_monitor(socketPtr, NULL, 0); + + socketPtr = NULL; + } +#endif + virtual void on_monitor_started() {} + virtual void on_event_connected(const zmq_event_t &event_, const char *addr_) + { + (void) event_; + (void) addr_; + } + virtual void on_event_connect_delayed(const zmq_event_t &event_, + const char *addr_) + { + (void) event_; + (void) addr_; + } + virtual void on_event_connect_retried(const zmq_event_t &event_, + const char *addr_) + { + (void) event_; + (void) addr_; + } + virtual void on_event_listening(const zmq_event_t &event_, const char *addr_) + { + (void) event_; + (void) addr_; + } + virtual void on_event_bind_failed(const zmq_event_t &event_, const char *addr_) + { + (void) event_; + (void) addr_; + } + virtual void on_event_accepted(const zmq_event_t &event_, const char *addr_) + { + (void) event_; + (void) addr_; + } + virtual void on_event_accept_failed(const zmq_event_t &event_, const char *addr_) + { + (void) event_; + (void) addr_; + } + virtual void on_event_closed(const zmq_event_t &event_, const char *addr_) + { + (void) event_; + (void) addr_; + } + virtual void on_event_close_failed(const zmq_event_t &event_, const char *addr_) + { + (void) event_; + (void) addr_; + } + virtual void on_event_disconnected(const zmq_event_t &event_, const char *addr_) + { + (void) event_; + (void) addr_; + } +#if ZMQ_VERSION >= ZMQ_MAKE_VERSION(4, 2, 3) + virtual void on_event_handshake_failed_no_detail(const zmq_event_t &event_, + const char *addr_) + { + (void) event_; + (void) addr_; + } + virtual void on_event_handshake_failed_protocol(const zmq_event_t &event_, + const char *addr_) + { + (void) event_; + (void) addr_; + } + virtual void on_event_handshake_failed_auth(const zmq_event_t &event_, + const char *addr_) + { + (void) event_; + (void) addr_; + } + virtual void on_event_handshake_succeeded(const zmq_event_t &event_, + const char *addr_) + { + (void) event_; + (void) addr_; + } +#elif ZMQ_VERSION >= ZMQ_MAKE_VERSION(4, 2, 1) + virtual void on_event_handshake_failed(const zmq_event_t &event_, + const char *addr_) + { + (void) event_; + (void) addr_; + } + virtual void on_event_handshake_succeed(const zmq_event_t &event_, + const char *addr_) + { + (void) event_; + (void) addr_; + } +#endif + virtual void on_event_unknown(const zmq_event_t &event_, const char *addr_) + { + (void) event_; + (void) addr_; + } + + private: + monitor_t(const monitor_t &) ZMQ_DELETED_FUNCTION; + void operator=(const monitor_t &) ZMQ_DELETED_FUNCTION; + + void *socketPtr; + void *monitor_socket; +}; + +#if defined(ZMQ_BUILD_DRAFT_API) && defined(ZMQ_CPP11) && defined(ZMQ_HAVE_POLLER) +template class poller_t +{ + public: + void add(zmq::socket_t &socket, short events, T *user_data) + { + if (0 + != zmq_poller_add(poller_ptr.get(), static_cast(socket), + user_data, events)) { + throw error_t(); + } + } + + void remove(zmq::socket_t &socket) + { + if (0 != zmq_poller_remove(poller_ptr.get(), static_cast(socket))) { + throw error_t(); + } + } + + void modify(zmq::socket_t &socket, short events) + { + if (0 + != zmq_poller_modify(poller_ptr.get(), static_cast(socket), + events)) { + throw error_t(); + } + } + + size_t wait_all(std::vector &poller_events, + const std::chrono::microseconds timeout) + { + int rc = zmq_poller_wait_all(poller_ptr.get(), poller_events.data(), + static_cast(poller_events.size()), + static_cast(timeout.count())); + if (rc > 0) + return static_cast(rc); + +#if ZMQ_VERSION >= ZMQ_MAKE_VERSION(4, 2, 3) + if (zmq_errno() == EAGAIN) +#else + if (zmq_errno() == ETIMEDOUT) +#endif + return 0; + + throw error_t(); + } + + private: + std::unique_ptr> poller_ptr{ + []() { + auto poller_new = zmq_poller_new(); + if (poller_new) + return poller_new; + throw error_t(); + }(), + [](void *ptr) { + int rc = zmq_poller_destroy(&ptr); + ZMQ_ASSERT(rc == 0); + }}; +}; +#endif // defined(ZMQ_BUILD_DRAFT_API) && defined(ZMQ_CPP11) && defined(ZMQ_HAVE_POLLER) + +inline std::ostream &operator<<(std::ostream &os, const message_t &msg) +{ + return os << msg.str(); +} + +} // namespace zmq + +#endif // __ZMQ_HPP_INCLUDED__ diff --git a/roofit/roofitZMQ/src/ZeroMQPoller.cpp b/roofit/roofitZMQ/src/ZeroMQPoller.cpp new file mode 100644 index 0000000000000..29a0dd7d610c7 --- /dev/null +++ b/roofit/roofitZMQ/src/ZeroMQPoller.cpp @@ -0,0 +1,164 @@ +/* + * ROOT + * Copyright (c) 2018, Patrick Bos + * Distributed under the terms of the BSD 3-Clause License. + * + * The full license is in the file LICENSE, distributed with this software. + */ + +#include + +#include "RooFit_ZMQ/ppoll.h" +#include "RooFit_ZMQ/ZeroMQPoller.h" + +std::vector> ZeroMQPoller::poll(int timeo) { + std::vector> r; + if (m_items.empty()) { + throw std::runtime_error("No sockets registered"); + } + int n = 0; + while (true) { + try { + n = zmq::poll(&m_items[0], m_items.size(), timeo); + if (n == 0) return r; + break; + } catch (const zmq::error_t& e) { + std::cerr << "in ZeroMQPoller::poll on PID " << getpid() << ": " << e.what() << std::endl; + if (e.num() != EINTR) { + throw; + } + } + } + // TODO: replace this with ranges::v3::zip + for (size_t i = 0; i < m_items.size(); ++i) { + void* socket = m_items[i].socket; + size_t index = 0; + int flags = 0; + if (socket == nullptr) { + // an fd was registered + std::tie(index, flags) = m_fds[m_items[i].fd]; + } else { + // a socket was registered + const zmq::socket_t* s; + std::tie(index, flags, s) = m_sockets[socket]; + } + if (m_items[i].revents & short(flags)) { + r.emplace_back(index, flags); + } + } + return r; +} + +// This function can throw (from inside ZMQ::ppoll), so wrap in try-catch! +std::vector> ZeroMQPoller::ppoll(int timeo, const sigset_t * sigmask_) { + if (m_items.empty()) { + throw std::runtime_error("No sockets registered"); + } + + std::vector> r; + + auto n = ZMQ::ppoll(m_items, timeo, sigmask_); + if (n == 0) return r; + + for (auto& m_item : m_items) { + size_t index = 0; + int flags = 0; + if (m_item.socket == nullptr) { + // an fd was registered + std::tie(index, flags) = m_fds[m_item.fd]; + } else { + // a socket was registered + const zmq::socket_t* s; + std::tie(index, flags, s) = m_sockets[m_item.socket]; + } + if (m_item.revents & short(flags)) { + r.emplace_back(index, flags); + } + } + return r; +} + + +size_t ZeroMQPoller::size() const { + return m_items.size(); +} + +size_t ZeroMQPoller::register_socket(zmq::socket_t& socket, zmq::PollType type) { + zmq::socket_t* s = &socket; + auto it = m_sockets.find(s); + if (it != m_sockets.end()) { + return std::get<0>(it->second); + } + size_t index = m_free.empty() ? m_items.size() : m_free.front(); + if (!m_free.empty()) m_free.pop_front(); + // NOTE: tis uses the conversion-to-void* operator of + // zmq::socket_t, which returns the wrapped object + m_items.push_back({socket, 0, type, 0}); + + // We need to lookup by the pointer to the object wrapped by zmq::socket_t + m_sockets.emplace(m_items.back().socket, std::make_tuple(index, type, s)); + return index; +} + +size_t ZeroMQPoller::register_socket(int fd, zmq::PollType type) { + auto it = m_fds.find(fd); + if (it != m_fds.end()) { + return std::get<0>(it->second); + } + size_t index = m_free.empty() ? m_items.size() : m_free.front(); + if (!m_free.empty()) m_free.pop_front(); + // NOTE: tis uses the conversion-to-void* operator of + // zmq::socket_t, which returns the wrapped object + m_items.push_back({nullptr, fd, type, 0}); + + // We need to lookup by the pointer to the object wrapped by zmq::socket_t + m_fds.emplace(fd, std::make_tuple(index, type)); + return index; +} + + +size_t ZeroMQPoller::unregister_socket(zmq::socket_t& socket) { + if (!m_sockets.count(socket.operator void*())) { + throw std::out_of_range("Socket is not registered"); + } + // Remove from m_sockets + // Can't search by the key of m_sockets, as that is the wrapped + // object, but have to use the pointer to the wrapper + // (zmq::socket_t) + auto it = std::find_if(begin(m_sockets), end(m_sockets), + [&socket](const decltype(m_sockets)::value_type& entry) { + return &socket == std::get<2>(entry.second); + }); + auto index = std::get<0>(it->second); + m_free.push_back(index); + m_sockets.erase(it); + + // Remove from m_items + auto iit = std::find_if(begin(m_items), end(m_items), [&it](const zmq::pollitem_t& item) { + return it->first == item.socket; + }); + assert(iit != end(m_items)); + m_items.erase(iit); + + return index; +} + +size_t ZeroMQPoller::unregister_socket(int fd) { + if (!m_fds.count(fd)) { + throw std::out_of_range("fileno is not registered"); + } + // Remove from m_fds + auto it = m_fds.find(fd); + auto index = std::get<0>(it->second); + m_free.push_back(index); + m_fds.erase(it); + + // Remove from m_items + auto iit = std::find_if(begin(m_items), end(m_items), [&it](const zmq::pollitem_t& item) { + return it->first == item.fd; + }); + assert(iit != end(m_items)); + m_items.erase(iit); + + return index; +} diff --git a/roofit/roofitZMQ/src/ZeroMQSvc.cpp b/roofit/roofitZMQ/src/ZeroMQSvc.cpp new file mode 100644 index 0000000000000..b69695349e0c3 --- /dev/null +++ b/roofit/roofitZMQ/src/ZeroMQSvc.cpp @@ -0,0 +1,103 @@ +//#include +#include // std::ref +#include + +#include "RooFit_ZMQ/ZeroMQSvc.h" + +ZeroMQSvc& zmqSvc() { + static std::unique_ptr svc; + if (!svc) { + svc = std::make_unique(); + } + return *svc; +} + +ZeroMQSvc::Encoding ZeroMQSvc::encoding() const { + return m_enc; +} + +void ZeroMQSvc::setEncoding(const ZeroMQSvc::Encoding &e) { + m_enc = e; +} + +zmq::context_t& ZeroMQSvc::context() const { + if (!m_context) { + try { + m_context = new zmq::context_t; + } catch (zmq::error_t& e) { + std::cerr << "ERROR: Creating ZeroMQ context failed. This only happens when PGM initialization failed or when a nullptr was returned from zmq_ctx_new because the created context was invalid. Contact ZMQ experts when this happens, because it shouldn't.\n"; + throw e; + } + } + return *m_context; +} + +zmq::socket_t ZeroMQSvc::socket(int type) const { + try { + // the actual work this function should do, all the rest is error handling: + return zmq::socket_t{context(), type}; + } catch (zmq::error_t& e) { + // all zmq errors not recoverable from here, only at call site + std::cerr << "ERROR in ZeroMQSvc::socket: " << e.what() << " (errno: " << e.num() << ")\n"; + throw e; + } +} + +zmq::socket_t* ZeroMQSvc::socket_ptr(int type) const { + try { + // the actual work this function should do, all the rest is error handling: + return new zmq::socket_t(context(), type); + } catch (zmq::error_t& e) { + // all zmq errors not recoverable from here, only at call site + std::cerr << "ERROR in ZeroMQSvc::socket_ptr: " << e.what() << " (errno: " << e.num() << ")\n"; + throw e; + } +} + +void ZeroMQSvc::close_context() const { + if (m_context) { + delete m_context; + m_context = nullptr; + } +} + + +zmq::message_t ZeroMQSvc::encode(const char* item) const { + std::function fun = ZMQ::stringLength; + return encode(*item, fun); +} + +zmq::message_t ZeroMQSvc::encode(const std::string& item) const { + return encode(item.c_str()); +} + +//zmq::message_t ZeroMQSvc::encode(const TObject& item) const { +// auto deleteBuffer = []( void* data, void* /* hint */ ) -> void { +// delete [] (char*)data; +// }; +// +// TBufferFile buffer(TBuffer::kWrite); +// buffer.WriteObject(&item); +// +// // Create message and detach buffer +// // This is the only ZMQ thing that can throw, and only when memory ran out +// // (errno ENOMEM), and that is something only the caller can fix, so we don't +// // catch it here: +// zmq::message_t message(buffer.Buffer(), buffer.Length(), deleteBuffer); +// buffer.DetachBuffer(); +// +// return message; +//} + + +bool ZeroMQSvc::send(zmq::socket_t& socket, const char* item, int flags) const { + return retry_send(socket, 2, encode(item), flags); +} + +bool ZeroMQSvc::send(zmq::socket_t& socket, zmq::message_t& msg, int flags) const { + return retry_send(socket, 2, std::ref(msg), flags); +} + +bool ZeroMQSvc::send(zmq::socket_t& socket, zmq::message_t&& msg, int flags) const { + return retry_send(socket, 2, std::move(msg), flags); +} diff --git a/roofit/roofitZMQ/src/functions.cpp b/roofit/roofitZMQ/src/functions.cpp new file mode 100644 index 0000000000000..9027bfc65b217 --- /dev/null +++ b/roofit/roofitZMQ/src/functions.cpp @@ -0,0 +1,9 @@ +#include +#include "RooFit_ZMQ/ZeroMQSvc.h" +#include "RooFit_ZMQ/functions.h" + +namespace ZMQ { + size_t stringLength(const char& cs) { + return strlen(&cs); + } +} diff --git a/roofit/roofitZMQ/src/ppoll.cpp b/roofit/roofitZMQ/src/ppoll.cpp new file mode 100644 index 0000000000000..5cfcda16222e1 --- /dev/null +++ b/roofit/roofitZMQ/src/ppoll.cpp @@ -0,0 +1,345 @@ +/* + * Project: RooFit + * Authors: + * PB, Patrick Bos, Netherlands eScience Center, p.bos@esciencecenter.nl + * + * Copyright (c) 2016-2019, Netherlands eScience Center + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ + +#include // pselect +#include // timespec +#include + +#include "likely.hpp" // unlikely +#include "clock.hpp" // zmq::clock_t +#include "err.hpp" // zmq_assert +#include "polling_util.hpp" // zmq::optimized_fd_set_t, zmq::compute_timeout, zmq::timeout_t +#include "fd.hpp" // zmq::fd_t + +#include + + +namespace ZMQ { + +int zmq_ppoll (zmq_pollitem_t *items_, int nitems_, long timeout_, + const sigset_t * sigmask_) +{ +#if defined ZMQ_HAVE_POLLER + // if poller is present, use that if there is at least 1 thread-safe socket, + // otherwise fall back to the previous implementation as it's faster. + for (int i = 0; i != nitems_; i++) { + if (items_[i].socket + && as_socket_base_t (items_[i].socket)->is_thread_safe ()) { + return zmq_poller_poll (items_, nitems_, timeout_); + } + } +#endif // ZMQ_HAVE_POLLER +#if defined ZMQ_POLL_BASED_ON_POLL || defined ZMQ_POLL_BASED_ON_SELECT + if (unlikely (nitems_ < 0)) { + errno = EINVAL; + return -1; + } + if (unlikely (nitems_ == 0)) { + if (timeout_ == 0) + return 0; +#if defined ZMQ_HAVE_WINDOWS + Sleep (timeout_ > 0 ? timeout_ : INFINITE); + return 0; +#elif defined ZMQ_HAVE_VXWORKS + struct timespec ns_; + ns_.tv_sec = timeout_ / 1000; + ns_.tv_nsec = timeout_ % 1000 * 1000000; + return nanosleep (&ns_, 0); +#else + return usleep (timeout_ * 1000); +#endif + } + if (!items_) { + errno = EFAULT; + return -1; + } + + zmq::clock_t clock; + uint64_t now = 0; + uint64_t end = 0; +#if defined ZMQ_POLL_BASED_ON_POLL + zmq::fast_vector_t pollfds (nitems_); + + // Build pollset for poll () system call. + for (int i = 0; i != nitems_; i++) { + // If the poll item is a 0MQ socket, we poll on the file descriptor + // retrieved by the ZMQ_FD socket option. + if (items_[i].socket) { + size_t zmq_fd_size = sizeof (zmq::fd_t); + if (zmq_getsockopt (items_[i].socket, ZMQ_FD, &pollfds[i].fd, + &zmq_fd_size) + == -1) { + return -1; + } + pollfds[i].events = items_[i].events ? POLLIN : 0; + } + // Else, the poll item is a raw file descriptor. Just convert the + // events to normal POLLIN/POLLOUT for poll (). + else { + pollfds[i].fd = items_[i].fd; + pollfds[i].events = + (items_[i].events & ZMQ_POLLIN ? POLLIN : 0) + | (items_[i].events & ZMQ_POLLOUT ? POLLOUT : 0) + | (items_[i].events & ZMQ_POLLPRI ? POLLPRI : 0); + } + } +#else + // Ensure we do not attempt to select () on more than FD_SETSIZE + // file descriptors. + // TODO since this function is called by a client, we could return errno EINVAL/ENOMEM/... here + zmq_assert (nitems_ <= FD_SETSIZE); + + zmq::optimized_fd_set_t pollset_in (nitems_); + FD_ZERO (pollset_in.get ()); + zmq::optimized_fd_set_t pollset_out (nitems_); + FD_ZERO (pollset_out.get ()); + zmq::optimized_fd_set_t pollset_err (nitems_); + FD_ZERO (pollset_err.get ()); + + zmq::fd_t maxfd = 0; + + // Build the fd_sets for passing to select (). + for (int i = 0; i != nitems_; i++) { + // If the poll item is a 0MQ socket we are interested in input on the + // notification file descriptor retrieved by the ZMQ_FD socket option. + if (items_[i].socket) { + size_t zmq_fd_size = sizeof (zmq::fd_t); + zmq::fd_t notify_fd; + if (zmq_getsockopt (items_[i].socket, ZMQ_FD, ¬ify_fd, + &zmq_fd_size) + == -1) + return -1; + if (items_[i].events) { + FD_SET (notify_fd, pollset_in.get ()); + if (maxfd < notify_fd) + maxfd = notify_fd; + } + } + // Else, the poll item is a raw file descriptor. Convert the poll item + // events to the appropriate fd_sets. + else { + if (items_[i].events & ZMQ_POLLIN) + FD_SET (items_[i].fd, pollset_in.get ()); + if (items_[i].events & ZMQ_POLLOUT) + FD_SET (items_[i].fd, pollset_out.get ()); + if (items_[i].events & ZMQ_POLLERR) + FD_SET (items_[i].fd, pollset_err.get ()); + if (maxfd < items_[i].fd) + maxfd = items_[i].fd; + } + } + + zmq::optimized_fd_set_t inset (nitems_); + zmq::optimized_fd_set_t outset (nitems_); + zmq::optimized_fd_set_t errset (nitems_); +#endif + + bool first_pass = true; + int nevents = 0; + + while (true) { +#if defined ZMQ_POLL_BASED_ON_POLL + + // Compute the timeout for the subsequent poll. + zmq::timeout_t timeout = + zmq::compute_timeout (first_pass, timeout_, now, end); + + // Wait for events. + { + int rc = ppoll (&pollfds[0], nitems_, timeout); + if (rc == -1 && errno == EINTR) { + return -1; + } + errno_assert (rc >= 0); + } + // Check for the events. + for (int i = 0; i != nitems_; i++) { + items_[i].revents = 0; + + // The poll item is a 0MQ socket. Retrieve pending events + // using the ZMQ_EVENTS socket option. + if (items_[i].socket) { + size_t zmq_events_size = sizeof (uint32_t); + uint32_t zmq_events; + if (zmq_getsockopt (items_[i].socket, ZMQ_EVENTS, &zmq_events, + &zmq_events_size) + == -1) { + return -1; + } + if ((items_[i].events & ZMQ_POLLOUT) + && (zmq_events & ZMQ_POLLOUT)) + items_[i].revents |= ZMQ_POLLOUT; + if ((items_[i].events & ZMQ_POLLIN) + && (zmq_events & ZMQ_POLLIN)) + items_[i].revents |= ZMQ_POLLIN; + } + // Else, the poll item is a raw file descriptor, simply convert + // the events to zmq_pollitem_t-style format. + else { + if (pollfds[i].revents & POLLIN) + items_[i].revents |= ZMQ_POLLIN; + if (pollfds[i].revents & POLLOUT) + items_[i].revents |= ZMQ_POLLOUT; + if (pollfds[i].revents & POLLPRI) + items_[i].revents |= ZMQ_POLLPRI; + if (pollfds[i].revents & ~(POLLIN | POLLOUT | POLLPRI)) + items_[i].revents |= ZMQ_POLLERR; + } + + if (items_[i].revents) + nevents++; + } + +#else + + // Compute the timeout for the subsequent poll. + struct timespec timeout; + struct timespec *ptimeout; + if (first_pass) { + timeout.tv_sec = 0; + timeout.tv_nsec = 0; + ptimeout = &timeout; + } else if (timeout_ < 0) + ptimeout = NULL; + else { + timeout.tv_sec = static_cast ((end - now) / 1000); + timeout.tv_nsec = static_cast ((end - now) % 1000 * 1000000); + ptimeout = &timeout; + } + + // Wait for events. Ignore interrupts if there's infinite timeout. + while (true) { + memcpy (inset.get (), pollset_in.get (), + zmq::valid_pollset_bytes (*pollset_in.get ())); + memcpy (outset.get (), pollset_out.get (), + zmq::valid_pollset_bytes (*pollset_out.get ())); + memcpy (errset.get (), pollset_err.get (), + zmq::valid_pollset_bytes (*pollset_err.get ())); +#if defined ZMQ_HAVE_WINDOWS + int rc = + select (0, inset.get (), outset.get (), errset.get (), ptimeout); + if (unlikely (rc == SOCKET_ERROR)) { + errno = zmq::wsa_error_to_errno (WSAGetLastError ()); + wsa_assert (errno == ENOTSOCK); + return -1; + } +#else + int rc = pselect (maxfd + 1, inset.get (), outset.get (), + errset.get (), ptimeout, sigmask_); + if (unlikely (rc == -1)) { + errno_assert (errno == EINTR || errno == EBADF); + return -1; + } +#endif + break; + } + + // Check for the events. + for (int i = 0; i != nitems_; i++) { + items_[i].revents = 0; + + // The poll item is a 0MQ socket. Retrieve pending events + // using the ZMQ_EVENTS socket option. + if (items_[i].socket) { + size_t zmq_events_size = sizeof (uint32_t); + uint32_t zmq_events; + if (zmq_getsockopt (items_[i].socket, ZMQ_EVENTS, &zmq_events, + &zmq_events_size) + == -1) + return -1; + if ((items_[i].events & ZMQ_POLLOUT) + && (zmq_events & ZMQ_POLLOUT)) + items_[i].revents |= ZMQ_POLLOUT; + if ((items_[i].events & ZMQ_POLLIN) + && (zmq_events & ZMQ_POLLIN)) + items_[i].revents |= ZMQ_POLLIN; + } + // Else, the poll item is a raw file descriptor, simply convert + // the events to zmq_pollitem_t-style format. + else { + if (FD_ISSET (items_[i].fd, inset.get ())) + items_[i].revents |= ZMQ_POLLIN; + if (FD_ISSET (items_[i].fd, outset.get ())) + items_[i].revents |= ZMQ_POLLOUT; + if (FD_ISSET (items_[i].fd, errset.get ())) + items_[i].revents |= ZMQ_POLLERR; + } + + if (items_[i].revents) + nevents++; + } +#endif + + // If timeout is zero, exit immediately whether there are events or not. + if (timeout_ == 0) + break; + + // If there are events to return, we can exit immediately. + if (nevents) + break; + + // At this point we are meant to wait for events but there are none. + // If timeout is infinite we can just loop until we get some events. + if (timeout_ < 0) { + if (first_pass) + first_pass = false; + continue; + } + + // The timeout is finite and there are no events. In the first pass + // we get a timestamp of when the polling have begun. (We assume that + // first pass have taken negligible time). We also compute the time + // when the polling should time out. + if (first_pass) { + now = clock.now_ms (); + end = now + timeout_; + if (now == end) + break; + first_pass = false; + continue; + } + + // Find out whether timeout have expired. + now = clock.now_ms (); + if (now >= end) + break; + } + + return nevents; +#else + // Exotic platforms that support neither poll() nor select(). + errno = ENOTSUP; + return -1; +#endif +} + + +// This function can throw, so wrap in try-catch! +int ppoll(zmq_pollitem_t *items_, size_t nitems_, long timeout_, const sigset_t * sigmask_) +{ + int rc = zmq_ppoll(items_, static_cast(nitems_), timeout_, sigmask_); + if (rc < 0) + throw ppoll_error_t(); + return rc; +} + + +// This function can throw, so wrap in try-catch! +int ppoll(std::vector &items, long timeout_, const sigset_t * sigmask_) +{ + return ppoll(items.data(), items.size(), timeout_, sigmask_); +} + +} + diff --git a/roofit/roofitZMQ/test/CMakeLists.txt b/roofit/roofitZMQ/test/CMakeLists.txt new file mode 100644 index 0000000000000..2db18e301aa1e --- /dev/null +++ b/roofit/roofitZMQ/test/CMakeLists.txt @@ -0,0 +1,10 @@ +#find_package(ZeroMQ REQUIRED) + +ROOT_ADD_GTEST(test_RooFitZMQ test_ZMQ.cpp LIBRARIES + RooFitZMQ ${ZeroMQ_LIBRARY} + RooFitMultiProcess # wait_for_child in util +) + +ROOT_ADD_GTEST(test_RooFitZMQ_polling test_polling.cxx LIBRARIES RooFitZMQ ${ZeroMQ_LIBRARY}) +ROOT_ADD_GTEST(test_RooFitZMQ_HWM test_HWM.cxx LIBRARIES RooFitZMQ ${ZeroMQ_LIBRARY}) +ROOT_ADD_GTEST(test_RooFitZMQ_load_balancing test_ZMQ_load_balancing.cxx LIBRARIES RooFitZMQ ${ZeroMQ_LIBRARY}) diff --git a/roofit/roofitZMQ/test/test_HWM.cxx b/roofit/roofitZMQ/test/test_HWM.cxx new file mode 100644 index 0000000000000..7ef11b5c3f5ed --- /dev/null +++ b/roofit/roofitZMQ/test/test_HWM.cxx @@ -0,0 +1,130 @@ +/* + * Project: RooFit + * Authors: + * PB, Patrick Bos, Netherlands eScience Center, p.bos@esciencecenter.nl + * + * Copyright (c) 2016-2021, Netherlands eScience Center + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ + +#include "gtest/gtest.h" + +#include // fork, usleep + +#include "RooFit_ZMQ/ZeroMQSvc.h" + +class HighWaterMarkTest : public ::testing::Test { +protected: + void SetUp() override { + do { + child_pid = fork(); + } while (child_pid == -1); // retry if fork fails + + if (child_pid > 0) { // parent + pusher.reset(zmqSvc().socket_ptr(zmq::PUSH)); + if (set_hwm) { + auto rc = zmq_setsockopt(*pusher, ZMQ_SNDHWM, &hwm, sizeof hwm); + assert(rc == 0); + } + pusher->bind("ipc:///tmp/ZMQ_test_fork_polling_P2C.ipc"); + } else { // child + puller.reset(zmqSvc().socket_ptr(zmq::PULL)); + if (set_hwm) { + auto rc = zmq_setsockopt(*puller, ZMQ_RCVHWM, &hwm, sizeof hwm); + assert(rc == 0); + } + puller->connect("ipc:///tmp/ZMQ_test_fork_polling_P2C.ipc"); + } + } + + void TearDown() override { + if (child_pid > 0) { // parent + // wait for child + int status = 0; + pid_t pid; + do { + pid = waitpid(child_pid, &status, 0); + } while (-1 == pid && EINTR == errno); // retry on interrupted system call + if (0 != status) { + if (WIFEXITED(status)) { printf("exited, status=%d\n", WEXITSTATUS(status)); } else if (WIFSIGNALED(status)) { printf("killed by signal %d\n", WTERMSIG(status)); } else if (WIFSTOPPED(status)) { printf("stopped by signal %d\n", WSTOPSIG(status)); } else if (WIFCONTINUED(status)) { printf("continued\n"); } + } + if (-1 == pid) { + throw std::runtime_error(std::string("waitpid, errno ") + std::to_string(errno)); + } + pusher.reset(nullptr); + zmqSvc().close_context(); + } else { // child + puller.reset(nullptr); + zmqSvc().close_context(); + } + } + + void run_test() { + std::size_t max_sends = 2000; + + if (child_pid > 0) { // parent + // start test + for (std::size_t ix = 0; ix < max_sends; ++ix) { + zmqSvc().send(*pusher, 0.1f); + zmqSvc().send(*pusher, 1); + zmqSvc().send(*pusher, true); + if (ix % 100 == 0) { + printf("parent at ix = %lu\n", ix); + } + } + } else { // child + // wait a few seconds before reading to allow the parent to overflow the HWM + printf("child waiting for 2 seconds...\n"); + sleep(2); + printf("child starts receiving\n"); + + for (std::size_t ix = 0; ix < max_sends; ++ix) { + zmqSvc().receive(*puller); + zmqSvc().receive(*puller); + zmqSvc().receive(*puller); + if (ix % 100 == 0) { + printf("child at ix = %lu\n", ix); + } + } + } + } + + pid_t child_pid {0}; + ZmqLingeringSocketPtr<> pusher, puller; + bool set_hwm = false; + int hwm = 0; +}; + + +TEST_F(HighWaterMarkTest, demonstrateHittingDefaultHWM) { + run_test(); +} + +class HighWaterMarkZeroTest : public HighWaterMarkTest { + void SetUp() override { + set_hwm = true; + hwm = 0; + HighWaterMarkTest::SetUp(); + } +}; + +TEST_F(HighWaterMarkZeroTest, zeroHWM) { + run_test(); +} + +class HighWaterMark2kTest : public HighWaterMarkTest { + void SetUp() override { + set_hwm = true; + hwm = 2000; + HighWaterMarkTest::SetUp(); + } +}; + +TEST_F(HighWaterMark2kTest, HWM2k) { + run_test(); +} diff --git a/roofit/roofitZMQ/test/test_ZMQ.cpp b/roofit/roofitZMQ/test/test_ZMQ.cpp new file mode 100644 index 0000000000000..324fe7eabd679 --- /dev/null +++ b/roofit/roofitZMQ/test/test_ZMQ.cpp @@ -0,0 +1,266 @@ +/***************************************************************************** + * Project: RooFit * + * Package: RooFitCore * + * @(#)root/roofitcore:$Id$ + * Authors: * + * PB, Patrick Bos, NL eScience Center, p.bos@esciencecenter.nl * + * * + * Redistribution and use in source and binary forms, * + * with or without modification, are permitted according to the terms * + * listed in LICENSE (http://roofit.sourceforge.net/license.txt) * + *****************************************************************************/ + +#include "gtest/gtest.h" + +#include // fork, usleep + +#include + +#include "RooFit_ZMQ/ZeroMQSvc.h" +#include // wait_for_child + +void elaborate_bind(const ZmqLingeringSocketPtr<>& socket, std::string name) { + try { + socket->bind(name); + } catch (const zmq::error_t& e) { + if (e.num() == EADDRINUSE) { + std::cerr << "address already in use, retrying bind in 500ms\n"; + usleep(500000); + try { + socket->bind(name); + } catch (const zmq::error_t& e2) { + if (e2.num() == EADDRINUSE) { + std::cerr + << "again: address already in use, aborting; please check whether there are any remaining improperly exited processes (zombies) around or whether some other program is using port 6660\n"; + } + throw e2; + } + // Sometimes, the socket from the previous test needs some time to close, so + // we introduce a latency here. A more robust and fast approach might be to + // do the following on the bind side: + // 1. first try another port, e.g. increase by one + // 2. if that doesn't work, do the latency and retry the original port + // The connect side then also needs to change, because it doesn't know which + // port the bind side will bind to. The connect side could try connecting to + // both options asynchronously, and then in a loop check both for signs of + // life. If one comes alive, transfer ownership of that pointer to the pointer + // you want to eventually use (`socket`) and that's it. + } else { + throw e; + } + } +} + +class AllSocketTypes : public ::testing::TestWithParam< std::tuple, std::pair /* socket_names */> > {}; + +TEST_P(AllSocketTypes, forkHandshake) { + auto socket_names = std::get<2>(GetParam()); + pid_t child_pid {0}; + do { + child_pid = fork(); + } while (child_pid == -1); // retry if fork fails + + if (child_pid > 0) { // master + ZmqLingeringSocketPtr<> socket; + socket.reset(zmqSvc().socket_ptr(std::get<1>(GetParam()).first)); + elaborate_bind(socket, socket_names.second); + // bind is on the master process to avoid zombie children to hold on to binds + + // start test + zmqSvc().send(*socket, std::string("breaker breaker")); + + auto receipt = zmqSvc().receive(*socket); + + EXPECT_EQ(receipt, 1212); + +// socket->close(); // this gives exception of type zmq::error_t: Socket operation on non-socket + socket.reset(nullptr); + zmqSvc().close_context(); // if you don't close context in parent process as well, the next repeat will hang + + RooFit::MultiProcess::wait_for_child(child_pid, true, 5); + } else { // child + ZmqLingeringSocketPtr<> socket; + socket.reset(zmqSvc().socket_ptr(std::get<1>(GetParam()).second)); + socket->connect(socket_names.first); + + // start test + auto receipt = zmqSvc().receive(*socket); + if (receipt == "breaker breaker") { + zmqSvc().send(*socket, 1212); + } + // take care, don't just use _exit, it will not cleanly destroy context etc! + // if you really need to, at least close and destroy everything properly +// socket->close(); // this gives exception of type zmq::error_t: Socket operation on non-socket + socket.reset(nullptr); + zmqSvc().close_context(); + _Exit(0); + } +} + +std::string ipc {"ipc:///tmp/ZMQ_test_fork.ipc"}; +std::string tcp_server {"tcp://127.0.0.1:6660"}; +std::string tcp_client {"tcp://*:6660"}; +auto socket_name_options = ::testing::Values( + std::make_pair(tcp_server, tcp_client), + std::make_pair(ipc, ipc) + ); + + +INSTANTIATE_TEST_SUITE_P(REQREP, AllSocketTypes, + ::testing::Combine(::testing::Range(0, 10), // repeat to probe connection stability + ::testing::Values(std::make_pair(zmq::REQ, zmq::REP)), + socket_name_options + )); +INSTANTIATE_TEST_SUITE_P(PAIRPAIR, AllSocketTypes, + ::testing::Combine(::testing::Range(0, 10), // repeat to probe connection stability + ::testing::Values(std::make_pair(zmq::PAIR, zmq::PAIR)), + socket_name_options + )); + + +class AsyncSocketTypes : public ::testing::TestWithParam< std::tuple, std::pair /* socket_names */, bool /* expect_throw */> > {}; + +TEST_P(AsyncSocketTypes, forkMultiSendReceive) { + bool expect_throw = std::get<3>(GetParam()); + ZmqLingeringSocketPtr<> socket; + auto socket_names = std::get<2>(GetParam()); + pid_t child_pid {0}; + do { + child_pid = fork(); + } while (child_pid == -1); // retry if fork fails + + if (child_pid > 0) { // master + socket.reset(zmqSvc().socket_ptr(std::get<1>(GetParam()).first)); + elaborate_bind(socket, socket_names.second); + // bind is on the master process to avoid zombie children to hold on to binds + + // start test: send 2 things, receive 1, send 1 more, finish + zmqSvc().send(*socket, std::string("breaker breaker")); + + if (expect_throw) { + EXPECT_ANY_THROW(zmqSvc().send(*socket, std::string("anybody out there?"))); + // NOTE: also in case of a throw, be sure to properly close down the connection! + // Otherwise, you may get zombies waiting for a reply. + socket.reset(nullptr); + zmqSvc().close_context(); // if you don't close context in parent process as well, the next repeat will hang + RooFit::MultiProcess::wait_for_child(child_pid, true, 5); + return; + } else { + EXPECT_NO_THROW(zmqSvc().send(*socket, std::string("anybody out there?"))); + } + + auto receipt = zmqSvc().receive(*socket); + + EXPECT_EQ(receipt, 1212); + + zmqSvc().send(*socket, std::string("kthxbye")); + +// socket->close(); // this gives exception of type zmq::error_t: Socket operation on non-socket + socket.reset(nullptr); + zmqSvc().close_context(); // if you don't close context in parent process as well, the next repeat will hang + + RooFit::MultiProcess::wait_for_child(child_pid, true, 5); + } else { // child + socket.reset(zmqSvc().socket_ptr(std::get<1>(GetParam()).second)); + socket->connect(socket_names.first); + + // start test, receive something + auto receipt1 = zmqSvc().receive(*socket); + auto receipt2 = zmqSvc().receive(*socket); + if (receipt1 == "breaker breaker" && receipt2 == "anybody out there?") { + zmqSvc().send(*socket, 1212); + } + auto receipt3 = zmqSvc().receive(*socket); + if (receipt3 != "kthxbye") { + std::cerr << "did not receive final reply correctly\n"; + } + + // take care, don't just use _exit, it will not cleanly destroy context etc! + // if you really need to, at least close and destroy everything properly +// socket->close(); // this gives exception of type zmq::error_t: Socket operation on non-socket + socket.reset(nullptr); + zmqSvc().close_context(); + _Exit(0); + } +} + +TEST_P(AsyncSocketTypes, forkIgnoreSomeMessages) { + bool expect_throw = std::get<3>(GetParam()); + ZmqLingeringSocketPtr<> socket; + auto socket_names = std::get<2>(GetParam()); + pid_t child_pid {0}; + do { + child_pid = fork(); + } while (child_pid == -1); // retry if fork fails + + if (child_pid > 0) { // master + socket.reset(zmqSvc().socket_ptr(std::get<1>(GetParam()).first)); + elaborate_bind(socket, socket_names.second); + // bind is on the master process to avoid zombie children to hold on to binds + + // start test: send 2 things, receive 1, send 1 more, finish + zmqSvc().send(*socket, std::string("breaker breaker")); + + if (expect_throw) { + EXPECT_ANY_THROW(zmqSvc().send(*socket, std::string("anybody out there?"))); + // NOTE: also in case of a throw, be sure to properly close down the connection! + // Otherwise, you may get zombies waiting for a reply. + socket.reset(nullptr); + zmqSvc().close_context(); // if you don't close context in parent process as well, the next repeat will hang + RooFit::MultiProcess::wait_for_child(child_pid, true, 5); + return; + } else { + EXPECT_NO_THROW(zmqSvc().send(*socket, std::string("anybody out there?"))); + } + + auto receipt = zmqSvc().receive(*socket); + + EXPECT_EQ(receipt, 1212); + + zmqSvc().send(*socket, std::string("kthxbye")); + +// socket->close(); // this gives exception of type zmq::error_t: Socket operation on non-socket + socket.reset(nullptr); + zmqSvc().close_context(); // if you don't close context in parent process as well, the next repeat will hang + + RooFit::MultiProcess::wait_for_child(child_pid, true, 5); + } else { // child + socket.reset(zmqSvc().socket_ptr(std::get<1>(GetParam()).second)); + socket->connect(socket_names.first); + + // start test, receive first thing + auto receipt = zmqSvc().receive(*socket); + if (receipt == "breaker breaker") { + zmqSvc().send(*socket, 1212); + } + + // ignore the rest of the sent messages, but give the other end a second to + // actually send its stuff, instead of hanging in retry_send because the + // connection has died; a better solution would be if retry_send (in + // ZeroMQSvc::send) had a callback mechanism that could be used to break + // out when a child has died, but ok + sleep(1); + + // take care, don't just use _exit, it will not cleanly destroy context etc! + // if you really need to, at least close and destroy everything properly +// socket->close(); // this gives exception of type zmq::error_t: Socket operation on non-socket + socket.reset(nullptr); + zmqSvc().close_context(); + _Exit(0); + } +} + + +INSTANTIATE_TEST_SUITE_P(PAIRPAIR, AsyncSocketTypes, + ::testing::Combine(::testing::Range(0, 10), // repeat to probe connection stability + ::testing::Values(std::make_pair(zmq::PAIR, zmq::PAIR)), + socket_name_options, + ::testing::Values(false) // don't expect throw + )); + +INSTANTIATE_TEST_SUITE_P(REQREP, AsyncSocketTypes, + ::testing::Combine(::testing::Values(0), // no repeats, we only care about the throw + ::testing::Values(std::make_pair(zmq::REQ, zmq::REP)), + socket_name_options, + ::testing::Values(true) // expect throw + )); diff --git a/roofit/roofitZMQ/test/test_ZMQ_load_balancing.cxx b/roofit/roofitZMQ/test/test_ZMQ_load_balancing.cxx new file mode 100644 index 0000000000000..ebb4680357104 --- /dev/null +++ b/roofit/roofitZMQ/test/test_ZMQ_load_balancing.cxx @@ -0,0 +1,133 @@ +/* + * Project: RooFit + * Authors: + * PB, Patrick Bos, Netherlands eScience Center, p.bos@esciencecenter.nl + * + * Copyright (c) 2016-2021, Netherlands eScience Center + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ + +#include "gtest/gtest.h" + +#include // fork, usleep + +#include "RooFit_ZMQ/ZeroMQSvc.h" +#include "RooFit_ZMQ/ZeroMQPoller.h" + +class ZMQPushPullTest : public ::testing::Test { +protected: + std::size_t N_children = 4; + std::size_t max_sends = 20; + + void SetUp() override { + for (std::size_t i = 0; i < N_children; ++i) { + do { + child_pid = fork(); + } while (child_pid == -1); // retry if fork fails + if (child_pid == 0) { // child + child_id = i; + break; + } else { + child_pids.push_back(child_pid); + } + } + + if (child_pid > 0) { // parent + pusher.reset(zmqSvc().socket_ptr(zmq::PUSH)); + pusher->bind("ipc:///tmp/ZMQ_test_push_pull_P2C.ipc"); + } else { // child + puller.reset(zmqSvc().socket_ptr(zmq::PULL)); + puller->connect("ipc:///tmp/ZMQ_test_push_pull_P2C.ipc"); + + poller.register_socket(*puller, zmq::POLLIN); + } + } + + void TearDown() override { + if (child_pid > 0) { // parent + // wait for children + int status = 0; + pid_t pid; + for (pid_t child_pid_i : child_pids) { + do { + pid = waitpid(child_pid_i, &status, 0); + } while (-1 == pid && EINTR == errno); // retry on interrupted system call + if (0 != status) { + if (WIFEXITED(status)) { printf("exited, status=%d\n", WEXITSTATUS(status)); } else if (WIFSIGNALED(status)) { printf("killed by signal %d\n", WTERMSIG(status)); } else if (WIFSTOPPED(status)) { printf("stopped by signal %d\n", WSTOPSIG(status)); } else if (WIFCONTINUED(status)) { printf("continued\n"); } + } + if (-1 == pid) { + throw std::runtime_error(std::string("waitpid, errno ") + std::to_string(errno)); + } + } + pusher.reset(nullptr); + zmqSvc().close_context(); + } else { // child + puller.reset(nullptr); + zmqSvc().close_context(); + } + } + + void run_parent() { + // start test + usleep(1000); // wait a second so that all pull sockets are connected for round-robin distribution + // if you don't wait a second above, the push socket will "round-robin" all the messages to just one or two connected sockets + for (std::size_t ix = 0; ix < max_sends; ++ix) { + zmqSvc().send(*pusher, 0); + } + for (std::size_t ix = 0; ix < N_children; ++ix) { + // end by sending some 1's to all children, to let them know the sending is over + zmqSvc().send(*pusher, 1); + } + } + + void run_child() { + std::size_t count = 0; + for (std::size_t ix = 0; ix < max_sends; ++ix) { + auto r = poller.poll(2000); + if (r.empty()) { + printf("poller of child %d timed out after 2 seconds\n", child_id); + break; + } + auto value = zmqSvc().receive(*puller, ZMQ_DONTWAIT); + usleep(200); // "do some work" + printf("value on child %d: %d\n", child_id, value); + if (value == 1) { + printf("child %d got value %d, done here\n", child_id, value); + break; + } + ++count; + } + printf("child %d got %lu values\n", child_id, count); + } + + pid_t child_pid {0}; + int child_id = -1; + std::vector child_pids; + ZmqLingeringSocketPtr<> pusher, puller; + ZeroMQPoller poller; +}; + + +/// This test shows how push-pull is unsuited for load balancing; messages are just sent to the first available pull socket without any dynamic load balancing +TEST_F(ZMQPushPullTest, demoRoundRobin) { + if (child_pid > 0) { + run_parent(); + } else { + run_child(); + } +} + +/// This test tries to see whether push-pull can be made to work as a bit of a load balancer, using a low HWM at the receiver +TEST_F(ZMQPushPullTest, demoHWM1LoadBalancing) { + if (child_pid > 0) { + run_parent(); + } else { + puller->setsockopt(ZMQ_RCVHWM, 1); + run_child(); + } +} diff --git a/roofit/roofitZMQ/test/test_polling.cxx b/roofit/roofitZMQ/test/test_polling.cxx new file mode 100644 index 0000000000000..9250b2ee6018b --- /dev/null +++ b/roofit/roofitZMQ/test/test_polling.cxx @@ -0,0 +1,149 @@ +/* + * Project: RooFit + * Authors: + * PB, Patrick Bos, Netherlands eScience Center, p.bos@esciencecenter.nl + * + * Copyright (c) 2016-2019, Netherlands eScience Center + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ + + +#include "gtest/gtest.h" + +#include // fork, usleep +#include // signal blocking +#include // strsignal() + +#include + +#include "RooFit_ZMQ/ZeroMQSvc.h" +#include "RooFit_ZMQ/ZeroMQPoller.h" + + +static bool terminated = false; + +void handle_sigterm(int signum) { + terminated = true; + std::cout << "handled signal " << strsignal(signum) << " on PID " << getpid() << std::endl; +} + + +TEST(Polling, doublePoll) { + pid_t child_pid {0}; + do { + child_pid = fork(); + } while (child_pid == -1); // retry if fork fails + + if (child_pid > 0) { // master + sigset_t sigmask, sigmask_old; + sigemptyset(&sigmask); + sigaddset(&sigmask, SIGCHLD); + sigprocmask(SIG_BLOCK, &sigmask, &sigmask_old); + +// std::cout << "master PID: " << getpid() << std::endl; + ZmqLingeringSocketPtr<> pusher, puller; + pusher.reset(zmqSvc().socket_ptr(zmq::PUSH)); + pusher->bind("ipc:///tmp/ZMQ_test_fork_polling_M2C.ipc"); + puller.reset(zmqSvc().socket_ptr(zmq::PULL)); + puller->bind("ipc:///tmp/ZMQ_test_fork_polling_C2M.ipc"); + + ZeroMQPoller poller1, poller2; + poller1.register_socket(*puller, zmq::POLLIN); + poller2.register_socket(*puller, zmq::POLLIN); + + // start test + zmqSvc().send(*pusher, std::string("breaker breaker")); + + auto result1a = poller1.poll(-1); + auto result1b = poller1.poll(-1); + auto result2 = poller2.poll(-1); + EXPECT_EQ(result1a.size(), result1b.size()); + EXPECT_EQ(result1a.size(), result2.size()); + + auto receipt = zmqSvc().receive(*puller, ZMQ_DONTWAIT); + + EXPECT_EQ(receipt, 1212); + + kill(child_pid, SIGTERM); + + // socket->close(); // this gives exception of type zmq::error_t: Socket operation on non-socket + pusher.reset(nullptr); + puller.reset(nullptr); + zmqSvc().close_context(); // if you don't close context in parent process as well, the next repeat will hang + + // wait for child + int status = 0; + pid_t pid; + do { + pid = waitpid(child_pid, &status, 0); + } while (-1 == pid && EINTR == errno); // retry on interrupted system call + + if (0 != status) { + if (WIFEXITED(status)) { printf("exited, status=%d\n", WEXITSTATUS(status)); } else if (WIFSIGNALED(status)) { printf("killed by signal %d\n", WTERMSIG(status)); } else if (WIFSTOPPED(status)) { printf("stopped by signal %d\n", WSTOPSIG(status)); } else if (WIFCONTINUED(status)) { printf("continued\n"); } + } + + if (-1 == pid) { + throw std::runtime_error(std::string("waitpid, errno ") + std::to_string(errno)); + } + + sigprocmask(SIG_SETMASK, &sigmask_old, nullptr); + } else { // child +// std::cout << "child PID: " << getpid() << std::endl; + + sigset_t sigmask, sigmask_old; + sigemptyset(&sigmask); + sigaddset(&sigmask, SIGTERM); + sigprocmask(SIG_BLOCK, &sigmask, &sigmask_old); + + struct sigaction sa; + memset (&sa, '\0', sizeof(sa)); + sa.sa_handler = handle_sigterm; + + if (sigaction(SIGTERM, &sa, NULL) < 0) { + std::perror("sigaction failed"); + std::exit(1); + } + + ZmqLingeringSocketPtr<> puller, pusher; + puller.reset(zmqSvc().socket_ptr(zmq::PULL)); + puller->connect("ipc:///tmp/ZMQ_test_fork_polling_M2C.ipc"); + pusher.reset(zmqSvc().socket_ptr(zmq::PUSH)); + pusher->connect("ipc:///tmp/ZMQ_test_fork_polling_C2M.ipc"); + + ZeroMQPoller poller1, poller2; + poller1.register_socket(*puller, zmq::POLLIN); + poller2.register_socket(*puller, zmq::POLLIN); + + // start test + auto result1a = poller1.poll(-1); + auto result1b = poller1.poll(-1); + auto result2 = poller2.poll(-1); + EXPECT_EQ(result1a.size(), result1b.size()); + EXPECT_EQ(result1a.size(), result2.size()); + + auto receipt = zmqSvc().receive(*puller, ZMQ_DONTWAIT); + if (receipt == "breaker breaker") { + zmqSvc().send(*pusher, 1212); + } + // take care, don't just use _exit, it will not cleanly destroy context etc! + // if you really need to, at least close and destroy everything properly + // socket->close(); // this gives exception of type zmq::error_t: Socket operation on non-socket + + sigprocmask(SIG_SETMASK, &sigmask_old, nullptr); + + while (!terminated) {} + +// std::cout << "child terminated" << std::endl; + + puller.reset(nullptr); + pusher.reset(nullptr); + zmqSvc().close_context(); + _Exit(0); + } + +} diff --git a/roofit/roofitcore/CMakeLists.txt b/roofit/roofitcore/CMakeLists.txt index afe21fe236a6f..39d2036ce0ec9 100644 --- a/roofit/roofitcore/CMakeLists.txt +++ b/roofit/roofitcore/CMakeLists.txt @@ -9,6 +9,22 @@ # @author Pere Mato, CERN ############################################################################ +#find_package(ZeroMQ REQUIRED) + +# this should really be used as a dependency for the RooFitCore target, but since the ROOT macros do some custom variable mangling, instead of just using target_link_libraries, defining this library as INTERFACE gives an error like this: https://gitlab.kitware.com/cmake/cmake/issues/18194 when trying to call get_property(dep_include_dirs... in RootNewMacros.cmake. +#add_library(ZeroMQ INTERFACE) +#target_include_directories(ZeroMQ INTERFACE ${ZeroMQ_INCLUDE_DIR}) +#target_link_libraries(ZeroMQ INTERFACE ${ZeroMQ_LIBRARY}) +# so instead, we just use the old style include_directories which is also used inside ROOT_GENERATE_DICTIONARY +#include_directories(${ZeroMQ_INCLUDE_DIR}) + +if(UNIX AND NOT APPLE) + # for Linux, BSD, Solaris, Minix: add rt library for timing in RooCPUTimer + find_library(RT_LIBRARY rt) +else() + set(RT_LIBRARY "") +endif() + ROOT_STANDARD_LIBRARY_PACKAGE(RooFitCore HEADERS Floats.h @@ -113,7 +129,6 @@ ROOT_STANDARD_LIBRARY_PACKAGE(RooFitCore RooGenProdProj.h RooGlobalFunc.h RooGrid.h - RooHashTable.h RooHistError.h RooHistFunc.h RooHist.h @@ -146,7 +161,6 @@ ROOT_STANDARD_LIBRARY_PACKAGE(RooFitCore RooMultiGenFunction.h RooMultiVarGaussian.h RooNameReg.h - RooNameSet.h RooNLLVar.h RooNormSetCache.h RooNumber.h @@ -223,16 +237,46 @@ ROOT_STANDARD_LIBRARY_PACKAGE(RooFitCore RooWorkspace.h RooWorkspaceHandle.h RooXYChi2Var.h + RooTimer.h + RooJsonListFile.h + RooTaskSpec.h + RooGaussMinimizer.h + RooGaussMinimizerFcn.h + NumericalDerivatorMinuit2.h + RooGradMinimizerFcn.h + MultiProcess/BidirMMapPipe.h +# TestStatistics/GradMinimizer.h + TestStatistics/LikelihoodGradientJob.h + TestStatistics/LikelihoodGradientWrapper.h + TestStatistics/LikelihoodJob.h + TestStatistics/LikelihoodWrapper.h + TestStatistics/LikelihoodSerial.h +# TestStatistics/Minimizer.h +# TestStatistics/MinimizerFcn.h +# TestStatistics/MinimizerType.h +# TestStatistics/Minimizer_impl.h + TestStatistics/MinuitFcnGrad.h + TestStatistics/RooAbsL.h + TestStatistics/RooBinnedL.h + TestStatistics/RooSubsidiaryL.h + TestStatistics/RooSumL.h + TestStatistics/RooRealL.h + TestStatistics/RooUnbinnedL.h + TestStatistics/kahan_sum.h + TestStatistics/optional_parameter_types.h + TestStatistics/likelihood_builders.h + TestStatistics/optimization.h + RooAbsMinimizerFcn.h RooHelpers.h RooWrapperPdf.h RooNaNPacker.h RooBinSamplingPdf.h RooFitLegacy/RooCatTypeLegacy.h RooFitLegacy/RooCategorySharedProperties.h + RooFitLegacy/RooHashTable.h + RooFitLegacy/RooNameSet.h RooFitLegacy/RooTreeData.h SOURCES - src/BidirMMapPipe.cxx - src/BidirMMapPipe.h src/Roo1DTable.cxx src/RooAbsAnaConvPdf.cxx src/RooAbsArg.cxx @@ -331,7 +375,6 @@ ROOT_STANDARD_LIBRARY_PACKAGE(RooFitCore src/RooGenProdProj.cxx src/RooGlobalFunc.cxx src/RooGrid.cxx - src/RooHashTable.cxx src/RooHist.cxx src/RooHistError.cxx src/RooHistFunc.cxx @@ -363,7 +406,6 @@ ROOT_STANDARD_LIBRARY_PACKAGE(RooFitCore src/RooMultiGenFunction.cxx src/RooMultiVarGaussian.cxx src/RooNameReg.cxx - src/RooNameSet.cxx src/RooNLLVar.cxx src/RooNormSetCache.cxx src/RooNumber.cxx @@ -437,16 +479,50 @@ ROOT_STANDARD_LIBRARY_PACKAGE(RooFitCore src/RooVectorDataStore.cxx src/RooWorkspace.cxx src/RooXYChi2Var.cxx + src/RooTimer.cxx + src/RooJsonListFile.cxx + src/RooTaskSpec.cxx + src/RooGaussMinimizer.cxx + src/RooGaussMinimizerFcn.cxx + src/NumericalDerivatorMinuit2.cxx + src/RooGradMinimizerFcn.cxx + src/MultiProcess/BidirMMapPipe.cxx +# src/TestStatistics/GradMinimizerFcn.cxx + src/TestStatistics/LikelihoodGradientJob.cxx + src/TestStatistics/LikelihoodGradientWrapper.cxx + src/TestStatistics/LikelihoodJob.cxx + src/TestStatistics/LikelihoodWrapper.cxx + src/TestStatistics/LikelihoodSerial.cxx +# src/TestStatistics/Minimizer.cxx +# src/TestStatistics/MinimizerFcn.cxx +# src/TestStatistics/MinimizerType.cxx + TestStatistics/MinuitFcnGrad.cxx + src/TestStatistics/RooAbsL.cxx + src/TestStatistics/RooBinnedL.cxx + src/TestStatistics/RooSubsidiaryL.cxx + src/TestStatistics/RooSumL.cxx + src/TestStatistics/RooRealL.cxx + src/TestStatistics/RooUnbinnedL.cxx + src/TestStatistics/optional_parameter_types.cxx + src/TestStatistics/likelihood_builders.cxx + src/TestStatistics/kahan_sum.cxx + src/TestStatistics/optimization.cxx + src/RooAbsMinimizerFcn.cxx src/RooHelpers.cxx src/RooWrapperPdf.cxx src/RooBinSamplingPdf.cxx src/RooFitLegacy/RooCatTypeLegacy.cxx src/RooFitLegacy/RooCategorySharedProperties.cxx + src/RooFitLegacy/RooHashTable.cxx src/RooFitLegacy/RooMultiCatIter.cxx + src/RooFitLegacy/RooNameSet.cxx DICTIONARY_OPTIONS "-writeEmptyRootPCM" +# -I${ZeroMQ_INCLUDE_DIR} # necessary because zmq.h is included in the RooFitCore dictionary as well via MultiProcess LIBRARIES RooBatchCompute + ${RT_LIBRARY} + ${ZeroMQ_LIBRARY} DEPENDENCIES Core Hist @@ -454,10 +530,14 @@ ROOT_STANDARD_LIBRARY_PACKAGE(RooFitCore Matrix Tree Minuit + Minuit2 RIO MathCore Foam Smatrix + RooFitZMQ # necessary for ppoll and zmqSvc that are necessary via MultiProcess includes + RooFitMultiProcess +# ZeroMQ # as noted above, this doesn't work here, so we instead list it as a library below LINKDEF inc/LinkDef.h ) @@ -468,4 +548,8 @@ if(NOT MSVC) target_compile_options(RooFitCore PUBLIC -fno-math-errno) endif() -ROOT_ADD_TEST_SUBDIRECTORY(test) +target_include_directories(RooFitCore PRIVATE ${ZeroMQ_INCLUDE_DIR}) + +if(testing) + ROOT_ADD_TEST_SUBDIRECTORY(test) +endif() diff --git a/roofit/roofitcore/inc/LinkDef.h b/roofit/roofitcore/inc/LinkDef.h index c11859e1b0ba4..e4caba54714ea 100644 --- a/roofit/roofitcore/inc/LinkDef.h +++ b/roofit/roofitcore/inc/LinkDef.h @@ -336,9 +336,11 @@ #pragma link C++ class std::pair+ ; #pragma link C++ class RooUnitTest+ ; #ifndef __ROOFIT_NOROOMINIMIZER -#pragma link C++ class RooMinimizer+ ; -#pragma link C++ class RooMinimizerFcn+ ; +#pragma link C++ class RooMinimizerFcn+; +#pragma link C++ class RooMinimizer+; +#pragma link C++ class RooGradMinimizerFcn+; #endif +#pragma link C++ class RooFit::TestStatistics::RooRealL+; #pragma link C++ class RooAbsMoment+ ; #pragma link C++ class RooMoment+ ; #pragma link C++ class RooFirstMoment+ ; diff --git a/roofit/roofitcore/src/BidirMMapPipe.h b/roofit/roofitcore/inc/MultiProcess/BidirMMapPipe.h similarity index 95% rename from roofit/roofitcore/src/BidirMMapPipe.h rename to roofit/roofitcore/inc/MultiProcess/BidirMMapPipe.h index 697dcef11a1a3..32f1174cad336 100644 --- a/roofit/roofitcore/src/BidirMMapPipe.h +++ b/roofit/roofitcore/inc/MultiProcess/BidirMMapPipe.h @@ -4,7 +4,9 @@ * serves as communications channel between parent and child * * @author Manuel Schiller - * @date 2013-07-07 + * @author Patrick Bos + * @author Inti Pelupessy + * @date 2013-2018 */ #ifndef BIDIRMMAPPIPE_H @@ -14,6 +16,18 @@ #include #include #include +#include +#include "RooTaskSpec.h" + +// forward declarations +namespace RooFit { + namespace MultiProcessV1 { + enum class M2Q; + enum class Q2M; + enum class W2Q; + enum class Q2W; + } +} #define BEGIN_NAMESPACE_ROOFIT namespace RooFit { #define END_NAMESPACE_ROOFIT } @@ -23,12 +37,37 @@ BEGIN_NAMESPACE_ROOFIT /// namespace for implementation details of BidirMMapPipe namespace BidirMMapPipe_impl { // forward declarations - class BidirMMapPipeException; class Page; class PagePool; class Pages; - /** @brief class representing a chunk of pages + /** @brief exception to throw if low-level OS calls go wrong + * + * @author Manuel Schiller + * @date 2013-07-07 + */ + class BidirMMapPipeException : public std::exception { + private: + enum { + s_sz = 256 ///< length of buffer + }; + char m_buf[s_sz]; ///< buffer containing the error message + + /// for the POSIX version of strerror_r + static int dostrerror_r(int err, char* buf, std::size_t sz, + int (*f)(int, char*, std::size_t)); + /// for the GNU version of strerror_r + static int dostrerror_r(int, char*, std::size_t, + char* (*f)(int, char*, std::size_t)); + public: + /// constructor taking error code, hint on operation (msg) + BidirMMapPipeException(const char* msg, int err); + /// return a destcription of what went wrong + virtual const char* what() const noexcept; + }; + + + /** @brief class representing a chunk of pages * * @author Manuel Schiller * @date 2013-07-24 @@ -294,7 +333,7 @@ namespace BidirMMapPipe_impl { * #include * #include * - * #include "BidirMMapPipe.h" + * #include * * int simplechild(BidirMMapPipe& pipe) * { @@ -403,6 +442,8 @@ class BidirMMapPipe { * @param useExceptions read()/write() error reporting also done using * exceptions * @param useSocketpair use a socketpair instead of a pair or pipes + * @param keepLocal deetermines whether existing pipes are retained + * at the parent (default) or transferred to children * * Normally, exceptions are thrown for all serious I/O errors (apart * from end of file). Setting useExceptions to false will force the @@ -416,7 +457,7 @@ class BidirMMapPipe { * similar on most platforms, especially if mmap works, since only * very little data is sent through the pipe(s)/socketpair. */ - BidirMMapPipe(bool useExceptions = true, bool useSocketpair = false); + BidirMMapPipe(bool useExceptions = true, bool useSocketpair = false, bool keepLocal = true); /** @brief destructor * @@ -575,7 +616,7 @@ class BidirMMapPipe { * #include * #include * - * #include "BidirMMapPipe.h" + * #include * * // what to execute in the child * int randomchild(BidirMMapPipe& pipe) @@ -812,7 +853,7 @@ class BidirMMapPipe { * @returns pipe written to */ template BidirMMapPipe& operator<<(const T* tptr) - { write(&tptr, sizeof(tptr)); return *this; } + { write(&tptr, sizeof(tptr)); return *this; } /** @brief read raw pointer to T from other side * @@ -856,6 +897,8 @@ class BidirMMapPipe { /// for usage a la "pipe << purge;" static BidirMMapPipe& purge(BidirMMapPipe& pipe) { pipe.purge(); return pipe; } + static int wait_for_child(pid_t child_pid, bool may_throw); + private: /// copy-construction forbidden BidirMMapPipe(const BidirMMapPipe&); @@ -905,6 +948,8 @@ class BidirMMapPipe { int m_flags; ///< flags (e.g. end of file) pid_t m_childPid; ///< pid of the child (zero if we're child) pid_t m_parentPid; ///< pid of the parent + bool kept_local; + /// cleanup routine - at exit, we want our children to get a SIGTERM... static void teardownall(void); diff --git a/roofit/roofitcore/inc/MultiProcess/GradMinimizer.h b/roofit/roofitcore/inc/MultiProcess/GradMinimizer.h new file mode 100644 index 0000000000000..ba968bd2966b6 --- /dev/null +++ b/roofit/roofitcore/inc/MultiProcess/GradMinimizer.h @@ -0,0 +1,71 @@ +/***************************************************************************** + * Project: RooFit * + * Package: RooFitCore * + * @(#)root/roofitcore:$Id$ + * Authors: * + * PB, Patrick Bos, NL eScience Center, p.bos@esciencecenter.nl * + * * + * Redistribution and use in source and binary forms, * + * with or without modification, are permitted according to the terms * + * listed in LICENSE (http://roofit.sourceforge.net/license.txt) * + *****************************************************************************/ + +#ifndef ROOFIT_MULTIPROCESS_GRADMINIMIZER_H +#define ROOFIT_MULTIPROCESS_GRADMINIMIZER_H + +#include +#include +#include +#include + +namespace RooFit { + namespace MultiProcessV1 { + class GradMinimizerFcn : public RooFit::MultiProcessV1::Vector { + public: + GradMinimizerFcn(RooAbsReal *funct, RooMinimizerGenericPtr context, + std::size_t _N_workers, bool verbose = false); + GradMinimizerFcn(const GradMinimizerFcn& other); + + ROOT::Math::IMultiGradFunction* Clone() const override; + + void update_state(); + void update_real(std::size_t ix, double val, bool is_constant) override; + + // the const is inherited from ...::evaluate. We are not + // actually const though, so we use a horrible hack. +// Double_t evaluate() const override; +// Double_t evaluate_non_const(); + + // --- RESULT LOGISTICS --- + void send_back_task_result_from_worker(std::size_t task) override; + void receive_task_result_on_queue(std::size_t task, std::size_t worker_id) override; + void send_back_results_from_queue_to_master() override; + void clear_results() override; + void receive_results_on_master() override; + + // overrides IGradientFunctionMultiDimTempl::Gradient etc from + // Math/IFunction.h, which are const, but we are not actually const, so + // we use a const cast hack (the mutable versions): + void Gradient(const double *x, double *grad) const override; + void mutable_Gradient(const double *x, double *grad); + void G2ndDerivative(const double *x, double *g2) const override; + void mutable_G2ndDerivative(const double *x, double *g2); + void GStepSize(const double *x, double *gstep) const override; + void mutable_GStepSize(const double *x, double *gstep); + + void CalculateAll(const double *x); + + private: + void evaluate_task(std::size_t task) override; + double get_task_result(std::size_t /*task*/) override; + + // members + std::size_t N_tasks = 0; + std::vector completed_task_ids; + }; + + using GradMinimizer = RooMinimizerTemplate; + } // namespace MultiProcessV1 +} // namespace RooFit + +#endif //ROOFIT_MULTIPROCESS_GRADMINIMIZER_H diff --git a/roofit/roofitcore/inc/MultiProcess/Job.h b/roofit/roofitcore/inc/MultiProcess/Job.h new file mode 100644 index 0000000000000..1d2c95ba6032b --- /dev/null +++ b/roofit/roofitcore/inc/MultiProcess/Job.h @@ -0,0 +1,98 @@ +/***************************************************************************** + * Project: RooFit * + * Package: RooFitCore * + * @(#)root/roofitcore:$Id$ + * Authors: * + * PB, Patrick Bos, NL eScience Center, p.bos@esciencecenter.nl * + * IP, Inti Pelupessy, NL eScience Center, i.pelupessy@esciencecenter.nl * + * * + * Redistribution and use in source and binary forms, * + * with or without modification, are permitted according to the terms * + * listed in LICENSE (http://roofit.sourceforge.net/license.txt) * + *****************************************************************************/ + +#ifndef ROOFIT_MULTIPROCESS_JOB_H +#define ROOFIT_MULTIPROCESS_JOB_H + +#include +#include // shared_ptr + +namespace RooFit { + namespace MultiProcessV1 { + // forward declaration + class TaskManager; + + /* + * @brief interface class for defining the actual work that TaskManager must do + * + * Think of "job" as in "employment", e.g. the job of a baker, which + * involves *tasks* like baking and selling bread. The Job must define the + * tasks through its execution (evaluate_task) and returning its result + * (get_task_result), based on a task index argument. + * + * Classes inheriting from Job must implement the pure virtual methods: + * - void evaluate_task(std::size_t task) + * - double get_task_result(std::size_t task) + * - void update_real(std::size_t ix, double val, bool is_constant) + * - void receive_task_result_on_queue(std::size_t task, std::size_t worker_id) + * - void send_back_results_from_queue_to_master() + * - void clear_results() + * - void receive_results_on_master() + * + * and can optionally override the virtual methods: + * - double call_double_const_method(std::string key) + * - void send_back_task_result_from_worker(std::size_t task) + * + * Note that Job::call_double_const_method throws a logic_error if not + * overridden. + */ + class Job { + public: + explicit Job(std::size_t _N_workers); + Job(const Job & other); + + virtual void evaluate_task(std::size_t task) = 0; + // TODO: replace get_task_result return type (double) with something more flexible + virtual double get_task_result(std::size_t task) = 0; + virtual void update_real(std::size_t ix, double val, bool is_constant) = 0; + + virtual double call_double_const_method(std::string /*key*/); + virtual void send_back_task_result_from_worker(std::size_t task); + + virtual void receive_task_result_on_queue(std::size_t task, std::size_t worker_id) = 0; + // an example implementation that receives only one double and stores it in + // a std::map: + // { + // double result; + // pipe >> job_object_id >> result; + // pipe << Q2W::result_received << BidirMMapPipe::flush; + // JobTask job_task(job_object_id, task); + // results[job_task] = result; + // } + + virtual void send_back_results_from_queue_to_master() = 0; + // after results have been retrieved, they should be cleared to ensure + // they won't be retrieved the next time again + virtual void clear_results() = 0; + + virtual void receive_results_on_master() = 0; + + TaskManager* get_manager(); + + static void worker_loop(); + + protected: + std::size_t N_workers; + std::size_t id; + bool waiting_for_queued_tasks = false; + + private: + // do not use _manager directly, it must first be initialized! use get_manager() + TaskManager* _manager = nullptr; + + static bool worker_loop_running; + }; + + } // namespace MultiProcessV1 +} // namespace RooFit +#endif //ROOFIT_MULTIPROCESS_JOB_H diff --git a/roofit/roofitcore/inc/MultiProcess/NLLVar.h b/roofit/roofitcore/inc/MultiProcess/NLLVar.h new file mode 100644 index 0000000000000..f456fd17fdcd4 --- /dev/null +++ b/roofit/roofitcore/inc/MultiProcess/NLLVar.h @@ -0,0 +1,112 @@ +/***************************************************************************** + * Project: RooFit * + * Package: RooFitCore * + * @(#)root/roofitcore:$Id$ + * Authors: * + * PB, Patrick Bos, NL eScience Center, p.bos@esciencecenter.nl * + * IP, Inti Pelupessy, NL eScience Center, i.pelupessy@esciencecenter.nl * + * * + * Redistribution and use in source and binary forms, * + * with or without modification, are permitted according to the terms * + * listed in LICENSE (http://roofit.sourceforge.net/license.txt) * + *****************************************************************************/ + +#ifndef ROOFIT_MULTIPROCESS_NLLVAR_H +#define ROOFIT_MULTIPROCESS_NLLVAR_H + +#include +#include +#include + +namespace RooFit { + namespace MultiProcessV1 { + + enum class NLLVarTask { + all_events, + single_event, + bulk_partition, + interleave + }; + + // for debugging: + std::ostream& operator<<(std::ostream& out, const NLLVarTask value); + + + // --- kahan summation templates --- + + template + typename C::value_type sum_kahan(const C& container) { + using ValueType = typename C::value_type; + ValueType sum = 0, carry = 0; + for (auto element : container) { + ValueType y = element - carry; + ValueType t = sum + y; + carry = (t - sum) - y; + sum = t; + } + return sum; + } + + template + ValueType sum_kahan(const std::map& map) { + ValueType sum = 0, carry = 0; + for (auto element : map) { + ValueType y = element.second - carry; + ValueType t = sum + y; + carry = (t - sum) - y; + sum = t; + } + return sum; + } + + template + std::pair sum_of_kahan_sums(const C& sum_values, const C& sum_carrys) { + using ValueType = typename C::value_type; + ValueType sum = 0, carry = 0; + for (std::size_t ix = 0; ix < sum_values.size(); ++ix) { + ValueType y = sum_values[ix]; + carry += sum_carrys[ix]; + y -= carry; + const ValueType t = sum + y; + carry = (t - sum) - y; + sum = t; + } + return std::pair(sum, carry); + } + + + + class NLLVar : public RooFit::MultiProcessV1::Vector { + public: + NLLVar(std::size_t NumCPU, NLLVarTask task_mode, const RooNLLVar& nll); + void init_vars(); + void update_parameters(); + + // the const is inherited from RooAbsTestStatistic::evaluate. We are not + // actually const though, so we use a horrible hack. + Double_t evaluate() const override; + Double_t evaluate_non_const(); + + // --- RESULT LOGISTICS --- + void send_back_task_result_from_worker(std::size_t task) override; + void receive_task_result_on_queue(std::size_t task, std::size_t worker_id) override; + void send_back_results_from_queue_to_master() override; + void clear_results() override; + void receive_results_on_master() override; + + private: + void evaluate_task(std::size_t task) override; + double get_task_result(std::size_t /*task*/) override; + + // members + std::map carrys; + double result = 0; + double carry = 0; + std::size_t N_tasks = 0; + NLLVarTask mp_task_mode; + }; + + } // namespace MultiProcessV1 +} // namespace RooFit + +#endif //ROOFIT_MULTIPROCESS_NLLVAR_H diff --git a/roofit/roofitcore/inc/MultiProcess/TaskManager.h b/roofit/roofitcore/inc/MultiProcess/TaskManager.h new file mode 100644 index 0000000000000..74c514b166b85 --- /dev/null +++ b/roofit/roofitcore/inc/MultiProcess/TaskManager.h @@ -0,0 +1,247 @@ +/***************************************************************************** + * Project: RooFit * + * Package: RooFitCore * + * @(#)root/roofitcore:$Id$ + * Authors: * + * PB, Patrick Bos, NL eScience Center, p.bos@esciencecenter.nl * + * IP, Inti Pelupessy, NL eScience Center, i.pelupessy@esciencecenter.nl * + * * + * Redistribution and use in source and binary forms, * + * with or without modification, are permitted according to the terms * + * listed in LICENSE (http://roofit.sourceforge.net/license.txt) * + *****************************************************************************/ + +#ifndef ROOFIT_MULTIPROCESS_TASKMANAGER_H +#define ROOFIT_MULTIPROCESS_TASKMANAGER_H + +#include +#include +#include +//#include +#include +#include +// N.B.: cannot use forward declarations for BidirMMapPipe, because we need +// the nested BidirMMapPipe::PollVector as well. + +#include // getpid + +namespace RooFit { + namespace MultiProcessV1 { + + // forward declarations + class Job; + enum class M2Q: int; + enum class W2Q: int; + + // some helper types + using Task = std::size_t; + using JobTask = std::pair; // combined job_object and task identifier type + + // TaskManager handles message passing and communication with a queue of + // tasks and workers that execute the tasks. The queue is in a separate + // process that can communicate with the master process (from where this + // object is created) and the queue process communicates with the worker + // processes. + // + // The TaskManager class does work defined by subclasses of the Job class. + // + // For message passing, enum class T based on int are used. The implementer + // must make sure that T can be sent over the BidirMMapPipe, i.e. that + // operator<<(BidirMMapPipe&, T) and >>(BidirMMapPipe&, T) are implemented. + // This is currently done in messages.cxx/.h, see examples there. + // + // Make sure that activate() is called soon after instantiation of TaskManager, + // because everything in between construction and activate() gets executed + // on all processes (master, queue and slaves). Activate starts the queue + // loop on the queue process, which means it can start doing its job. + // Worker processes have to be activated separately from the Job objects + // themselves. Activate cannot be called from inside the constructor, since + // the loops would prevent the constructor from returning a constructed + // object (thus defying its purpose). Note that at the end of activate, the + // queue and child processes are killed. This is achieved by sending the + // terminate message, which is done automatically in the destructor of this + // class, but can also be done manually via terminate(). + // + // When using everything as intended, i.e. by only instantiating via the + // instance() method, activate() is called from Job::get_manager() + // immediately after creation, so one need not worry about the above. + class TaskManager { + public: + static TaskManager* instance(std::size_t N_workers); + + static TaskManager* instance(); + + static bool is_instantiated(); + + void identify_processes(); + + explicit TaskManager(std::size_t N_workers); + + ~TaskManager(); + + static std::size_t add_job_object(Job *job_object); + + static Job *get_job_object(std::size_t job_object_id); + + static bool remove_job_object(std::size_t job_object_id); + + void terminate() noexcept; + + void close_worker_connections(); + + void terminate_workers(); + + void activate(); + + bool is_activated(); + + bool process_queue_pipe_message(M2Q message); + + void retrieve(); + + void process_worker_pipe_message(std::size_t this_worker_id, W2Q message); + + void queue_loop(); + + bool from_queue(JobTask &job_task); + + void to_queue(JobTask job_task); + + bool is_master(); + + bool is_queue(); + + bool is_worker(); + + std::size_t get_worker_id(); + +// std::map& get_results(); + double call_double_const_method(const std::string& method_key, std::size_t job_id, std::size_t worker_id_call); + + + // -- WORKER - QUEUE COMMUNICATION -- + + void send_from_worker_to_queue(); + + template + void send_from_worker_to_queue(T item, Ts ... items) { + try { + zmqSvc().send(*this_worker_qw_socket, item); + } catch (zmq::error_t& e) { + std::cerr << e.what() << " -- errnum: " << e.num() << std::endl; + throw; + }; +// if (sizeof...(items) > 0) { // this will only work with if constexpr, c++17 + send_from_worker_to_queue(items...); + } + + template + value_t receive_from_worker_on_queue(std::size_t this_worker_id) { + try { + auto value = zmqSvc().receive(*qw_sockets[this_worker_id]); + return value; + } catch (zmq::error_t& e) { + std::cerr << e.what() << " -- errnum: " << e.num() << std::endl; + throw; + }; + } + + void send_from_queue_to_worker(std::size_t this_worker_id); + + template + void send_from_queue_to_worker(std::size_t this_worker_id, T item, Ts ... items) { + try { + zmqSvc().send(*qw_sockets[this_worker_id], item); + } catch (zmq::error_t& e) { + std::cerr << e.what() << " -- errnum: " << e.num() << std::endl; + throw; + }; +// if (sizeof...(items) > 0) { // this will only work with if constexpr, c++17 + send_from_queue_to_worker(this_worker_id, items...); + } + + template + value_t receive_from_queue_on_worker() { + try { + auto value = zmqSvc().receive(*this_worker_qw_socket); + return value; + } catch (zmq::error_t& e) { + std::cerr << e.what() << " -- errnum: " << e.num() << std::endl; + throw; + }; + } + + + // -- QUEUE - MASTER COMMUNICATION -- + + void send_from_queue_to_master(); + + template + void send_from_queue_to_master(T item, Ts ... items) { + try { + zmqSvc().send(*mq_socket, item); + } catch (zmq::error_t& e) { + std::cerr << e.what() << " -- errnum: " << e.num() << std::endl; + throw; + }; +// if (sizeof...(items) > 0) { // this will only work with if constexpr, c++17 + send_from_queue_to_master(items...); + } + + template + value_t receive_from_queue_on_master() { + try { + auto value = zmqSvc().receive(*mq_socket); + return value; + } catch (zmq::error_t& e) { + std::cerr << e.what() << " -- errnum: " << e.num() << std::endl; + throw; + }; + } + + void send_from_master_to_queue(); + + template + void send_from_master_to_queue(T item, Ts ... items) { + send_from_queue_to_master(item, items...); + } + + template + value_t receive_from_master_on_queue() { + return receive_from_queue_on_master(); + } + + void flush_ostreams(); + + private: + void initialize_processes(bool cpu_pinning = true); + void shutdown_processes(); +// BidirMMapPipe::PollVector get_poll_vector(); + + std::size_t N_workers; + std::vector > qw_sockets; + std::vector worker_pids; // master must wait for workers after completion, for which it needs their PIDs + pid_t queue_pid; + // for convenience on the worker processes, we use this_worker_pipe as an + // alias for worker_pipes.back() + ZmqLingeringSocketPtr<> this_worker_qw_socket; + ZmqLingeringSocketPtr<> mq_socket; + std::size_t worker_id; + bool _is_master = false; + bool _is_queue = false; + std::queue queue; + std::size_t N_tasks = 0; // total number of received tasks + std::size_t N_tasks_completed = 0; + bool queue_activated = false; + bool processes_initialized = false; +// std::unique_ptr zmq_context; + + static std::map job_objects; + static std::size_t job_counter; + static std::unique_ptr _instance; + }; + + } // namespace MultiProcessV1 +} // namespace RooFit + +#endif //ROOFIT_MULTIPROCESS_TASKMANAGER_H diff --git a/roofit/roofitcore/inc/MultiProcess/Vector.h b/roofit/roofitcore/inc/MultiProcess/Vector.h new file mode 100644 index 0000000000000..02fe0f7adfd35 --- /dev/null +++ b/roofit/roofitcore/inc/MultiProcess/Vector.h @@ -0,0 +1,182 @@ +/***************************************************************************** + * Project: RooFit * + * Package: RooFitCore * + * @(#)root/roofitcore:$Id$ + * Authors: * + * PB, Patrick Bos, NL eScience Center, p.bos@esciencecenter.nl * + * IP, Inti Pelupessy, NL eScience Center, i.pelupessy@esciencecenter.nl * + * * + * Redistribution and use in source and binary forms, * + * with or without modification, are permitted according to the terms * + * listed in LICENSE (http://roofit.sourceforge.net/license.txt) * + *****************************************************************************/ + +#ifndef ROOFIT_MULTIPROCESS_VECTOR_H +#define ROOFIT_MULTIPROCESS_VECTOR_H + +#include +#include +#include +#include +#include + +namespace RooFit { + namespace MultiProcessV1 { + + // Vector defines an interface and communication machinery to build a + // parallelized subclass of an existing non-concurrent numerical class that + // can be expressed as a vector of independent sub-calculations. + // + // The way to use Vector is to define a new class that inherits from + // Vector. Vector itself inherits from its template + // parameter class, so the inheritance tree will become: + // + // ParallelClass -> Vector -> ConcurrentClass + // + // where -> denotes "inherits from". + // + // While Vector is an abstract class, it provides some default method + // implementations that define the result of each task as a single vector + // element, i.e. a double precision floating point number. By overriding + // these methods, the result of a Vector task can be redefined as, for + // instance, a sum and a carry value for when the actual result of the task + // is given by Kahan summation and the carry value needs to be propagated + // back to the master process for calculating the total sum value. These + // methods are: + // + // - receive_task_result_on_queue + // - send_back_results_from_queue_to_master + // - clear_results + // - receive_results_on_master + // - send_back_task_result_from_worker + // + // Note that the results vector "defines" result type as well. In + // some cases this may not be wanted, but we expect that in such a case the + // Vector class itself is not suitable and a different Job subclass should + // be defined. One clear exception is the case where a vector is wanted, + // but not one of doubles but of another type. This use-case is + // accommodated through the result_t template parameter. + // + // Classes inheriting from Vector must implement the pure virtual methods: + // - void evaluate_task(std::size_t task) + // - double get_task_result(std::size_t task) + // + // and can optionally override the virtual methods (in addition to the + // result logistics methods mentioned above): + // - void update_real(std::size_t ix, double val, bool is_constant) + // - double call_double_const_method(std::string key) + // + template + class Vector : public Base, public Job { + public: + template + Vector(std::size_t _N_workers, Targs ...args) : + Base(args...), + Job(_N_workers) + { + id = TaskManager::add_job_object(this); + } + + Vector(const Vector & other) : + Base(other), + Job(other), + results(other.results), + _vars(other._vars), + _saveVars(other._saveVars), + _forceCalc(other._forceCalc) + { + id = TaskManager::add_job_object(this); + } + + ~Vector() { + TaskManager::remove_job_object(id); + } + + void update_real(std::size_t ix, double val, bool is_constant) override { + if (get_manager()->is_worker()) { + RooRealVar *rvar = (RooRealVar *) _vars.at(ix); + rvar->setVal(static_cast(val)); + if (rvar->isConstant() != is_constant) { + rvar->setConstant(static_cast(is_constant)); + } + } + } + + protected: + void gather_worker_results() { + if (waiting_for_queued_tasks) { + get_manager()->retrieve(); + waiting_for_queued_tasks = false; + } + } + + void receive_task_result_on_queue(std::size_t task, std::size_t worker_id) override { + result_t result = get_manager()->template receive_from_worker_on_queue(worker_id); + results[task] = result; + } + + void send_back_results_from_queue_to_master() override { + get_manager()->send_from_queue_to_master(results.size()); + for (auto const &item : results) { + get_manager()->send_from_queue_to_master(item.first, item.second); + } + } + + void clear_results() override { + // empty results cache + results.clear(); + } + + void receive_results_on_master() override { + std::size_t N_job_tasks = get_manager()->template receive_from_queue_on_master(); + for (std::size_t task_ix = 0ul; task_ix < N_job_tasks; ++task_ix) { + std::size_t task_id = get_manager()->template receive_from_queue_on_master(); + results[task_id] = get_manager()->template receive_from_queue_on_master(); + } + } + + // Here we define maps of functions that a subclass may want to call on + // the worker process and have the result sent back to master. Each + // function type needs custom implementation, so we only allow a selected + // number of function pointer types. Templates could make the Vector + // header slightly more compact, but the implementation would not change + // much, so this explicit approach seems preferable. + using double_const_method_t = double (Vector::*)() const; + std::map double_const_methods; + double call_double_const_method(std::string key) override { + return (this->*double_const_methods[key])(); + } + // Another example would be: + // std::map::*)(double)> double_from_double_methods; + // We leave this out for now, as we don't currently need it. + // + // Every type also needs a corresponding set of: + // - messages from master to queue + // - messages from queue to worker + // - method to return the method pointer from the object to be able to + // call it from the static worker_loop. + // The method could be implemented using templates, but the messages + // cannot, further motivating the use of explicit implementation of + // every specific case. + // + // Note that due to the relatively expensive nature of these calls, + // they should be used only in non-work-mode. + + // -- members -- + protected: + std::map results; + + // the following members are used for syncing, but might be replaced by + // remote "mapped function calls" (see above): + + // used in NLLVar + RooListProxy _vars; // Variables + RooArgList _saveVars; // Copy of variables + bool _forceCalc = false; + + }; // class Vector + + } // namespace MultiProcessV1 +} // namespace RooFit + +#endif //ROOFIT_MULTIPROCESS_VECTOR_H diff --git a/roofit/roofitcore/inc/MultiProcess/messages.h b/roofit/roofitcore/inc/MultiProcess/messages.h new file mode 100644 index 0000000000000..2f37c03027153 --- /dev/null +++ b/roofit/roofitcore/inc/MultiProcess/messages.h @@ -0,0 +1,80 @@ +/***************************************************************************** + * Project: RooFit * + * Package: RooFitCore * + * @(#)root/roofitcore:$Id$ + * Authors: * + * PB, Patrick Bos, NL eScience Center, p.bos@esciencecenter.nl * + * IP, Inti Pelupessy, NL eScience Center, i.pelupessy@esciencecenter.nl * + * * + * Redistribution and use in source and binary forms, * + * with or without modification, are permitted according to the terms * + * listed in LICENSE (http://roofit.sourceforge.net/license.txt) * + *****************************************************************************/ + +#ifndef ROOFIT_MULTIPROCESS_MESSAGES_H +#define ROOFIT_MULTIPROCESS_MESSAGES_H + +#include + +namespace RooFit { + namespace MultiProcessV1 { + + // Messages from master to queue + enum class M2Q : int { + terminate = 100, + enqueue = 10, + retrieve = 11, + update_real = 12, +// update_cat = 13, + call_double_const_method = 15, + flush_ostreams = 16 + }; + + // Messages from queue to master + enum class Q2M : int { + retrieve_rejected = 20, + retrieve_accepted = 21 + }; + + // Messages from worker to queue + enum class W2Q : int { + dequeue = 30, + send_result = 31 + }; + + // Messages from queue to worker + enum class Q2W : int { + terminate = 400, + dequeue_rejected = 40, + dequeue_accepted = 41, + result_received = 43, + update_real = 44, +// update_cat = 45 + call_double_const_method = 46, + flush_ostreams = 47 + }; + + // stream output operators for debugging + std::ostream& operator<<(std::ostream& out, const M2Q value); + std::ostream& operator<<(std::ostream& out, const Q2M value); + std::ostream& operator<<(std::ostream& out, const Q2W value); + std::ostream& operator<<(std::ostream& out, const W2Q value); + + } // namespace MultiProcessV1 + +// // forward declaration: +// class BidirMMapPipe; +// +// // bipe stream operators for message enum classes +// BidirMMapPipe& operator<<(BidirMMapPipe& bipe, const MultiProcess::M2Q& sent); +// BidirMMapPipe& operator>>(BidirMMapPipe& bipe, MultiProcess::M2Q& received); +// BidirMMapPipe& operator<<(BidirMMapPipe& bipe, const MultiProcess::Q2M& sent); +// BidirMMapPipe& operator>>(BidirMMapPipe& bipe, MultiProcess::Q2M& received); +// BidirMMapPipe& operator<<(BidirMMapPipe& bipe, const MultiProcess::Q2W& sent); +// BidirMMapPipe& operator>>(BidirMMapPipe& bipe, MultiProcess::Q2W& received); +// BidirMMapPipe& operator<<(BidirMMapPipe& bipe, const MultiProcess::W2Q& sent); +// BidirMMapPipe& operator>>(BidirMMapPipe& bipe, MultiProcess::W2Q& received); + +} // namespace RooFit + +#endif //ROOFIT_MULTIPROCESS_MESSAGES_H diff --git a/roofit/roofitcore/inc/MultiProcess/util.h b/roofit/roofitcore/inc/MultiProcess/util.h new file mode 100644 index 0000000000000..2a2d9551bbbe2 --- /dev/null +++ b/roofit/roofitcore/inc/MultiProcess/util.h @@ -0,0 +1,22 @@ +/***************************************************************************** + * Project: RooFit * + * Package: RooFitCore * + * @(#)root/roofitcore:$Id$ + * Authors: * + * PB, Patrick Bos, NL eScience Center, p.bos@esciencecenter.nl * + * * + * Redistribution and use in source and binary forms, * + * with or without modification, are permitted according to the terms * + * listed in LICENSE (http://roofit.sourceforge.net/license.txt) * + *****************************************************************************/ + +#ifndef ROOFIT_MULTIPROCESS_UTIL_H +#define ROOFIT_MULTIPROCESS_UTIL_H + +#include // getpid, pid_t +namespace RooFit { + namespace MultiProcessV1 { + int wait_for_child(pid_t child_pid, bool may_throw, int retries_before_killing); + } +} +#endif //ROOFIT_MULTIPROCESS_UTIL_H diff --git a/roofit/roofitcore/inc/NumericalDerivatorMinuit2.h b/roofit/roofitcore/inc/NumericalDerivatorMinuit2.h new file mode 100644 index 0000000000000..b9e28256510b1 --- /dev/null +++ b/roofit/roofitcore/inc/NumericalDerivatorMinuit2.h @@ -0,0 +1,124 @@ +// @(#)root/mathcore:$Id$ +// Authors: L. Moneta, J.T. Offermann, E.G.P. Bos 2013-2018 +// +/********************************************************************** + * * + * Copyright (c) 2013 , LCG ROOT MathLib Team * + * Copyright (c) 2017 Patrick Bos, Netherlands eScience Center * + * * + **********************************************************************/ +/* + * NumericalDerivatorMinuit2.h + * + * Original version (NumericalDerivator) created on: Aug 14, 2013 + * Authors: L. Moneta, J. T. Offermann + * Modified version (NumericalDerivatorMinuit2) created on: Sep 27, 2017 + * Author: E. G. P. Bos + */ + +#ifndef RooFit_NumericalDerivatorMinuit2 +#define RooFit_NumericalDerivatorMinuit2 + +#ifndef ROOT_Math_IFunctionfwd +#include +#endif + +#include +#include "Fit/ParameterSettings.h" +#include "Minuit2/SinParameterTransformation.h" +#include "Minuit2/SqrtUpParameterTransformation.h" +#include "Minuit2/SqrtLowParameterTransformation.h" +#include "Minuit2/MnMachinePrecision.h" + +namespace RooFit { + +// Holds all necessary derivatives and associated numbers (per parameter) used in the NumericalDerivatorMinuit2 class. +struct MinuitDerivatorElement { + double derivative; + double second_derivative; + double step_size; +}; + +class NumericalDerivatorMinuit2 { +public: + explicit NumericalDerivatorMinuit2(bool always_exactly_mimic_minuit2 = true); + NumericalDerivatorMinuit2(const NumericalDerivatorMinuit2 &other); + NumericalDerivatorMinuit2(double step_tolerance, double grad_tolerance, unsigned int ncycles, double error_level, + bool always_exactly_mimic_minuit2 = true); + virtual ~NumericalDerivatorMinuit2(); + + void setup_differentiate(const ROOT::Math::IBaseFunctionMultiDim *function, const double *cx, + const std::vector ¶meters); + std::vector + Differentiate(const ROOT::Math::IBaseFunctionMultiDim *function, const double *x, + const std::vector ¶meters, + const std::vector& previous_gradient); + + MinuitDerivatorElement + partial_derivative(const ROOT::Math::IBaseFunctionMultiDim *function, const double *x, + const std::vector ¶meters, unsigned int i_component, MinuitDerivatorElement previous); + MinuitDerivatorElement fast_partial_derivative(const ROOT::Math::IBaseFunctionMultiDim *function, + const std::vector ¶meters, unsigned int i_component, const MinuitDerivatorElement& previous); + MinuitDerivatorElement operator()(const ROOT::Math::IBaseFunctionMultiDim *function, const double *x, + const std::vector ¶meters, + unsigned int i_component, const MinuitDerivatorElement& previous); + + double GetFValue() const { return fVal; } + // const double *GetG2() const { return fG.G2().Data(); } + void SetStepTolerance(double value); + void SetGradTolerance(double value); + void SetNCycles(int value); + + double Int2ext(const ROOT::Fit::ParameterSettings ¶meter, double val) const; + double Ext2int(const ROOT::Fit::ParameterSettings ¶meter, double val) const; + double DInt2Ext(const ROOT::Fit::ParameterSettings ¶meter, double val) const; + double D2Int2Ext(const ROOT::Fit::ParameterSettings ¶meter, double val) const; + double GStepInt2Ext(const ROOT::Fit::ParameterSettings ¶meter, double val) const; + + void SetInitialGradient(const ROOT::Math::IBaseFunctionMultiDim *function, + const std::vector ¶meters, + std::vector& gradient); + + void set_step_tolerance(double step_tolerance); + void set_grad_tolerance(double grad_tolerance); + void set_ncycles(unsigned int ncycles); + void set_error_level(double error_level); + +private: + double fStepTolerance = 0.5; + double fGradTolerance = 0.1; + unsigned int fNCycles = 2; + double Up = 1; + double fVal = 0; + + std::vector vx, vx_external; + double dfmin; + double vrysml; + + // MODIFIED: Minuit2 determines machine precision in a slightly different way than + // std::numeric_limits::epsilon()). We go with the Minuit2 one. + ROOT::Minuit2::MnMachinePrecision precision; + + ROOT::Minuit2::SinParameterTransformation fDoubleLimTrafo; + ROOT::Minuit2::SqrtUpParameterTransformation fUpperLimTrafo; + ROOT::Minuit2::SqrtLowParameterTransformation fLowerLimTrafo; + +private: + bool _always_exactly_mimic_minuit2; + +public: + bool always_exactly_mimic_minuit2() const; + void set_always_exactly_mimic_minuit2(bool flag); + +private: + std::vector vx_fVal_cache; +//#ifndef NDEBUG +// std::size_t fVal_eval_counter = 0; //! +//#endif +}; + +std::ostream& operator<<(std::ostream& out, const MinuitDerivatorElement value); + +} // namespace RooFit + +#endif /* NumericalDerivatorMinuit2_H_ */ \ No newline at end of file diff --git a/roofit/roofitcore/inc/RooAbsData.h b/roofit/roofitcore/inc/RooAbsData.h index 5d77b4ab740c8..ce0720bb080d6 100644 --- a/roofit/roofitcore/inc/RooAbsData.h +++ b/roofit/roofitcore/inc/RooAbsData.h @@ -44,6 +44,12 @@ class RooFormulaVar; namespace RooBatchCompute{ struct RunContext; } +namespace RooFit { +namespace TestStatistics { +class RooAbsL; +struct ConstantTermsOptimizer; +} +} class RooAbsData : public TNamed, public RooPrintable { @@ -275,6 +281,8 @@ class RooAbsData : public TNamed, public RooPrintable { friend class RooAbsReal ; friend class RooAbsOptTestStatistic ; friend class RooAbsCachedPdf ; + friend class RooFit::TestStatistics::RooAbsL; + friend struct RooFit::TestStatistics::ConstantTermsOptimizer; virtual void cacheArgs(const RooAbsArg* owner, RooArgSet& varSet, const RooArgSet* nset=0, Bool_t skipZeroWeights=kFALSE) ; virtual void resetCache() ; diff --git a/roofit/roofitcore/inc/RooAbsMinimizerFcn.h b/roofit/roofitcore/inc/RooAbsMinimizerFcn.h new file mode 100644 index 0000000000000..249df305479a5 --- /dev/null +++ b/roofit/roofitcore/inc/RooAbsMinimizerFcn.h @@ -0,0 +1,130 @@ +/***************************************************************************** + * Project: RooFit * + * Package: RooFitCore * + * @(#)root/roofitcore:$Id$ + * Authors: * + * AL, Alfio Lazzaro, INFN Milan, alfio.lazzaro@mi.infn.it * + * * + * * + * Redistribution and use in source and binary forms, * + * with or without modification, are permitted according to the terms * + * listed in LICENSE (http://roofit.sourceforge.net/license.txt) * + *****************************************************************************/ + +#ifndef __ROOFIT_NOROOMINIMIZER + +#ifndef ROO_ABS_MINIMIZER_FCN +#define ROO_ABS_MINIMIZER_FCN + +#include "Math/IFunction.h" +#include "Fit/ParameterSettings.h" +#include "Fit/FitResult.h" + +#include "TMatrixDSym.h" + +#include "RooAbsReal.h" +#include "RooArgList.h" +#include "RooRealVar.h" + +#include +#include +#include + +// forward declaration +class RooMinimizer; + +class RooAbsMinimizerFcn { + +public: + RooAbsMinimizerFcn(RooArgList paramList, RooMinimizer *context, bool verbose = false); + RooAbsMinimizerFcn(const RooAbsMinimizerFcn &other); + virtual ~RooAbsMinimizerFcn(); + + // inform Minuit through its parameter_settings vector of RooFit parameter properties + Bool_t synchronize_parameter_settings(std::vector ¶meters, Bool_t optConst, Bool_t verbose); + // same, but can be overridden to e.g. also include gradient strategy synchronization in subclasses: + virtual Bool_t Synchronize(std::vector ¶meters, Bool_t optConst, Bool_t verbose); + + // used for export to RooFitResult from Minimizer: + RooArgList *GetFloatParamList(); + RooArgList *GetConstParamList(); + RooArgList *GetInitFloatParamList(); + RooArgList *GetInitConstParamList(); + Int_t GetNumInvalidNLL() const; + + // need access from Minimizer: + void SetEvalErrorWall(Bool_t flag); + /// Try to recover from invalid function values. When invalid function values are encountered, + /// a penalty term is returned to the minimiser to make it back off. This sets the strength of this penalty. + /// \note A strength of zero is equivalent to a constant penalty (= the gradient vanishes, ROOT < 6.24). + /// Positive values lead to a gradient pointing away from the undefined regions. Use ~10 to force the minimiser + /// away from invalid function values. + void SetRecoverFromNaNStrength(double strength) { _recoverFromNaNStrength = strength; } + void SetPrintEvalErrors(Int_t numEvalErrors); + Double_t &GetMaxFCN(); + Int_t evalCounter() const; + void zeroEvalCount(); + /// Return a possible offset that's applied to the function to separate invalid function values from valid ones. + double getOffset() const { return _funcOffset; } + void SetVerbose(Bool_t flag = kTRUE); + + // put Minuit results back into RooFit objects: + void BackProp(const ROOT::Fit::FitResult &results); + + // used in several Minimizer functions: + virtual std::string getFunctionName() const = 0; + virtual std::string getFunctionTitle() const = 0; + + // set different external covariance matrix + void ApplyCovarianceMatrix(TMatrixDSym &V); + + Bool_t SetLogFile(const char *inLogfile); + std::ofstream *GetLogFile() { return _logfile; } + + unsigned int get_nDim() const { return _nDim; } + + virtual void setOptimizeConst(Int_t flag) = 0; + + bool getOptConst(); + std::vector get_parameter_values() const; + + Bool_t SetPdfParamVal(int index, double value) const; + +protected: + virtual void optimizeConstantTerms(bool constStatChange, bool constValChange) = 0; + + // used in BackProp (Minuit results -> RooFit) and ApplyCovarianceMatrix + void SetPdfParamErr(Int_t index, Double_t value); + void ClearPdfParamAsymErr(Int_t index); + void SetPdfParamErr(Int_t index, Double_t loVal, Double_t hiVal); + + void printEvalErrors() const; + + // members + RooMinimizer *_context; + + // the following four are mutable because DoEval is const (in child classes) + // Reset the *largest* negative log-likelihood value we have seen so far: + mutable double _maxFCN = -std::numeric_limits::infinity(); + mutable double _funcOffset{0.}; + double _recoverFromNaNStrength{10.}; + mutable int _numBadNLL = 0; + mutable int _printEvalErrors = 10; + mutable int _evalCounter{0}; + + unsigned int _nDim = 0; + + Bool_t _optConst = kFALSE; + + RooArgList *_floatParamList; + RooArgList *_constParamList; + RooArgList *_initFloatParamList; + RooArgList *_initConstParamList; + + std::ofstream *_logfile = nullptr; + bool _doEvalErrorWall{true}; + bool _verbose; +}; + +#endif +#endif diff --git a/roofit/roofitcore/inc/RooAbsOptTestStatistic.h b/roofit/roofitcore/inc/RooAbsOptTestStatistic.h index 852dc3f17339e..7950d16c90dad 100644 --- a/roofit/roofitcore/inc/RooAbsOptTestStatistic.h +++ b/roofit/roofitcore/inc/RooAbsOptTestStatistic.h @@ -38,11 +38,11 @@ class RooAbsOptTestStatistic : public RooAbsTestStatistic { virtual Double_t combinedValue(RooAbsReal** gofArray, Int_t nVal) const ; - RooAbsReal& function() { return *_funcClone ; } - const RooAbsReal& function() const { return *_funcClone ; } + virtual RooAbsReal& function() { return *_funcClone ; } + virtual const RooAbsReal& function() const { return *_funcClone ; } - RooAbsData& data() ; - const RooAbsData& data() const ; + virtual RooAbsData& data() ; + virtual const RooAbsData& data() const ; virtual const char* cacheUniqueSuffix() const { return Form("_%lx", (ULong_t)_dataClone) ; } diff --git a/roofit/roofitcore/inc/RooAbsPdf.h b/roofit/roofitcore/inc/RooAbsPdf.h index c64528638a941..d7fa97e9a68ac 100644 --- a/roofit/roofitcore/inc/RooAbsPdf.h +++ b/roofit/roofitcore/inc/RooAbsPdf.h @@ -17,8 +17,6 @@ #define ROO_ABS_PDF #include "RooAbsReal.h" -//#include "RooRealIntegral.h" -#include "RooNameSet.h" #include "RooObjCacheManager.h" #include "RooCmdArg.h" @@ -365,7 +363,11 @@ class RooAbsPdf : public RooAbsReal { template int calculateSumW2CorrectedCovMatrix(Minimizer& minimizer, RooAbsReal const& nll) const; - + +public: + Bool_t num_int_timing_flag() const; + void set_num_int_timing_flag(Bool_t flag); + ClassDef(RooAbsPdf,4) // Abstract PDF with normalization support }; diff --git a/roofit/roofitcore/inc/RooAbsReal.h b/roofit/roofitcore/inc/RooAbsReal.h index 465407e5325b2..759d31586cf7c 100644 --- a/roofit/roofitcore/inc/RooAbsReal.h +++ b/roofit/roofitcore/inc/RooAbsReal.h @@ -477,7 +477,7 @@ class RooAbsReal : public RooAbsArg { PlotOpt() : drawOptions("L"), scaleFactor(1.0), stype(Relative), projData(0), binProjData(kFALSE), projSet(0), precision(1e-3), shiftToZero(kFALSE),projDataSet(0),normRangeName(0),rangeLo(0),rangeHi(0),postRangeFracScale(kFALSE),wmode(RooCurve::Extended), projectionRangeName(0),curveInvisible(kFALSE), curveName(0),addToCurveName(0),addToWgtSelf(1.),addToWgtOther(1.), - numCPU(1),interleave(RooFit::Interleave),curveNameSuffix(""), numee(10), eeval(0), doeeval(kFALSE), progress(kFALSE), errorFR(0) {} ; + numCPU(1),interleave(RooFit::Interleave),CPUAffinity(kTRUE),curveNameSuffix(""), numee(10), eeval(0), doeeval(kFALSE), progress(kFALSE), errorFR(0) {} ; Option_t* drawOptions ; Double_t scaleFactor ; ScaleType stype ; @@ -500,6 +500,7 @@ class RooAbsReal : public RooAbsArg { Double_t addToWgtOther ; Int_t numCPU ; RooFit::MPSplit interleave ; + Bool_t CPUAffinity; const char* curveNameSuffix ; Int_t numee ; Double_t eeval ; diff --git a/roofit/roofitcore/inc/RooAbsTestStatistic.h b/roofit/roofitcore/inc/RooAbsTestStatistic.h index 5274220bbb121..1e4c0866efc3d 100644 --- a/roofit/roofitcore/inc/RooAbsTestStatistic.h +++ b/roofit/roofitcore/inc/RooAbsTestStatistic.h @@ -37,7 +37,8 @@ typedef RooAbsData* pRooAbsData ; typedef RooRealMPFE* pRooRealMPFE ; class RooAbsTestStatistic : public RooAbsReal { - friend class RooRealMPFE; + friend class RooRealMPFE; + friend class RooWrapperL; public: struct Configuration { @@ -46,6 +47,7 @@ class RooAbsTestStatistic : public RooAbsReal { std::string addCoefRangeName = ""; int nCPU = 1; RooFit::MPSplit interleave = RooFit::BulkPartition; + bool CPUAffinity = true; bool verbose = true; bool splitCutRange = false; bool cloneInputData = true; @@ -71,12 +73,21 @@ class RooAbsTestStatistic : public RooAbsReal { } Bool_t setData(RooAbsData& data, Bool_t cloneData=kTRUE) ; + //vinces accessors + Int_t numSimultaneous() const { return _nGof ; } + RooAbsTestStatistic** simComponents() { return _gofArray ; } void enableOffsetting(Bool_t flag) ; Bool_t isOffsetting() const { return _doOffset ; } virtual Double_t offset() const { return _offset.Sum() ; } virtual Double_t offsetCarry() const { return _offset.Carry(); } + virtual RooAbsReal& function() { return *_func ; } + virtual const RooAbsReal& function() const { return *_func ; } + + virtual RooAbsData& data() { return *_data ; } + virtual const RooAbsData& data() const { return *_data ; } + protected: virtual void printCompactTreeHook(std::ostream& os, const char* indent="") ; @@ -154,12 +165,19 @@ class RooAbsTestStatistic : public RooAbsReal { pRooRealMPFE* _mpfeArray = nullptr; //! Array of parallel execution frond ends RooFit::MPSplit _mpinterl = RooFit::BulkPartition; // Use interleaving strategy rather than N-wise split for partioning of dataset for multiprocessor-split + Bool_t _CPUAffinity = true; // Use CPU affinity to pin processes to cores Bool_t _doOffset = false; // Apply interval value offset to control numeric precision? mutable ROOT::Math::KahanSum _offset = 0.0; //! Offset as KahanSum to avoid loss of precision mutable Double_t _evalCarry = 0.0; //! carry of Kahan sum in evaluatePartition - ClassDef(RooAbsTestStatistic,2) // Abstract base class for real-valued test statistics +private: + void _collectNumIntTimings(Bool_t clear_timings = kTRUE) const; + + void _setNumIntTimingInPdfs(Bool_t flag = kTRUE); + + void _initTiming(); + ClassDef(RooAbsTestStatistic,3) // Abstract base class for real-valued test statistics }; #endif diff --git a/roofit/roofitcore/inc/RooAddModel.h b/roofit/roofitcore/inc/RooAddModel.h index 47a98917f91cd..2b7cb77ecd97f 100644 --- a/roofit/roofitcore/inc/RooAddModel.h +++ b/roofit/roofitcore/inc/RooAddModel.h @@ -21,7 +21,6 @@ #include "RooSetProxy.h" #include "RooAICRegistry.h" #include "RooNormSetCache.h" -#include "RooNameSet.h" #include "RooObjCacheManager.h" class RooAddModel : public RooResolutionModel { diff --git a/roofit/roofitcore/inc/RooAddPdf.h b/roofit/roofitcore/inc/RooAddPdf.h index 0e7a36cc0863c..b438b9ea7427c 100644 --- a/roofit/roofitcore/inc/RooAddPdf.h +++ b/roofit/roofitcore/inc/RooAddPdf.h @@ -21,7 +21,6 @@ #include "RooSetProxy.h" #include "RooAICRegistry.h" #include "RooNormSetCache.h" -#include "RooNameSet.h" #include "RooObjCacheManager.h" #include "RooNameReg.h" diff --git a/roofit/roofitcore/inc/RooAddition.h b/roofit/roofitcore/inc/RooAddition.h index 8fc59b4a2e7bf..499abb108167a 100644 --- a/roofit/roofitcore/inc/RooAddition.h +++ b/roofit/roofitcore/inc/RooAddition.h @@ -19,12 +19,15 @@ #include "RooAbsReal.h" #include "RooListProxy.h" #include "RooObjCacheManager.h" +#include "RooTaskSpec.h" #include class RooRealVar; -class RooArgList ; +class RooArgList; + class RooAddition : public RooAbsReal { + friend class RooTaskSpec; public: RooAddition() ; diff --git a/roofit/roofitcore/inc/RooCacheManager.h b/roofit/roofitcore/inc/RooCacheManager.h index eec16643e91c1..7bc968bcc88d5 100644 --- a/roofit/roofitcore/inc/RooCacheManager.h +++ b/roofit/roofitcore/inc/RooCacheManager.h @@ -26,9 +26,10 @@ #include "RooAbsCache.h" #include "RooAbsCacheElement.h" #include "RooNameReg.h" +#include "RooHelpers.h" +#include "ROOT/RMakeUnique.hxx" #include - -class RooNameSet ; +#include template @@ -84,8 +85,8 @@ class RooCacheManager : public RooAbsCache { } T* getObjByIndex(Int_t index) const ; - const RooNameSet* nameSet1ByIndex(Int_t index) const ; - const RooNameSet* nameSet2ByIndex(Int_t index) const ; + RooArgSet selectFromSet1(RooArgSet const& argSet, int index) const ; + RooArgSet selectFromSet2(RooArgSet const& argSet, int index) const ; virtual void insertObjectHook(T&) { // Interface function to perform post-insert operations on cached object @@ -317,29 +318,21 @@ T* RooCacheManager::getObjByIndex(Int_t index) const } -/// Retrieve RooNameSet associated with slot at given index. +/// Create RooArgSet contatining the objects that are both in the cached set 1 +//with a given index and an input argSet. template -const RooNameSet* RooCacheManager::nameSet1ByIndex(Int_t index) const +RooArgSet RooCacheManager::selectFromSet1(RooArgSet const& argSet, int index) const { - if (index<0||index>=_size) { - oocoutE(_owner,ObjectHandling) << "RooCacheManager::getNormListByIndex: ERROR index (" - << index << ") out of range [0," << _size-1 << "]" << std::endl ; - return 0 ; - } - return &_nsetCache[index].nameSet1() ; + return RooHelpers::selectFromArgSet(argSet, _nsetCache.at(index).nameSet1()); } -/// Retrieve RooNameSet associated with slot at given index. +/// Create RooArgSet contatining the objects that are both in the cached set 2 +//with a given index and an input argSet. template -const RooNameSet* RooCacheManager::nameSet2ByIndex(Int_t index) const +RooArgSet RooCacheManager::selectFromSet2(RooArgSet const& argSet, int index) const { - if (index<0||index>=_size) { - oocoutE(_owner,ObjectHandling) << "RooCacheManager::getNormListByIndex: ERROR index (" - << index << ") out of range [0," << _size-1 << "]" << std::endl ; - return 0 ; - } - return &_nsetCache[index].nameSet2() ; + return RooHelpers::selectFromArgSet(argSet, _nsetCache.at(index).nameSet2()); } diff --git a/roofit/roofitcore/inc/RooDataHist.h b/roofit/roofitcore/inc/RooDataHist.h index b21fc124f3190..772f493f23466 100644 --- a/roofit/roofitcore/inc/RooDataHist.h +++ b/roofit/roofitcore/inc/RooDataHist.h @@ -33,6 +33,11 @@ class RooAbsArg; class RooCategory ; class RooPlot; class RooAbsLValue ; +namespace RooFit { +namespace TestStatistics { +class RooAbsL; +} +} class RooDataHist : public RooAbsData, public RooDirItem { public: @@ -214,6 +219,8 @@ class RooDataHist : public RooAbsData, public RooDirItem { friend class RooAbsCachedPdf ; friend class RooAbsCachedReal ; friend class RooDataHistSliceIter ; + friend class RooAbsOptTestStatistic ; + friend class RooFit::TestStatistics::RooAbsL; std::size_t calcTreeIndex(const RooAbsCollection& coords, bool fast) const; /// Legacy overload to calculate the tree index from the current value of `_vars`. diff --git a/roofit/roofitcore/inc/RooHashTable.h b/roofit/roofitcore/inc/RooFitLegacy/RooHashTable.h similarity index 96% rename from roofit/roofitcore/inc/RooHashTable.h rename to roofit/roofitcore/inc/RooFitLegacy/RooHashTable.h index 4b1e1f75818fc..0dfa9409327d3 100644 --- a/roofit/roofitcore/inc/RooHashTable.h +++ b/roofit/roofitcore/inc/RooFitLegacy/RooHashTable.h @@ -19,6 +19,8 @@ #include "TObject.h" #include "TString.h" +#include + class RooAbsArg ; class RooLinkedList ; class RooLinkedListElem ; @@ -67,9 +69,6 @@ class RooHashTable : public TObject { RooLinkedList** _arr ; //! Array of linked lists storing elements in each slot ClassDef(RooHashTable,1) // Hash table -}; - - - +} R__SUGGEST_ALTERNATIVE("Please use std::unordered_map, which is also a hash table."); #endif diff --git a/roofit/roofitcore/inc/RooNameSet.h b/roofit/roofitcore/inc/RooFitLegacy/RooNameSet.h similarity index 94% rename from roofit/roofitcore/inc/RooNameSet.h rename to roofit/roofitcore/inc/RooFitLegacy/RooNameSet.h index dcfe6923163e8..cf4056c25c6d3 100644 --- a/roofit/roofitcore/inc/RooNameSet.h +++ b/roofit/roofitcore/inc/RooFitLegacy/RooNameSet.h @@ -19,6 +19,8 @@ #include "TObject.h" #include "RooPrintable.h" +#include + class RooArgSet; class RooNameSet : public TObject, public RooPrintable { @@ -59,6 +61,6 @@ class RooNameSet : public TObject, public RooPrintable { static void strdup(Int_t& dstlen, char* &dstbuf, const char* str); ClassDef(RooNameSet,1) // A sterile version of RooArgSet, containing only the names of the contained RooAbsArgs -}; +} R__SUGGEST_ALTERNATIVE("Please use RooHelpers::getColonSeparatedNameString() and RooHelpers::selectFromArgSet()."); #endif diff --git a/roofit/roofitcore/inc/RooFitResult.h b/roofit/roofitcore/inc/RooFitResult.h index 135ca0c10372e..a89b4d2c0ef02 100644 --- a/roofit/roofitcore/inc/RooFitResult.h +++ b/roofit/roofitcore/inc/RooFitResult.h @@ -37,6 +37,7 @@ class TObject ; class TH2 ; typedef RooArgSet* pRooArgSet ; + class RooFitResult : public TNamed, public RooPrintable, public RooDirItem { public: @@ -160,8 +161,8 @@ class RooFitResult : public TNamed, public RooPrintable, public RooDirItem { friend class RooAbsPdf ; friend class RooMinuit ; - friend class RooMinimizer ; - void setCovarianceMatrix(TMatrixDSym& V) ; + friend class RooMinimizer; + void setCovarianceMatrix(TMatrixDSym& V) ; void setConstParList(const RooArgList& list) ; void setInitParList(const RooArgList& list) ; void setFinalParList(const RooArgList& list) ; diff --git a/roofit/roofitcore/inc/RooGaussMinimizer.h b/roofit/roofitcore/inc/RooGaussMinimizer.h new file mode 100644 index 0000000000000..3b88bdabe5256 --- /dev/null +++ b/roofit/roofitcore/inc/RooGaussMinimizer.h @@ -0,0 +1,104 @@ +/***************************************************************************** + * Project: RooFit * + * Package: RooFitCore * + * File: $Id$ + * Authors: * + * WV, Wouter Verkerke, UC Santa Barbara, verkerke@slac.stanford.edu * + * DK, David Kirkby, UC Irvine, dkirkby@uci.edu * + * AL, Alfio Lazzaro, INFN Milan, alfio.lazzaro@mi.infn.it * + * PB, Patrick Bos, NL eScience Center, p.bos@esciencecenter.nl * + * VC, Vince Croft, DIANA / NYU, vincent.croft@cern.ch * + * * + * * + * Redistribution and use in source and binary forms, * + * with or without modification, are permitted according to the terms * + * listed in LICENSE (http://roofit.sourceforge.net/license.txt) * + *****************************************************************************/ +#ifndef ROO_GAUSS_MINIMIZER +#define ROO_GAUSS_MINIMIZER + +#include "TObject.h" +#include "TStopwatch.h" +#include +#include "TMatrixDSymfwd.h" + + +#include "Fit/Fitter.h" +#include "RooMinimizerFcn.h" +#include "RooGaussMinimizerFcn.h" + +class RooAbsReal ; +class RooFitResult ; +class RooArgList ; +class RooRealVar ; +class RooArgSet ; +class TH2F ; +class RooPlot ; + +class RooGaussMinimizer : public TObject { +public: + + RooGaussMinimizer(RooAbsReal& function) ; + virtual ~RooGaussMinimizer() ; + + enum Strategy { Speed=0, Balance=1, Robustness=2 } ; + enum PrintLevel { None=-1, Reduced=0, Normal=1, ExtraForProblem=2, Maximum=3 } ; + void optimizeConst(Int_t flag) ; + + RooFitResult* fit(const char* options) ; + + Int_t migrad() ; + Int_t hesse() ; + Int_t minos() ; + Int_t minos(const RooArgSet& minosParamList) ; + + Int_t minimize(const char* type, const char* alg=0) ; + + static void cleanup() ; + + void saveStatus(const char* label, Int_t status) { _statusHistory.push_back(std::pair(label,status)) ; } + + Int_t evalCounter() const { return fitterFcn()->evalCounter() ; } + void zeroEvalCount() { fitterFcn()->zeroEvalCount() ; } + + ROOT::Fit::Fitter* fitter() ; + const ROOT::Fit::Fitter* fitter() const ; + +protected: + + friend class RooAbsPdf ; + + inline Int_t getNPar() const { return fitterFcn()->NDim() ; } + inline std::ofstream* logfile() { return fitterFcn()->GetLogFile(); } + inline Double_t& maxFCN() { return fitterFcn()->GetMaxFCN() ; } + + const RooGaussMinimizerFcn* fitterFcn() const { return ( fitter()->GetFCN() ? (dynamic_cast(fitter()->GetFCN())) : _fcn ) ; } + RooGaussMinimizerFcn* fitterFcn() { return ( fitter()->GetFCN() ? (dynamic_cast(fitter()->GetFCN())) : _fcn ) ; } + +private: + + Int_t _printLevel ; + Int_t _status ; + Bool_t _optConst ; + Bool_t _profile ; + RooAbsReal* _func ; + + Bool_t _verbose ; + TStopwatch _timer ; + TStopwatch _cumulTimer ; + Bool_t _profileStart ; + + TMatrixDSym* _extV ; + + RooGaussMinimizerFcn *_fcn; + std::string _minimizerType; + + static ROOT::Fit::Fitter *_theFitter ; + + std::vector > _statusHistory ; + + RooGaussMinimizer(const RooGaussMinimizer&) ; + +} ; + +#endif diff --git a/roofit/roofitcore/inc/RooGaussMinimizerFcn.h b/roofit/roofitcore/inc/RooGaussMinimizerFcn.h new file mode 100644 index 0000000000000..37b3aa9243f42 --- /dev/null +++ b/roofit/roofitcore/inc/RooGaussMinimizerFcn.h @@ -0,0 +1,112 @@ +/***************************************************************************** + * Project: RooFit * + * Package: RooFitCore * + * @(#)root/roofitcore:$Id$ + * Authors: * + * AL, Alfio Lazzaro, INFN Milan, alfio.lazzaro@mi.infn.it * + * PB, Patrick Bos, NL eScience Center, p.bos@esciencecenter.nl * + * VC, Vince Croft, DIANA / NYU, vincent.croft@cern.ch * + * * + * * + * Redistribution and use in source and binary forms, * + * with or without modification, are permitted according to the terms * + * listed in LICENSE (http://roofit.sourceforge.net/license.txt) * + *****************************************************************************/ + +#ifndef __ROOFIT_NOROOGAUSSMINIMIZER + +#ifndef ROO_GAUSS_MINIMIZER_FCN +#define ROO_GAUSS_MINIMIZER_FCN + +#include "Math/IFunction.h" +#include "Fit/ParameterSettings.h" +#include "Fit/FitResult.h" + +#include "TMatrixDSym.h" + +#include "RooAbsReal.h" +#include "RooArgList.h" + +#include +#include + +class RooGaussMinimizer; + + +class RooGaussMinimizerFcn : public ROOT::Math::IMultiGradFunction { + + public: + + RooGaussMinimizerFcn(RooAbsReal *funct, RooGaussMinimizer *context, + bool verbose = false); + RooGaussMinimizerFcn(const RooGaussMinimizerFcn& other); + virtual ~RooGaussMinimizerFcn(); + + virtual ROOT::Math::IMultiGradFunction* Clone() const; + virtual unsigned int NDim() const { return _nDim; } + + RooArgList* GetFloatParamList() { return _floatParamList; } + RooArgList* GetConstParamList() { return _constParamList; } + RooArgList* GetInitFloatParamList() { return _initFloatParamList; } + RooArgList* GetInitConstParamList() { return _initConstParamList; } + + void SetEvalErrorWall(Bool_t flag) { _doEvalErrorWall = flag ; } + void SetPrintEvalErrors(Int_t numEvalErrors) { _printEvalErrors = numEvalErrors ; } + Bool_t SetLogFile(const char* inLogfile); + std::ofstream* GetLogFile() { return _logfile; } + void SetVerbose(Bool_t flag=kTRUE) { _verbose = flag ; } + + Double_t& GetMaxFCN() { return _maxFCN; } + Int_t GetNumInvalidNLL() { return _numBadNLL; } + + Bool_t Synchronize(std::vector& parameters, + Bool_t optConst, Bool_t verbose); + void BackProp(const ROOT::Fit::FitResult &results); + void ApplyCovarianceMatrix(TMatrixDSym& V); + + Int_t evalCounter() const { return _evalCounter ; } + void zeroEvalCount() { _evalCounter = 0 ; } + + + private: + + Double_t GetPdfParamVal(Int_t index); + Double_t GetPdfParamErr(Int_t index); + void SetPdfParamErr(Int_t index, Double_t value); + void ClearPdfParamAsymErr(Int_t index); + void SetPdfParamErr(Int_t index, Double_t loVal, Double_t hiVal); + + inline Bool_t SetPdfParamVal(const Int_t &index, const Double_t &value) const; + + + virtual double DoEval(const double * x) const; + void updateFloatVec() ; + + virtual double DoDerivative(const double *x, unsigned int icoord) const; + +private: + + mutable Int_t _evalCounter ; + + RooAbsReal *_funct; + RooGaussMinimizer *_context; + + mutable double _maxFCN; + mutable int _numBadNLL; + mutable int _printEvalErrors; + Bool_t _doEvalErrorWall; + + int _nDim; + std::ofstream *_logfile; + bool _verbose; + + RooArgList* _floatParamList; + std::vector _floatParamVec ; + RooArgList* _constParamList; + RooArgList* _initFloatParamList; + RooArgList* _initConstParamList; + +}; + +#endif +#endif diff --git a/roofit/roofitcore/inc/RooGlobalFunc.h b/roofit/roofitcore/inc/RooGlobalFunc.h index 9f1f15add4460..ad19fa6aea98c 100644 --- a/roofit/roofitcore/inc/RooGlobalFunc.h +++ b/roofit/roofitcore/inc/RooGlobalFunc.h @@ -199,6 +199,7 @@ RooCmdArg EventRange(Int_t nStart, Int_t nStop) ; RooCmdArg Extended(Bool_t flag=kTRUE) ; RooCmdArg DataError(Int_t) ; RooCmdArg NumCPU(Int_t nCPU, Int_t interleave=0) ; +RooCmdArg CPUAffinity(Bool_t flag=kTRUE); RooCmdArg BatchMode(bool flag=true); RooCmdArg IntegrateBins(double precision); @@ -207,7 +208,9 @@ RooCmdArg PrefitDataFraction(Double_t data_ratio = 0.0) ; RooCmdArg FitOptions(const char* opts) ; RooCmdArg Optimize(Int_t flag=2) ; RooCmdArg ProjectedObservables(const RooArgSet& set) ; // obsolete, for backward compatibility +RooCmdArg ProjectedObservables(RooArgSet && set) ; // obsolete, for backward compatibility RooCmdArg ConditionalObservables(const RooArgSet& set) ; +RooCmdArg ConditionalObservables(RooArgSet && set) ; RooCmdArg Verbose(Bool_t flag=kTRUE) ; RooCmdArg Save(Bool_t flag=kTRUE) ; RooCmdArg Timer(Bool_t flag=kTRUE) ; diff --git a/roofit/roofitcore/inc/RooGradMinimizerFcn.h b/roofit/roofitcore/inc/RooGradMinimizerFcn.h new file mode 100644 index 0000000000000..d5ab91c8cba35 --- /dev/null +++ b/roofit/roofitcore/inc/RooGradMinimizerFcn.h @@ -0,0 +1,101 @@ +/***************************************************************************** + * Project: RooFit * + * Package: RooFitCore * + * @(#)root/roofitcore:$Id$ + * Authors: * + * AL, Alfio Lazzaro, INFN Milan, alfio.lazzaro@mi.infn.it * + * PB, Patrick Bos, NL eScience Center, p.bos@esciencecenter.nl * + * VC, Vince Croft, DIANA / NYU, vincent.croft@cern.ch * + * * + * * + * Redistribution and use in source and binary forms, * + * with or without modification, are permitted according to the terms * + * listed in LICENSE (http://roofit.sourceforge.net/license.txt) * + *****************************************************************************/ + +#ifndef __ROOFIT_NORooGradMinimizer + +#ifndef ROO_GRAD_MINIMIZER_FCN +#define ROO_GRAD_MINIMIZER_FCN + +#include + +//#include "Fit/FitResult.h" +#include "Minuit2/MnStrategy.h" +#include "Minuit2/MnMatrix.h" // MnAlgebraicVector +//#include "TMatrixDSym.h" +#include "Math/IFunction.h" // IMultiGradFunction +#include "Fit/ParameterSettings.h" +#include "NumericalDerivatorMinuit2.h" + +#include "RooAbsMinimizerFcn.h" + +class RooGradMinimizerFcn : public ROOT::Math::IMultiGradFunction, public RooAbsMinimizerFcn { +public: + RooGradMinimizerFcn(RooAbsReal *funct, RooMinimizer *context, bool verbose = false); + RooGradMinimizerFcn(const RooGradMinimizerFcn& other); + ROOT::Math::IMultiGradFunction *Clone() const override; + + ROOT::Minuit2::MnStrategy get_strategy() const; + double get_error_def() const; + void set_strategy(int istrat); + + Bool_t Synchronize(std::vector ¶meter_settings, Bool_t optConst, + Bool_t verbose = kFALSE) override; + + void synchronize_gradient_parameter_settings(std::vector ¶meter_settings) const; + + bool returnsInMinuit2ParameterSpace() const override; + + unsigned int NDim() const override; + + void set_step_tolerance(double step_tolerance) const; + void set_grad_tolerance(double grad_tolerance) const; + void set_ncycles(unsigned int ncycles) const; + void set_error_level(double error_level) const; + + std::string getFunctionName() const override; + std::string getFunctionTitle() const override; + + void setOptimizeConst(Int_t flag) override; + +private: + void run_derivator(unsigned int i_component) const; + + void reset_has_been_calculated_flags() const; + bool sync_parameter(double x, std::size_t ix) const; + bool sync_parameters(const double *x) const; + + void optimizeConstantTerms(bool constStatChange, bool constValChange) override; + +public: + enum class GradientCalculatorMode { + ExactlyMinuit2, AlmostMinuit2 + }; + +private: + // IMultiGradFunction overrides + double DoEval(const double *x) const override; + double DoDerivative(const double *x, unsigned int icoord) const override; + bool hasG2ndDerivative() const override; + double DoSecondDerivative(const double *x, unsigned int icoord) const override; + bool hasGStepSize() const override; + double DoStepSize(const double *x, unsigned int icoord) const override; + + // members + // mutable because ROOT::Math::IMultiGradFunction::DoDerivative is const +protected: + mutable std::vector _grad; + mutable std::vector _grad_params; +private: + mutable RooFit::NumericalDerivatorMinuit2 _gradf; + RooAbsReal *_funct; + mutable std::vector has_been_calculated; + mutable bool none_have_been_calculated = false; + +public: + // for debugging, wraps ROOT::Math::IMultiGradFunction::Gradient, can be used for further inspection: + void Gradient(const double *x, double *grad) const override; +}; +#endif +#endif diff --git a/roofit/roofitcore/inc/RooHelpers.h b/roofit/roofitcore/inc/RooHelpers.h index 1879f4e84cbee..a426d737c38e9 100644 --- a/roofit/roofitcore/inc/RooHelpers.h +++ b/roofit/roofitcore/inc/RooHelpers.h @@ -121,6 +121,9 @@ std::pair getRangeOrBinningInterval(RooAbsArg const* arg, const bool checkIfRangesOverlap(RooAbsPdf const& pdf, RooAbsData const& data, std::vector const& rangeNames); +std::string getColonSeparatedNameString(RooArgSet const& argSet); +RooArgSet selectFromArgSet(RooArgSet const&, std::string const& names); + } diff --git a/roofit/roofitcore/inc/RooJsonListFile.h b/roofit/roofitcore/inc/RooJsonListFile.h new file mode 100644 index 0000000000000..281051684d81c --- /dev/null +++ b/roofit/roofitcore/inc/RooJsonListFile.h @@ -0,0 +1,74 @@ +#ifndef ROO_JSON_LIST_FILE +#define ROO_JSON_LIST_FILE + +#include +#include +#include + + +class RooJsonListFile { +public: + // ctors + RooJsonListFile(): _member_index(0) {} + RooJsonListFile(const std::string & filename); + // default move ctors + RooJsonListFile(RooJsonListFile&& other) = default; + RooJsonListFile& operator=(RooJsonListFile&& other) = default; + // dtor + ~RooJsonListFile(); + + void open(const std::string & filename); + + template + void set_member_names(Iter begin, Iter end, bool reset_index = true); + + RooJsonListFile& add_member_name(const std::string &name); + + template + RooJsonListFile& operator<< (const T& obj); + +private: + std::ofstream _out; + std::vector _member_names; + unsigned long _next_member_index(); + unsigned long _member_index; +}; + + +template +void RooJsonListFile::set_member_names(Iter begin, Iter end, bool reset_index) { + _member_names.clear(); + for(Iter it = begin; it != end; ++it) { + _member_names.push_back(*it); + } + if (reset_index) { + _member_index = 0; + } +} + +//////////////////////////////////////////////////////////////////////////////// +/// This method assumes that std::ofstream::operator<<(T) exists. + +template +RooJsonListFile& RooJsonListFile::operator<< (const T& obj) +{ + auto ix = _next_member_index(); + if (ix == 0) { + _out << "{"; + } + + // `"member name": ` + _out << "\"" << _member_names[ix] << "\": "; + // `"value"` (comma added below, if not last value in list element) + _out << "\"" << obj << "\""; + + if (ix == _member_names.size() - 1) { + _out << "},\n"; + } else { + _out << ", "; + } + + return *this; +} + +#endif \ No newline at end of file diff --git a/roofit/roofitcore/inc/RooMinimizer.h b/roofit/roofitcore/inc/RooMinimizer.h index f664166813763..a0ad470a0286e 100644 --- a/roofit/roofitcore/inc/RooMinimizer.h +++ b/roofit/roofitcore/inc/RooMinimizer.h @@ -3,9 +3,10 @@ * Package: RooFitCore * * File: $Id$ * Authors: * - * WV, Wouter Verkerke, UC Santa Barbara, verkerke@slac.stanford.edu * - * DK, David Kirkby, UC Irvine, dkirkby@uci.edu * - * AL, Alfio Lazzaro, INFN Milan, alfio.lazzaro@mi.infn.it * + * WV, Wouter Verkerke, UC Santa Barbara, verkerke@slac.stanford.edu * + * DK, David Kirkby, UC Irvine, dkirkby@uci.edu * + * AL, Alfio Lazzaro, INFN Milan, alfio.lazzaro@mi.infn.it * + * PB, Patrick Bos, NL eScience Center, p.bos@esciencecenter.nl * * * * * * Redistribution and use in source and binary forms, * @@ -18,6 +19,8 @@ #ifndef ROO_MINIMIZER #define ROO_MINIMIZER +#include // shared_ptr, unique_ptr + #include "TObject.h" #include "TStopwatch.h" #include @@ -25,116 +28,273 @@ #include #include #include "TMatrixDSymfwd.h" +#include "Math/IFunction.h" + +#include "RooArgList.h" // cannot just use forward decl due to default argument in lastMinuitFit -#include "Fit/Fitter.h" #include "RooMinimizerFcn.h" +#include "RooGradMinimizerFcn.h" +#include "TestStatistics/RooAbsL.h" -class RooAbsReal ; -class RooFitResult ; -class RooArgList ; -class RooRealVar ; -class RooArgSet ; -class TH2F ; -class RooPlot ; +#include "RooSentinel.h" +#include "RooMsgService.h" + +#include "Fit/Fitter.h" +#include // logic_error + +// forward declarations +class RooAbsReal; +class RooFitResult; +class RooRealVar; +class RooArgSet; +class TH2F; +class RooPlot; +namespace RooFit { +namespace TestStatistics { +class MinuitFcnGrad; // this one is necessary due to circular include dependencies +class LikelihoodJob; +class LikelihoodGradientJob; +} +} // namespace RooFit class RooMinimizer : public TObject { public: + enum class FcnMode { classic, gradient, generic_wrapper }; - RooMinimizer(RooAbsReal& function) ; - virtual ~RooMinimizer() ; - - enum Strategy { Speed=0, Balance=1, Robustness=2 } ; - enum PrintLevel { None=-1, Reduced=0, Normal=1, ExtraForProblem=2, Maximum=3 } ; - void setStrategy(Int_t strat) ; - void setErrorLevel(Double_t level) ; - void setEps(Double_t eps) ; - void optimizeConst(Int_t flag) ; - void setEvalErrorWall(Bool_t flag) { fitterFcn()->SetEvalErrorWall(flag); } - /// \copydoc RooMinimizerFcn::SetRecoverFromNaNStrength() - void setRecoverFromNaNStrength(double strength) { fitterFcn()->SetRecoverFromNaNStrength(strength); } - void setOffsetting(Bool_t flag) ; - void setMaxIterations(Int_t n) ; - void setMaxFunctionCalls(Int_t n) ; - - RooFitResult* fit(const char* options) ; - - Int_t migrad() ; - Int_t hesse() ; - Int_t minos() ; - Int_t minos(const RooArgSet& minosParamList) ; - Int_t seek() ; - Int_t simplex() ; - Int_t improve() ; - - Int_t minimize(const char* type, const char* alg=0) ; - - RooFitResult* save(const char* name=0, const char* title=0) ; - RooPlot* contour(RooRealVar& var1, RooRealVar& var2, - Double_t n1=1, Double_t n2=2, Double_t n3=0, - Double_t n4=0, Double_t n5=0, Double_t n6=0, unsigned int npoints = 50) ; - - Int_t setPrintLevel(Int_t newLevel) ; - void setPrintEvalErrors(Int_t numEvalErrors) { fitterFcn()->SetPrintEvalErrors(numEvalErrors); } - void setVerbose(Bool_t flag=kTRUE) { _verbose = flag ; fitterFcn()->SetVerbose(flag); } - void setProfile(Bool_t flag=kTRUE) { _profile = flag ; } - Bool_t setLogFile(const char* logf=0) { return fitterFcn()->SetLogFile(logf); } - - void setMinimizerType(const char* type) ; - - static void cleanup() ; - static RooFitResult* lastMinuitFit(const RooArgList& varList=RooArgList()) ; - - void saveStatus(const char* label, Int_t status) { _statusHistory.push_back(std::pair(label,status)) ; } - - Int_t evalCounter() const { return fitterFcn()->evalCounter() ; } - void zeroEvalCount() { fitterFcn()->zeroEvalCount() ; } - - ROOT::Fit::Fitter* fitter() ; - const ROOT::Fit::Fitter* fitter() const ; - -protected: + RooMinimizer(RooAbsReal &function); + template + static std::unique_ptr create(RooAbsReal &function); + template + static std::unique_ptr create(std::shared_ptr likelihood); - friend class RooAbsPdf ; - void applyCovarianceMatrix(TMatrixDSym& V) ; + virtual ~RooMinimizer(); - void profileStart() ; - void profileStop() ; + enum Strategy { Speed = 0, Balance = 1, Robustness = 2 }; + enum PrintLevel { None = -1, Reduced = 0, Normal = 1, ExtraForProblem = 2, Maximum = 3 }; + void setStrategy(Int_t strat); + void setErrorLevel(Double_t level); + void setEps(Double_t eps); + void optimizeConst(Int_t flag); + void setEvalErrorWall(Bool_t flag) { fitterFcn()->SetEvalErrorWall(flag); } + /// \copydoc RooMinimizerFcn::SetRecoverFromNaNStrength() + void setRecoverFromNaNStrength(double strength) { fitterFcn()->SetRecoverFromNaNStrength(strength); } + void setOffsetting(Bool_t flag) ; + void setMaxIterations(Int_t n); + void setMaxFunctionCalls(Int_t n); - inline Int_t getNPar() const { return fitterFcn()->NDim() ; } - inline std::ofstream* logfile() { return fitterFcn()->GetLogFile(); } - inline Double_t& maxFCN() { return fitterFcn()->GetMaxFCN() ; } - - const RooMinimizerFcn* fitterFcn() const { return ( fitter()->GetFCN() ? ((RooMinimizerFcn*) fitter()->GetFCN()) : _fcn ) ; } - RooMinimizerFcn* fitterFcn() { return ( fitter()->GetFCN() ? ((RooMinimizerFcn*) fitter()->GetFCN()) : _fcn ) ; } + RooFitResult *fit(const char *options); -private: + Int_t migrad(); + Int_t hesse(); + Int_t minos(); + Int_t minos(const RooArgSet &minosParamList); + Int_t seek(); + Int_t simplex(); + Int_t improve(); + + Int_t minimize(const char *type, const char *alg = 0); + + RooFitResult *save(const char *name = 0, const char *title = 0); + RooPlot* contour(RooRealVar& var1, RooRealVar& var2, + Double_t n1=1, Double_t n2=2, Double_t n3=0, + Double_t n4=0, Double_t n5=0, Double_t n6=0, unsigned int npoints = 50) ; - Int_t _printLevel ; - Int_t _status ; - Bool_t _optConst ; - Bool_t _profile ; - RooAbsReal* _func ; + Int_t setPrintLevel(Int_t newLevel); + void setPrintEvalErrors(Int_t numEvalErrors) { fitterFcn()->SetPrintEvalErrors(numEvalErrors); } + void setVerbose(Bool_t flag = kTRUE) + { + _verbose = flag; + fitterFcn()->SetVerbose(flag); + } + void setProfile(Bool_t flag = kTRUE) { _profile = flag; } + Bool_t setLogFile(const char *logf = 0) { return fitterFcn()->SetLogFile(logf); } - Bool_t _verbose ; - TStopwatch _timer ; - TStopwatch _cumulTimer ; - Bool_t _profileStart ; + // necessary from RooAbsMinimizerFcn subclasses + Int_t getPrintLevel() const; - TMatrixDSym* _extV ; + void setMinimizerType(const char *type); - RooMinimizerFcn *_fcn; - std::string _minimizerType; + static void cleanup(); + static RooFitResult *lastMinuitFit(const RooArgList &varList = RooArgList()); - static ROOT::Fit::Fitter *_theFitter ; + void saveStatus(const char *label, Int_t status) + { + _statusHistory.push_back(std::pair(label, status)); + } - std::vector > _statusHistory ; + Int_t evalCounter() const { return fitterFcn()->evalCounter(); } + void zeroEvalCount() { fitterFcn()->zeroEvalCount(); } - RooMinimizer(const RooMinimizer&) ; - - ClassDef(RooMinimizer,0) // RooFit interface to ROOT::Fit::Fitter -} ; + ROOT::Fit::Fitter *fitter(); + const ROOT::Fit::Fitter *fitter() const; + std::vector get_function_parameter_values() const { return fitterFcn()->get_parameter_values(); } + void set_function_parameter_value(std::size_t ix, double value) const; -#endif + inline Int_t getNPar() const { return fitterFcn()->get_nDim(); } -#endif + ROOT::Math::IMultiGenFunction* getFitterMultiGenFcn() const; + ROOT::Math::IMultiGenFunction* getMultiGenFcn() const; + + void enable_likelihood_offsetting(bool flag); + +protected: + friend class RooAbsPdf; + void applyCovarianceMatrix(TMatrixDSym &V); + + void profileStart(); + void profileStop(); + + inline std::ofstream *logfile() { return fitterFcn()->GetLogFile(); } + inline Double_t &maxFCN() { return fitterFcn()->GetMaxFCN(); } + + const RooAbsMinimizerFcn *fitterFcn() const; + RooAbsMinimizerFcn *fitterFcn(); + + bool fitFcn() const; + +private: + template + RooMinimizer(RooAbsReal &function, MinimizerFcn* /* used only for template deduction */); + template + RooMinimizer(std::shared_ptr likelihood, + LikelihoodWrapperT* /* used only for template deduction */ = static_cast(nullptr), + LikelihoodGradientWrapperT* /* used only for template deduction */ = static_cast(nullptr)); + + Int_t _printLevel = 1; + Int_t _status = -99; + Bool_t _profile = kFALSE; + + Bool_t _verbose = kFALSE; + TStopwatch _timer; + TStopwatch _cumulTimer; + Bool_t _profileStart = kFALSE; + + TMatrixDSym *_extV = 0; + + RooAbsMinimizerFcn *_fcn; + std::string _minimizerType = "Minuit2"; + FcnMode _fcnMode; + + static ROOT::Fit::Fitter *_theFitter; + + std::vector> _statusHistory; + + RooMinimizer(const RooMinimizer &); + + ClassDef(RooMinimizer,1) // RooFit interface to ROOT::Fit::Fitter +}; + + +// include here to avoid circular dependency issues in class definitions +#include "TestStatistics/MinuitFcnGrad.h" + + +//////////////////////////////////////////////////////////////////////////////// +/// Construct MINUIT interface to given function. Function can be anything, +/// but is typically a -log(likelihood) implemented by RooNLLVar or a chi^2 +/// (implemented by RooChi2Var). Other frequent use cases are a RooAddition +/// of a RooNLLVar plus a penalty or constraint term. This class propagates +/// all RooFit information (floating parameters, their values and errors) +/// to MINUIT before each MINUIT call and propagates all MINUIT information +/// back to the RooFit object at the end of each call (updated parameter +/// values, their (asymmetric errors) etc. The default MINUIT error level +/// for HESSE and MINOS error analysis is taken from the defaultErrorLevel() +/// value of the input function. + +template +RooMinimizer::RooMinimizer(RooAbsReal &function, MinimizerFcn* /* value unused */) +{ + RooSentinel::activate(); + + if (_theFitter) + delete _theFitter; + _theFitter = new ROOT::Fit::Fitter; + _theFitter->Config().SetMinimizer(_minimizerType.c_str()); + setEps(1.0); // default tolerance + + _fcn = new MinimizerFcn(&function, this, _verbose); + + // make sure to order correctly so that child classes get checked for first TODO + if (dynamic_cast(_fcn)) { + _fcnMode = FcnMode::gradient; + } else if (dynamic_cast(_fcn)) { + _fcnMode = FcnMode::classic; + } else { + throw std::logic_error("RooMinimizer's MinimizerFcn template argument must be (a subclass of) RooMinimizerFcn or " + "RooGradMinimizerFcn. MinuitFcnGrad is used in the other RooMinimizer ctor."); + } + + // default max number of calls + _theFitter->Config().MinimizerOptions().SetMaxIterations(500 * _fcn->get_nDim()); + _theFitter->Config().MinimizerOptions().SetMaxFunctionCalls(500 * _fcn->get_nDim()); + + // Shut up for now + setPrintLevel(-1); + + // Use +0.5 for 1-sigma errors + setErrorLevel(function.defaultErrorLevel()); + + // Declare our parameters to MINUIT + _fcn->Synchronize(_theFitter->Config().ParamsSettings(), _fcn->getOptConst(), _verbose); + + // Now set default verbosity + if (RooMsgService::instance().silentMode()) { + setPrintLevel(-1); + } else { + setPrintLevel(1); + } +} + +template +RooMinimizer::RooMinimizer(std::shared_ptr likelihood, LikelihoodWrapperT* /* value unused */, + LikelihoodGradientWrapperT* /* value unused */) : + _fcnMode(FcnMode::generic_wrapper) +{ + RooSentinel::activate(); + + if (_theFitter) + delete _theFitter; + _theFitter = new ROOT::Fit::Fitter; + _theFitter->Config().SetMinimizer(_minimizerType.c_str()); + setEps(1.0); // default tolerance + + _fcn = RooFit::TestStatistics::MinuitFcnGrad::create(likelihood, this, _verbose); + + // default max number of calls + _theFitter->Config().MinimizerOptions().SetMaxIterations(500 * _fcn->get_nDim()); + _theFitter->Config().MinimizerOptions().SetMaxFunctionCalls(500 * _fcn->get_nDim()); + + // Shut up for now + setPrintLevel(-1); + + // Use +0.5 for 1-sigma errors + setErrorLevel(likelihood->defaultErrorLevel()); + + // Declare our parameters to MINUIT + _fcn->Synchronize(_theFitter->Config().ParamsSettings(), _fcn->getOptConst(), _verbose); + + // Now set default verbosity + if (RooMsgService::instance().silentMode()) { + setPrintLevel(-1); + } else { + setPrintLevel(1); + } +} + +// static function +template +std::unique_ptr RooMinimizer::create(RooAbsReal &function) { + return std::unique_ptr(new RooMinimizer(function, static_cast(nullptr))); +} + +// static function +template +std::unique_ptr RooMinimizer::create(std::shared_ptr likelihood) { + return std::unique_ptr(new RooMinimizer(likelihood, static_cast(nullptr), + static_cast(nullptr))); +} + + +#endif // ROO_MINIMIZER +#endif // __ROOFIT_NOROOMINIMIZER diff --git a/roofit/roofitcore/inc/RooMinimizerFcn.h b/roofit/roofitcore/inc/RooMinimizerFcn.h index 957d073804050..811b2b0860244 100644 --- a/roofit/roofitcore/inc/RooMinimizerFcn.h +++ b/roofit/roofitcore/inc/RooMinimizerFcn.h @@ -1,6 +1,6 @@ /***************************************************************************** * Project: RooFit * - * Package: RooFitCore * + * Package: RooFitCore * * @(#)root/roofitcore:$Id$ * Authors: * * AL, Alfio Lazzaro, INFN Milan, alfio.lazzaro@mi.infn.it * @@ -26,83 +26,34 @@ #include #include -class RooMinimizer; +#include + template class TMatrixTSym; using TMatrixDSym = TMatrixTSym; -class RooMinimizerFcn : public ROOT::Math::IBaseFunctionMultiDim { - - public: - - RooMinimizerFcn(RooAbsReal *funct, RooMinimizer *context, - bool verbose = false); - RooMinimizerFcn(const RooMinimizerFcn& other); - virtual ~RooMinimizerFcn(); - - virtual ROOT::Math::IBaseFunctionMultiDim* Clone() const; - virtual unsigned int NDim() const { return _nDim; } - - RooArgList* GetFloatParamList() { return _floatParamList; } - RooArgList* GetConstParamList() { return _constParamList; } - RooArgList* GetInitFloatParamList() { return _initFloatParamList; } - RooArgList* GetInitConstParamList() { return _initConstParamList; } - - void SetEvalErrorWall(Bool_t flag) { _doEvalErrorWall = flag ; } - /// Try to recover from invalid function values. When invalid function values are encountered, - /// a penalty term is returned to the minimiser to make it back off. This sets the strength of this penalty. - /// \note A strength of zero is equivalent to a constant penalty (= the gradient vanishes, ROOT < 6.24). - /// Positive values lead to a gradient pointing away from the undefined regions. Use ~10 to force the minimiser - /// away from invalid function values. - void SetRecoverFromNaNStrength(double strength) { _recoverFromNaNStrength = strength; } - void SetPrintEvalErrors(Int_t numEvalErrors) { _printEvalErrors = numEvalErrors ; } - Bool_t SetLogFile(const char* inLogfile); - std::ofstream* GetLogFile() { return _logfile; } - void SetVerbose(Bool_t flag=kTRUE) { _verbose = flag ; } - - Double_t& GetMaxFCN() { return _maxFCN; } - Int_t GetNumInvalidNLL() const { return _numBadNLL; } - - Bool_t Synchronize(std::vector& parameters, - Bool_t optConst, Bool_t verbose); - void BackProp(const ROOT::Fit::FitResult &results); - void ApplyCovarianceMatrix(TMatrixDSym& V); - - Int_t evalCounter() const { return _evalCounter ; } - void zeroEvalCount() { _evalCounter = 0 ; } - /// Return a possible offset that's applied to the function to separate invalid function values from valid ones. - double getOffset() const { return _funcOffset; } - - private: - void SetPdfParamErr(Int_t index, Double_t value); - void ClearPdfParamAsymErr(Int_t index); - void SetPdfParamErr(Int_t index, Double_t loVal, Double_t hiVal); - - Bool_t SetPdfParamVal(int index, double value) const; - void printEvalErrors() const; +// forward declaration +class RooMinimizer; - virtual double DoEval(const double * x) const; +class RooMinimizerFcn : public RooAbsMinimizerFcn, public ROOT::Math::IBaseFunctionMultiDim { +public: + RooMinimizerFcn(RooAbsReal *funct, RooMinimizer *context, bool verbose = false); + RooMinimizerFcn(const RooMinimizerFcn &other); + virtual ~RooMinimizerFcn(); - RooAbsReal *_funct; - const RooMinimizer *_context; + ROOT::Math::IBaseFunctionMultiDim *Clone() const override; + unsigned int NDim() const override { return get_nDim(); } - mutable double _maxFCN; - mutable double _funcOffset{0.}; - double _recoverFromNaNStrength{10.}; - mutable int _numBadNLL; - mutable int _printEvalErrors; - mutable int _evalCounter{0}; - int _nDim; + std::string getFunctionName() const override; + std::string getFunctionTitle() const override; - RooArgList* _floatParamList; - RooArgList* _constParamList; - RooArgList* _initFloatParamList; - RooArgList* _initConstParamList; + void setOptimizeConst(Int_t flag) override; - std::ofstream *_logfile; - bool _doEvalErrorWall{true}; - bool _verbose; +private: + double DoEval(const double *x) const override; + void optimizeConstantTerms(bool constStatChange, bool constValChange) override; + RooAbsReal *_funct; }; #endif diff --git a/roofit/roofitcore/inc/RooNLLVar.h b/roofit/roofitcore/inc/RooNLLVar.h index daecdc9b4a8df..384eb77d46259 100644 --- a/roofit/roofitcore/inc/RooNLLVar.h +++ b/roofit/roofitcore/inc/RooNLLVar.h @@ -52,7 +52,7 @@ class RooNLLVar : public RooAbsOptTestStatistic { virtual ~RooNLLVar(); - void applyWeightSquared(Bool_t flag) ; + void applyWeightSquared(Bool_t flag) ; virtual Double_t defaultErrorLevel() const { return 0.5 ; } diff --git a/roofit/roofitcore/inc/RooNormSetCache.h b/roofit/roofitcore/inc/RooNormSetCache.h index b6a181018b4cf..14a582757910f 100644 --- a/roofit/roofitcore/inc/RooNormSetCache.h +++ b/roofit/roofitcore/inc/RooNormSetCache.h @@ -18,9 +18,9 @@ #include #include +#include #include "Rtypes.h" -#include "RooNameSet.h" class RooAbsArg; class RooArgSet; @@ -75,8 +75,8 @@ class RooNormSetCache { const RooArgSet* lastSet1() const { return _pairs.empty()?0:_pairs.back().first; } const RooArgSet* lastSet2() const { return _pairs.empty()?0:_pairs.back().second; } - const RooNameSet& nameSet1() const { return _name1; } - const RooNameSet& nameSet2() const { return _name2; } + const std::string& nameSet1() const { return _name1; } + const std::string& nameSet2() const { return _name2; } Bool_t autoCache(const RooAbsArg* self, const RooArgSet* set1, const RooArgSet* set2 = 0, const TNamed* set2RangeName = 0, @@ -94,8 +94,8 @@ class RooNormSetCache { ULong_t _max; //! ULong_t _next; //! - RooNameSet _name1; //! - RooNameSet _name2; //! + std::string _name1; //! + std::string _name2; //! TNamed* _set2RangeName; //! ClassDef(RooNormSetCache, 0) // Management tool for tracking sets of similar integration/normalization sets diff --git a/roofit/roofitcore/inc/RooObjCacheManager.h b/roofit/roofitcore/inc/RooObjCacheManager.h index c9ec150bfcd95..fbf49e52ce9ab 100644 --- a/roofit/roofitcore/inc/RooObjCacheManager.h +++ b/roofit/roofitcore/inc/RooObjCacheManager.h @@ -26,8 +26,6 @@ #include "RooAbsCacheElement.h" #include "RooCacheManager.h" -class RooNameSet; - class RooObjCacheManager : public RooCacheManager { diff --git a/roofit/roofitcore/inc/RooProfileLL.h b/roofit/roofitcore/inc/RooProfileLL.h index 8bc30139e046a..f76b84bfd4e25 100644 --- a/roofit/roofitcore/inc/RooProfileLL.h +++ b/roofit/roofitcore/inc/RooProfileLL.h @@ -17,9 +17,9 @@ #include "RooSetProxy.h" #include #include +#include -class RooMinimizer ; -class RooMinuit ; +class RooMinuit ; #define MINIMIZER RooMinimizer diff --git a/roofit/roofitcore/inc/RooRealIntegral.h b/roofit/roofitcore/inc/RooRealIntegral.h index f26769dcb4dff..d1b8890bfd6f2 100644 --- a/roofit/roofitcore/inc/RooRealIntegral.h +++ b/roofit/roofitcore/inc/RooRealIntegral.h @@ -139,6 +139,12 @@ class RooRealIntegral : public RooAbsReal { Bool_t _cacheNum ; // Cache integral if numeric static Int_t _cacheAllNDim ; //! Cache all integrals with given numeric dimension +public: + void setNumIntTiming(Bool_t flag); + void activateTimingNumInts(); +private: + mutable Bool_t _timeNumInt ; //! do not persist + ClassDef(RooRealIntegral,3) // Real-valued function representing an integral over a RooAbsReal object }; diff --git a/roofit/roofitcore/inc/RooRealMPFE.h b/roofit/roofitcore/inc/RooRealMPFE.h index e8035995c5ab9..35f8493878b92 100644 --- a/roofit/roofitcore/inc/RooRealMPFE.h +++ b/roofit/roofitcore/inc/RooRealMPFE.h @@ -22,7 +22,12 @@ #include "RooArgList.h" #include "RooMPSentinel.h" #include "TStopwatch.h" -#include +#include "RooTaskSpec.h" +#include +#include +#include +// getpid and getppid (and pid_t): +#include "unistd.h" class RooArgSet ; namespace RooFit { class BidirMMapPipe; } @@ -30,7 +35,8 @@ namespace RooFit { class BidirMMapPipe; } class RooRealMPFE : public RooAbsReal { public: // Constructors, assignment etc - RooRealMPFE(const char *name, const char *title, RooAbsReal& arg, Bool_t calcInline=kFALSE) ; + RooRealMPFE(const char *name, const char *title, RooAbsReal& arg, Int_t inSetNum, Int_t inNumSets, + Bool_t calcInline=kFALSE) ; RooRealMPFE(const RooRealMPFE& other, const char* name=0); virtual TObject* clone(const char* newname) const { return new RooRealMPFE(*this,newname); } virtual ~RooRealMPFE(); @@ -46,7 +52,7 @@ class RooRealMPFE : public RooAbsReal { void enableOffsetting(Bool_t flag) ; void followAsSlave(RooRealMPFE& master) { _updateMaster = &master ; } - + protected: // Function evaluation @@ -59,9 +65,17 @@ class RooRealMPFE : public RooAbsReal { State _state ; enum Message { SendReal=0, SendCat, Calculate, Retrieve, ReturnValue, Terminate, - ConstOpt, Verbose, LogEvalError, ApplyNLLW2, EnableOffset, CalculateNoOffset } ; - - void initialize() ; + ConstOpt, Verbose, LogEvalError, ApplyNLLW2, EnableOffset, CalculateNoOffset, + SetCpuAffinity, TaskSpec, + EnableTimingNumInts, DisableTimingNumInts, + MeasureCommunicationTime, + RetrieveTimings, + GetPID + }; + + friend std::ostream& operator<<(std::ostream& out, const RooRealMPFE::Message value); + + void initialize() ; void initVars() ; void serverLoop() ; @@ -71,6 +85,8 @@ class RooRealMPFE : public RooAbsReal { RooListProxy _vars ; // Variables RooArgList _saveVars ; // Copy of variables mutable Bool_t _calcInProgress ; + // RooTaskSpec _taskspecification; + Bool_t _useTaskSpec ; Bool_t _verboseClient ; Bool_t _verboseServer ; Bool_t _inlineMode ; @@ -87,7 +103,28 @@ class RooRealMPFE : public RooAbsReal { static RooMPSentinel _sentinel ; - ClassDef(RooRealMPFE,2) // Multi-process front-end for parallel calculation of a real valued function + void setCpuAffinity(int cpu); + void setTaskSpec(); + pid_t getPIDFromServer() const; + void setMPSet(Int_t inSetNum, Int_t inNumSets); + +private: +// RooArgSet* _components = 0; +// RooAbsArg* _findComponent(std::string name); + + void _time_communication_overhead() const; + + void setTimingNumInts(Bool_t flag = kTRUE); + std::map collectTimingsFromServer(Bool_t clear_timings = kTRUE) const; + + void _initTiming(); + // RooTaskSpec _taskspecification; + Int_t _setNum ; //! Partition number of this instance in parallel calculation mode + Int_t _numSets ; //! Total number of partitions in parallel calculation mode + + ClassDef(RooRealMPFE,3) // Multi-process front-end for parallel calculation of a real valued function }; +std::ostream& operator<<(std::ostream& out, const RooRealMPFE::Message value); + #endif diff --git a/roofit/roofitcore/inc/RooTaskSpec.h b/roofit/roofitcore/inc/RooTaskSpec.h new file mode 100644 index 0000000000000..2e6e92d2b52e3 --- /dev/null +++ b/roofit/roofitcore/inc/RooTaskSpec.h @@ -0,0 +1,47 @@ +/***************************************************************************** + * Project: RooFit * + * Package: RooFitCore * + * File: $Id: RooRealMPFE.h,v 1.7 2007/05/11 09:11:30 verkerke Exp $ + * Authors: * + * WV, Wouter Verkerke, UC Santa Barbara, verkerke@slac.stanford.edu * + * DK, David Kirkby, UC Irvine, dkirkby@uci.edu * + * * + * Copyright (c) 2000-2005, Regents of the University of California * + * and Stanford University. All rights reserved. * + * * + * Redistribution and use in source and binary forms, * + * with or without modification, are permitted according to the terms * + * listed in LICENSE (http://roofit.sourceforge.net/license.txt) * + *****************************************************************************/ +#ifndef ROO_TASK_SPEC +#define ROO_TASK_SPEC + +#include "RooFit.h" +#include +#include +#include "RooAbsTestStatistic.h" + +class RooArgList; + +class RooTaskSpec { + public: + RooTaskSpec(); + RooTaskSpec(RooAbsTestStatistic* rats_nll); + RooTaskSpec(RooAbsReal* nll); + struct Task { + Int_t id; + Bool_t binned; + std::string name; + Int_t entries; + Bool_t is_done; + }; + std::list tasks; + private: + void _initialise(RooAbsTestStatistic* rats); + Task _fill_task(const Int_t n, RooAbsTestStatistic* rats); + Int_t _fit_case; + Int_t n_tasks = tasks.size(); + + +}; +#endif diff --git a/roofit/roofitcore/inc/RooTimer.h b/roofit/roofitcore/inc/RooTimer.h new file mode 100644 index 0000000000000..7fad6b7a3489b --- /dev/null +++ b/roofit/roofitcore/inc/RooTimer.h @@ -0,0 +1,49 @@ +#ifndef ROO_TIMER +#define ROO_TIMER + +#include +#include +#include +#include +#include "RooJsonListFile.h" + +class RooTimer { +public: + virtual void start() = 0; + virtual void stop() = 0; + double timing_s(); + void set_timing_s(double timing_s); + void store_timing_in_RooTrace(const std::string &name); + + static std::vector timing_outfiles; + static std::vector timings; + +private: + double _timing_s; +}; + +class RooWallTimer: public RooTimer { +public: + RooWallTimer(); + virtual void start(); + virtual void stop(); + +private: + std::chrono::time_point _timing_begin, _timing_end; +}; + +/// @class RooCPUTimer +/// Measures the CPU time on the local process. Note that for multi-process runs, +/// e.g. when using RooRealMPFE, the child process CPU times are not included! +/// Use a separate timer in child processes to measure their CPU timing. +class RooCPUTimer: public RooTimer { +public: + RooCPUTimer(); + virtual void start(); + virtual void stop(); + +private: + struct timespec _timing_begin, _timing_end; +}; + +#endif diff --git a/roofit/roofitcore/inc/RooTrace.h b/roofit/roofitcore/inc/RooTrace.h index 451528f080879..daea848b721c2 100644 --- a/roofit/roofitcore/inc/RooTrace.h +++ b/roofit/roofitcore/inc/RooTrace.h @@ -51,6 +51,16 @@ class RooTrace { static void printObjectCounts() ; + static std::map objectTiming; + + // TODO: for windows version, change to private _timing_flag and use public method that returns its value + static int timing_flag; + + static Bool_t time_numInts(); + static void set_time_numInts(Bool_t flag); + +private: + static Bool_t _time_numInts; protected: @@ -81,7 +91,7 @@ class RooTrace { std::map _specialCount ; std::map _specialSize ; - ClassDef(RooTrace,0) // Memory tracer utility for RooFit objects + ClassDef(RooTrace,1) // Memory tracer utility for RooFit objects }; diff --git a/roofit/roofitcore/inc/TestStatistics/LikelihoodGradientJob.h b/roofit/roofitcore/inc/TestStatistics/LikelihoodGradientJob.h new file mode 100644 index 0000000000000..a08e41500bc46 --- /dev/null +++ b/roofit/roofitcore/inc/TestStatistics/LikelihoodGradientJob.h @@ -0,0 +1,109 @@ +/* + * Project: RooFit + * Authors: + * PB, Patrick Bos, Netherlands eScience Center, p.bos@esciencecenter.nl + * + * Copyright (c) 2016-2020, Netherlands eScience Center + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +#ifndef ROOT_ROOFIT_TESTSTATISTICS_LikelihoodGradientJob +#define ROOT_ROOFIT_TESTSTATISTICS_LikelihoodGradientJob + +#include +#include "Math/MinimizerOptions.h" +#include +#include +#include +#include "Minuit2/MnMatrix.h" + +namespace RooFit { +namespace TestStatistics { + +class LikelihoodGradientJob : public MultiProcess::Job, public LikelihoodGradientWrapper { +public: + LikelihoodGradientJob(std::shared_ptr likelihood, std::shared_ptr calculation_is_clean, std::size_t N_dim, RooMinimizer* minimizer); + LikelihoodGradientJob* clone() const override; + LikelihoodGradientJob(const LikelihoodGradientJob& other); + + void fill_gradient(double *grad) override; + void fill_second_derivative(double *g2) override; + void fill_step_size(double *gstep) override; + + void update_state() override; + + // ----- BEGIN PASTE UIT RooGradientFunction.h ----- + // ----- BEGIN PASTE UIT RooGradientFunction.h ----- + // ----- BEGIN PASTE UIT RooGradientFunction.h ----- + // ----- BEGIN PASTE UIT RooGradientFunction.h ----- + // ----- BEGIN PASTE UIT RooGradientFunction.h ----- + +public: + enum class GradientCalculatorMode { + ExactlyMinuit2, AlmostMinuit2 + }; + +private: + // TODO: are mutables here still necessary? + // mutables below are because ROOT::Math::IMultiGradFunction::DoDerivative is const + + mutable std::vector _grad; + mutable RooFit::NumericalDerivatorMinuit2 _gradf; + + void run_derivator(unsigned int i_component) const; + + // ----- END PASTE UIT RooGradientFunction.h ----- + // ----- END PASTE UIT RooGradientFunction.h ----- + // ----- END PASTE UIT RooGradientFunction.h ----- + // ----- END PASTE UIT RooGradientFunction.h ----- + // ----- END PASTE UIT RooGradientFunction.h ----- + + void synchronize_parameter_settings(ROOT::Math::IMultiGenFunction* function, const std::vector ¶meter_settings) override; + + void synchronize_with_minimizer(const ROOT::Math::MinimizerOptions & options) override; + void set_strategy(int istrat); + void set_step_tolerance(double step_tolerance) const; + void set_grad_tolerance(double grad_tolerance) const; + void set_ncycles(unsigned int ncycles) const; + void set_error_level(double error_level) const; + + void update_minuit_internal_parameter_values(const std::vector& minuit_internal_x) override; + + bool uses_minuit_internal_values() override; + + // Job overrides: + void evaluate_task(std::size_t task) override; + void update_real(std::size_t ix, double val, bool is_constant) override; + void update_bool(std::size_t ix, bool value) override; + void send_back_task_result_from_worker(std::size_t task) override; + void receive_task_result_on_queue(std::size_t task, std::size_t worker_id) override; + void send_back_results_from_queue_to_master() override; + void clear_results() override; + void receive_results_on_master() override; + + struct task_result_t { + std::size_t job_id; + std::size_t task_id; + MinuitDerivatorElement grad; + }; + bool receive_task_result_on_master(const zmq::message_t & message) override; + + void update_workers_state(); + void calculate_all(); + + // members + std::size_t N_tasks = 0; + std::size_t N_tasks_at_workers = 0; + std::vector completed_task_ids; + std::vector minuit_internal_x_; +}; + +} +} + + +#endif // ROOT_ROOFIT_LikelihoodGradientJob diff --git a/roofit/roofitcore/inc/TestStatistics/LikelihoodGradientWrapper.h b/roofit/roofitcore/inc/TestStatistics/LikelihoodGradientWrapper.h new file mode 100644 index 0000000000000..2ac22e15f1927 --- /dev/null +++ b/roofit/roofitcore/inc/TestStatistics/LikelihoodGradientWrapper.h @@ -0,0 +1,65 @@ +/* + * Project: RooFit + * Authors: + * PB, Patrick Bos, Netherlands eScience Center, p.bos@esciencecenter.nl + * + * Copyright (c) 2016-2020, Netherlands eScience Center + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +#ifndef ROOT_ROOFIT_TESTSTATISTICS_LikelihoodGradientWrapper +#define ROOT_ROOFIT_TESTSTATISTICS_LikelihoodGradientWrapper + +#include +#include // shared_ptr +#include +#include +#include "Math/MinimizerOptions.h" + +// forward declaration +class RooMinimizer; + +namespace RooFit { +namespace TestStatistics { + +// forward declaration +class RooAbsL; +struct WrapperCalculationCleanFlags; + +class LikelihoodGradientWrapper { +public: + LikelihoodGradientWrapper(std::shared_ptr likelihood, std::shared_ptr calculation_is_clean, std::size_t N_dim, RooMinimizer* minimizer); + virtual ~LikelihoodGradientWrapper() = default; + virtual LikelihoodGradientWrapper* clone() const = 0; + + virtual void fill_gradient(double *grad) = 0; + virtual void fill_second_derivative(double *g2) = 0; + virtual void fill_step_size(double *gstep) = 0; + + // synchronize minimizer settings with calculators in child classes + virtual void synchronize_with_minimizer(const ROOT::Math::MinimizerOptions &options); + virtual void synchronize_parameter_settings(const std::vector ¶meter_settings); + virtual void synchronize_parameter_settings(ROOT::Math::IMultiGenFunction* function, const std::vector ¶meter_settings) = 0; + // Minuit passes in parameter values that may not conform to RooFit internal standards (like applying range clipping), + // but that the specific calculator does need. This function can be implemented to receive these Minuit-internal values: + virtual void update_minuit_internal_parameter_values(const std::vector& minuit_internal_x); + virtual void update_minuit_external_parameter_values(const std::vector& minuit_external_x); + + // completely depends on the implementation, so pure virtual + virtual bool uses_minuit_internal_values() = 0; + +protected: + std::shared_ptr likelihood; + RooMinimizer* _minimizer; + std::shared_ptr calculation_is_clean; +// MinuitFcnGrad* _minimizer_fcn; +}; + +} // namespace TestStatistics +} // namespace RooFit + +#endif // ROOT_ROOFIT_TESTSTATISTICS_LikelihoodGradientWrapper diff --git a/roofit/roofitcore/inc/TestStatistics/LikelihoodJob.h b/roofit/roofitcore/inc/TestStatistics/LikelihoodJob.h new file mode 100644 index 0000000000000..677206c899161 --- /dev/null +++ b/roofit/roofitcore/inc/TestStatistics/LikelihoodJob.h @@ -0,0 +1,74 @@ +/* + * Project: RooFit + * Authors: + * PB, Patrick Bos, Netherlands eScience Center, p.bos@esciencecenter.nl + * + * Copyright (c) 2016-2020, Netherlands eScience Center + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +#ifndef ROOT_ROOFIT_TESTSTATISTICS_LikelihoodJob +#define ROOT_ROOFIT_TESTSTATISTICS_LikelihoodJob + +#include + +#include "Math/MinimizerOptions.h" +#include +#include +#include + +#include "RooArgList.h" + +namespace RooFit { +namespace TestStatistics { + +class LikelihoodJob : public MultiProcess::Job, public LikelihoodWrapper { +public: + LikelihoodJob(std::shared_ptr _likelihood, std::shared_ptr calculation_is_clean/*, RooMinimizer *minimizer*/); + LikelihoodJob* clone() const override; + + void init_vars(); + + // TODO: implement override if necessary +// void synchronize_with_minimizer(const ROOT::Math::MinimizerOptions & options) override; + + void evaluate() override; + double return_result() const override; + + void update_parameters(); // helper for evaluate + + // Job overrides: + void evaluate_task(std::size_t task) override; + void update_real(std::size_t ix, double val, bool is_constant) override; + void update_bool(std::size_t ix, bool value) override; + // --- RESULT LOGISTICS --- + void send_back_task_result_from_worker(std::size_t task) override; + void receive_task_result_on_queue(std::size_t task, std::size_t worker_id) override; + void send_back_results_from_queue_to_master() override; + void clear_results() override; + void receive_results_on_master() override; + bool receive_task_result_on_master(const zmq::message_t & message) override; + + void enable_offsetting(bool flag) override; + +private: + double result = 0; + double carry = 0; + std::map results; + std::map carrys; + + RooArgList _vars; // Variables + RooArgList _saveVars; // Copy of variables + + LikelihoodType likelihood_type; + std::size_t N_tasks_at_workers = 0; +}; + +} +} + +#endif // ROOT_ROOFIT_LikelihoodJob diff --git a/roofit/roofitcore/inc/TestStatistics/LikelihoodSerial.h b/roofit/roofitcore/inc/TestStatistics/LikelihoodSerial.h new file mode 100644 index 0000000000000..fc96ed060467a --- /dev/null +++ b/roofit/roofitcore/inc/TestStatistics/LikelihoodSerial.h @@ -0,0 +1,53 @@ +/* + * Project: RooFit + * Authors: + * PB, Patrick Bos, Netherlands eScience Center, p.bos@esciencecenter.nl + * + * Copyright (c) 2016-2020, Netherlands eScience Center + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +#ifndef ROOT_ROOFIT_LikelihoodSerial +#define ROOT_ROOFIT_LikelihoodSerial + +#include + +#include "Math/MinimizerOptions.h" +#include + +#include "RooArgList.h" + +namespace RooFit { +namespace TestStatistics { + +class LikelihoodSerial : public LikelihoodWrapper { +public: + LikelihoodSerial(std::shared_ptr likelihood, std::shared_ptr calculation_is_clean/*, RooMinimizer *minimizer*/); + LikelihoodSerial* clone() const override; + + void init_vars(); + + // TODO: implement override if necessary +// void synchronize_with_minimizer(const ROOT::Math::MinimizerOptions & options) override; + + void evaluate() override; + double return_result() const override; + +private: + double result = 0; + double carry = 0; + + RooArgList _vars; // Variables + RooArgList _saveVars; // Copy of variables + + LikelihoodType likelihood_type; +}; + +} +} + +#endif // ROOT_ROOFIT_LikelihoodSerial diff --git a/roofit/roofitcore/inc/TestStatistics/LikelihoodWrapper.h b/roofit/roofitcore/inc/TestStatistics/LikelihoodWrapper.h new file mode 100644 index 0000000000000..9ed4da3dbd26c --- /dev/null +++ b/roofit/roofitcore/inc/TestStatistics/LikelihoodWrapper.h @@ -0,0 +1,101 @@ +/* + * Project: RooFit + * Authors: + * PB, Patrick Bos, Netherlands eScience Center, p.bos@esciencecenter.nl + * + * Copyright (c) 2016-2020, Netherlands eScience Center + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +#ifndef ROOT_ROOFIT_TESTSTATISTICS_LikelihoodWrapper +#define ROOT_ROOFIT_TESTSTATISTICS_LikelihoodWrapper + +#include // shared_ptr +#include +#include +#include "Math/MinimizerOptions.h" +#include "RooArgSet.h" +#include "RooAbsArg.h" // enum ConstOpCode + +// forward declaration +class RooMinimizer; + +namespace RooFit { +namespace TestStatistics { + +// forward declaration +class RooAbsL; +struct WrapperCalculationCleanFlags; + +enum class LikelihoodType { + unbinned, + binned, + subsidiary, + sum +}; + +// Previously, offsetting was only implemented for RooNLLVar components of a likelihood, +// not for RooConstraintSum terms. To emulate this behavior, use OffsettingMode::legacy. To +// also offset the RooSubsidiaryL component (equivalent of RooConstraintSum) of RooSumL +// likelihoods, use OffsettingMode::full. +enum class OffsettingMode { + legacy, + full +}; + +class LikelihoodWrapper { +public: + LikelihoodWrapper(std::shared_ptr likelihood, std::shared_ptr calculation_is_clean/*, RooMinimizer* minimizer*/); + virtual ~LikelihoodWrapper() = default; + virtual LikelihoodWrapper* clone() const = 0; + + virtual void evaluate() = 0; + virtual double return_result() const = 0; + + // synchronize minimizer settings with calculators in child classes + virtual void synchronize_with_minimizer(const ROOT::Math::MinimizerOptions & options); + virtual void synchronize_parameter_settings(const std::vector ¶meter_settings); + // Minuit passes in parameter values that may not conform to RooFit internal standards (like applying range clipping), + // but that the specific calculator does need. This function can be implemented to receive these Minuit-internal values: + virtual void update_minuit_internal_parameter_values(const std::vector& minuit_internal_x); + virtual void update_minuit_external_parameter_values(const std::vector& minuit_external_x); + + // necessary from MinuitFcnGrad to reach likelihood properties: + void constOptimizeTestStatistic(RooAbsArg::ConstOpCode opcode, bool doAlsoTrackingOpt); + + double defaultErrorLevel() const; + + virtual std::string GetName() const; + virtual std::string GetTitle() const; + + virtual bool is_offsetting() const; + virtual void enable_offsetting(bool flag); + void set_offsetting_mode(OffsettingMode mode); + double offset(); + double offset_carry(); + void set_apply_weight_squared(bool flag); + +protected: + std::shared_ptr likelihood_; +// RooMinimizer* minimizer_; +// RooAbsMinimizerFcn* minimizer_fcn_; + std::shared_ptr calculation_is_clean_; + + bool do_offset_ = false; + double offset_ = 0; + double offset_carry_ = 0; + double offset_save_ = 0; //! + double offset_carry_save_ = 0; //! + OffsettingMode offsetting_mode_ = OffsettingMode::legacy; + void apply_offsetting(double ¤t_value, double &carry); + void swap_offsets(); +}; + +} +} + +#endif // ROOT_ROOFIT_TESTSTATISTICS_LikelihoodWrapper diff --git a/roofit/roofitcore/inc/TestStatistics/MinuitFcnGrad.h b/roofit/roofitcore/inc/TestStatistics/MinuitFcnGrad.h new file mode 100644 index 0000000000000..ad7fdb6b7dfc3 --- /dev/null +++ b/roofit/roofitcore/inc/TestStatistics/MinuitFcnGrad.h @@ -0,0 +1,168 @@ +/* + * Project: RooFit + * Authors: + * PB, Patrick Bos, Netherlands eScience Center, p.bos@esciencecenter.nl + * + * Copyright (c) 2016-2020, Netherlands eScience Center + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +#ifndef ROOT_ROOFIT_TESTSTATISTICS_MinuitFcnGrad +#define ROOT_ROOFIT_TESTSTATISTICS_MinuitFcnGrad + +#include +#include "ROOT/RMakeUnique.hxx" +#include "Math/IFunction.h" // ROOT::Math::IMultiGradFunction +#include "RooArgList.h" +#include "RooRealVar.h" +#include "TestStatistics/RooAbsL.h" +#include "TestStatistics/LikelihoodWrapper.h" +#include "TestStatistics/LikelihoodGradientWrapper.h" +#include "TestStatistics/LikelihoodJob.h" +#include "TestStatistics/LikelihoodGradientJob.h" +#include "RooAbsMinimizerFcn.h" + +// forward declaration +class RooAbsReal; +class RooMinimizer; + +namespace RooFit { +namespace TestStatistics { + +// -- for communication with wrappers: -- +struct WrapperCalculationCleanFlags { + // indicate whether that part has been calculated since the last parameter update + bool likelihood = false; + bool gradient = false; + bool g2 = false; + bool gstep = false; + + void set_all(bool value) { + likelihood = value; + gradient = value; + g2 = value; + gstep = value; + } +}; + +class MinuitFcnGrad : public ROOT::Math::IMultiGradFunction, public RooAbsMinimizerFcn { +public: + // factory + template + static MinuitFcnGrad *create(const std::shared_ptr &likelihood, + RooMinimizer *context, bool verbose = false); + +// MinuitFcnGrad(const MinuitFcnGrad &other); + ROOT::Math::IMultiGradFunction *Clone() const override; + + // override to include gradient strategy synchronization: + Bool_t Synchronize(std::vector ¶meter_settings, Bool_t optConst, + Bool_t verbose = kFALSE) override; + + // used inside Minuit: + bool returnsInMinuit2ParameterSpace() const override; + + void setOptimizeConst(Int_t flag) override; + +private: + // IMultiGradFunction overrides necessary for Minuit: DoEval, Gradient, (has)G2ndDerivative and (has)GStepSize + double DoEval(const double *x) const override; + +public: + void Gradient(const double *x, double *grad) const override; + void G2ndDerivative(const double *x, double *g2) const override; + void GStepSize(const double *x, double *gstep) const override; + bool hasG2ndDerivative() const override; + bool hasGStepSize() const override; + + // part of IMultiGradFunction interface, used widely both in Minuit and in RooFit: + unsigned int NDim() const override; + + std::string getFunctionName() const override; + std::string getFunctionTitle() const override; + + void enable_likelihood_offsetting(bool flag); + +private: + template + MinuitFcnGrad(const std::shared_ptr &_likelihood, RooMinimizer *context, + bool verbose, + LikelihoodWrapperT * /* used only for template deduction */ = + static_cast(nullptr), + LikelihoodGradientWrapperT * /* used only for template deduction */ = + static_cast(nullptr)); + + // The following three overrides will not actually be used in this class, so they will throw: + double DoDerivative(const double *x, unsigned int icoord) const override; + double DoSecondDerivative(const double * /*x*/, unsigned int /*icoord*/) const override; + double DoStepSize(const double * /*x*/, unsigned int /*icoord*/) const override; + + void optimizeConstantTerms(bool constStatChange, bool constValChange) override; + + bool sync_parameter_values_from_minuit_calls(const double *x, bool minuit_internal) const; + + // members + std::shared_ptr likelihood; + std::shared_ptr gradient; + +public: + mutable std::shared_ptr calculation_is_clean; +private: + mutable std::vector minuit_internal_x_; + mutable std::vector minuit_external_x_; +public: + mutable bool minuit_internal_roofit_x_mismatch_ = false; +}; + +} // namespace TestStatistics +} // namespace RooFit + + +// include here to avoid circular dependency issues in class definitions +#include "RooMinimizer.h" + + +namespace RooFit { +namespace TestStatistics { + +template +MinuitFcnGrad::MinuitFcnGrad(const std::shared_ptr &_likelihood, RooMinimizer *context, + bool verbose, LikelihoodWrapperT * /* value unused */, + LikelihoodGradientWrapperT * /* value unused */) + : RooAbsMinimizerFcn(RooArgList(*_likelihood->getParameters()), context, verbose), minuit_internal_x_(NDim(), 0), + minuit_external_x_(NDim(), 0) +{ + auto parameters = _context->fitter()->Config().ParamsSettings(); + synchronize_parameter_settings(parameters, kTRUE, verbose); + + calculation_is_clean = std::make_shared(); + likelihood = std::make_shared(_likelihood, calculation_is_clean/*, _context*/); + gradient = std::make_shared(_likelihood, calculation_is_clean, get_nDim(), _context); + + likelihood->synchronize_parameter_settings(parameters); + gradient->synchronize_parameter_settings(this, parameters); + + // Note: can be different than RooGradMinimizerFcn, where default options are passed (ROOT::Math::MinimizerOptions::DefaultStrategy() and ROOT::Math::MinimizerOptions::DefaultErrorDef()) + likelihood->synchronize_with_minimizer(ROOT::Math::MinimizerOptions()); + gradient->synchronize_with_minimizer(ROOT::Math::MinimizerOptions()); +} + +// static function +template +MinuitFcnGrad *MinuitFcnGrad::create(const std::shared_ptr& likelihood, + RooMinimizer *context, bool verbose) +{ + return new MinuitFcnGrad(likelihood, context, verbose, static_cast(nullptr), + static_cast(nullptr)); +} + +} // namespace TestStatistics +} // namespace RooFit + +#endif // ROOT_ROOFIT_TESTSTATISTICS_MinuitFcnGrad diff --git a/roofit/roofitcore/inc/TestStatistics/RooAbsL.h b/roofit/roofitcore/inc/TestStatistics/RooAbsL.h new file mode 100644 index 0000000000000..13bfd87034070 --- /dev/null +++ b/roofit/roofitcore/inc/TestStatistics/RooAbsL.h @@ -0,0 +1,132 @@ +/* + * Project: RooFit + * Authors: + * PB, Patrick Bos, Netherlands eScience Center, p.bos@esciencecenter.nl + * + * Copyright (c) 2016-2020, Netherlands eScience Center + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +#ifndef ROOT_ROOFIT_TESTSTATISTICS_RooAbsL +#define ROOT_ROOFIT_TESTSTATISTICS_RooAbsL + +#include // std::size_t +#include +#include "RooArgSet.h" +#include "RooAbsArg.h" // enum ConstOpCode + +// forward declarations +class RooAbsPdf; +class RooAbsData; + +namespace RooFit { +namespace TestStatistics { + +class RooAbsL { +public: + enum class Extended { + Auto, Yes, No + }; + static bool is_extended(RooAbsPdf* pdf, Extended extended); + + /// wrapper class used to distinguish ctors + struct ClonePdfData { + RooAbsPdf * pdf; + RooAbsData * data; + }; + +// RooAbsL() = default; +private: + RooAbsL(std::shared_ptr pdf, std::shared_ptr data, + std::size_t N_events, std::size_t N_components, Extended extended); + +public: + RooAbsL(RooAbsPdf *pdf, RooAbsData *data, std::size_t N_events, + std::size_t N_components, Extended extended = Extended::Auto); + RooAbsL(ClonePdfData in, std::size_t N_events, + std::size_t N_components, Extended extended = Extended::Auto); + RooAbsL(const RooAbsL& other); + virtual ~RooAbsL() = default; + + void init_clones(RooAbsPdf& inpdf, RooAbsData& indata); + + /// A part of some range delimited by two fractional points between 0 and 1 (inclusive). + struct Section { + Section(double begin, double end) : begin_fraction(begin), end_fraction(end) + { + if ((begin > end) || (begin < 0) || (end > 1)) { + throw std::domain_error("Invalid input values for section; begin must be >= 0, end <= 1 and begin < end."); + } + } + + Section(const Section & section) = default; + + std::size_t begin(std::size_t N_total) const { + return static_cast(N_total * begin_fraction); + } + + std::size_t end(std::size_t N_total) const { + if (end_fraction == 1) { + return N_total; + } else { + return static_cast(N_total * end_fraction); + } + } + + double begin_fraction; + double end_fraction; + }; + + virtual double evaluate_partition(Section events, std::size_t components_begin, std::size_t components_end) = 0; + double get_carry() const; + + // necessary from MinuitFcnGrad to reach likelihood properties: + virtual RooArgSet *getParameters(); + virtual void constOptimizeTestStatistic(RooAbsArg::ConstOpCode opcode, bool doAlsoTrackingOpt); + + virtual std::string GetName() const; + virtual std::string GetTitle() const; + + // necessary in RooMinimizer (via LikelihoodWrapper) + virtual double defaultErrorLevel() const; + + // necessary in LikelihoodJob + virtual std::size_t numDataEntries() const; + + std::size_t get_N_events() const; + std::size_t get_N_components() const; + + bool is_extended() const; + + void set_sim_count(std::size_t value); + +protected: + virtual void optimize_pdf(); + // Note: pdf_ and data_ can be constructed in two ways, one of which implies ownership and the other does not. + // Inspired by this: https://stackoverflow.com/a/61227865/1199693. + // The owning variant is used for classes that need a pdf/data clone (RooBinnedL and RooUnbinnedL), whereas the + // non-owning version is used for when a reference to the external pdf/dataset is good enough (RooSumL). + // This means that pdf_ and data_ are not meant to actually be shared! If there were a unique_ptr with optional + // ownership, we would have used that instead. + std::shared_ptr pdf_; + std::shared_ptr data_; + std::unique_ptr _normSet; // Pointer to set with observables used for normalization + + std::size_t N_events = 1; + std::size_t N_components = 1; + + bool extended_ = false; + + std::size_t sim_count_ = 1; // Total number of component p.d.f.s in RooSimultaneous (if any) + + mutable double eval_carry_ = 0; //! carry of Kahan sum in evaluatePartition +}; + +} // namespace TestStatistics +} // namespace RooFit + +#endif // ROOT_ROOFIT_TESTSTATISTICS_RooAbsL diff --git a/roofit/roofitcore/inc/TestStatistics/RooBinnedL.h b/roofit/roofitcore/inc/TestStatistics/RooBinnedL.h new file mode 100644 index 0000000000000..ced439de90b98 --- /dev/null +++ b/roofit/roofitcore/inc/TestStatistics/RooBinnedL.h @@ -0,0 +1,47 @@ +/***************************************************************************** + * Project: RooFit * + * Package: RooFitCore * + * File: $Id: RooBinnedL.h,v 1.10 2007/07/21 21:32:52 wouter Exp $ + * Authors: * + * WV, Wouter Verkerke, UC Santa Barbara, verkerke@slac.stanford.edu * + * DK, David Kirkby, UC Irvine, dkirkby@uci.edu * + * PB, Patrick Bos, NL eScience Center, p.bos@esciencecenter.nl * + * * + * Copyright (c) 2000-2020, Regents of the University of California * + * and Stanford University. All rights reserved. * + * * + * Redistribution and use in source and binary forms, * + * with or without modification, are permitted according to the terms * + * listed in LICENSE (http://roofit.sourceforge.net/license.txt) * + *****************************************************************************/ + +#ifndef ROOT_ROOFIT_TESTSTATISTICS_RooBinnedL +#define ROOT_ROOFIT_TESTSTATISTICS_RooBinnedL + +#include "RooAbsReal.h" +#include + +#include + +// forward declarations +class RooAbsPdf; +class RooAbsData; + +namespace RooFit { +namespace TestStatistics { + +class RooBinnedL : + public RooAbsL { +public: + RooBinnedL(RooAbsPdf* pdf, RooAbsData* data); + double evaluate_partition(Section bins, std::size_t components_begin, + std::size_t components_end) override; +private: + mutable bool _first = true; //! + mutable std::vector _binw; //! +}; + +} // namespace TestStatistics +} // namespace RooFit + +#endif // ROOT_ROOFIT_TESTSTATISTICS_RooBinnedL diff --git a/roofit/roofitcore/inc/TestStatistics/RooRealL.h b/roofit/roofitcore/inc/TestStatistics/RooRealL.h new file mode 100644 index 0000000000000..65bd40ec40ed0 --- /dev/null +++ b/roofit/roofitcore/inc/TestStatistics/RooRealL.h @@ -0,0 +1,53 @@ +/* + * Project: RooFit + * Authors: + * PB, Patrick Bos, Netherlands eScience Center, p.bos@esciencecenter.nl + * + * Copyright (c) 2016-2020, Netherlands eScience Center + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +#ifndef ROOT_ROOFIT_TESTSTATISTICS_RooRealL +#define ROOT_ROOFIT_TESTSTATISTICS_RooRealL + +#include "Rtypes.h" // ClassDef, ClassImp +#include // shared_ptr +#include +#include "RooSetProxy.h" + +namespace RooFit { +namespace TestStatistics { + +// forward declaration +class RooAbsL; + +class RooRealL : public RooAbsReal { +public: + RooRealL(const char *name, const char *title, std::shared_ptr likelihood); + RooRealL(const RooRealL& other, const char* name=0); + + // pure virtual overrides: + Double_t evaluate() const override; + TObject* clone(const char* newname) const override; + // virtual overrides: + double globalNormalization() const; + + double get_carry() const; + + Double_t defaultErrorLevel() const override; +private: + std::shared_ptr likelihood_; + mutable double eval_carry = 0; + RooSetProxy vars_proxy_; // sets up client-server connections + + ClassDefOverride(RooRealL, 0); +}; + +} +} + +#endif // ROOT_ROOFIT_TESTSTATISTICS_RooRealL diff --git a/roofit/roofitcore/inc/TestStatistics/RooSubsidiaryL.h b/roofit/roofitcore/inc/TestStatistics/RooSubsidiaryL.h new file mode 100644 index 0000000000000..396ec313792ee --- /dev/null +++ b/roofit/roofitcore/inc/TestStatistics/RooSubsidiaryL.h @@ -0,0 +1,52 @@ +/* + * Project: RooFit + * Authors: + * PB, Patrick Bos, Netherlands eScience Center, p.bos@esciencecenter.nl + * + * Copyright (c) 2016-2020, Netherlands eScience Center + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +#ifndef ROOT_ROOFIT_TESTSTATISTICS_RooSubsidiaryL +#define ROOT_ROOFIT_TESTSTATISTICS_RooSubsidiaryL + +#include +#include +#include + +namespace RooFit { +namespace TestStatistics { + +/// Gathers all subsidiary PDF terms from the component PDFs of RooSumL likelihoods. +/// These are summed separately for increased numerical stability, since these terms are often +/// small and cause numerical variances in their original PDFs, whereas by summing as one +/// separate subsidiary collective term, it is numerically very stable. +/// Note that when a subsidiary PDF is part of multiple component PDFs, it will only be summed +/// once in this class! This doesn't change the derivative of the log likelihood (which is what +/// matters in fitting the likelihood), but does change the value of the (log-)likelihood itself. +class RooSubsidiaryL : public RooAbsL { +public: + RooSubsidiaryL(const std::string & parent_pdf_name, const RooArgSet & pdfs, const RooArgSet & parameter_set); + + double evaluate_partition(Section events, std::size_t components_begin, std::size_t components_end) override; + RooArgSet * getParameters() override; + std::string GetName() const override; + std::string GetTitle() const override; + std::size_t numDataEntries() const override; + + void constOptimizeTestStatistic(RooAbsArg::ConstOpCode opcode, bool doAlsoTrackingOpt) override; + +private: + std::string parent_pdf_name_; + RooArgList subsidiary_pdfs_{"subsidiary_pdfs"}; // Set of subsidiary PDF or "constraint" terms + RooArgSet parameter_set_{"parameter_set"}; // Set of parameters to which constraints apply +}; + +} +} + +#endif // ROOT_ROOFIT_TESTSTATISTICS_RooSubsidiaryL diff --git a/roofit/roofitcore/inc/TestStatistics/RooSumL.h b/roofit/roofitcore/inc/TestStatistics/RooSumL.h new file mode 100644 index 0000000000000..9bf1ae041e3d6 --- /dev/null +++ b/roofit/roofitcore/inc/TestStatistics/RooSumL.h @@ -0,0 +1,47 @@ +/* + * Project: RooFit + * Authors: + * PB, Patrick Bos, Netherlands eScience Center, p.bos@esciencecenter.nl + * + * Copyright (c) 2016-2020, Netherlands eScience Center + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +#ifndef ROOT_ROOFIT_TESTSTATISTICS_RooSumL +#define ROOT_ROOFIT_TESTSTATISTICS_RooSumL + +#include +#include + +namespace RooFit { +namespace TestStatistics { + +class RooSumL : public RooAbsL { +public: + // main constructor + RooSumL(RooAbsPdf* pdf, RooAbsData* data, std::vector> components, + RooAbsL::Extended extended = RooAbsL::Extended::Auto); + // Note: when above ctor is called without std::moving components, you get a really obscure error. Pass as std::move(components)! + + double evaluate_partition(Section events, std::size_t components_begin, + std::size_t components_end) override; + + // necessary only for legacy offsetting mode in LikelihoodWrapper; TODO: remove this if legacy mode is ever removed + std::tuple get_subsidiary_value(); + + void constOptimizeTestStatistic(RooAbsArg::ConstOpCode opcode, bool doAlsoTrackingOpt) override; + +private: + bool processEmptyDataSets() const; + + std::vector> components_; +}; + +} +} + +#endif // ROOT_ROOFIT_TESTSTATISTICS_RooSumL diff --git a/roofit/roofitcore/inc/TestStatistics/RooUnbinnedL.h b/roofit/roofitcore/inc/TestStatistics/RooUnbinnedL.h new file mode 100644 index 0000000000000..36111d6ee3c59 --- /dev/null +++ b/roofit/roofitcore/inc/TestStatistics/RooUnbinnedL.h @@ -0,0 +1,50 @@ +/***************************************************************************** + * Project: RooFit * + * Package: RooFitCore * + * File: $Id: RooUnbinnedL.h,v 1.10 2007/07/21 21:32:52 wouter Exp $ + * Authors: * + * WV, Wouter Verkerke, UC Santa Barbara, verkerke@slac.stanford.edu * + * DK, David Kirkby, UC Irvine, dkirkby@uci.edu * + * PB, Patrick Bos, NL eScience Center, p.bos@esciencecenter.nl * + * * + * Copyright (c) 2000-2020, Regents of the University of California * + * and Stanford University. All rights reserved. * + * * + * Redistribution and use in source and binary forms, * + * with or without modification, are permitted according to the terms * + * listed in LICENSE (http://roofit.sourceforge.net/license.txt) * + *****************************************************************************/ + +#ifndef ROOT_ROOFIT_TESTSTATISTICS_RooUnbinnedL +#define ROOT_ROOFIT_TESTSTATISTICS_RooUnbinnedL + +#include + +// forward declarations +class RooAbsPdf; +class RooAbsData; +class RooArgSet; + +namespace RooFit { +namespace TestStatistics { + +class RooUnbinnedL : + public RooAbsL { +public: + RooUnbinnedL(RooAbsPdf* pdf, RooAbsData* data, RooAbsL::Extended extended = RooAbsL::Extended::Auto); + RooUnbinnedL(const RooUnbinnedL &other); + bool set_apply_weight_squared(bool flag); + + double evaluate_partition(Section events, std::size_t components_begin, + std::size_t components_end) override; + +private: + bool processEmptyDataSets() const; + bool apply_weight_squared = false; // Apply weights squared? + mutable bool _first = true; //! +}; + +} // namespace TestStatistics +} // namespace RooFit + +#endif // ROOT_ROOFIT_TESTSTATISTICS_RooUnbinnedL diff --git a/roofit/roofitcore/inc/TestStatistics/kahan_sum.h b/roofit/roofitcore/inc/TestStatistics/kahan_sum.h new file mode 100644 index 0000000000000..b6e3b695c503a --- /dev/null +++ b/roofit/roofitcore/inc/TestStatistics/kahan_sum.h @@ -0,0 +1,86 @@ +/* + * Project: RooFit + * Authors: + * PB, Patrick Bos, Netherlands eScience Center, p.bos@esciencecenter.nl + * + * Copyright (c) 2016-2020, Netherlands eScience Center + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ + +// --- kahan summation templates --- + +#ifndef ROOT_ROOFIT_kahan_sum +#define ROOT_ROOFIT_kahan_sum + +#include + +namespace RooFit { + +template +typename C::value_type sum_kahan(const C& container) { + using ValueType = typename C::value_type; + ValueType sum = 0, carry = 0; + for (auto element : container) { + ValueType y = element - carry; + ValueType t = sum + y; + carry = (t - sum) - y; + sum = t; + } + return sum; +} + +template +ValueType sum_kahan(const std::map& map) { + ValueType sum = 0, carry = 0; + for (auto const& element : map) { + ValueType y = element.second - carry; + ValueType t = sum + y; + carry = (t - sum) - y; + sum = t; + } + return sum; +} + +template +std::pair sum_of_kahan_sums(const C& sum_values, const C& sum_carrys) { + using ValueType = typename C::value_type; + ValueType sum = 0, carry = 0; + for (std::size_t ix = 0; ix < sum_values.size(); ++ix) { + ValueType y = sum_values[ix]; + carry += sum_carrys[ix]; + y -= carry; + const ValueType t = sum + y; + carry = (t - sum) - y; + sum = t; + } + return std::pair(sum, carry); +} + + +template +std::pair sum_of_kahan_sums(const std::map& sum_values, const std::map& sum_carrys) { + ValueType sum = 0, carry = 0; + assert(sum_values.size() == sum_carrys.size()); + auto it_values = sum_values.cbegin(); + auto it_carrys = sum_carrys.cbegin(); + for (; it_values != sum_values.cend(); ++it_values, ++it_carrys) { + ValueType y = it_values->second; + carry += it_carrys->second; + y -= carry; + const ValueType t = sum + y; + carry = (t - sum) - y; + sum = t; + } + return std::pair(sum, carry); +} + +std::tuple kahan_add(double sum, double additive, double carry); + +} + +#endif // ROOT_ROOFIT_kahan_sum diff --git a/roofit/roofitcore/inc/TestStatistics/likelihood_builders.h b/roofit/roofitcore/inc/TestStatistics/likelihood_builders.h new file mode 100644 index 0000000000000..faccc96aa3ae6 --- /dev/null +++ b/roofit/roofitcore/inc/TestStatistics/likelihood_builders.h @@ -0,0 +1,51 @@ +/* + * Project: RooFit + * Authors: + * PB, Patrick Bos, Netherlands eScience Center, p.bos@esciencecenter.nl + * + * Copyright (c) 2016-2021, Netherlands eScience Center + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +#ifndef ROOT_ROOFIT_TESTSTATISTICS_likelihood_builders +#define ROOT_ROOFIT_TESTSTATISTICS_likelihood_builders + +#include +#include + +// forward declarations +class RooAbsPdf; +class RooAbsData; + +namespace RooFit { +namespace TestStatistics { + +std::shared_ptr build_simultaneous_likelihood(RooAbsPdf *pdf, RooAbsData *data, RooAbsL::Extended extended = RooAbsL::Extended::Auto, + ConstrainedParameters constrained_parameters = {}, ExternalConstraints external_constraints = {}, + GlobalObservables global_observables = {}, std::string global_observables_tag = {}); + +// delegating builder calls, for more convenient "optional" parameter passing +std::shared_ptr build_simultaneous_likelihood(RooAbsPdf* pdf, RooAbsData* data, ConstrainedParameters constrained_parameters); +std::shared_ptr build_simultaneous_likelihood(RooAbsPdf* pdf, RooAbsData* data, ExternalConstraints external_constraints); +std::shared_ptr build_simultaneous_likelihood(RooAbsPdf* pdf, RooAbsData* data, GlobalObservables global_observables); +std::shared_ptr build_simultaneous_likelihood(RooAbsPdf* pdf, RooAbsData* data, std::string global_observables_tag); +std::shared_ptr build_simultaneous_likelihood(RooAbsPdf* pdf, RooAbsData* data, ConstrainedParameters constrained_parameters, GlobalObservables global_observables); + +std::shared_ptr build_unbinned_constrained_likelihood(RooAbsPdf *pdf, RooAbsData *data, RooAbsL::Extended extended = RooAbsL::Extended::Auto, + ConstrainedParameters constrained_parameters = {}, ExternalConstraints external_constraints = {}, + GlobalObservables global_observables = {}, std::string global_observables_tag = {}); + +// delegating builder calls, for more convenient "optional" parameter passing +std::shared_ptr build_unbinned_constrained_likelihood(RooAbsPdf* pdf, RooAbsData* data, ConstrainedParameters constrained_parameters); +std::shared_ptr build_unbinned_constrained_likelihood(RooAbsPdf* pdf, RooAbsData* data, ExternalConstraints external_constraints); +std::shared_ptr build_unbinned_constrained_likelihood(RooAbsPdf* pdf, RooAbsData* data, GlobalObservables global_observables); +std::shared_ptr build_unbinned_constrained_likelihood(RooAbsPdf* pdf, RooAbsData* data, std::string global_observables_tag); + +} +} + +#endif // ROOT_ROOFIT_TESTSTATISTICS_likelihood_builders diff --git a/roofit/roofitcore/inc/TestStatistics/optimization.h b/roofit/roofitcore/inc/TestStatistics/optimization.h new file mode 100644 index 0000000000000..df2d90d241b32 --- /dev/null +++ b/roofit/roofitcore/inc/TestStatistics/optimization.h @@ -0,0 +1,37 @@ +/* + * Project: RooFit + * Authors: + * PB, Patrick Bos, Netherlands eScience Center, p.bos@esciencecenter.nl + * + * Copyright (c) 2016-2021, Netherlands eScience Center + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +#ifndef ROOT_ROOFIT_TESTSTATISTICS_optimization +#define ROOT_ROOFIT_TESTSTATISTICS_optimization + +// forward declarations +class RooAbsReal; +class RooArgSet; +class RooAbsData; + +namespace RooFit { +namespace TestStatistics { + +// this is a class only for convenience: it saves multiple friend definitions in RooAbsData for otherwise free functions +struct ConstantTermsOptimizer { + static void enable_constant_terms_optimization(RooAbsReal *function, RooArgSet *norm_set, RooAbsData *dataset, + bool applyTrackingOpt); + static void optimize_caching(RooAbsReal *function, RooArgSet *norm_set, RooArgSet* observables, RooAbsData *dataset); + static void disable_constant_terms_optimization(RooAbsReal *function, RooArgSet *norm_set, RooArgSet* observables, RooAbsData *dataset); + static RooArgSet requiredExtraObservables(); +}; + +} +} + +#endif // ROOT_ROOFIT_TESTSTATISTICS_optimization diff --git a/roofit/roofitcore/inc/TestStatistics/optional_parameter_types.h b/roofit/roofitcore/inc/TestStatistics/optional_parameter_types.h new file mode 100644 index 0000000000000..ff1b2fc860c47 --- /dev/null +++ b/roofit/roofitcore/inc/TestStatistics/optional_parameter_types.h @@ -0,0 +1,45 @@ +/* + * Project: RooFit + * Authors: + * PB, Patrick Bos, Netherlands eScience Center, p.bos@esciencecenter.nl + * + * Copyright (c) 2016-2021, Netherlands eScience Center + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +#ifndef ROOT_ROOFIT_TESTSTATISTICS_optional_parameter_types +#define ROOT_ROOFIT_TESTSTATISTICS_optional_parameter_types + +#include + +namespace RooFit { +namespace TestStatistics { + +// strongly named container types for use as optional parameters in the test statistics constructors + +struct ConstrainedParameters { + ConstrainedParameters() = default; + explicit ConstrainedParameters(const RooArgSet ¶meters); + RooArgSet set; +}; + +struct ExternalConstraints { + ExternalConstraints() = default; + explicit ExternalConstraints(const RooArgSet &constraints); + RooArgSet set; +}; + +struct GlobalObservables { + GlobalObservables() = default; + explicit GlobalObservables(const RooArgSet &observables); + RooArgSet set; +}; + +} +} + +#endif // ROOT_ROOFIT_TESTSTATISTICS_optional_parameter_types diff --git a/roofit/roofitcore/src/BidirMMapPipe.cxx b/roofit/roofitcore/src/MultiProcess/BidirMMapPipe.cxx similarity index 94% rename from roofit/roofitcore/src/BidirMMapPipe.cxx rename to roofit/roofitcore/src/MultiProcess/BidirMMapPipe.cxx index ee942b421eecf..a08f51aef370e 100644 --- a/roofit/roofitcore/src/BidirMMapPipe.cxx +++ b/roofit/roofitcore/src/MultiProcess/BidirMMapPipe.cxx @@ -4,7 +4,9 @@ * and serves as communications channel between parent and child * * @author Manuel Schiller - * @date 2013-07-07 + * @author Patrick Bos + * @author Inti Pelupessy + * @date 2013-2018 */ #ifndef _WIN32 #include @@ -28,7 +30,8 @@ #include #include -#include "BidirMMapPipe.h" +#include +#include #define BEGIN_NAMESPACE_ROOFIT namespace RooFit { #define END_NAMESPACE_ROOFIT } @@ -37,33 +40,16 @@ BEGIN_NAMESPACE_ROOFIT /// namespace for implementation details of BidirMMapPipe namespace BidirMMapPipe_impl { - /** @brief exception to throw if low-level OS calls go wrong - * - * @author Manuel Schiller - * @date 2013-07-07 - */ - class BidirMMapPipeException : public std::exception - { - private: - enum { - s_sz = 256 ///< length of buffer - }; - char m_buf[s_sz]; ///< buffer containing the error message - - /// for the POSIX version of strerror_r - static int dostrerror_r(int err, char* buf, std::size_t sz, - int (*f)(int, char*, std::size_t)) - { return f(err, buf, sz); } - /// for the GNU version of strerror_r - static int dostrerror_r(int, char*, std::size_t, - char* (*f)(int, char*, std::size_t)); - public: - /// constructor taking error code, hint on operation (msg) - BidirMMapPipeException(const char* msg, int err); - /// return a destcription of what went wrong - virtual const char* what() const noexcept { return m_buf; } - }; - + // static function: + int BidirMMapPipeException::dostrerror_r(int err, char* buf, std::size_t sz, + int (*f)(int, char*, std::size_t)) { + return f(err, buf, sz); + } + + const char* BidirMMapPipeException::what() const noexcept { + return m_buf; + } + BidirMMapPipeException::BidirMMapPipeException(const char* msg, int err) { std::size_t msgsz = std::strlen(msg); @@ -240,6 +226,7 @@ namespace BidirMMapPipe_impl { /// zap the pool (unmap all but Pages p) void zap(Pages& p); + void clear_freelist(); private: /// list of chunks used by the pool @@ -624,6 +611,20 @@ namespace BidirMMapPipe_impl { m_cursz = minsz; } + void PagePool::clear_freelist() + { +// m_freelist.clear(); + for (auto chunk_it = m_freelist.begin(); chunk_it != m_freelist.end();) { + // clear only the chunks which are fully unused + if ( (*chunk_it)->empty()) { + chunk_it = m_freelist.erase(chunk_it); + } else { + ++chunk_it; + } + } + + } + Pages PagePool::pop() { if (m_freelist.empty()) { @@ -649,8 +650,10 @@ namespace BidirMMapPipe_impl { // find chunk on freelist and remove ChunkList::iterator it = std::find( m_freelist.begin(), m_freelist.end(), chunk); + std::stringstream exception_message; + exception_message << "PagePool::release(PageChunk*) on PID " << getpid(); if (m_freelist.end() == it) - throw Exception("PagePool::release(PageChunk*)", EINVAL); + throw Exception(exception_message.str().c_str(), EINVAL); m_freelist.erase(it); // find chunk in m_chunks and remove it = std::find(m_chunks.begin(), m_chunks.end(), chunk); @@ -717,8 +720,9 @@ int BidirMMapPipe::s_debugflag = 0; BidirMMapPipe_impl::PagePool& BidirMMapPipe::pagepool() { - if (!s_pagepool) + if (!s_pagepool) { s_pagepool = new BidirMMapPipe_impl::PagePool(TotPages); + } return *s_pagepool; } @@ -746,11 +750,15 @@ BidirMMapPipe::BidirMMapPipe(const BidirMMapPipe&) : } } -BidirMMapPipe::BidirMMapPipe(bool useExceptions, bool useSocketpair) : + +// When creating with keepLocal, the master (which loses its bipe pointers to +// the child) must manually wait for its children after they are closed! +// Otherwise zombies are created. The static function wait_for_child can be +// used for this by master. +BidirMMapPipe::BidirMMapPipe(bool useExceptions, bool useSocketpair, bool keepLocal) : m_pages(pagepool().pop()), m_busylist(0), m_freelist(0), m_dirtylist(0), m_inpipe(-1), m_outpipe(-1), m_flags(failbit), m_childPid(0), - m_parentPid(::getpid()) - + m_parentPid(::getpid()), kept_local(keepLocal) { ++s_pagepoolrefcnt; assert(0 < TotPages && 0 == (TotPages & 1) && TotPages <= 256); @@ -783,7 +791,14 @@ BidirMMapPipe::BidirMMapPipe(bool useExceptions, bool useSocketpair) : // fork the child pthread_mutex_lock(&s_openpipesmutex); char c; - switch ((m_childPid = ::fork())) { + m_childPid = ::fork(); +//#ifndef NDEBUG +// if (m_childPid == 0) { +// std::cerr << "ignoring SIGTRAP in child process PID " << getpid() << " to allow debugger breakpoints in parent; signal(SIGTRAP, SIG_IGN) return code: " << signal(SIGTRAP, SIG_IGN) << std::endl; +// sleep(10); +// } +//#endif // NDEBUG + switch (m_childPid) { case -1: // error in fork() myerrno = errno; pthread_mutex_unlock(&s_openpipesmutex); @@ -811,21 +826,27 @@ BidirMMapPipe::BidirMMapPipe(bool useExceptions, bool useSocketpair) : fds[0] = -1; m_inpipe = m_outpipe = fds[1]; } - // close other pipes our parent may have open - we have no business - // reading from/writing to those... - for (std::list::iterator it = s_openpipes.begin(); - s_openpipes.end() != it; ) { - BidirMMapPipe* p = *it; - it = s_openpipes.erase(it); - p->doClose(true, true); + // choose whether to retain other pipes on parent or on children + // (the latter can be useful in special cases) + if(keepLocal) { + for (std::list::iterator it = s_openpipes.begin(); + s_openpipes.end() != it; ) { + BidirMMapPipe* p = *it; + it = s_openpipes.erase(it); + p->doClose(true, true); + } + // if new pages are made independently this should allow + // new BidirMMapPipes to be constructed in the child ?? + //~ s_pagepoolrefcnt = 0; + //~ delete s_pagepool; + //~ s_pagepool = 0; + pagepool().zap(m_pages); + } else { + pagepool().clear_freelist(); } - pagepool().zap(m_pages); - s_pagepoolrefcnt = 0; - delete s_pagepool; - s_pagepool = 0; s_openpipes.push_front(this); pthread_mutex_unlock(&s_openpipesmutex); - // ok, put our pages on freelist + //~ // ok, put our pages on freelist m_freelist = m_pages[PagesPerEnd]; // handshare with other end (to make sure it's alive)... c = 'C'; // ...hild @@ -859,10 +880,22 @@ BidirMMapPipe::BidirMMapPipe(bool useExceptions, bool useSocketpair) : } // put on list of open pipes (so we can kill child processes // if things go wrong) + if(keepLocal) { + // + } else { + for (std::list::iterator it = s_openpipes.begin(); + s_openpipes.end() != it; ) { + BidirMMapPipe* p = *it; + it = s_openpipes.erase(it); + p->doClose(true, true); + } + pagepool().zap(m_pages); + } s_openpipes.push_front(this); pthread_mutex_unlock(&s_openpipesmutex); // ok, put our pages on freelist m_freelist = m_pages[0u]; + // handshare with other end (to make sure it's alive)... c = 'P'; // ...arent if (1 != xferraw(m_outpipe, &c, 1, ::write)) @@ -912,6 +945,31 @@ int BidirMMapPipe::close() return doClose(false); } + +// static function +int BidirMMapPipe::wait_for_child(pid_t child_pid, bool may_throw) { + int status = 0; + int tmp; + do { + tmp = waitpid(child_pid, &status, WNOHANG); + } while (-1 == tmp && EINTR == errno); + if (-1 == tmp && may_throw) throw Exception("waitpid", errno); + + if (WIFEXITED(status)) { + printf("exited, status=%d\n", WEXITSTATUS(status)); + } else if (WIFSIGNALED(status)) { + printf("killed by signal %d\n", WTERMSIG(status)); + } else if (WIFSTOPPED(status)) { + printf("stopped by signal %d\n", WSTOPSIG(status)); + } else if (WIFCONTINUED(status)) { + printf("continued\n"); + } + + return status; +} + + + int BidirMMapPipe::doClose(bool force, bool holdlock) { if (m_flags & failbit) return 0; @@ -963,6 +1021,7 @@ int BidirMMapPipe::doClose(bool force, bool holdlock) // unmap memory try { { BidirMMapPipe_impl::Pages p; p.swap(m_pages); } + assert(s_pagepoolrefcnt!=0); if (!--s_pagepoolrefcnt) { delete s_pagepool; s_pagepool = 0; @@ -972,16 +1031,16 @@ int BidirMMapPipe::doClose(bool force, bool holdlock) } m_busylist = m_freelist = m_dirtylist = 0; // wait for child process - int retVal = 0; - if (isParent()) { - int tmp; - do { - tmp = waitpid(m_childPid, &retVal, 0); - } while (-1 == tmp && EINTR == errno); - if (-1 == tmp) - if (!force) throw Exception("waitpid", errno); - m_childPid = 0; + int retval = 0; + if (!kept_local && isParent() && ::getpid() == m_parentPid) { // double check whether actually parent + retval = BidirMMapPipe::wait_for_child(m_childPid, !force); + m_childPid = 0; // feeling that m_childPid can become zero when it should not be } + + // When created with keepLocal (so kept_local is true), the master (which + // lost its bipe pointers to the child) must manually wait for its children + // after they are closed! Otherwise zombies are created. + // remove from list of open pipes if (!holdlock) pthread_mutex_lock(&s_openpipesmutex); std::list::iterator it = std::find( @@ -989,11 +1048,13 @@ int BidirMMapPipe::doClose(bool force, bool holdlock) if (s_openpipes.end() != it) s_openpipes.erase(it); if (!holdlock) pthread_mutex_unlock(&s_openpipesmutex); m_flags |= failbit; - return retVal; + + return retval; } BidirMMapPipe::~BidirMMapPipe() -{ doClose(false); } +{ + doClose(false); } BidirMMapPipe::size_type BidirMMapPipe::xferraw( int fd, void* addr, size_type len, @@ -1771,6 +1832,7 @@ int main() if (retVal) return retVal; delete pipe; } + // simple poll test - children send 5 results in random intervals { unsigned nch = 20; diff --git a/roofit/roofitcore/src/MultiProcess/GradMinimizerFcn.cxx b/roofit/roofitcore/src/MultiProcess/GradMinimizerFcn.cxx new file mode 100644 index 0000000000000..6c2cabeb0cda3 --- /dev/null +++ b/roofit/roofitcore/src/MultiProcess/GradMinimizerFcn.cxx @@ -0,0 +1,232 @@ +/***************************************************************************** + * Project: RooFit * + * Package: RooFitCore * + * @(#)root/roofitcore:$Id$ + * Authors: * + * PB, Patrick Bos, NL eScience Center, p.bos@esciencecenter.nl * + * * + * Redistribution and use in source and binary forms, * + * with or without modification, are permitted according to the terms * + * listed in LICENSE (http://roofit.sourceforge.net/license.txt) * + *****************************************************************************/ + +#include +#include +#include + +#include + +namespace RooFit { + namespace MultiProcessV1 { + GradMinimizerFcn::GradMinimizerFcn(RooAbsReal *funct, RooMinimizerGenericPtr context, std::size_t _N_workers, + bool verbose) : + RooFit::MultiProcessV1::Vector(_N_workers, funct, context, verbose) { + N_tasks = NDim(); + completed_task_ids.reserve(N_tasks); + // TODO: make sure that the full gradients are sent back so that the + // derivator will depart from correct state next step everywhere! + } + + // copy ctor (necessary for Clone) + GradMinimizerFcn::GradMinimizerFcn(const GradMinimizerFcn& other) : + RooFit::MultiProcessV1::Vector(other), + N_tasks(other.N_tasks), + completed_task_ids(other.completed_task_ids) {} + + ROOT::Math::IMultiGradFunction* GradMinimizerFcn::Clone() const { + return new GradMinimizerFcn(*this) ; + } + + // SYNCHRONIZATION FROM MASTER TO WORKERS + + void GradMinimizerFcn::update_state() { + // TODO optimization: only send changed parameters (now sending all) + RooFit::MultiProcessV1::M2Q msg = RooFit::MultiProcessV1::M2Q::update_real; + for (std::size_t ix = 0; ix < NDim(); ++ix) { + get_manager()->send_from_master_to_queue(msg, id, ix, _grad.Grad()(ix), false); + } + for (std::size_t ix = 0; ix < NDim(); ++ix) { + get_manager()->send_from_master_to_queue(msg, id, ix + 1 * NDim(), _grad.G2()(ix), false); + } + for (std::size_t ix = 0; ix < NDim(); ++ix) { + get_manager()->send_from_master_to_queue(msg, id, ix + 2 * NDim(), _grad.Gstep()(ix), false); + } + + std::size_t ix = 0; + for (auto parameter : _grad_params) { + get_manager()->send_from_master_to_queue(msg, id, ix + 3 * NDim(), parameter, false); + ++ix; + } + } + + void GradMinimizerFcn::update_real(std::size_t ix, double val, bool /*is_constant*/) + { + if (get_manager()->is_worker()) { + // ix is defined in "flat" FunctionGradient space ix_dim * size + ix_component + switch (ix / NDim()) { + case 0: { + _grad[ix % NDim()].derivative = val; + break; + } + case 1: { + _grad[ix % NDim()].second_derivative = val; + break; + } + case 2: { + _grad[ix % NDim()].step_size = val; + break; + } + case 3: { + sync_parameter(val, ix % NDim()); + break; + } + default: throw std::runtime_error("ix out of range in GradMinimizerFcn::update_real!"); + } + } + } + + // END SYNCHRONIZATION FROM MASTER TO WORKERS + + + // SYNCHRONIZATION FROM WORKERS TO MASTER + + void GradMinimizerFcn::send_back_task_result_from_worker(std::size_t task) { + get_manager()->send_from_worker_to_queue(id, task, _grad[task].derivative, _grad[task].second_derivative, _grad[task].step_size); + } + + void GradMinimizerFcn::receive_task_result_on_queue(std::size_t task, std::size_t worker_id) + { + completed_task_ids.push_back(task); + _grad[task].derivative = get_manager()->receive_from_worker_on_queue(worker_id); + _grad[task].second_derivative = get_manager()->receive_from_worker_on_queue(worker_id); + _grad[task].step_size = get_manager()->receive_from_worker_on_queue(worker_id); + } + + void GradMinimizerFcn::send_back_results_from_queue_to_master() { + get_manager()->send_from_queue_to_master(completed_task_ids.size()); + for (auto task : completed_task_ids) { + get_manager()->send_from_queue_to_master(task, _grad[task].derivative, _grad[task].second_derivative, _grad[task].step_size); + } + } + + void GradMinimizerFcn::clear_results() { + completed_task_ids.clear(); + } + + void GradMinimizerFcn::receive_results_on_master() + { + std::size_t N_completed_tasks = get_manager()->receive_from_queue_on_master(); + for (unsigned int sync_ix = 0u; sync_ix < N_completed_tasks; ++sync_ix) { + std::size_t task = get_manager()->receive_from_queue_on_master(); + _grad[task].derivative = get_manager()->receive_from_queue_on_master(); + _grad[task].second_derivative = get_manager()->receive_from_queue_on_master(); + _grad[task].step_size = get_manager()->receive_from_queue_on_master(); + } + } + + // END SYNCHRONIZATION FROM WORKERS TO MASTER + + + // ACTUAL WORK + + void GradMinimizerFcn::evaluate_task(std::size_t task) { + RooWallTimer timer; + RooCPUTimer ctimer; + run_derivator(task); + ctimer.stop(); + timer.stop(); + oocxcoutD((TObject*)nullptr,Benchmarking1) << "worker_id: " << get_manager()->get_worker_id() << ", task: " << task << ", partial derivative time: " << timer.timing_s() << "s -- cputime: " << ctimer.timing_s() << "s" << std::endl; + } + + double GradMinimizerFcn::get_task_result(std::size_t task) { + // this is useless here + return _grad.Grad()(task); + } + + + void GradMinimizerFcn::CalculateAll(const double *x) { + auto get_time = [](){return std::chrono::duration_cast(std::chrono::high_resolution_clock::now().time_since_epoch()).count();}; + decltype(get_time()) t1, t2; + + if (get_manager()->is_master()) { + // do Grad, G2 and Gstep here and then just return results from the + // separate functions below + bool was_not_synced = sync_parameters(x); + if (was_not_synced) { + // update parameters and object states that changed since last calculation (or creation if first time) + t1 = get_time(); + update_state(); + t2 = get_time(); + + RooWallTimer timer; + + // master fills queue with tasks + for (std::size_t ix = 0; ix < N_tasks; ++ix) { + JobTask job_task(id, ix); + get_manager()->to_queue(job_task); + } + waiting_for_queued_tasks = true; + + // wait for task results back from workers to master (put into _grad) + gather_worker_results(); + + timer.stop(); + + oocxcoutD((TObject*)nullptr,Benchmarking1) << "update_state: " << (t2 - t1)/1.e9 << "s (from " << t1 << " to " << t2 << "ns), gradient work: " << timer.timing_s() << "s" << std::endl; + } + } + } + + void GradMinimizerFcn::Gradient(const double *x, double *grad) const { + const_cast(this)->mutable_Gradient(x, grad); + } + + void GradMinimizerFcn::mutable_Gradient(const double *x, double *grad) { + if (get_manager()->is_master()) { + CalculateAll(x); + + // TODO: maybe make a flag to avoid this copy operation, but maybe not worth the effort + // put the results from _grad into *grad + for (std::size_t ix = 0; ix < NDim(); ++ix) { + grad[ix] = _grad.Grad()(ix); + } + } + } + + void GradMinimizerFcn::G2ndDerivative(const double *x, double *g2) const { + const_cast(this)->mutable_G2ndDerivative(x, g2); + } + + void GradMinimizerFcn::mutable_G2ndDerivative(const double *x, double *g2) { + if (get_manager()->is_master()) { + CalculateAll(x); + + // TODO: maybe make a flag to avoid this copy operation, but maybe not worth the effort + // put the results from _grad into *grad + for (std::size_t ix = 0; ix < NDim(); ++ix) { + g2[ix] = _grad.G2()(ix); + } + } + } + + void GradMinimizerFcn::GStepSize(const double *x, double *gstep) const { + const_cast(this)->mutable_GStepSize(x, gstep); + } + + void GradMinimizerFcn::mutable_GStepSize(const double *x, double *gstep) { + if (get_manager()->is_master()) { + CalculateAll(x); + + // TODO: maybe make a flag to avoid this copy operation, but maybe not worth the effort + // put the results from _grad into *grad + for (std::size_t ix = 0; ix < NDim(); ++ix) { + gstep[ix] = _grad.Gstep()(ix); + } + } + } + + // END ACTUAL WORK + +// RooGradMinimizerFcn(funct, context, verbose), N_workers() {} + } // namespace MultiProcess +} // namespace RooFit diff --git a/roofit/roofitcore/src/MultiProcess/Job.cxx b/roofit/roofitcore/src/MultiProcess/Job.cxx new file mode 100644 index 0000000000000..0913e008f7944 --- /dev/null +++ b/roofit/roofitcore/src/MultiProcess/Job.cxx @@ -0,0 +1,176 @@ +/***************************************************************************** + * Project: RooFit * + * Package: RooFitCore * + * @(#)root/roofitcore:$Id$ + * Authors: * + * PB, Patrick Bos, NL eScience Center, p.bos@esciencecenter.nl * + * IP, Inti Pelupessy, NL eScience Center, i.pelupessy@esciencecenter.nl * + * * + * Redistribution and use in source and binary forms, * + * with or without modification, are permitted according to the terms * + * listed in LICENSE (http://roofit.sourceforge.net/license.txt) * + *****************************************************************************/ + +#include // getpid + +#include + +#include +#include +#include + +namespace RooFit { + namespace MultiProcessV1 { + Job::Job(std::size_t _N_workers) : N_workers(_N_workers) {} + Job::Job(const Job & other) : + N_workers(other.N_workers), + waiting_for_queued_tasks(other.waiting_for_queued_tasks), + _manager(other._manager) + {} + + double Job::call_double_const_method(std::string /*key*/) { + throw std::logic_error("call_double_const_method not implemented for this Job"); + } + + // This default sends back only one double as a result; can be overloaded + // e.g. for RooAbsCategorys, for tuples, etc. The queue_loop and master + // process must implement corresponding result receivers. + void Job::send_back_task_result_from_worker(std::size_t task) { + double result = get_task_result(task); + get_manager()->send_from_worker_to_queue(id, task, result); + } + + // static function + void Job::worker_loop() { + assert(TaskManager::instance()->is_worker()); + worker_loop_running = true; + bool carry_on = true; + Task task; + std::size_t job_id; + Q2W message_q2w; + + // use a flag to not ask twice + bool dequeue_acknowledged = true; + + auto get_time = [](){return std::chrono::duration_cast(std::chrono::high_resolution_clock::now().time_since_epoch()).count();}; + + while (carry_on) { + decltype(get_time()) t1 = 0, t2 = 0, t3 = 0; + + // try to dequeue a task + if (dequeue_acknowledged) { // don't ask twice + t1 = get_time(); + TaskManager::instance()->send_from_worker_to_queue(W2Q::dequeue); + dequeue_acknowledged = false; + } + + // receive handshake + message_q2w = TaskManager::instance()->receive_from_queue_on_worker(); + + switch (message_q2w) { + case Q2W::terminate: { + carry_on = false; + break; + } + + case Q2W::dequeue_rejected: { + t2 = get_time(); + oocxcoutD((TObject*)nullptr,Benchmarking2) << "no work: worker " << TaskManager::instance()->get_worker_id() << " asked at " << t1 << " and got rejected at " << t2 << std::endl; + + dequeue_acknowledged = true; + break; + } + case Q2W::dequeue_accepted: { + dequeue_acknowledged = true; + job_id = TaskManager::instance()->receive_from_queue_on_worker(); + task = TaskManager::instance()->receive_from_queue_on_worker(); + + t2 = get_time(); + TaskManager::get_job_object(job_id)->evaluate_task(task); + + t3 = get_time(); + oocxcoutD((TObject*)nullptr,Benchmarking2) << "job done: worker " << TaskManager::instance()->get_worker_id() << " asked at " << t1 << ", started at " << t2 << " and finished at " << t3 << std::endl; + + TaskManager::instance()->send_from_worker_to_queue(W2Q::send_result); + TaskManager::get_job_object(job_id)->send_back_task_result_from_worker(task); + + message_q2w = TaskManager::instance()->receive_from_queue_on_worker(); + if (message_q2w != Q2W::result_received) { + std::cerr << "worker " << getpid() << " sent result, but did not receive Q2W::result_received handshake! Got " << message_q2w << " instead." << std::endl; + throw std::runtime_error(""); + } + break; + } + + // previously this was non-work mode + + case Q2W::update_real: { + auto t1 = get_time(); + + job_id = TaskManager::instance()->receive_from_queue_on_worker(); + std::size_t ix = TaskManager::instance()->receive_from_queue_on_worker(); + double val = TaskManager::instance()->receive_from_queue_on_worker(); + bool is_constant = TaskManager::instance()->receive_from_queue_on_worker(); + TaskManager::get_job_object(job_id)->update_real(ix, val, is_constant); + + auto t2 = get_time(); + oocxcoutD((TObject*)nullptr,Benchmarking1) << "update_real on worker " << TaskManager::instance()->get_worker_id() << ": " << (t2 - t1)/1.e9 << "s (from " << t1 << " to " << t2 << "ns)" << std::endl; + + break; + } + + case Q2W::call_double_const_method: { + job_id = TaskManager::instance()->receive_from_queue_on_worker(); + std::string key = TaskManager::instance()->receive_from_queue_on_worker(); + Job * job = TaskManager::get_job_object(job_id); +// double (* method)() = job->get_double_const_method(key); + double result = job->call_double_const_method(key); + TaskManager::instance()->send_from_worker_to_queue(result); + break; + } + + case Q2W::flush_ostreams: { + TaskManager::instance()->flush_ostreams(); + break; + } + + case Q2W::result_received: { + std::cerr << "In worker_loop: " << message_q2w << " message received, but should only be received as handshake!" << std::endl; + break; + } + + } + } + } + + TaskManager* Job::get_manager() { + if (!_manager) { + _manager = TaskManager::instance(N_workers); + +// _manager->identify_processes(); +// sleep(10); + } + + // N.B.: must check for activation here, otherwise get_manager is not callable + // from queue loop! + if (!_manager->is_activated()) { + _manager->activate(); + } + + if (!worker_loop_running && _manager->is_worker()) { + Job::worker_loop(); + _manager->close_worker_connections(); + // flush remaining output + _manager->flush_ostreams(); +// std::cout << "exiting worker process " << getpid() << std::endl; + std::_Exit(0); + } + + return _manager; + } + + // initialize static members + bool Job::worker_loop_running = false; + + } // namespace MultiProcess +} // namespace RooFit diff --git a/roofit/roofitcore/src/MultiProcess/NLLVar.cxx b/roofit/roofitcore/src/MultiProcess/NLLVar.cxx new file mode 100644 index 0000000000000..ddd46b05704f5 --- /dev/null +++ b/roofit/roofitcore/src/MultiProcess/NLLVar.cxx @@ -0,0 +1,225 @@ +/***************************************************************************** + * Project: RooFit * + * Package: RooFitCore * + * @(#)root/roofitcore:$Id$ + * Authors: * + * PB, Patrick Bos, NL eScience Center, p.bos@esciencecenter.nl * + * IP, Inti Pelupessy, NL eScience Center, i.pelupessy@esciencecenter.nl * + * * + * Redistribution and use in source and binary forms, * + * with or without modification, are permitted according to the terms * + * listed in LICENSE (http://roofit.sourceforge.net/license.txt) * + *****************************************************************************/ + +#include +#include +#include +#include // make_unique +#include // std::tie + +namespace RooFit { + namespace MultiProcessV1 { + + std::ostream& operator<<(std::ostream& out, const NLLVarTask value) { + const char* s = 0; +#define PROCESS_VAL(p) case(p): s = #p; break; + switch(value){ + PROCESS_VAL(NLLVarTask::all_events); + PROCESS_VAL(NLLVarTask::single_event); + PROCESS_VAL(NLLVarTask::bulk_partition); + PROCESS_VAL(NLLVarTask::interleave); + } +#undef PROCESS_VAL + return out << s; + } + + + NLLVar::NLLVar(std::size_t NumCPU, NLLVarTask task_mode, const RooNLLVar& nll) : + RooFit::MultiProcessV1::Vector(NumCPU, nll), // uses copy constructor for the RooNLLVar part + mp_task_mode(task_mode) + { + if (_gofOpMode == RooAbsTestStatistic::GOFOpMode::MPMaster) { + TaskManager::remove_job_object(id); + throw std::logic_error("Cannot create MPRooNLLVar based on a multi-CPU enabled RooNLLVar! The use of the BidirMMapPipe by MPFE in RooNLLVar conflicts with the use of BidirMMapPipe by MultiProcess classes."); + } + + _vars = RooListProxy("vars", "vars", this); + init_vars(); + switch (mp_task_mode) { + case NLLVarTask::all_events: { + N_tasks = 1; + break; + } + case NLLVarTask::single_event: { + N_tasks = static_cast(_data->numEntries()); + break; + } + case NLLVarTask::bulk_partition: + case NLLVarTask::interleave: { + N_tasks = NumCPU; + break; + } + } + + double_const_methods["getCarry"] = &NLLVar::getCarry; + } + + void NLLVar::init_vars() { + // Empty current lists + _vars.removeAll() ; + _saveVars.removeAll() ; + + // Retrieve non-constant parameters + auto vars = std::make_unique(*getParameters(RooArgSet())); + RooArgList varList(*vars); + + // Save in lists + _vars.add(varList); + _saveVars.addClone(varList); + } + + void NLLVar::update_parameters() { + if (get_manager()->is_master()) { + for (std::size_t ix = 0u; ix < static_cast(_vars.getSize()); ++ix) { + bool valChanged = !_vars[ix].isIdentical(_saveVars[ix], kTRUE); + bool constChanged = (_vars[ix].isConstant() != _saveVars[ix].isConstant()); + + if (valChanged || constChanged) { + if (constChanged) { + ((RooRealVar *) &_saveVars[ix])->setConstant(_vars[ix].isConstant()); + } + // TODO: Check with Wouter why he uses copyCache in MPFE; makes it very difficult to extend, because copyCache is protected (so must be friend). Moved setting value to if-block below. + // _saveVars[ix].copyCache(&_vars[ix]); + + // send message to queue (which will relay to workers) + RooAbsReal * rar_val = dynamic_cast(&_vars[ix]); + if (rar_val) { + Double_t val = rar_val->getVal(); + dynamic_cast(&_saveVars[ix])->setVal(val); + RooFit::MultiProcessV1::M2Q msg = RooFit::MultiProcessV1::M2Q::update_real; + Bool_t isC = _vars[ix].isConstant(); + get_manager()->send_from_master_to_queue(msg, id, ix, val, isC); + } + // TODO: implement category handling + // } else if (dynamic_cast(var)) { + // M2Q msg = M2Q::update_cat ; + // UInt_t cat_ix = ((RooAbsCategory*)var)->getIndex(); + // *_pipe << msg << ix << cat_ix; + // } + } + } + } + } + + Double_t NLLVar::evaluate() const { + return const_cast(this)->evaluate_non_const(); + } + + Double_t NLLVar::evaluate_non_const() { + if (get_manager()->is_master()) { + // update parameters that changed since last calculation (or creation if first time) + update_parameters(); + + // master fills queue with tasks + for (std::size_t ix = 0; ix < N_tasks; ++ix) { + JobTask job_task(id, ix); + get_manager()->to_queue(job_task); + } + waiting_for_queued_tasks = true; + + // wait for task results back from workers to master + gather_worker_results(); + + // put the results in vectors for calling sum_of_kahan_sums (TODO: make a map-friendly sum_of_kahan_sums) + std::vector results_vec, carrys_vec; + for (auto const &item : results) { + results_vec.emplace_back(item.second); + carrys_vec.emplace_back(carrys[item.first]); + } + + // sum task results + std::tie(result, carry) = sum_of_kahan_sums(results_vec, carrys_vec); + } + return result; + } + + // --- RESULT LOGISTICS --- + + void NLLVar::send_back_task_result_from_worker(std::size_t task) { + result = get_task_result(task); + carry = getCarry(); + get_manager()->send_from_worker_to_queue(id, task, result, carry); + } + + void NLLVar::receive_task_result_on_queue(std::size_t task, std::size_t worker_id) { + result = get_manager()->receive_from_worker_on_queue(worker_id); + carry = get_manager()->receive_from_worker_on_queue(worker_id); + results[task] = result; + carrys[task] = carry; + } + + void NLLVar::send_back_results_from_queue_to_master() { + get_manager()->send_from_queue_to_master(results.size()); + for (auto const &item : results) { + get_manager()->send_from_queue_to_master(item.first, item.second, carrys[item.first]); + } + } + + void NLLVar::clear_results() { + // empty results caches + results.clear(); + carrys.clear(); + } + + void NLLVar::receive_results_on_master() { + std::size_t N_job_tasks = get_manager()->receive_from_queue_on_master(); + for (std::size_t task_ix = 0ul; task_ix < N_job_tasks; ++task_ix) { + std::size_t task_id = get_manager()->receive_from_queue_on_master(); + results[task_id] = get_manager()->receive_from_queue_on_master(); + carrys[task_id] = get_manager()->receive_from_queue_on_master(); + } + } + + // --- END OF RESULT LOGISTICS --- + + void NLLVar::evaluate_task(std::size_t task) { + assert(get_manager()->is_worker()); + std::size_t N_events = static_cast(_data->numEntries()); + // "default" values (all events in one task) + std::size_t first = task; + std::size_t last = N_events; + std::size_t step = 1; + switch (mp_task_mode) { + case NLLVarTask::all_events: { + // default values apply + break; + } + case NLLVarTask::single_event: { + last = task + 1; + break; + } + case NLLVarTask::bulk_partition: { + first = N_events * task / N_tasks; + last = N_events * (task + 1) / N_tasks; + break; + } + case NLLVarTask::interleave: { + step = N_tasks; + break; + } + } + + result = evaluatePartition(first, last, step); + } + + double NLLVar::get_task_result(std::size_t /*task*/) { + // TODO: this is quite ridiculous, having a get_task_result without task + // argument. We should have a cache, e.g. a map, that gives the result for + // a given task. The caller (usually send_back_task_result_from_worker) can + // then decide whether to erase the value from the cache to keep it clean. + assert(get_manager()->is_worker()); + return result; + } + + } // namespace MultiProcess +} // namespace RooFit diff --git a/roofit/roofitcore/src/MultiProcess/TaskManager.cxx b/roofit/roofitcore/src/MultiProcess/TaskManager.cxx new file mode 100644 index 0000000000000..59abd5ea74678 --- /dev/null +++ b/roofit/roofitcore/src/MultiProcess/TaskManager.cxx @@ -0,0 +1,622 @@ +/***************************************************************************** + * Project: RooFit * + * Package: RooFitCore * + * @(#)root/roofitcore:$Id$ + * Authors: * + * PB, Patrick Bos, NL eScience Center, p.bos@esciencecenter.nl * + * IP, Inti Pelupessy, NL eScience Center, i.pelupessy@esciencecenter.nl * + * * + * Redistribution and use in source and binary forms, * + * with or without modification, are permitted according to the terms * + * listed in LICENSE (http://roofit.sourceforge.net/license.txt) * + *****************************************************************************/ + +#include // logic_error +#include +#include // getpid +// for cpu affinity +#if !defined(__APPLE__) && !defined(_WIN32) +#include +#endif + +#include + +#include // make_unique in C++11 +#include +#include +#include +#include +#include +#include // for_each + +namespace RooFit { + namespace MultiProcessV1 { + + void set_socket_immediate(ZmqLingeringSocketPtr<> & socket) { + int optval = 1; + socket->setsockopt(ZMQ_IMMEDIATE, &optval, sizeof(optval)); + } + + // static function + TaskManager* TaskManager::instance(std::size_t N_workers) { + if (!TaskManager::is_instantiated()) { + assert(N_workers != 0); + _instance = std::make_unique(N_workers); + } + // these sanity checks no longer make sense with the worker_pipes only being maintained on the queue process +// } else { +// // some sanity checks +// if(_instance->is_master() && N_workers != _instance->worker_pipes.size()) { +// std::cerr << "On PID " << getpid() << ": N_workers != tmp->worker_pipes.size())! N_workers = " << N_workers << ", tmp->worker_pipes.size() = " << _instance->worker_pipes.size() << std::endl; +// throw std::logic_error(""); +// } else if (_instance->is_worker()) { +// if (_instance->get_worker_id() + 1 != _instance->worker_pipes.size()) { +// std::cerr << "On PID " << getpid() << ": tmp->get_worker_id() + 1 != tmp->worker_pipes.size())! tmp->get_worker_id() = " << _instance->get_worker_id() << ", tmp->worker_pipes.size() = " << _instance->worker_pipes.size() << std::endl; +// throw std::logic_error(""); +// } +// } +// } + return _instance.get(); + } + + // static function + TaskManager* TaskManager::instance() { + if (!TaskManager::is_instantiated()) { + throw std::runtime_error("in TaskManager::instance(): no instance was created yet! Call TaskManager::instance(std::size_t N_workers) first."); + } + return _instance.get(); + } + + // static function + bool TaskManager::is_instantiated() { + return static_cast(_instance); + } + + + void TaskManager::identify_processes() { + // identify yourselves (for debugging) + if (!(_is_master || _is_queue)) { + std::cout << "I'm a worker, PID " << getpid() << '\n'; + } else if (_is_master) { + std::cout << "I'm master, PID " << getpid() << '\n'; + } else if (_is_queue) { + std::cout << "I'm queue, PID " << getpid() << '\n'; + } + } + + // constructor + // Don't construct IPQM objects manually, use the static instance if + // you need to run multiple jobs. + TaskManager::TaskManager(std::size_t N_workers) : N_workers(N_workers) { + // This class defines three types of processes: + // 1. master: the initial main process. It defines and enqueues tasks + // and processes results. + // 2. workers: a pool of processes that will try to take tasks from the + // queue. These are first forked from master. + // 3. queue: communication between the other types (necessarily) goes + // through this process. This process runs the queue_loop and + // maintains the queue of tasks. It is forked last and initialized + // with third BidirMMapPipe parameter false, which makes it the + // process that manages all pipes, though the pool of pages remains + // on the master process. + // The reason for using this layout is that we use BidirMMapPipe for + // forking and communicating between processes, and BidirMMapPipe only + // supports forking from one process, not from an already forked + // process (if forked using BidirMMapPipe). The latter layout would + // allow us to fork first the queue from the main process and then fork + // the workers from the queue, which may feel more natural. + + initialize_processes(); + } + + void TaskManager::initialize_processes(bool cpu_pinning) { + // Initialize processes; + // ... first workers: + worker_pids.resize(N_workers); + pid_t child_pid {}; + for (std::size_t ix = 0; ix < N_workers; ++ix) { + child_pid = fork(); + if (!child_pid) { // we're on the worker + worker_id = ix; + break; + } else { // we're on master + worker_pids[ix] = child_pid; + } + } + + // ... then queue: + if (child_pid) { // we're on master + queue_pid = fork(); + if (!queue_pid) { // we're now on queue + _is_queue = true; + } else { + _is_master = true; + } + } + + // after all forks, create zmq connections (zmq context is automatically created in the ZeroMQSvc class and maintained as singleton) + try { + if (is_master()) { + mq_socket.reset(zmqSvc().socket_ptr(zmq::PAIR)); //REQ)); + set_socket_immediate(mq_socket); + mq_socket->bind("ipc:///tmp/roofitMP_master_queue"); + // mq_socket->bind("tcp://*:55555"); + } else if (is_queue()) { + // first the queue-worker sockets + qw_sockets.resize(N_workers); // do resize instead of reserve so that the unique_ptrs are initialized (to nullptr) so that we can do reset below, alternatively you can do push/emplace_back with move or something + for (std::size_t ix = 0; ix < N_workers; ++ix) { + qw_sockets[ix].reset(zmqSvc().socket_ptr(zmq::PAIR)); //REP)); + set_socket_immediate(qw_sockets[ix]); + std::stringstream socket_name; + socket_name << "ipc:///tmp/roofitMP_queue_worker_" << ix; + // socket_name << "tcp://*:" << 55556 + ix; + qw_sockets[ix]->bind(socket_name.str()); + } + // then the master-queue socket + mq_socket.reset(zmqSvc().socket_ptr(zmq::PAIR)); //REP)); + set_socket_immediate(mq_socket); + mq_socket->connect("ipc:///tmp/roofitMP_master_queue"); + // mq_socket->connect("tcp://127.0.0.1:55555"); + } else if (is_worker()) { + this_worker_qw_socket.reset(zmqSvc().socket_ptr(zmq::PAIR)); //REQ)); + set_socket_immediate(this_worker_qw_socket); + std::stringstream socket_name; + socket_name << "ipc:///tmp/roofitMP_queue_worker_" << worker_id; + // socket_name << "tcp://127.0.0.1:" << 55556 + worker_id; + this_worker_qw_socket->connect(socket_name.str()); + } else { + // should never get here + throw std::runtime_error("TaskManager::initialize_processes: I'm neither master, nor queue, nor a worker"); + } + } catch (zmq::error_t& e) { + std::cerr << e.what() << " -- errnum: " << e.num() << std::endl; + throw; + }; + + if (cpu_pinning) { + #if defined(__APPLE__) + static bool affinity_warned = false; + if (is_master() & !affinity_warned) { + ooccoutD(static_cast(nullptr),Eval) << "CPU affinity cannot be set on macOS"; + } + #elif defined(_WIN32) + if (is_master()) std::cerr << "WARNING: CPU affinity setting not implemented on Windows, continuing...\n"; + #else + cpu_set_t mask; + // zero all bits in mask + CPU_ZERO(&mask); + // set correct bit + std::size_t set_cpu; + if (is_master()) { + set_cpu = N_workers + 1; + } else if (is_queue()) { + set_cpu = N_workers; + } else { + set_cpu = worker_id; + } + CPU_SET(set_cpu, &mask); + /* sched_setaffinity returns 0 in success */ + + if (sched_setaffinity(0, sizeof(mask), &mask) == -1) { + std::cerr << "WARNING: Could not set CPU affinity, continuing...\n"; + } else { + std::cerr << "CPU affinity set to cpu " << set_cpu << " in process " << getpid() << '\n'; + } + #endif + } + +// identify_processes(); + + processes_initialized = true; + } + + + TaskManager::~TaskManager() { + ooccoutD(static_cast(nullptr),Eval) << "destroying TaskManager on PID " << getpid() << (is_worker() ? " worker" : (is_queue()? " queue" : " master")); + // The TM instance gets created by some Job. Once all Jobs are gone, the + // TM will get destroyed. In this case, the job_objects map should have + // been emptied. This check makes sure: + assert(TaskManager::job_objects.empty()); + terminate(); + } + + + // static function + // returns job_id for added job_object + std::size_t TaskManager::add_job_object(Job *job_object) { + if (TaskManager::is_instantiated()) { + if (_instance->is_activated()) { + std::stringstream ss; + ss << "Cannot add Job to activated TaskManager instantiation (forking has already taken place)! Instance object at raw ptr " << _instance.get(); + throw std::logic_error("Cannot add Job to activated TaskManager instantiation (forking has already taken place)! Call terminate() on the instance before adding new Jobs."); + } + } + std::size_t job_id = job_counter++; + job_objects[job_id] = job_object; + return job_id; + } + + // static function + Job* TaskManager::get_job_object(std::size_t job_object_id) { + return job_objects[job_object_id]; + } + + // static function + bool TaskManager::remove_job_object(std::size_t job_object_id) { + bool removed_succesfully = job_objects.erase(job_object_id) == 1; + if (job_objects.empty()) { + _instance.reset(nullptr); + } + return removed_succesfully; + } + + + void TaskManager::terminate() noexcept { + try { + if (is_master()) { + send_from_master_to_queue(M2Q::terminate); +// int period = 0; +// mq_socket->setsockopt(ZMQ_LINGER, &period, sizeof(period)); + mq_socket.reset(nullptr); + zmqSvc().close_context(); + queue_activated = false; + shutdown_processes(); + } +// } catch (const BidirMMapPipe::Exception& e) { +// std::cerr << "WARNING: in TaskManager::terminate, something in BidirMMapPipe threw an exception! Message:\n\t" << e.what() << std::endl; + } catch (const std::exception& e) { + std::cerr << "WARNING: something in TaskManager::terminate threw an exception! Original exception message:\n" << e.what() << '\n'; + } + } + + + void TaskManager::shutdown_processes() { + if (is_master()) { +// send_from_master_to_queue(M2Q::terminate); +// *queue_pipe << M2Q::terminate << BidirMMapPipe::flush; + + // first wait for the workers that will be terminated by the queue + for (auto pid : worker_pids) { + wait_for_child(pid, true, 5); + } + // then wait for the queue + wait_for_child(queue_pid, true, 5); + +// // delete queue_pipe (not worker_pipes, only on queue process!) +// // CAUTION: the following invalidates a possibly created PollVector +// queue_pipe.reset(); // sets to nullptr + +// for (auto it = worker_pids.begin(); it != worker_pids.end(); ++it) { +// BidirMMapPipe::wait_for_child(*it, true); +// } + } + + processes_initialized = false; + _is_master = false; + } + + void TaskManager::close_worker_connections() { +// int period = 0; + if (is_worker()) { +// this_worker_qw_socket->setsockopt(ZMQ_LINGER, &period, sizeof(period)); + this_worker_qw_socket.reset(nullptr); + zmqSvc().close_context(); + } else if (is_queue()) { + for (std::size_t worker_ix = 0ul; worker_ix < N_workers; ++worker_ix) { +// qw_sockets[worker_ix]->setsockopt(ZMQ_LINGER, &period, sizeof(period)); + qw_sockets[worker_ix].reset(nullptr); + } + } + } + + void TaskManager::terminate_workers() { + if (is_queue()) { +// for (std::unique_ptr &worker_pipe : worker_pipes) { +// *worker_pipe << Q2W::terminate << BidirMMapPipe::flush; +// for(auto& worker_socket : qw_sockets) { +// zmqSvc().send(*worker_socket, Q2W::terminate); + for(std::size_t worker_ix = 0; worker_ix < N_workers; ++worker_ix) { + send_from_queue_to_worker(worker_ix, Q2W::terminate); +// int retval = worker_pipe->close(); +// if (0 != retval) { +// std::cerr << "error terminating worker_pipe for worker with PID " << worker_pipe->pidOtherEnd() << "; child return value is " << retval << std::endl; +// } + } + close_worker_connections(); + } + } + + + // start message loops on child processes and quit processes afterwards + void TaskManager::activate() { +// std::cout << "activating" << std::endl; + // should be called soon after creation of this object, because everything in + // between construction and activate gets executed both on the master process + // and on the slaves + if (!processes_initialized) { +// std::cout << "intializing" << std::endl; + initialize_processes(); + } + + queue_activated = true; // set on all processes, master, queue and slaves + + if (is_queue()) { + queue_loop(); + terminate_workers(); +// int period = 0; +// mq_socket->setsockopt(ZMQ_LINGER, &period, sizeof(period)); + mq_socket.reset(nullptr); // delete/close master-queue socket from queue side + zmqSvc().close_context(); + std::_Exit(0); + } + } + + + bool TaskManager::is_activated() { + return queue_activated; + } + + + // CAUTION: + // this function returns a vector of pointers that may get invalidated by + // the terminate function! +// BidirMMapPipe::PollVector TaskManager::get_poll_vector() { +// BidirMMapPipe::PollVector poll_vector; +// poll_vector.reserve(1 + worker_pipes.size()); +// poll_vector.emplace_back(queue_pipe.get(), BidirMMapPipe::Readable); +// for (std::unique_ptr& pipe : worker_pipes) { +// poll_vector.emplace_back(pipe.get(), BidirMMapPipe::Readable); +// } +// return poll_vector; +// } + + + bool TaskManager::process_queue_pipe_message(M2Q message) { + bool carry_on = true; + + switch (message) { + case M2Q::terminate: { + carry_on = false; + } + break; + + case M2Q::enqueue: { + // enqueue task + auto job_object_id = receive_from_master_on_queue(); + auto task = receive_from_master_on_queue(); + JobTask job_task(job_object_id, task); + to_queue(job_task); + N_tasks++; + } + break; + + case M2Q::retrieve: { + // retrieve task results after queue is empty and all + // tasks have been completed + if (queue.empty() && N_tasks_completed == N_tasks) { + send_from_queue_to_master(Q2M::retrieve_accepted, job_objects.size()); + for (auto job_tuple : job_objects) { + send_from_queue_to_master(job_tuple.first); // job id + job_tuple.second->send_back_results_from_queue_to_master(); // N_job_tasks, task_ids and results + job_tuple.second->clear_results(); + } + // reset number of received and completed tasks + N_tasks = 0; + N_tasks_completed = 0; + } else { + send_from_queue_to_master(Q2M::retrieve_rejected); // handshake message: tasks not done yet, try again + } + } + break; + + case M2Q::update_real: { + auto get_time = [](){return std::chrono::duration_cast(std::chrono::high_resolution_clock::now().time_since_epoch()).count();}; + auto t1 = get_time(); + auto job_id = receive_from_master_on_queue(); + auto ix = receive_from_master_on_queue(); + auto val = receive_from_master_on_queue(); + auto is_constant = receive_from_master_on_queue(); + for (std::size_t worker_ix = 0; worker_ix < N_workers; ++worker_ix) { + send_from_queue_to_worker(worker_ix, Q2W::update_real, job_id, ix, val, is_constant); + } + auto t2 = get_time(); + oocxcoutD((TObject*)nullptr,Benchmarking1) << "update_real on queue: " << (t2 - t1)/1.e9 << "s (from " << t1 << " to " << t2 << "ns)" << std::endl; + } + break; + + case M2Q::call_double_const_method: { + auto job_id = receive_from_master_on_queue(); + auto worker_id_call = receive_from_master_on_queue(); + auto key = receive_from_master_on_queue(); + send_from_queue_to_worker(worker_id_call, Q2W::call_double_const_method, job_id, key); + auto result = receive_from_worker_on_queue(worker_id_call); + send_from_queue_to_master(result); + } + break; + + case M2Q::flush_ostreams: { + flush_ostreams(); + } + break; + } + + return carry_on; + } + + + void TaskManager::retrieve() { + if (_is_master) { + bool carry_on = true; + while (carry_on) { + send_from_master_to_queue(M2Q::retrieve); + auto handshake = receive_from_queue_on_master(); + if (handshake == Q2M::retrieve_accepted) { + carry_on = false; + auto N_jobs = receive_from_queue_on_master(); + for (std::size_t job_ix = 0; job_ix < N_jobs; ++job_ix) { + auto job_object_id = receive_from_queue_on_master(); + TaskManager::get_job_object(job_object_id)->receive_results_on_master(); + } + } + } + } + } + + + double TaskManager::call_double_const_method(const std::string& method_key, std::size_t job_id, std::size_t worker_id_call) { + send_from_master_to_queue(M2Q::call_double_const_method, job_id, worker_id_call, method_key); + auto result = receive_from_queue_on_master(); + return result; + } + + // -- WORKER - QUEUE COMMUNICATION -- + + void TaskManager::send_from_worker_to_queue() { +// *this_worker_pipe << BidirMMapPipe::flush; + } + + void TaskManager::send_from_queue_to_worker(std::size_t /*this_worker_id*/) { +// *worker_pipes[this_worker_id] << BidirMMapPipe::flush; + } + + // -- QUEUE - MASTER COMMUNICATION -- + + void TaskManager::send_from_queue_to_master() { +// *queue_pipe << BidirMMapPipe::flush; + } + + void TaskManager::send_from_master_to_queue() { + send_from_queue_to_master(); + } + + + void TaskManager::process_worker_pipe_message(std::size_t this_worker_id, W2Q message) { + switch (message) { + case W2Q::dequeue: { + // dequeue task + JobTask job_task; + if (from_queue(job_task)) { + send_from_queue_to_worker(this_worker_id, Q2W::dequeue_accepted, job_task.first, job_task.second); + } else { + send_from_queue_to_worker(this_worker_id, Q2W::dequeue_rejected); + } + break; + } + + case W2Q::send_result: { + // receive back task result + auto job_object_id = receive_from_worker_on_queue(this_worker_id); + auto task = receive_from_worker_on_queue(this_worker_id); + TaskManager::get_job_object(job_object_id)->receive_task_result_on_queue(task, this_worker_id); + send_from_queue_to_worker(this_worker_id, Q2W::result_received); + N_tasks_completed++; + break; + } + } + } + + + void TaskManager::queue_loop() { + if (_is_queue) { + bool carry_on = true; + ZeroMQPoller poller; + auto mq_index = poller.register_socket(*mq_socket, zmq::POLLIN); + for(auto& s : qw_sockets) { + poller.register_socket(*s, zmq::POLLIN); + } + + while (carry_on) { + // poll: wait until status change (-1: infinite timeout) + auto poll_result = poller.poll(-1); + // then process incoming messages from sockets + for (auto readable_socket : poll_result) { + // message comes from the master/queue socket (first element): + if (readable_socket.first == mq_index) { + auto message = receive_from_master_on_queue(); + carry_on = process_queue_pipe_message(message); + // on terminate, also stop for-loop, no need to check other + // sockets anymore: + if (!carry_on) { + break; + } + } else { // from a worker socket + auto this_worker_id = readable_socket.first - 1; + auto message = receive_from_worker_on_queue(this_worker_id); + process_worker_pipe_message(this_worker_id, message); + } + } + } + } + } + + + // Have a worker ask for a task-message from the queue + bool TaskManager::from_queue(JobTask &job_task) { + if (queue.empty()) { + return false; + } else { + job_task = queue.front(); + queue.pop(); + return true; + } + } + + + // Enqueue a task + void TaskManager::to_queue(JobTask job_task) { + if (is_master()) { + if (!queue_activated) { + activate(); + } + send_from_master_to_queue(M2Q::enqueue, job_task.first, job_task.second); + } else if (is_queue()) { + queue.push(job_task); + } else { + throw std::logic_error("calling Communicator::to_master_queue from slave process"); + } + } + + + bool TaskManager::is_master() { + return _is_master; + } + + bool TaskManager::is_queue() { + return _is_queue; + } + + bool TaskManager::is_worker() { + return !(_is_master || _is_queue); + } + + std::size_t TaskManager::get_worker_id() { + return worker_id; + } + + void TaskManager::flush_ostreams() { + if (is_master()) { + send_from_master_to_queue(M2Q::flush_ostreams); + } else if (is_queue()) { + // flush output + std::cout << std::flush; + std::clog << std::flush; + for (std::size_t worker_ix = 0; worker_ix < N_workers; ++worker_ix) { + send_from_queue_to_worker(worker_ix, Q2W::flush_ostreams); + } + } else if (is_worker()) { + // flush output + std::cout << std::flush; + std::clog << std::flush; + } else { + throw std::logic_error("calling TaskManager::flush_ostreams from unknown type of process (not master, queue or worker)"); + } + } + + + // initialize static members + std::map TaskManager::job_objects; + std::size_t TaskManager::job_counter = 0; + std::unique_ptr TaskManager::_instance {nullptr}; + + } // namespace MultiProcess +} // namespace RooFit \ No newline at end of file diff --git a/roofit/roofitcore/src/MultiProcess/messages.cxx b/roofit/roofitcore/src/MultiProcess/messages.cxx new file mode 100644 index 0000000000000..ac9595760dff8 --- /dev/null +++ b/roofit/roofitcore/src/MultiProcess/messages.cxx @@ -0,0 +1,70 @@ +/***************************************************************************** + * Project: RooFit * + * Package: RooFitCore * + * @(#)root/roofitcore:$Id$ + * Authors: * + * PB, Patrick Bos, NL eScience Center, p.bos@esciencecenter.nl * + * IP, Inti Pelupessy, NL eScience Center, i.pelupessy@esciencecenter.nl * + * * + * Redistribution and use in source and binary forms, * + * with or without modification, are permitted according to the terms * + * listed in LICENSE (http://roofit.sourceforge.net/license.txt) * + *****************************************************************************/ + +#include + +namespace RooFit { + namespace MultiProcessV1 { + + // for debugging +#define PROCESS_VAL(p) case(p): s = #p; break; + + std::ostream& operator<<(std::ostream& out, const M2Q value){ + const char* s = 0; + switch(value){ + PROCESS_VAL(M2Q::terminate); + PROCESS_VAL(M2Q::enqueue); + PROCESS_VAL(M2Q::retrieve); + PROCESS_VAL(M2Q::update_real); + PROCESS_VAL(M2Q::call_double_const_method); + PROCESS_VAL(M2Q::flush_ostreams); + } + return out << s; + } + + std::ostream& operator<<(std::ostream& out, const Q2M value){ + const char* s = 0; + switch(value){ + PROCESS_VAL(Q2M::retrieve_rejected); + PROCESS_VAL(Q2M::retrieve_accepted); + } + return out << s; + } + + std::ostream& operator<<(std::ostream& out, const W2Q value){ + const char* s = 0; + switch(value){ + PROCESS_VAL(W2Q::dequeue); + PROCESS_VAL(W2Q::send_result); + } + return out << s; + } + + std::ostream& operator<<(std::ostream& out, const Q2W value){ + const char* s = 0; + switch(value){ + PROCESS_VAL(Q2W::terminate); + PROCESS_VAL(Q2W::dequeue_rejected); + PROCESS_VAL(Q2W::dequeue_accepted); + PROCESS_VAL(Q2W::update_real); + PROCESS_VAL(Q2W::result_received); + PROCESS_VAL(Q2W::call_double_const_method); + PROCESS_VAL(Q2W::flush_ostreams); + } + return out << s; + } + +#undef PROCESS_VAL + + } // namespace MultiProcess +} // namespace RooFit diff --git a/roofit/roofitcore/src/MultiProcess/util.cxx b/roofit/roofitcore/src/MultiProcess/util.cxx new file mode 100644 index 0000000000000..91c0a99c26bb6 --- /dev/null +++ b/roofit/roofitcore/src/MultiProcess/util.cxx @@ -0,0 +1,62 @@ +/***************************************************************************** + * Project: RooFit * + * Package: RooFitCore * + * @(#)root/roofitcore:$Id$ + * Authors: * + * PB, Patrick Bos, NL eScience Center, p.bos@esciencecenter.nl * + * * + * Redistribution and use in source and binary forms, * + * with or without modification, are permitted according to the terms * + * listed in LICENSE (http://roofit.sourceforge.net/license.txt) * + *****************************************************************************/ + +#include // kill, SIGKILL +#include // cerr, and indirectly WNOHANG, EINTR, W* macros +#include // runtime_error +#include // waitpid +#include + +#include + +#include + +namespace RooFit { + namespace MultiProcessV1 { + + int wait_for_child(pid_t child_pid, bool may_throw, int retries_before_killing) { + int status = 0; + int patience = retries_before_killing; + pid_t tmp; + do { + if (patience-- < 1) { + ::kill(child_pid, SIGKILL); + } + tmp = waitpid(child_pid, &status, WNOHANG); + } while ( + tmp == 0 // child has not yet changed state, try again + || (-1 == tmp && EINTR == errno) // retry on interrupted system call + ); + + if (patience < 1) { + ooccoutD(static_cast(nullptr),Eval) << "Had to send PID " << child_pid << " " << (-patience+1) << " SIGKILLs"; + } + + if (0 != status) { + if (WIFEXITED(status)) { + printf("exited, status=%d\n", WEXITSTATUS(status)); + } else if (WIFSIGNALED(status)) { + printf("killed by signal %d\n", WTERMSIG(status)); + } else if (WIFSTOPPED(status)) { + printf("stopped by signal %d\n", WSTOPSIG(status)); + } else if (WIFCONTINUED(status)) { + printf("continued\n"); + } + } + + if (-1 == tmp && may_throw) throw std::runtime_error(std::string("waitpid, errno ") + std::to_string(errno)); + + return status; + } + + } +} \ No newline at end of file diff --git a/roofit/roofitcore/src/NumericalDerivatorMinuit2.cxx b/roofit/roofitcore/src/NumericalDerivatorMinuit2.cxx new file mode 100644 index 0000000000000..18348470517c5 --- /dev/null +++ b/roofit/roofitcore/src/NumericalDerivatorMinuit2.cxx @@ -0,0 +1,451 @@ +// @(#)root/mathcore:$Id$ +// Authors: L. Moneta, J.T. Offermann, E.G.P. Bos 2013-2018 +// +/********************************************************************** + * * + * Copyright (c) 2013 , LCG ROOT MathLib Team * + * Copyright (c) 2017 Patrick Bos, Netherlands eScience Center * + * * + **********************************************************************/ +/* + * NumericalDerivatorMinuit2.cxx + * + * Original version (NumericalDerivator) created on: Aug 14, 2013 + * Authors: L. Moneta, J. T. Offermann + * Modified version (NumericalDerivatorMinuit2) created on: Sep 27, 2017 + * Author: E. G. P. Bos + * + * NumericalDerivator was essentially a slightly modified copy of code + * written by M. Winkler, F. James, L. Moneta, and A. Zsenei for Minuit2, + * Copyright (c) 2005 LCG ROOT Math team, CERN/PH-SFT. + * + * This class attempts to more closely follow the Minuit2 implementation. + * Modified things (w.r.t. NumericalDerivator) are indicated by MODIFIED. + */ + +#include "NumericalDerivatorMinuit2.h" +#include +#include +#include +#include +#include +#include +#include "Fit/ParameterSettings.h" + +#include // needed here because in Fitter is only a forward declaration + +#include +#include + +//#include + +#define DEBUG_STREAM(var) << " " #var "=" << var + +namespace RooFit { + +NumericalDerivatorMinuit2::NumericalDerivatorMinuit2(bool always_exactly_mimic_minuit2) + : _always_exactly_mimic_minuit2(always_exactly_mimic_minuit2) +{ +} + +NumericalDerivatorMinuit2::NumericalDerivatorMinuit2(double step_tolerance, double grad_tolerance, unsigned int ncycles, + double error_level, bool always_exactly_mimic_minuit2) + : fStepTolerance(step_tolerance), fGradTolerance(grad_tolerance), fNCycles(ncycles), Up(error_level), + _always_exactly_mimic_minuit2(always_exactly_mimic_minuit2) +{ +} + +// deep copy constructor +NumericalDerivatorMinuit2::NumericalDerivatorMinuit2(const RooFit::NumericalDerivatorMinuit2 &other) + : fStepTolerance(other.fStepTolerance), fGradTolerance(other.fGradTolerance), fNCycles(other.fNCycles), Up(other.Up), + fVal(other.fVal), vx(other.vx), vx_external(other.vx_external), dfmin(other.dfmin), vrysml(other.vrysml), + precision(other.precision), _always_exactly_mimic_minuit2(other._always_exactly_mimic_minuit2), + vx_fVal_cache(other.vx_fVal_cache) +{ +} + +void NumericalDerivatorMinuit2::SetStepTolerance(double value) +{ + fStepTolerance = value; +} + +void NumericalDerivatorMinuit2::SetGradTolerance(double value) +{ + fGradTolerance = value; +} + +void NumericalDerivatorMinuit2::SetNCycles(int value) +{ + fNCycles = value; +} + +NumericalDerivatorMinuit2::~NumericalDerivatorMinuit2() +{ + // TODO Auto-generated destructor stub +} + +// This function sets internal state based on input parameters. This state +// setup is used in the actual (partial) derivative calculations. +void NumericalDerivatorMinuit2::setup_differentiate(const ROOT::Math::IBaseFunctionMultiDim *function, const double *cx, + const std::vector ¶meters) +{ + + assert(function != nullptr && "function is a nullptr"); + + auto get_time = []() { + return std::chrono::duration_cast( + std::chrono::high_resolution_clock::now().time_since_epoch()) + .count(); + }; + decltype(get_time()) t1, t2, t3, t4, t5, t6, t7, t8; + + t1 = get_time(); + if (vx.size() != function->NDim()) { + vx.resize(function->NDim()); + } + t2 = get_time(); + if (vx_external.size() != function->NDim()) { + vx_external.resize(function->NDim()); + } + t3 = get_time(); + if (vx_fVal_cache.size() != function->NDim()) { + vx_fVal_cache.resize(function->NDim()); + } + t4 = get_time(); + std::copy(cx, cx + function->NDim(), vx.data()); + t5 = get_time(); + + // convert to Minuit external parameters + for (unsigned i = 0; i < function->NDim(); i++) { + vx_external[i] = Int2ext(parameters[i], vx[i]); + } + + t6 = get_time(); + + if (vx != vx_fVal_cache) { + //#ifndef NDEBUG + // ++fVal_eval_counter; + //#endif + vx_fVal_cache = vx; + fVal = (*function)(vx_external.data()); // value of function at given points + // #ifndef NDEBUG + // std::cout << "NumericalDerivatorMinuit2::setup_differentiate, fVal evaluations: " << fVal_eval_counter + // << std::endl; + //#endif + } + t7 = get_time(); + + dfmin = 8. * precision.Eps2() * (std::abs(fVal) + Up); + vrysml = 8. * precision.Eps() * precision.Eps(); + + t8 = get_time(); + + // if (RooFit::MultiProcess::TaskManager::is_instantiated()) { + // oocxcoutD((TObject *) nullptr, Benchmarking2) << "NumericalDerivatorMinuit2::setup_differentiate on worker + // " + // << MultiProcess::TaskManager::instance()->get_worker_id() + // << ", timestamps: " << t1 << " " << t2 << " " << t3 << " " + // << t4 + // << " " << t5 << " " << t6 << " " << t7 << " " << t8 << + // std::endl; + // } +} + +MinuitDerivatorElement +NumericalDerivatorMinuit2::partial_derivative(const ROOT::Math::IBaseFunctionMultiDim *function, const double *x, + const std::vector ¶meters, + unsigned int i_component, MinuitDerivatorElement previous) +{ + setup_differentiate(function, x, parameters); + return fast_partial_derivative(function, parameters, i_component, previous); +} + +// leaves the parameter setup to the caller +MinuitDerivatorElement NumericalDerivatorMinuit2::fast_partial_derivative(const ROOT::Math::IBaseFunctionMultiDim *function, + const std::vector ¶meters, + unsigned int ix, const MinuitDerivatorElement& previous) +{ + MinuitDerivatorElement deriv {previous}; + + double xtf = vx[ix]; + double epspri = precision.Eps2() + std::abs(deriv.derivative * precision.Eps2()); + double step_old = 0.; + + for (unsigned int j = 0; j < fNCycles; ++j) { + double optstp = std::sqrt(dfmin / (std::abs(deriv.second_derivative) + epspri)); + double step = std::max(optstp, std::abs(0.1 * deriv.step_size)); + + if (parameters[ix].IsBound()) { + if (step > 0.5) + step = 0.5; + } + + double stpmax = 10. * std::abs(deriv.step_size); + if (step > stpmax) + step = stpmax; + + double stpmin = std::max(vrysml, 8. * std::abs(precision.Eps2() * vx[ix])); + if (step < stpmin) + step = stpmin; + if (std::abs((step - step_old) / step) < fStepTolerance) { + break; + } + deriv.step_size = step; + step_old = step; + vx[ix] = xtf + step; + vx_external[ix] = Int2ext(parameters[ix], vx[ix]); + double fs1 = (*function)(vx_external.data()); + + vx[ix] = xtf - step; + vx_external[ix] = Int2ext(parameters[ix], vx[ix]); + double fs2 = (*function)(vx_external.data()); + + vx[ix] = xtf; + vx_external[ix] = Int2ext(parameters[ix], vx[ix]); + + double fGrd_old = deriv.derivative; + deriv.derivative = 0.5 * (fs1 - fs2) / step; + + deriv.second_derivative = (fs1 + fs2 - 2. * fVal) / step / step; + + if (std::abs(fGrd_old - deriv.derivative) / (std::abs(deriv.derivative) + dfmin / step) < fGradTolerance) { + break; + } + } + return deriv; +} + +MinuitDerivatorElement +NumericalDerivatorMinuit2::operator()(const ROOT::Math::IBaseFunctionMultiDim *function, const double *x, + const std::vector ¶meters, + unsigned int i_component, const MinuitDerivatorElement& previous) +{ + return partial_derivative(function, x, parameters, i_component, previous); +} + +std::vector +NumericalDerivatorMinuit2::Differentiate(const ROOT::Math::IBaseFunctionMultiDim *function, const double *cx, + const std::vector ¶meters, + const std::vector& previous_gradient) +{ + setup_differentiate(function, cx, parameters); + + std::vector gradient; + gradient.reserve(function->NDim()); + + for (unsigned int ix = 0; ix < function->NDim(); ++ix) { + gradient.emplace_back(fast_partial_derivative(function, parameters, ix, previous_gradient[ix])); + } + + return gradient; +} + + +double NumericalDerivatorMinuit2::Int2ext(const ROOT::Fit::ParameterSettings ¶meter, double val) const +{ + // return external value from internal value for parameter i + if (parameter.IsBound()) { + if (parameter.IsDoubleBound()) { + return fDoubleLimTrafo.Int2ext(val, parameter.UpperLimit(), parameter.LowerLimit()); + } else if (parameter.HasUpperLimit() && !parameter.HasLowerLimit()) { + return fUpperLimTrafo.Int2ext(val, parameter.UpperLimit()); + } else { + return fLowerLimTrafo.Int2ext(val, parameter.LowerLimit()); + } + } + + return val; +} + +double NumericalDerivatorMinuit2::Ext2int(const ROOT::Fit::ParameterSettings ¶meter, double val) const +{ + // return the internal value for parameter i with external value val + + if (parameter.IsBound()) { + if (parameter.IsDoubleBound()) { + return fDoubleLimTrafo.Ext2int(val, parameter.UpperLimit(), parameter.LowerLimit(), precision); + } else if (parameter.HasUpperLimit() && !parameter.HasLowerLimit()) { + return fUpperLimTrafo.Ext2int(val, parameter.UpperLimit(), precision); + } else { + return fLowerLimTrafo.Ext2int(val, parameter.LowerLimit(), precision); + } + } + + return val; +} + +double NumericalDerivatorMinuit2::DInt2Ext(const ROOT::Fit::ParameterSettings ¶meter, double val) const +{ + // return the derivative of the int->ext transformation: dPext(i) / dPint(i) + // for the parameter i with value val + + double dd = 1.; + if (parameter.IsBound()) { + if (parameter.IsDoubleBound()) { + dd = fDoubleLimTrafo.DInt2Ext(val, parameter.UpperLimit(), parameter.LowerLimit()); + } else if (parameter.HasUpperLimit() && !parameter.HasLowerLimit()) { + dd = fUpperLimTrafo.DInt2Ext(val, parameter.UpperLimit()); + } else { + dd = fLowerLimTrafo.DInt2Ext(val, parameter.LowerLimit()); + } + } + + return dd; +} + +double NumericalDerivatorMinuit2::D2Int2Ext(const ROOT::Fit::ParameterSettings ¶meter, double val) const +{ + double dd = 1.; + if (parameter.IsBound()) { + if (parameter.IsDoubleBound()) { + dd = fDoubleLimTrafo.D2Int2Ext(val, parameter.UpperLimit(), parameter.LowerLimit()); + } else if (parameter.HasUpperLimit() && !parameter.HasLowerLimit()) { + dd = fUpperLimTrafo.D2Int2Ext(val, parameter.UpperLimit()); + } else { + dd = fLowerLimTrafo.D2Int2Ext(val, parameter.LowerLimit()); + } + } + + return dd; +} + +double NumericalDerivatorMinuit2::GStepInt2Ext(const ROOT::Fit::ParameterSettings ¶meter, double val) const +{ + double dd = 1.; + if (parameter.IsBound()) { + if (parameter.IsDoubleBound()) { + dd = fDoubleLimTrafo.GStepInt2Ext(val, parameter.UpperLimit(), parameter.LowerLimit()); + } else if (parameter.HasUpperLimit() && !parameter.HasLowerLimit()) { + dd = fUpperLimTrafo.GStepInt2Ext(val, parameter.UpperLimit()); + } else { + dd = fLowerLimTrafo.GStepInt2Ext(val, parameter.LowerLimit()); + } + } + + return dd; +} + +// MODIFIED: +// This function was not implemented as in Minuit2. Now it copies the behavior +// of InitialGradientCalculator. See https://github.com/roofit-dev/root/issues/10 +void NumericalDerivatorMinuit2::SetInitialGradient(const ROOT::Math::IBaseFunctionMultiDim *function, + const std::vector ¶meters, + std::vector& gradient) +{ + // set an initial gradient using some given steps + // (used in the first iteration) + auto get_time = []() { + return std::chrono::duration_cast( + std::chrono::high_resolution_clock::now().time_since_epoch()) + .count(); + }; + decltype(get_time()) t1, t2; + + RooWallTimer timer; + t1 = get_time(); + + assert(function != nullptr && "function is a nullptr"); + + double eps2 = precision.Eps2(); + + unsigned ix = 0; + for (auto parameter = parameters.begin(); parameter != parameters.end(); ++parameter, ++ix) { + // What Minuit2 calls "Error" is stepsize on the ROOT side. + double werr = parameter->StepSize(); + + // Actually, sav in Minuit2 is the external parameter value, so that is + // what we called var before and var is unnecessary here. + double sav = parameter->Value(); + + // However, we do need var below, so let's calculate it using Ext2int: + double var = Ext2int(*parameter, sav); + +// std::cout DEBUG_STREAM(ix) DEBUG_STREAM(werr) DEBUG_STREAM(sav) DEBUG_STREAM(var); + + if (_always_exactly_mimic_minuit2) { + // this transformation can lose a few bits, but Minuit2 does it too + sav = Int2ext(*parameter, var); + } + + double sav2 = sav + werr; + +// std::cout DEBUG_STREAM(sav) DEBUG_STREAM(sav2); + + if (parameter->HasUpperLimit() && sav2 > parameter->UpperLimit()) { + sav2 = parameter->UpperLimit(); + } + + double var2 = Ext2int(*parameter, sav2); + double vplu = var2 - var; + +// std::cout DEBUG_STREAM(sav2) DEBUG_STREAM(var2) DEBUG_STREAM(vplu); + + sav2 = sav - werr; + +// std::cout DEBUG_STREAM(sav2); + + if (parameter->HasLowerLimit() && sav2 < parameter->LowerLimit()) { + sav2 = parameter->LowerLimit(); + } + + var2 = Ext2int(*parameter, sav2); + double vmin = var2 - var; + double gsmin = 8. * eps2 * (fabs(var) + eps2); + // protect against very small step sizes which can cause dirin to zero and then nan values in grd + double dirin = std::max(0.5 * (fabs(vplu) + fabs(vmin)), gsmin); + double g2 = 2.0 * Up / (dirin * dirin); + double gstep = std::max(gsmin, 0.1 * dirin); + double grd = g2 * dirin; + +// std::cout DEBUG_STREAM(sav2) DEBUG_STREAM(var2) DEBUG_STREAM(vmin) DEBUG_STREAM(gsmin) DEBUG_STREAM(dirin) DEBUG_STREAM(g2) DEBUG_STREAM(gstep) DEBUG_STREAM(grd); + + if (parameter->IsBound()) { + if (gstep > 0.5) + gstep = 0.5; + } + + gradient[ix].derivative = grd; + gradient[ix].second_derivative = g2; + gradient[ix].step_size = gstep; + +// std::cout DEBUG_STREAM(gstep) DEBUG_STREAM(gradient[ix].derivative) DEBUG_STREAM(gradient[ix].second_derivative) DEBUG_STREAM(gradient[ix].step_size) << std::endl; + } + + t2 = get_time(); + timer.stop(); +// oocxcoutD((TObject *)nullptr, Benchmarking1) +// << "SetInitialGradient time: " << timer.timing_s() << "s (from " << t1 << " to " << t2 << "ns)" << std::endl; +} + +bool NumericalDerivatorMinuit2::always_exactly_mimic_minuit2() const +{ + return _always_exactly_mimic_minuit2; +}; + +void NumericalDerivatorMinuit2::set_always_exactly_mimic_minuit2(bool flag) +{ + _always_exactly_mimic_minuit2 = flag; +} + +void NumericalDerivatorMinuit2::set_step_tolerance(double step_tolerance) +{ + fStepTolerance = step_tolerance; +} +void NumericalDerivatorMinuit2::set_grad_tolerance(double grad_tolerance) +{ + fGradTolerance = grad_tolerance; +} +void NumericalDerivatorMinuit2::set_ncycles(unsigned int ncycles) +{ + fNCycles = ncycles; +} +void NumericalDerivatorMinuit2::set_error_level(double error_level) +{ + Up = error_level; +} + +std::ostream& operator<<(std::ostream& out, const MinuitDerivatorElement value){ + return out << "(derivative: " << value.derivative << ", second_derivative: " << value.second_derivative << ", step_size: " << value.step_size << ")"; +} + +} // namespace RooFit diff --git a/roofit/roofitcore/src/RooAbsArg.cxx b/roofit/roofitcore/src/RooAbsArg.cxx index 2f735fdccdd6b..8ecebb07468b6 100644 --- a/roofit/roofitcore/src/RooAbsArg.cxx +++ b/roofit/roofitcore/src/RooAbsArg.cxx @@ -74,7 +74,6 @@ for single nodes. #include "strlcpy.h" #include "RooSecondMoment.h" -#include "RooNameSet.h" #include "RooWorkspace.h" #include "RooMsgService.h" @@ -641,11 +640,12 @@ RooArgSet* RooAbsArg::getParameters(const RooArgSet* observables, bool stripDisc bool RooAbsArg::getParameters(const RooArgSet* observables, RooArgSet& outputSet, bool stripDisconnected) const { + using RooHelpers::getColonSeparatedNameString; // Check for cached parameter set if (_myws) { - RooNameSet nsetObs(observables ? *observables : RooArgSet()); - const RooArgSet *paramSet = _myws->set(Form("CACHE_PARAMS_OF_PDF_%s_FOR_OBS_%s", GetName(), nsetObs.content())); + auto nsetObs = getColonSeparatedNameString(observables ? *observables : RooArgSet()); + const RooArgSet *paramSet = _myws->set(Form("CACHE_PARAMS_OF_PDF_%s_FOR_OBS_%s", GetName(), nsetObs.c_str())); if (paramSet) { outputSet.add(*paramSet); return false; @@ -660,9 +660,9 @@ bool RooAbsArg::getParameters(const RooArgSet* observables, RooArgSet& outputSet outputSet.sort(); // Cache parameter set - if (_myws && outputSet.getSize() > 10) { - RooNameSet nsetObs(observables ? *observables : RooArgSet()); - _myws->defineSetInternal(Form("CACHE_PARAMS_OF_PDF_%s_FOR_OBS_%s", GetName(), nsetObs.content()), outputSet); + if (_myws && outputSet.size() > 10) { + auto nsetObs = getColonSeparatedNameString(observables ? *observables : RooArgSet()); + _myws->defineSetInternal(Form("CACHE_PARAMS_OF_PDF_%s_FOR_OBS_%s", GetName(), nsetObs.c_str()), outputSet); // cout << " caching parameters in workspace for pdf " << IsA()->GetName() << "::" << GetName() << endl ; } diff --git a/roofit/roofitcore/src/RooAbsMinimizerFcn.cxx b/roofit/roofitcore/src/RooAbsMinimizerFcn.cxx new file mode 100644 index 0000000000000..d1b46537ba10e --- /dev/null +++ b/roofit/roofitcore/src/RooAbsMinimizerFcn.cxx @@ -0,0 +1,539 @@ +/***************************************************************************** + * Project: RooFit * + * Package: RooFitCore * + * @(#)root/roofitcore:$Id$ + * Authors: * + * AL, Alfio Lazzaro, INFN Milan, alfio.lazzaro@mi.infn.it * + * * + * * + * Redistribution and use in source and binary forms, * + * with or without modification, are permitted according to the terms * + * listed in LICENSE (http://roofit.sourceforge.net/license.txt) * + *****************************************************************************/ + +#ifndef __ROOFIT_NOROOMINIMIZER + +////////////////////////////////////////////////////////////////////////////// +// +// RooAbsMinimizerFcn is an interface class to the ROOT::Math function +// for minimization. It contains only the "logistics" of synchronizing +// between Minuit and RooFit. Its subclasses implement actual interfacing +// to Minuit by subclassing IMultiGenFunction or IMultiGradFunction. +// + +#include "RooAbsMinimizerFcn.h" + +#include "RooAbsArg.h" +#include "RooAbsPdf.h" +#include "RooArgSet.h" +#include "RooRealVar.h" +#include "RooAbsRealLValue.h" +#include "RooMsgService.h" +#include "RooMinimizer.h" +#include "RooNaNPacker.h" + +#include "TMatrixDSym.h" + +#include +#include + +using namespace std; + +RooAbsMinimizerFcn::RooAbsMinimizerFcn(RooArgList paramList, RooMinimizer *context, bool verbose) + : _context(context), _verbose(verbose) +{ + // Examine parameter list + _floatParamList = (RooArgList *)paramList.selectByAttrib("Constant", kFALSE); + if (_floatParamList->getSize() > 1) { + _floatParamList->sort(); + } + _floatParamList->setName("floatParamList"); + + _constParamList = (RooArgList *)paramList.selectByAttrib("Constant", kTRUE); + if (_constParamList->getSize() > 1) { + _constParamList->sort(); + } + _constParamList->setName("constParamList"); + + // Remove all non-RooRealVar parameters from list (MINUIT cannot handle them) + for (unsigned int i = 0; i < _floatParamList->size(); ) { // Note: Counting loop, since removing from collection! + const RooAbsArg* arg = (*_floatParamList).at(i); + if (!arg->IsA()->InheritsFrom(RooAbsRealLValue::Class())) { + oocoutW(_context,Minimization) << "RooMinimizerFcn::RooMinimizerFcn: removing parameter " + << arg->GetName() << " from list because it is not of type RooRealVar" << endl; + _floatParamList->remove(*arg); + } else { + ++i; + } + } + + _nDim = _floatParamList->getSize(); + + // Save snapshot of initial lists + _initFloatParamList = (RooArgList *)_floatParamList->snapshot(kFALSE); + _initConstParamList = (RooArgList *)_constParamList->snapshot(kFALSE); +} + +RooAbsMinimizerFcn::RooAbsMinimizerFcn(const RooAbsMinimizerFcn &other) + : _context(other._context), _maxFCN(other._maxFCN), + _funcOffset(other._funcOffset), + _recoverFromNaNStrength(other._recoverFromNaNStrength), + _numBadNLL(other._numBadNLL), + _printEvalErrors(other._printEvalErrors), _evalCounter(other._evalCounter), + _nDim(other._nDim), _optConst(other._optConst), _logfile(other._logfile), _doEvalErrorWall(other._doEvalErrorWall), _verbose(other._verbose) +{ + _floatParamList = new RooArgList(*other._floatParamList); + _constParamList = new RooArgList(*other._constParamList); + _initFloatParamList = (RooArgList *)other._initFloatParamList->snapshot(kFALSE); + _initConstParamList = (RooArgList *)other._initConstParamList->snapshot(kFALSE); +} + +RooAbsMinimizerFcn::~RooAbsMinimizerFcn() +{ + delete _floatParamList; + delete _initFloatParamList; + delete _constParamList; + delete _initConstParamList; +} + +/// Internal function to synchronize TMinimizer with current +/// information in RooAbsReal function parameters +Bool_t RooAbsMinimizerFcn::synchronize_parameter_settings(std::vector ¶meters, Bool_t optConst, Bool_t verbose) +{ + Bool_t constValChange(kFALSE); + Bool_t constStatChange(kFALSE); + + Int_t index(0); + + // Handle eventual migrations from constParamList -> floatParamList + for (index = 0; index < _constParamList->getSize(); index++) { + + RooRealVar *par = dynamic_cast(_constParamList->at(index)); + if (!par) + continue; + + RooRealVar *oldpar = dynamic_cast(_initConstParamList->at(index)); + if (!oldpar) + continue; + + // Test if constness changed + if (!par->isConstant()) { + + // Remove from constList, add to floatList + _constParamList->remove(*par); + _floatParamList->add(*par); + _initFloatParamList->addClone(*oldpar); + _initConstParamList->remove(*oldpar); + constStatChange = kTRUE; + _nDim++; + + if (verbose) { + oocoutI(_context, Minimization) + << "RooAbsMinimizerFcn::synchronize: parameter " << par->GetName() << " is now floating." << endl; + } + } + + // Test if value changed + if (par->getVal() != oldpar->getVal()) { + constValChange = kTRUE; + if (verbose) { + oocoutI(_context, Minimization) + << "RooAbsMinimizerFcn::synchronize: value of constant parameter " << par->GetName() << " changed from " + << oldpar->getVal() << " to " << par->getVal() << endl; + } + } + } + + // Update reference list + *_initConstParamList = *_constParamList; + + // Synchronize MINUIT with function state + // Handle floatParamList + for (index = 0; index < _floatParamList->getSize(); index++) { + RooRealVar *par = dynamic_cast(_floatParamList->at(index)); + + if (!par) + continue; + + Double_t pstep(0); + Double_t pmin(0); + Double_t pmax(0); + + if (!par->isConstant()) { + + // Verify that floating parameter is indeed of type RooRealVar + if (!par->IsA()->InheritsFrom(RooRealVar::Class())) { + oocoutW(_context, Minimization) << "RooAbsMinimizerFcn::fit: Error, non-constant parameter " + << par->GetName() << " is not of type RooRealVar, skipping" << endl; + _floatParamList->remove(*par); + index--; + _nDim--; + continue; + } + // make sure the parameter are in dirty state to enable + // a real NLL computation when the minimizer calls the function the first time + // (see issue #7659) + par->setValueDirty(); + + // Set the limits, if not infinite + if (par->hasMin()) + pmin = par->getMin(); + if (par->hasMax()) + pmax = par->getMax(); + + // Calculate step size + pstep = par->getError(); + if (pstep <= 0) { + // Floating parameter without error estitimate + if (par->hasMin() && par->hasMax()) { + pstep = 0.1 * (pmax - pmin); + + // Trim default choice of error if within 2 sigma of limit + if (pmax - par->getVal() < 2 * pstep) { + pstep = (pmax - par->getVal()) / 2; + } else if (par->getVal() - pmin < 2 * pstep) { + pstep = (par->getVal() - pmin) / 2; + } + + // If trimming results in zero error, restore default + if (pstep == 0) { + pstep = 0.1 * (pmax - pmin); + } + + } else { + pstep = 1; + } + if (verbose) { + oocoutW(_context, Minimization) + << "RooAbsMinimizerFcn::synchronize: WARNING: no initial error estimate available for " + << par->GetName() << ": using " << pstep << endl; + } + } + } else { + pmin = par->getVal(); + pmax = par->getVal(); + } + + // new parameter + if (index >= Int_t(parameters.size())) { + + if (par->hasMin() && par->hasMax()) { + parameters.emplace_back(par->GetName(), par->getVal(), pstep, pmin, pmax); + } else { + parameters.emplace_back(par->GetName(), par->getVal(), pstep); + if (par->hasMin()) + parameters.back().SetLowerLimit(pmin); + else if (par->hasMax()) + parameters.back().SetUpperLimit(pmax); + } + + continue; + } + + Bool_t oldFixed = parameters[index].IsFixed(); + Double_t oldVar = parameters[index].Value(); + Double_t oldVerr = parameters[index].StepSize(); + Double_t oldVlo = parameters[index].LowerLimit(); + Double_t oldVhi = parameters[index].UpperLimit(); + + if (par->isConstant() && !oldFixed) { + + // Parameter changes floating -> constant : update only value if necessary + if (oldVar != par->getVal()) { + parameters[index].SetValue(par->getVal()); + if (verbose) { + oocoutI(_context, Minimization) + << "RooAbsMinimizerFcn::synchronize: value of parameter " << par->GetName() << " changed from " + << oldVar << " to " << par->getVal() << endl; + } + } + parameters[index].Fix(); + constStatChange = kTRUE; + if (verbose) { + oocoutI(_context, Minimization) + << "RooAbsMinimizerFcn::synchronize: parameter " << par->GetName() << " is now fixed." << endl; + } + + } else if (par->isConstant() && oldFixed) { + + // Parameter changes constant -> constant : update only value if necessary + if (oldVar != par->getVal()) { + parameters[index].SetValue(par->getVal()); + constValChange = kTRUE; + + if (verbose) { + oocoutI(_context, Minimization) + << "RooAbsMinimizerFcn::synchronize: value of fixed parameter " << par->GetName() << " changed from " + << oldVar << " to " << par->getVal() << endl; + } + } + + } else { + // Parameter changes constant -> floating + if (!par->isConstant() && oldFixed) { + parameters[index].Release(); + constStatChange = kTRUE; + + if (verbose) { + oocoutI(_context, Minimization) + << "RooAbsMinimizerFcn::synchronize: parameter " << par->GetName() << " is now floating." << endl; + } + } + + // Parameter changes constant -> floating : update all if necessary + if (oldVar != par->getVal() || oldVlo != pmin || oldVhi != pmax || oldVerr != pstep) { + parameters[index].SetValue(par->getVal()); + parameters[index].SetStepSize(pstep); + if (par->hasMin() && par->hasMax()) + parameters[index].SetLimits(pmin, pmax); + else if (par->hasMin()) + parameters[index].SetLowerLimit(pmin); + else if (par->hasMax()) + parameters[index].SetUpperLimit(pmax); + } + + // Inform user about changes in verbose mode + if (verbose) { + // if ierr<0, par was moved from the const list and a message was already printed + + if (oldVar != par->getVal()) { + oocoutI(_context, Minimization) + << "RooAbsMinimizerFcn::synchronize: value of parameter " << par->GetName() << " changed from " + << oldVar << " to " << par->getVal() << endl; + } + if (oldVlo != pmin || oldVhi != pmax) { + oocoutI(_context, Minimization) + << "RooAbsMinimizerFcn::synchronize: limits of parameter " << par->GetName() << " changed from [" + << oldVlo << "," << oldVhi << "] to [" << pmin << "," << pmax << "]" << endl; + } + + // If oldVerr=0, then parameter was previously fixed + if (oldVerr != pstep && oldVerr != 0) { + oocoutI(_context, Minimization) + << "RooAbsMinimizerFcn::synchronize: error/step size of parameter " << par->GetName() + << " changed from " << oldVerr << " to " << pstep << endl; + } + } + } + } + + if (optConst) { + optimizeConstantTerms(constStatChange, constValChange); + } + + return 0; +} + +Bool_t +RooAbsMinimizerFcn::Synchronize(std::vector ¶meters, Bool_t optConst, Bool_t verbose) { + return synchronize_parameter_settings(parameters, optConst, verbose); +} + +/// Modify PDF parameter error by ordinal index (needed by MINUIT) +void RooAbsMinimizerFcn::SetPdfParamErr(Int_t index, Double_t value) +{ + static_cast(_floatParamList->at(index))->setError(value); +} + +/// Modify PDF parameter error by ordinal index (needed by MINUIT) +void RooAbsMinimizerFcn::ClearPdfParamAsymErr(Int_t index) +{ + static_cast(_floatParamList->at(index))->removeAsymError(); +} + +/// Modify PDF parameter error by ordinal index (needed by MINUIT) +void RooAbsMinimizerFcn::SetPdfParamErr(Int_t index, Double_t loVal, Double_t hiVal) +{ + static_cast(_floatParamList->at(index))->setAsymError(loVal, hiVal); +} + +/// Transfer MINUIT fit results back into RooFit objects. +void RooAbsMinimizerFcn::BackProp(const ROOT::Fit::FitResult &results) +{ + for (unsigned int index = 0; index < _nDim; index++) { + Double_t value = results.Value(index); + SetPdfParamVal(index, value); + + // Set the parabolic error + Double_t err = results.Error(index); + SetPdfParamErr(index, err); + + Double_t eminus = results.LowerError(index); + Double_t eplus = results.UpperError(index); + + if (eplus > 0 || eminus < 0) { + // Store the asymmetric error, if it is available + SetPdfParamErr(index, eminus, eplus); + } else { + // Clear the asymmetric error + ClearPdfParamAsymErr(index); + } + } +} + +/// Change the file name for logging of a RooMinimizer of all MINUIT steppings +/// through the parameter space. If inLogfile is null, the current log file +/// is closed and logging is stopped. +Bool_t RooAbsMinimizerFcn::SetLogFile(const char *inLogfile) +{ + if (_logfile) { + oocoutI(_context, Minimization) << "RooAbsMinimizerFcn::setLogFile: closing previous log file" << endl; + _logfile->close(); + delete _logfile; + _logfile = 0; + } + _logfile = new ofstream(inLogfile); + if (!_logfile->good()) { + oocoutI(_context, Minimization) << "RooAbsMinimizerFcn::setLogFile: cannot open file " << inLogfile << endl; + _logfile->close(); + delete _logfile; + _logfile = 0; + } + + return kFALSE; +} + +/// Apply results of given external covariance matrix. i.e. propagate its errors +/// to all RRV parameter representations and give this matrix instead of the +/// HESSE matrix at the next save() call +void RooAbsMinimizerFcn::ApplyCovarianceMatrix(TMatrixDSym &V) +{ + for (unsigned int i = 0; i < _nDim; i++) { + // Skip fixed parameters + if (_floatParamList->at(i)->isConstant()) { + continue; + } + SetPdfParamErr(i, sqrt(V(i, i))); + } +} + +/// Set value of parameter i. +Bool_t RooAbsMinimizerFcn::SetPdfParamVal(int index, double value) const +{ + auto par = static_cast(&(*_floatParamList)[index]); + + if (par->getVal()!=value) { + if (_verbose) cout << par->GetName() << "=" << value << ", " ; + + par->setVal(value); + return kTRUE; + } + + return kFALSE; +} + + +/// Print information about why evaluation failed. +/// Using _printEvalErrors, the number of errors printed can be steered. +/// Negative values disable printing. +void RooAbsMinimizerFcn::printEvalErrors() const { + if (_printEvalErrors < 0) + return; + + std::ostringstream msg; + if (_doEvalErrorWall) { + msg << "RooMinimizerFcn: Minimized function has error status." << endl + << "Returning maximum FCN so far (" << _maxFCN + << ") to force MIGRAD to back out of this region. Error log follows.\n"; + } else { + msg << "RooMinimizerFcn: Minimized function has error status but is ignored.\n"; + } + + msg << "Parameter values: " ; + for (const auto par : *_floatParamList) { + auto var = static_cast(par); + msg << "\t" << var->GetName() << "=" << var->getVal() ; + } + msg << std::endl; + + RooAbsReal::printEvalErrors(msg, _printEvalErrors); + ooccoutW(_context,Minimization) << msg.str() << endl; +} + + +//////////////////////////////////////////////////////////////////////////////// +/// Logistics + +RooArgList *RooAbsMinimizerFcn::GetFloatParamList() +{ + return _floatParamList; +} +RooArgList *RooAbsMinimizerFcn::GetConstParamList() +{ + return _constParamList; +} +RooArgList *RooAbsMinimizerFcn::GetInitFloatParamList() +{ + return _initFloatParamList; +} +RooArgList *RooAbsMinimizerFcn::GetInitConstParamList() +{ + return _initConstParamList; +} + +void RooAbsMinimizerFcn::SetEvalErrorWall(Bool_t flag) +{ + _doEvalErrorWall = flag; +} +void RooAbsMinimizerFcn::SetPrintEvalErrors(Int_t numEvalErrors) +{ + _printEvalErrors = numEvalErrors; +} + +Double_t &RooAbsMinimizerFcn::GetMaxFCN() +{ + return _maxFCN; +} +Int_t RooAbsMinimizerFcn::GetNumInvalidNLL() const +{ + return _numBadNLL; +} + +Int_t RooAbsMinimizerFcn::evalCounter() const +{ + return _evalCounter; +} +void RooAbsMinimizerFcn::zeroEvalCount() +{ + _evalCounter = 0; +} + +void RooAbsMinimizerFcn::SetVerbose(Bool_t flag) +{ + _verbose = flag; +} + +bool RooAbsMinimizerFcn::getOptConst() +{ + return _optConst; +} + + +//Double_t RooAbsMinimizerFcn::GetPdfParamVal(Int_t index) +//{ +// // Access PDF parameter value by ordinal index (needed by MINUIT) +// +// return ((RooRealVar *)_floatParamList->at(index))->getVal(); +//} +// +//Double_t RooAbsMinimizerFcn::GetPdfParamErr(Int_t index) +//{ +// // Access PDF parameter error by ordinal index (needed by MINUIT) +// return ((RooRealVar *)_floatParamList->at(index))->getError(); +//} + +std::vector RooAbsMinimizerFcn::get_parameter_values() const +{ + // TODO: make a cache for this somewhere so it doesn't have to be recreated on each call + std::vector values; + values.reserve(_nDim); + + for (std::size_t index = 0; index < _nDim; ++index) { + RooRealVar *par = (RooRealVar *)_floatParamList->at(index); + values.push_back(par->getVal()); + } + + return values; +} + +#endif diff --git a/roofit/roofitcore/src/RooAbsOptTestStatistic.cxx b/roofit/roofitcore/src/RooAbsOptTestStatistic.cxx index 42ae9f78af5c2..7a2cb43e060e6 100644 --- a/roofit/roofitcore/src/RooAbsOptTestStatistic.cxx +++ b/roofit/roofitcore/src/RooAbsOptTestStatistic.cxx @@ -770,33 +770,33 @@ Bool_t RooAbsOptTestStatistic::setDataSlave(RooAbsData& indata, Bool_t cloneData //////////////////////////////////////////////////////////////////////////////// -RooAbsData& RooAbsOptTestStatistic::data() -{ +RooAbsData& RooAbsOptTestStatistic::data() +{ if (_sealed) { Bool_t notice = (sealNotice() && strlen(sealNotice())) ; - coutW(ObjectHandling) << "RooAbsOptTestStatistic::data(" << GetName() - << ") WARNING: object sealed by creator - access to data is not permitted: " + coutW(ObjectHandling) << "RooAbsOptTestStatistic::data(" << GetName() + << ") WARNING: object sealed by creator - access to data is not permitted: " << (notice?sealNotice():"") << endl ; static RooDataSet dummy ("dummy","dummy",RooArgSet()) ; return dummy ; } - return *_dataClone ; + return *_dataClone ; } //////////////////////////////////////////////////////////////////////////////// -const RooAbsData& RooAbsOptTestStatistic::data() const -{ +const RooAbsData& RooAbsOptTestStatistic::data() const +{ if (_sealed) { Bool_t notice = (sealNotice() && strlen(sealNotice())) ; - coutW(ObjectHandling) << "RooAbsOptTestStatistic::data(" << GetName() - << ") WARNING: object sealed by creator - access to data is not permitted: " + coutW(ObjectHandling) << "RooAbsOptTestStatistic::data(" << GetName() + << ") WARNING: object sealed by creator - access to data is not permitted: " << (notice?sealNotice():"") << endl ; static RooDataSet dummy ("dummy","dummy",RooArgSet()) ; return dummy ; } - return *_dataClone ; + return *_dataClone ; } diff --git a/roofit/roofitcore/src/RooAbsPdf.cxx b/roofit/roofitcore/src/RooAbsPdf.cxx index 04aef69c26b2f..90674bc08c925 100644 --- a/roofit/roofitcore/src/RooAbsPdf.cxx +++ b/roofit/roofitcore/src/RooAbsPdf.cxx @@ -191,6 +191,8 @@ called for each data event. using namespace std; +using RooHelpers::getColonSeparatedNameString; + ClassImp(RooAbsPdf); ClassImp(RooAbsPdf::GenSpec); @@ -583,15 +585,13 @@ Bool_t RooAbsPdf::syncNormalization(const RooArgSet* nset, Bool_t adjustProxies) std::unique_ptr intParams{normInt->getVariables()} ; - RooNameSet cacheParamNames ; - cacheParamNames.setNameList(cacheParamsStr) ; - std::unique_ptr cacheParams{cacheParamNames.select(*intParams)} ; + RooArgSet cacheParams = RooHelpers::selectFromArgSet(*intParams, cacheParamsStr); - if (cacheParams->getSize()>0) { - cxcoutD(Caching) << "RooAbsReal::createIntObj(" << GetName() << ") INFO: constructing " << cacheParams->getSize() - << "-dim value cache for integral over " << depList << " as a function of " << *cacheParams << " in range " << (nr?nr:"") << endl ; - string name = Form("%s_CACHE_[%s]",normInt->GetName(),cacheParams->contentsString().c_str()) ; - RooCachedReal* cachedIntegral = new RooCachedReal(name.c_str(),name.c_str(),*normInt,*cacheParams) ; + if (!cacheParams.empty()) { + cxcoutD(Caching) << "RooAbsReal::createIntObj(" << GetName() << ") INFO: constructing " << cacheParams.getSize() + << "-dim value cache for integral over " << depList << " as a function of " << cacheParams << " in range " << (nr?nr:"") << endl ; + string name = Form("%s_CACHE_[%s]",normInt->GetName(),cacheParams.contentsString().c_str()) ; + RooCachedReal* cachedIntegral = new RooCachedReal(name.c_str(),name.c_str(),*normInt,cacheParams) ; cachedIntegral->setInterpolationOrder(2) ; cachedIntegral->addOwnedComponents(*normInt) ; cachedIntegral->setCacheSource(kTRUE) ; @@ -987,10 +987,11 @@ RooAbsReal* RooAbsPdf::createNLL(RooAbsData& data, const RooLinkedList& cmdList) pc.defineInt("ext","Extended",0,2) ; pc.defineInt("numcpu","NumCPU",0,1) ; pc.defineInt("interleave","NumCPU",1,0) ; + pc.defineInt("cpuAffinity","CPUAffinity",0,1); pc.defineInt("verbose","Verbose",0,0) ; pc.defineInt("optConst","Optimize",0,0) ; pc.defineInt("cloneData","CloneData", 0, 2); - pc.defineSet("projDepSet","ProjectedObservables",0,0) ; + pc.defineObject("projDepSet","ProjectedObservables",0,0) ; pc.defineSet("cPars","Constrain",0,0) ; pc.defineSet("glObs","GlobalObservables",0,0) ; // pc.defineInt("constrAll","Constrained",0,0) ; @@ -1022,6 +1023,7 @@ RooAbsReal* RooAbsPdf::createNLL(RooAbsData& data, const RooLinkedList& cmdList) numcpu_strategy = 0; } RooFit::MPSplit interl = (RooFit::MPSplit) numcpu_strategy; + Bool_t cpuAffinity = static_cast(pc.getInt("cpuAffinity")); Int_t splitr = pc.getInt("splitRange") ; Bool_t verbose = pc.getInt("verbose") ; @@ -1096,7 +1098,7 @@ RooAbsReal* RooAbsPdf::createNLL(RooAbsData& data, const RooLinkedList& cmdList) } RooArgSet projDeps ; - RooArgSet* tmp = pc.getSet("projDepSet") ; + auto tmp = static_cast(pc.getObject("projDepSet")) ; if (tmp) { projDeps.add(*tmp) ; } @@ -1109,6 +1111,7 @@ RooAbsReal* RooAbsPdf::createNLL(RooAbsData& data, const RooLinkedList& cmdList) cfg.addCoefRangeName = addCoefRangeName ? addCoefRangeName : ""; cfg.nCPU = numcpu; cfg.interleave = interl; + cfg.CPUAffinity = cpuAffinity; cfg.verbose = verbose; cfg.splitCutRange = static_cast(splitr); cfg.cloneInputData = static_cast(cloneData); @@ -1156,11 +1159,11 @@ RooAbsReal* RooAbsPdf::createNLL(RooAbsData& data, const RooLinkedList& cmdList) // Collect internal and external constraint specifications RooArgSet allConstraints ; - if (_myws && _myws->set(Form("CACHE_CONSTR_OF_PDF_%s_FOR_OBS_%s", GetName(), RooNameSet(*data.get()).content()))) { + if (_myws && _myws->set(Form("CACHE_CONSTR_OF_PDF_%s_FOR_OBS_%s", GetName(), getColonSeparatedNameString(*data.get()).c_str()))) { // retrieve from cache const RooArgSet *constr = - _myws->set(Form("CACHE_CONSTR_OF_PDF_%s_FOR_OBS_%s", GetName(), RooNameSet(*data.get()).content())); + _myws->set(Form("CACHE_CONSTR_OF_PDF_%s_FOR_OBS_%s", GetName(), getColonSeparatedNameString(*data.get()).c_str())); coutI(Minimization) << "createNLL picked up cached constraints from workspace with " << constr->getSize() << " entries" << endl; allConstraints.add(*constr); @@ -1180,10 +1183,10 @@ RooAbsReal* RooAbsPdf::createNLL(RooAbsData& data, const RooLinkedList& cmdList) if (_myws) { // cout << "createNLL: creating cache for allconstraints=" << allConstraints << endl ; coutI(Minimization) << "createNLL: caching constraint set under name " - << Form("CONSTR_OF_PDF_%s_FOR_OBS_%s", GetName(), RooNameSet(*data.get()).content()) + << Form("CONSTR_OF_PDF_%s_FOR_OBS_%s", GetName(), getColonSeparatedNameString(*data.get()).c_str()) << " with " << allConstraints.getSize() << " entries" << endl; _myws->defineSetInternal( - Form("CACHE_CONSTR_OF_PDF_%s_FOR_OBS_%s", GetName(), RooNameSet(*data.get()).content()), allConstraints); + Form("CACHE_CONSTR_OF_PDF_%s_FOR_OBS_%s", GetName(), getColonSeparatedNameString(*data.get()).c_str()), allConstraints); } } @@ -1510,7 +1513,7 @@ RooFitResult* RooAbsPdf::fitTo(RooAbsData& data, const RooLinkedList& cmdList) RooLinkedList fitCmdList(cmdList) ; RooLinkedList nllCmdList = pc.filterCmdList(fitCmdList,"ProjectedObservables,Extended,Range," - "RangeWithName,SumCoefRange,NumCPU,SplitRange,Constrained,Constrain,ExternalConstraints," + "RangeWithName,SumCoefRange,NumCPU,CPUAffinity,SplitRange,Constrained,Constrain,ExternalConstraints," "CloneData,GlobalObservables,GlobalObservablesTag,OffsetLikelihood,BatchMode,IntegrateBins"); pc.defineDouble("prefit", "Prefit",0,0); @@ -1527,6 +1530,7 @@ RooFitResult* RooAbsPdf::fitTo(RooAbsData& data, const RooLinkedList& cmdList) pc.defineInt("minos","Minos",0,0) ; pc.defineInt("ext","Extended",0,2) ; pc.defineInt("numcpu","NumCPU",0,1) ; + pc.defineInt("cpuAffinity","CPUAffinity",0,1) ; pc.defineInt("numee","PrintEvalErrors",0,10) ; pc.defineInt("doEEWall","EvalErrorWall",0,1) ; pc.defineInt("doWarn","Warnings",0,1) ; @@ -1871,7 +1875,7 @@ RooFitResult* RooAbsPdf::chi2FitTo(RooDataHist& data, const RooLinkedList& cmdLi // Pull arguments to be passed to chi2 construction from list RooLinkedList fitCmdList(cmdList) ; - RooLinkedList chi2CmdList = pc.filterCmdList(fitCmdList,"Range,RangeWithName,NumCPU,Optimize,ProjectedObservables,AddCoefRange,SplitRange,DataError,Extended,IntegrateBins") ; + RooLinkedList chi2CmdList = pc.filterCmdList(fitCmdList,"Range,RangeWithName,NumCPU,CPUAffinity,Optimize,ProjectedObservables,AddCoefRange,SplitRange,DataError,Extended,IntegrateBins") ; RooAbsReal* chi2 = createChi2(data,chi2CmdList) ; RooFitResult* ret = chi2FitDriver(*chi2,fitCmdList) ; @@ -3715,3 +3719,13 @@ void RooAbsPdf::setNormRangeOverride(const char* rangeName) _norm = 0 ; } } + +//////////////////////////////////////////////////////////////////////////////// + +Bool_t RooAbsPdf::num_int_timing_flag() const { + return getAttribute("num_int_timing_on"); +} + +void RooAbsPdf::set_num_int_timing_flag(Bool_t flag) { + setAttribute("num_int_timing_on", flag); +} \ No newline at end of file diff --git a/roofit/roofitcore/src/RooAbsReal.cxx b/roofit/roofitcore/src/RooAbsReal.cxx index 2b49641e9aa3d..1acb056470570 100644 --- a/roofit/roofitcore/src/RooAbsReal.cxx +++ b/roofit/roofitcore/src/RooAbsReal.cxx @@ -701,15 +701,13 @@ RooAbsReal* RooAbsReal::createIntObj(const RooArgSet& iset2, const RooArgSet* ns RooArgSet* intParams = integral->getVariables() ; - RooNameSet cacheParamNames ; - cacheParamNames.setNameList(cacheParamsStr) ; - RooArgSet* cacheParams = cacheParamNames.select(*intParams) ; - - if (cacheParams->getSize()>0) { - cxcoutD(Caching) << "RooAbsReal::createIntObj(" << GetName() << ") INFO: constructing " << cacheParams->getSize() - << "-dim value cache for integral over " << iset2 << " as a function of " << *cacheParams << " in range " << (rangeName?rangeName:"") << endl ; - string name = Form("%s_CACHE_[%s]",integral->GetName(),cacheParams->contentsString().c_str()) ; - RooCachedReal* cachedIntegral = new RooCachedReal(name.c_str(),name.c_str(),*integral,*cacheParams) ; + RooArgSet cacheParams = RooHelpers::selectFromArgSet(*intParams, cacheParamsStr); + + if (cacheParams.getSize()>0) { + cxcoutD(Caching) << "RooAbsReal::createIntObj(" << GetName() << ") INFO: constructing " << cacheParams.getSize() + << "-dim value cache for integral over " << iset2 << " as a function of " << cacheParams << " in range " << (rangeName?rangeName:"") << endl ; + string name = Form("%s_CACHE_[%s]",integral->GetName(),cacheParams.contentsString().c_str()) ; + RooCachedReal* cachedIntegral = new RooCachedReal(name.c_str(),name.c_str(),*integral,cacheParams) ; cachedIntegral->setInterpolationOrder(2) ; cachedIntegral->addOwnedComponents(*integral) ; cachedIntegral->setCacheSource(kTRUE) ; @@ -720,7 +718,6 @@ RooAbsReal* RooAbsReal::createIntObj(const RooArgSet& iset2, const RooArgSet* ns integral = cachedIntegral ; } - delete cacheParams ; delete intParams ; } @@ -1372,7 +1369,7 @@ TH1* RooAbsReal::createHistogram(const char *name, const RooAbsRealLValue& xvar, pc.defineObject("compSet","SelectCompSet",0) ; pc.defineString("compSpec","SelectCompSpec",0) ; - pc.defineSet("projObs","ProjectedObservables",0,0) ; + pc.defineObject("projObs","ProjectedObservables",0,0) ; pc.defineObject("yvar","YVar",0,0) ; pc.defineObject("zvar","ZVar",0,0) ; pc.defineMutex("SelectCompSet","SelectCompSpec") ; @@ -1397,7 +1394,7 @@ TH1* RooAbsReal::createHistogram(const char *name, const RooAbsRealLValue& xvar, vars.add(*zvar) ; } - RooArgSet* projObs = pc.getSet("projObs") ; + auto projObs = static_cast(pc.getObject("projObs")) ; RooArgSet* intObs = 0 ; Bool_t doScaling = pc.getInt("scaling") ; @@ -1618,6 +1615,8 @@ void RooAbsReal::plotOnCompSelect(RooArgSet* selNodes) const /// ///
`NumCPU(Int_t ncpu)` Number of CPUs to use simultaneously to calculate data-weighted projections (only in combination with ProjWData) /// +/// CPUAffinity(Bool_t flag) -- Set CPU affinity to fix multi core processes to their own CPU cores +/// /// ///
Misc content control ///
`PrintEvalErrors(Int_t numErr)` Control number of p.d.f evaluation errors printed per curve. A negative @@ -1780,6 +1779,7 @@ RooPlot* RooAbsReal::plotOn(RooPlot* frame, RooLinkedList& argList) const pc.defineInt("showProg","ShowProgress",0,0) ; pc.defineInt("numCPU","NumCPU",0,1) ; pc.defineInt("interleave","NumCPU",1,0) ; + pc.defineInt("cpuAffinity","CPUAffinity",0,1) ; pc.defineString("addToCurveName","AddTo",0,"") ; pc.defineDouble("addToWgtSelf","AddTo",0,1.) ; pc.defineDouble("addToWgtOther","AddTo",1,1.) ; @@ -1819,6 +1819,7 @@ RooPlot* RooAbsReal::plotOn(RooPlot* frame, RooLinkedList& argList) const o.projDataSet = (const RooArgSet*) pc.getObject("projDataSet") ; o.numCPU = pc.getInt("numCPU") ; o.interleave = (RooFit::MPSplit) pc.getInt("interleave") ; + o.CPUAffinity = static_cast(pc.getInt("cpuAffinity")); o.eeval = pc.getDouble("evalErrorVal") ; o.doeeval = pc.getInt("doEvalError") ; @@ -2196,6 +2197,7 @@ RooPlot* RooAbsReal::plotOn(RooPlot *frame, PlotOpt o) const RooAbsTestStatistic::Configuration cfg; cfg.nCPU = o.numCPU; cfg.interleave = o.interleave; + cfg.CPUAffinity=o.CPUAffinity; RooDataWeightedAverage dwa(Form("%sDataWgtAvg",GetName()),"Data Weighted average",*projection,*projDataSel,RooArgSet()/**projDataSel->get()*/, std::move(cfg), true) ; //RooDataWeightedAverage dwa(Form("%sDataWgtAvg",GetName()),"Data Weighted average",*projection,*projDataSel,*projDataSel->get(),o.numCPU,o.interleave,kTRUE) ; @@ -2570,6 +2572,7 @@ RooPlot* RooAbsReal::plotAsymOn(RooPlot *frame, const RooAbsCategoryLValue& asym RooAbsTestStatistic::Configuration cfg; cfg.nCPU = o.numCPU; cfg.interleave = o.interleave; + cfg.CPUAffinity=o.CPUAffinity; RooDataWeightedAverage dwa(Form("%sDataWgtAvg",GetName()),"Data Weighted average",*funcAsym,*projDataSel,RooArgSet()/**projDataSel->get()*/, std::move(cfg),true) ; //RooDataWeightedAverage dwa(Form("%sDataWgtAvg",GetName()),"Data Weighted average",*funcAsym,*projDataSel,*projDataSel->get(),o.numCPU,o.interleave,kTRUE) ; @@ -4357,6 +4360,7 @@ RooMultiGenFunction* RooAbsReal::iGenFunction(const RooArgSet& observables, cons ///
`Range(const char* name)` Fit only data inside range with given name ///
`Range(Double_t lo, Double_t hi)` Fit only data inside given range. A range named "fit" is created on the fly on all observables. /// Multiple comma separated range names can be specified. +///
`CPUAffinity(Bool_t)` Set CPU affinity to fix multi core processes to their own CPU cores ///
`NumCPU(int num)` Parallelize NLL calculation on num CPUs ///
`Optimize(Bool_t flag)` Activate constant term optimization (on by default) ///
`IntegrateBins()` Integrate PDF within each bin. This sets the desired precision. @@ -4408,7 +4412,7 @@ RooFitResult* RooAbsReal::chi2FitTo(RooDataHist& data, const RooLinkedList& cmdL // Pull arguments to be passed to chi2 construction from list RooLinkedList fitCmdList(cmdList) ; - RooLinkedList chi2CmdList = pc.filterCmdList(fitCmdList,"Range,RangeWithName,NumCPU,Optimize,IntegrateBins") ; + RooLinkedList chi2CmdList = pc.filterCmdList(fitCmdList,"Range,RangeWithName,NumCPU,CPUAffinity,Optimize,IntegrateBins") ; RooAbsReal* chi2 = createChi2(data,chi2CmdList) ; RooFitResult* ret = chi2FitDriver(*chi2,fitCmdList) ; @@ -4430,6 +4434,7 @@ RooFitResult* RooAbsReal::chi2FitTo(RooDataHist& data, const RooLinkedList& cmdL /// |-|----------------------------------------- /// | `DataError(RooAbsData::ErrorType)` | Choose between Poisson errors and Sum-of-weights errors /// | `NumCPU(Int_t)` | Activate parallel processing feature on N processes +/// | `CPUAffinity(Bool_t)` | Set CPU affinity to fix multi core processes to their own CPU cores /// | `Range()` | Calculate \f$ \chi^2 \f$ only in selected region /// | `IntegrateBins()` | Integrate PDF within each bin. This sets the desired precision. /// diff --git a/roofit/roofitcore/src/RooAbsTestStatistic.cxx b/roofit/roofitcore/src/RooAbsTestStatistic.cxx index 2c4f465cdcdd3..01d1d9c50cf44 100644 --- a/roofit/roofitcore/src/RooAbsTestStatistic.cxx +++ b/roofit/roofitcore/src/RooAbsTestStatistic.cxx @@ -48,11 +48,20 @@ combined in the main thread. #include "RooMsgService.h" #include "RooProdPdf.h" #include "RooRealSumPdf.h" +#include "RooConstVar.h" +#include "RooRealIntegral.h" #include "RooAbsCategoryLValue.h" #include "TTimeStamp.h" #include "TClass.h" #include +#include +#include + +// timing +#include "RooTimer.h" +// getpid and getppid: +#include "unistd.h" #include using namespace std; @@ -100,7 +109,8 @@ RooAbsTestStatistic::RooAbsTestStatistic(const char *name, const char *title, Ro _gofOpMode{(cfg.nCPU>1 || cfg.nCPU==-1) ? MPMaster : (dynamic_cast(_func) ? SimMaster : Slave)}, _nEvents{data.numEntries()}, _nCPU(cfg.nCPU != -1 ? cfg.nCPU : 1), - _mpinterl(cfg.interleave) + _mpinterl(cfg.interleave), + _CPUAffinity(cfg.CPUAffinity) { // Register all parameters as servers _paramSet.add(*std::unique_ptr{real.getParameters(&data)}); @@ -137,6 +147,7 @@ RooAbsTestStatistic::RooAbsTestStatistic(const RooAbsTestStatistic& other, const _gofSplitMode(other._gofSplitMode), _nCPU(other._nCPU != -1 ? other._nCPU : 1), _mpinterl(other._mpinterl), + _CPUAffinity(other._CPUAffinity), _doOffset(other._doOffset), _offset(other._offset), _evalCarry(other._evalCarry) @@ -214,7 +225,7 @@ Double_t RooAbsTestStatistic::evaluate() const return ret ; } else if (MPMaster == _gofOpMode) { - + // Start calculations in parallel for (Int_t i = 0; i < _nCPU; ++i) _mpfeArray[i]->calculate(); @@ -274,7 +285,7 @@ Double_t RooAbsTestStatistic::evaluate() const ret /= norm; _evalCarry /= norm; } - + return ret ; } @@ -290,7 +301,7 @@ Double_t RooAbsTestStatistic::evaluate() const Bool_t RooAbsTestStatistic::initialize() { if (_init) return kFALSE; - + if (MPMaster == _gofOpMode) { initMPMode(_func,_data,_projDeps,_rangeName,_addCoefRangeName) ; } else if (SimMaster == _gofOpMode) { @@ -301,7 +312,6 @@ Bool_t RooAbsTestStatistic::initialize() } - //////////////////////////////////////////////////////////////////////////////// /// Forward server redirect calls to component test statistics @@ -410,6 +420,7 @@ void RooAbsTestStatistic::initMPMode(RooAbsReal* real, RooAbsData* data, const R cfg.addCoefRangeName = addCoefRangeName; cfg.nCPU = 1; cfg.interleave = _mpinterl; + cfg.CPUAffinity=_CPUAffinity; cfg.verbose = _verbose; cfg.splitCutRange = _splitRange; // This configuration parameter is stored in the RooAbsOptTestStatistic. @@ -427,20 +438,55 @@ void RooAbsTestStatistic::initMPMode(RooAbsReal* real, RooAbsData* data, const R gof->SetTitle(Form("%s_GOF%d",GetTitle(),i)); ccoutD(Eval) << "RooAbsTestStatistic::initMPMode: starting remote server process #" << i << endl; - _mpfeArray[i] = new RooRealMPFE(Form("%s_%lx_MPFE%d",GetName(),(ULong_t)this,i),Form("%s_%lx_MPFE%d",GetTitle(),(ULong_t)this,i),*gof,false); + _mpfeArray[i] = new RooRealMPFE(Form("%s_%lx_MPFE%d",GetName(),(ULong_t)this,i),Form("%s_%lx_MPFE%d",GetTitle(),(ULong_t)this,i),*gof, i, _nCPU,false); //_mpfeArray[i]->setVerbose(kTRUE,kTRUE); _mpfeArray[i]->initialize(); if (i > 0) { _mpfeArray[i]->followAsSlave(*_mpfeArray[0]); } + + if (_CPUAffinity == kTRUE) { + _mpfeArray[i]->setCpuAffinity(i); + } } _mpfeArray[_nCPU - 1]->addOwnedComponents(*gof); coutI(Eval) << "RooAbsTestStatistic::initMPMode: started " << _nCPU << " remote server process." << endl; - //cout << "initMPMode --- done" << endl ; return ; } +//////////////////////////////////////////////////////////////////////////////// +/// Activate timing of numerical integral normalization terms in the pdf. +/// This function should be called from the process that evaluates the pdf. +/// Using RooRealMPFE this means it should be called from the serverLoop(). + +void RooAbsTestStatistic::_setNumIntTimingInPdfs(Bool_t flag) { + // find all pdf nodes with a normalization integral and set the activate timing flag on them + // Get list of branch nodes in expression + RooArgSet blist; + RooAbsArg* node; + + _func->branchNodeServerList(&blist); + + // Iterator over branch nodes + RooFIter iter = blist.fwdIterator(); + while((node = iter.next())) { + RooAbsPdf *pdfNode = dynamic_cast(node); + if (!pdfNode) continue; + // Skip self-normalized nodes + if (pdfNode->selfNormalized()) continue; + + // TODO: move everything below to RooAbsPdf::setNumIntTiming(RooArgSet& obsSet, Bool_t flag) and only call that here? Or do we not want to pass around the observables? + // set attribute on or off + pdfNode->set_num_int_timing_flag(flag); + + // Retrieve normalization integral object for branch nodes that are pdfs + RooRealIntegral *normint = const_cast(dynamic_cast(pdfNode->getNormIntegral(*(_data->get())))); + if (!normint) continue; + + normint->activateTimingNumInts(); + } +} //////////////////////////////////////////////////////////////////////////////// /// Initialize simultaneous p.d.f processing mode. Strip simultaneous @@ -456,7 +502,7 @@ void RooAbsTestStatistic::initSimMode(RooSimultaneous* simpdf, RooAbsData* data, RooAbsCategoryLValue& simCat = const_cast(simpdf->indexCat()); TString simCatName(simCat.GetName()); - TList* dsetList = const_cast(data)->split(simCat,processEmptyDataSets()); + std::unique_ptr dsetList{const_cast(data)->split(simCat, processEmptyDataSets())}; if (!dsetList) { coutE(Fitting) << "RooAbsTestStatistic::initSimMode(" << GetName() << ") ERROR: index category of simultaneous pdf is missing in dataset, aborting" << endl; throw std::runtime_error("RooAbsTestStatistic::initSimMode() ERROR, index category of simultaneous pdf is missing in dataset, aborting"); @@ -521,6 +567,7 @@ void RooAbsTestStatistic::initSimMode(RooSimultaneous* simpdf, RooAbsData* data, Configuration cfg; cfg.addCoefRangeName = addCoefRangeName; cfg.interleave = _mpinterl; + cfg.CPUAffinity=_CPUAffinity; cfg.verbose = _verbose; cfg.splitCutRange = _splitRange; cfg.binnedL = binnedL; @@ -556,15 +603,12 @@ void RooAbsTestStatistic::initSimMode(RooSimultaneous* simpdf, RooAbsData* data, // Servers may have been redirected between instantiation and (deferred) initialization - RooArgSet *actualParams = binnedPdf ? binnedPdf->getParameters(dset) : pdf->getParameters(dset); - RooArgSet* selTargetParams = (RooArgSet*) _paramSet.selectCommon(*actualParams); + std::unique_ptr actualParams{binnedPdf ? binnedPdf->getParameters(dset) : pdf->getParameters(dset)}; + std::unique_ptr selTargetParams{(RooArgSet*) _paramSet.selectCommon(*actualParams)}; _gofArray[n]->recursiveRedirectServers(*selTargetParams); - delete selTargetParams; - delete actualParams; - - ++n; + ++n; } else { if ((!dset || (0. != dset->sumEntries() && !processEmptyDataSets())) && pdf) { @@ -576,9 +620,15 @@ void RooAbsTestStatistic::initSimMode(RooSimultaneous* simpdf, RooAbsData* data, } } coutI(Fitting) << "RooAbsTestStatistic::initSimMode: created " << n << " slave calculators." << endl; - - dsetList->Delete(); // delete the content. - delete dsetList; + +// // Delete datasets by hand as TList::Delete() doesn't see our datasets as 'on the heap'... +// TIterator* iter = dsetList->MakeIterator(); +// TObject* ds; +// while((ds = iter->Next())) { +// delete ds; +// } +// delete iter; + // NOTE: commented out, because it actually causes two error messages, "Error in TList::Clear: A list is accessing an object (0x7f8320ae8f64) already deleted (list name = TList)" and another like that } diff --git a/roofit/roofitcore/src/RooAddModel.cxx b/roofit/roofitcore/src/RooAddModel.cxx index cc72e8e55aa21..40aee617b813d 100644 --- a/roofit/roofitcore/src/RooAddModel.cxx +++ b/roofit/roofitcore/src/RooAddModel.cxx @@ -741,16 +741,12 @@ Double_t RooAddModel::analyticalIntegralWN(Int_t code, const RooArgSet* normSet, // If cache has been sterilized, revive this slot if (cache==0) { - RooArgSet* vars = getParameters(RooArgSet()) ; - RooArgSet* nset = _intCacheMgr.nameSet1ByIndex(code-1)->select(*vars) ; - RooArgSet* iset = _intCacheMgr.nameSet2ByIndex(code-1)->select(*vars) ; + std::unique_ptr vars{getParameters(RooArgSet())} ; + RooArgSet nset = _intCacheMgr.selectFromSet1(*vars, code-1) ; + RooArgSet iset = _intCacheMgr.selectFromSet2(*vars, code-1) ; - Int_t code2(-1) ; - getCompIntList(nset,iset,compIntList,code2,rangeName) ; - - delete vars ; - delete nset ; - delete iset ; + int code2 = -1 ; + getCompIntList(&nset,&iset,compIntList,code2,rangeName) ; } else { compIntList = &cache->_intList ; diff --git a/roofit/roofitcore/src/RooAddition.cxx b/roofit/roofitcore/src/RooAddition.cxx index e90c8f11cdd3f..ed80856cea13f 100644 --- a/roofit/roofitcore/src/RooAddition.cxx +++ b/roofit/roofitcore/src/RooAddition.cxx @@ -303,9 +303,9 @@ Double_t RooAddition::analyticalIntegral(Int_t code, const char* rangeName) cons if (cache==0) { // cache got sterilized, trigger repopulation of this slot, then try again... std::unique_ptr vars( getParameters(RooArgSet()) ); - std::unique_ptr iset( _cacheMgr.nameSet2ByIndex(code-1)->select(*vars) ); + RooArgSet iset = _cacheMgr.selectFromSet2(*vars, code-1); RooArgSet dummy; - Int_t code2 = getAnalyticalIntegral(*iset,dummy,rangeName); + Int_t code2 = getAnalyticalIntegral(iset,dummy,rangeName); assert(code==code2); // must have revived the right (sterilized) slot... return analyticalIntegral(code2,rangeName); } diff --git a/roofit/roofitcore/src/RooChi2Var.cxx b/roofit/roofitcore/src/RooChi2Var.cxx index 93b8a88b66fc5..afc35142d6511 100644 --- a/roofit/roofitcore/src/RooChi2Var.cxx +++ b/roofit/roofitcore/src/RooChi2Var.cxx @@ -75,6 +75,7 @@ namespace { cfg.rangeName = RooCmdConfig::decodeStringOnTheFly("RooChi2Var::RooChi2Var","RangeWithName",0,"",args...); cfg.nCPU = RooCmdConfig::decodeIntOnTheFly("RooChi2Var::RooChi2Var","NumCPU",0,1,args...); cfg.interleave = RooFit::Interleave; + cfg.CPUAffinity = static_cast(RooCmdConfig::decodeIntOnTheFly("RooChi2Var::RooChi2Var","CPUAffinity",0,1,args...)); cfg.verbose = static_cast(RooCmdConfig::decodeIntOnTheFly("RooChi2Var::RooChi2Var","Verbose",0,1,args...)); cfg.cloneInputData = false; cfg.integrateOverBinsPrecision = RooCmdConfig::decodeDoubleOnTheFly("RooChi2Var::RooChi2Var", "IntegrateBins", 0, -1., {args...}); @@ -110,6 +111,8 @@ RooArgSet RooChi2Var::_emptySet ; ///
/// NumCPU() Activate parallel processing feature ///
+/// CPUAffinity() Set CPU affinity to pin parallel processes to their own CPU cores +///
/// Range() Fit only selected region ///
/// Verbose() Verbose output of GOF framework @@ -164,6 +167,8 @@ RooChi2Var::RooChi2Var(const char *name, const char* title, RooAbsReal& func, Ro ///
/// NumCPU() Activate parallel processing feature ///
+/// CPUAffinity() Set CPU affinity to pin parallel processes to their own CPU cores +///
/// Range() Fit only selected region ///
/// SumCoefRange() Set the range in which to interpret the coefficients of RooAddPdf components diff --git a/roofit/roofitcore/src/RooHashTable.cxx b/roofit/roofitcore/src/RooFitLegacy/RooHashTable.cxx similarity index 99% rename from roofit/roofitcore/src/RooHashTable.cxx rename to roofit/roofitcore/src/RooFitLegacy/RooHashTable.cxx index effd87a6a8a9f..41f828dac7b16 100644 --- a/roofit/roofitcore/src/RooHashTable.cxx +++ b/roofit/roofitcore/src/RooFitLegacy/RooHashTable.cxx @@ -14,11 +14,12 @@ * listed in LICENSE (http://roofit.sourceforge.net/license.txt) * *****************************************************************************/ +#include "RooFitLegacy/RooHashTable.h" + #include "RooFit.h" #include "TMath.h" #include "TCollection.h" -#include "RooHashTable.h" #include "RooLinkedList.h" #include "RooAbsArg.h" #include "RooSetPair.h" diff --git a/roofit/roofitcore/src/RooNameSet.cxx b/roofit/roofitcore/src/RooFitLegacy/RooNameSet.cxx similarity index 99% rename from roofit/roofitcore/src/RooNameSet.cxx rename to roofit/roofitcore/src/RooFitLegacy/RooNameSet.cxx index d010d1066809c..1bc7a260cd3c7 100644 --- a/roofit/roofitcore/src/RooNameSet.cxx +++ b/roofit/roofitcore/src/RooFitLegacy/RooNameSet.cxx @@ -17,7 +17,7 @@ /** \file RooNameSet.cxx \class RooNameSet -\ingroup Roofitcore +\ingroup Roofitlegacy RooNameSet is a utility class that stores the names the objects in a RooArget. This allows to preserve the contents of a RooArgSet @@ -26,16 +26,17 @@ the RooArgSet. A new RooArgSet can be created from a RooNameSet by offering it a list of new RooAbsArg objects. **/ -#include +#include "RooFitLegacy/RooNameSet.h" #include "RooFit.h" #include "Riostream.h" #include "TClass.h" -#include "RooNameSet.h" #include "RooArgSet.h" #include "RooArgList.h" +#include + ClassImp(RooNameSet); //////////////////////////////////////////////////////////////////////////////// diff --git a/roofit/roofitcore/src/RooGaussMinimizer.cxx b/roofit/roofitcore/src/RooGaussMinimizer.cxx new file mode 100644 index 0000000000000..00a6c01abe094 --- /dev/null +++ b/roofit/roofitcore/src/RooGaussMinimizer.cxx @@ -0,0 +1,275 @@ +/***************************************************************************** + * Project: RooFit * + * Package: RooFitCore * + * @(#)root/roofitcore:$Id$ + * Authors: * + * WV, Wouter Verkerke, UC Santa Barbara, verkerke@slac.stanford.edu * + * DK, David Kirkby, UC Irvine, dkirkby@uci.edu * + * AL, Alfio Lazzaro, INFN Milan, alfio.lazzaro@mi.infn.it * + * PB, Patrick Bos, NL eScience Center, p.bos@esciencecenter.nl * + * VC, Vince Croft, DIANA / NYU, vincent.croft@cern.ch * + * * + * * + * Redistribution and use in source and binary forms, * + * with or without modification, are permitted according to the terms * + * listed in LICENSE (http://roofit.sourceforge.net/license.txt) * + *****************************************************************************/ + +/** +\file RooGaussMinimizer.cxx +\class RooGaussMinimizer +\ingroup Roofitcore + +RooGaussMinimizer is a test class for extracting the gradient +functionality from Minuit2 using only a Gaussian function for +easy testing. This approach will be generalized in RooGradMinimizer. +This class is based on RooMinimizer. +**/ + +#include "RooFit.h" +#include "Riostream.h" + +#include "TClass.h" + +#include +#include + +#include "TH1.h" +#include "TH2.h" +#include "TMarker.h" +#include "TGraph.h" +#include "Fit/FitConfig.h" +#include "TStopwatch.h" +#include "TDirectory.h" +#include "TMatrixDSym.h" + +#include "RooArgSet.h" +#include "RooArgList.h" +#include "RooAbsReal.h" +#include "RooAbsRealLValue.h" +#include "RooRealVar.h" +#include "RooAbsPdf.h" +#include "RooSentinel.h" +#include "RooMsgService.h" +#include "RooPlot.h" + +#include "RooMinimizer.h" +#include "RooGaussMinimizer.h" +#include "RooGaussMinimizerFcn.h" +#include "RooFitResult.h" + +#include "Math/Minimizer.h" + +#if (__GNUC__==3&&__GNUC_MINOR__==2&&__GNUC_PATCHLEVEL__==3) +char* operator+( streampos&, char* ); +#endif + +using namespace std; + +ROOT::Fit::Fitter *RooGaussMinimizer::_theFitter = 0 ; + + + +//////////////////////////////////////////////////////////////////////////////// +/// Cleanup method called by atexit handler installed by RooSentinel +/// to delete all global heap objects when the program is terminated + +void RooGaussMinimizer::cleanup() +{ + if (_theFitter) { + delete _theFitter ; + _theFitter =0 ; + } +} + + + +//////////////////////////////////////////////////////////////////////////////// +/// Construct MINUIT interface to given function. Function can be anything, +/// but is typically a -log(likelihood) implemented by RooNLLVar or a chi^2 +/// (implemented by RooChi2Var). Other frequent use cases are a RooAddition +/// of a RooNLLVar plus a penalty or constraint term. This class propagates +/// all RooFit information (floating parameters, their values and errors) +/// to MINUIT before each MINUIT call and propagates all MINUIT information +/// back to the RooFit object at the end of each call (updated parameter +/// values, their (asymmetric errors) etc. The default MINUIT error level +/// for HESSE and MINOS error analysis is taken from the defaultErrorLevel() +/// value of the input function. + +RooGaussMinimizer::RooGaussMinimizer(RooAbsReal& function) +{ + RooSentinel::activate() ; + + // Store function reference + _extV = 0 ; + _func = &function ; + _optConst = kFALSE ; + _verbose = kFALSE ; + _profile = kFALSE ; + _profileStart = kFALSE ; + _printLevel = 1 ; + _minimizerType = "Minuit"; // default minimizer + + if (_theFitter) delete _theFitter ; + _theFitter = new ROOT::Fit::Fitter; + // RooMinimizer *theRooMinimizer = dynamic_cast(this); + _fcn = new RooGaussMinimizerFcn(_func,this,_verbose); + _theFitter->Config().SetMinimizer(_minimizerType.c_str()); + // default max number of calls + _theFitter->Config().MinimizerOptions().SetMaxIterations(500*_fcn->NDim()); + _theFitter->Config().MinimizerOptions().SetMaxFunctionCalls(500*_fcn->NDim()); + + // Declare our parameters to MINUIT + _fcn->Synchronize(_theFitter->Config().ParamsSettings(), + _optConst,_verbose) ; +} + + + +//////////////////////////////////////////////////////////////////////////////// +/// Destructor + +RooGaussMinimizer::~RooGaussMinimizer() +{ + if (_extV) { + delete _extV ; + } + + if (_fcn) { + delete _fcn; + } + +} + + + +//////////////////////////////////////////////////////////////////////////////// +/// Change MINUIT strategy to istrat. Accepted codes +/// are 0,1,2 and represent MINUIT strategies for dealing +/// most efficiently with fast FCNs (0), expensive FCNs (2) +/// and 'intermediate' FCNs (1) +Int_t RooGaussMinimizer::migrad() +{ + _fcn->Synchronize(_theFitter->Config().ParamsSettings(), + _optConst,_verbose) ; + // profileStart() ; + RooAbsReal::setEvalErrorLoggingMode(RooAbsReal::CollectErrors) ; + RooAbsReal::clearEvalErrorLog() ; + + _theFitter->Config().SetMinimizer(_minimizerType.c_str(),"migrad"); + bool ret = _theFitter->FitFCN(*_fcn); + _status = ((ret) ? _theFitter->Result().Status() : -1); + + RooAbsReal::setEvalErrorLoggingMode(RooAbsReal::PrintErrors) ; + // profileStop() ; + _fcn->BackProp(_theFitter->Result()); + + saveStatus("MIGRAD",_status) ; + + return _status ; +} + + +//////////////////////////////////////////////////////////////////////////////// +/// Execute HESSE. Changes in parameter values +/// and calculated errors are automatically +/// propagated back the RooRealVars representing +/// the floating parameters in the MINUIT operation + +Int_t RooGaussMinimizer::hesse() +{ + if (_theFitter->GetMinimizer()==0) { + coutW(Minimization) << "RooGaussMinimizer::hesse: Error, run Migrad before Hesse!" + << endl ; + _status = -1; + } + else { + + _fcn->Synchronize(_theFitter->Config().ParamsSettings(), + _optConst,_verbose) ; + // profileStart() ; + RooAbsReal::setEvalErrorLoggingMode(RooAbsReal::CollectErrors) ; + RooAbsReal::clearEvalErrorLog() ; + + _theFitter->Config().SetMinimizer(_minimizerType.c_str()); + bool ret = _theFitter->CalculateHessErrors(); + _status = ((ret) ? _theFitter->Result().Status() : -1); + + RooAbsReal::setEvalErrorLoggingMode(RooAbsReal::PrintErrors) ; + // profileStop() ; + _fcn->BackProp(_theFitter->Result()); + + saveStatus("HESSE",_status) ; + + } + + return _status ; + +} + +//////////////////////////////////////////////////////////////////////////////// +/// Execute MINOS. Changes in parameter values +/// and calculated errors are automatically +/// propagated back the RooRealVars representing +/// the floating parameters in the MINUIT operation + +Int_t RooGaussMinimizer::minos() +{ + if (_theFitter->GetMinimizer()==0) { + coutW(Minimization) << "RooGaussMinimizer::minos: Error, run Migrad before Minos!" + << endl ; + _status = -1; + } + else { + + _fcn->Synchronize(_theFitter->Config().ParamsSettings(), + _optConst,_verbose) ; + // profileStart() ; + RooAbsReal::setEvalErrorLoggingMode(RooAbsReal::CollectErrors) ; + RooAbsReal::clearEvalErrorLog() ; + + _theFitter->Config().SetMinimizer(_minimizerType.c_str()); + bool ret = _theFitter->CalculateMinosErrors(); + _status = ((ret) ? _theFitter->Result().Status() : -1); + + RooAbsReal::setEvalErrorLoggingMode(RooAbsReal::PrintErrors) ; + // profileStop() ; + _fcn->BackProp(_theFitter->Result()); + + saveStatus("MINOS",_status) ; + + } + + return _status ; + +} + + +//////////////////////////////////////////////////////////////////////////////// +/// If flag is true, perform constant term optimization on +/// function being minimized. + +void RooGaussMinimizer::optimizeConst(Int_t flag) +{ + RooAbsReal::setEvalErrorLoggingMode(RooAbsReal::CollectErrors) ; + + if (_optConst && !flag){ + if (_printLevel>-1) coutI(Minimization) << "RooGaussMinimizer::optimizeConst: deactivating const optimization" << endl ; + _func->constOptimizeTestStatistic(RooAbsArg::DeActivate) ; + _optConst = flag ; + } else if (!_optConst && flag) { + if (_printLevel>-1) coutI(Minimization) << "RooGaussMinimizer::optimizeConst: activating const optimization" << endl ; + _func->constOptimizeTestStatistic(RooAbsArg::Activate,flag>1) ; + _optConst = flag ; + } else if (_optConst && flag) { + if (_printLevel>-1) coutI(Minimization) << "RooGaussMinimizer::optimizeConst: const optimization already active" << endl ; + } else { + if (_printLevel>-1) coutI(Minimization) << "RooGaussMinimizer::optimizeConst: const optimization wasn't active" << endl ; + } + + RooAbsReal::setEvalErrorLoggingMode(RooAbsReal::PrintErrors) ; + +} + + + diff --git a/roofit/roofitcore/src/RooGaussMinimizerFcn.cxx b/roofit/roofitcore/src/RooGaussMinimizerFcn.cxx new file mode 100644 index 0000000000000..09a48fd81d077 --- /dev/null +++ b/roofit/roofitcore/src/RooGaussMinimizerFcn.cxx @@ -0,0 +1,627 @@ +/***************************************************************************** + * Project: RooFit * + * Package: RooFitCore * + * @(#)root/roofitcore:$Id$ + * Authors: * + * AL, Alfio Lazzaro, INFN Milan, alfio.lazzaro@mi.infn.it * + * PB, Patrick Bos, NL eScience Center, p.bos@esciencecenter.nl * + * VC, Vince Croft, DIANA / NYU, vincent.croft@cern.ch * + * * + * * + * Redistribution and use in source and binary forms, * + * with or without modification, are permitted according to the terms * + * listed in LICENSE (http://roofit.sourceforge.net/license.txt) * + *****************************************************************************/ + +#ifndef __ROOFIT_NOROOMINIMIZER + +////////////////////////////////////////////////////////////////////////////// +// +// RooGaussMinimizerFcn is am interface class to the ROOT::Math function +// for minization. See RooGaussMinimizer.cxx for more information. +// + +#include + +#include "RooFit.h" +#include "RooMinimizerFcn.h" + +#include "Riostream.h" + +#include "TIterator.h" +#include "TClass.h" + +#include "RooAbsArg.h" +#include "RooAbsPdf.h" +#include "RooArgSet.h" +#include "RooRealVar.h" +#include "RooAbsRealLValue.h" +#include "RooMsgService.h" + +#include "RooMinimizer.h" +#include "RooGaussMinimizer.h" + +using namespace std; + +RooGaussMinimizerFcn::RooGaussMinimizerFcn(RooAbsReal *funct, RooGaussMinimizer* context, + bool verbose) : + _funct(funct), _context(context), + // Reset the *largest* negative log-likelihood value we have seen so far + _maxFCN(-1e30), _numBadNLL(0), + _printEvalErrors(10), _doEvalErrorWall(kTRUE), + _nDim(0), _logfile(0), + _verbose(verbose) +{ + + _evalCounter = 0 ; + + // Examine parameter list + RooArgSet* paramSet = _funct->getParameters(RooArgSet()); + RooArgList paramList(*paramSet); + delete paramSet; + + _floatParamList = (RooArgList*) paramList.selectByAttrib("Constant",kFALSE); + if (_floatParamList->getSize()>1) { + _floatParamList->sort(); + } + _floatParamList->setName("floatParamList"); + + _constParamList = (RooArgList*) paramList.selectByAttrib("Constant",kTRUE); + if (_constParamList->getSize()>1) { + _constParamList->sort(); + } + _constParamList->setName("constParamList"); + + // Remove all non-RooRealVar parameters from list (MINUIT cannot handle them) + TIterator* pIter = _floatParamList->createIterator(); + RooAbsArg* arg; + while ((arg=(RooAbsArg*)pIter->Next())) { + if (!arg->IsA()->InheritsFrom(RooAbsRealLValue::Class())) { + oocoutW(_context,Minimization) << "RooGaussMinimizerFcn::RooGaussMinimizerFcn: removing parameter " + << arg->GetName() + << " from list because it is not of type RooRealVar" << endl; + _floatParamList->remove(*arg); + } + } + delete pIter; + + _nDim = _floatParamList->getSize(); + + updateFloatVec() ; + + // Save snapshot of initial lists + _initFloatParamList = (RooArgList*) _floatParamList->snapshot(kFALSE) ; + _initConstParamList = (RooArgList*) _constParamList->snapshot(kFALSE) ; + +} + + + +RooGaussMinimizerFcn::RooGaussMinimizerFcn(const RooGaussMinimizerFcn& other) : ROOT::Math::IMultiGradFunction(other), + _evalCounter(other._evalCounter), + _funct(other._funct), + _context(other._context), + _maxFCN(other._maxFCN), + _numBadNLL(other._numBadNLL), + _printEvalErrors(other._printEvalErrors), + _doEvalErrorWall(other._doEvalErrorWall), + _nDim(other._nDim), + _logfile(other._logfile), + _verbose(other._verbose), + _floatParamVec(other._floatParamVec) +{ + _floatParamList = new RooArgList(*other._floatParamList) ; + _constParamList = new RooArgList(*other._constParamList) ; + _initFloatParamList = (RooArgList*) other._initFloatParamList->snapshot(kFALSE) ; + _initConstParamList = (RooArgList*) other._initConstParamList->snapshot(kFALSE) ; +} + + +RooGaussMinimizerFcn::~RooGaussMinimizerFcn() +{ + delete _floatParamList; + delete _initFloatParamList; + delete _constParamList; + delete _initConstParamList; +} + + +ROOT::Math::IMultiGradFunction* RooGaussMinimizerFcn::Clone() const +{ + return new RooGaussMinimizerFcn(*this) ; +} + + +Bool_t RooGaussMinimizerFcn::Synchronize(std::vector& parameters, + Bool_t optConst, Bool_t verbose) +{ + + // Internal function to synchronize TMinimizer with current + // information in RooAbsReal function parameters + + Bool_t constValChange(kFALSE) ; + Bool_t constStatChange(kFALSE) ; + + Int_t index(0) ; + + // Handle eventual migrations from constParamList -> floatParamList + for(index= 0; index < _constParamList->getSize() ; index++) { + + RooRealVar *par= dynamic_cast(_constParamList->at(index)) ; + if (!par) continue ; + + RooRealVar *oldpar= dynamic_cast(_initConstParamList->at(index)) ; + if (!oldpar) continue ; + + // Test if constness changed + if (!par->isConstant()) { + + // Remove from constList, add to floatList + _constParamList->remove(*par) ; + _floatParamList->add(*par) ; + _initFloatParamList->addClone(*oldpar) ; + _initConstParamList->remove(*oldpar) ; + constStatChange=kTRUE ; + _nDim++ ; + + if (verbose) { + oocoutI(_context,Minimization) << "RooGaussMinimizerFcn::synchronize: parameter " + << par->GetName() << " is now floating." << endl ; + } + } + + // Test if value changed + if (par->getVal()!= oldpar->getVal()) { + constValChange=kTRUE ; + if (verbose) { + oocoutI(_context,Minimization) << "RooGaussMinimizerFcn::synchronize: value of constant parameter " + << par->GetName() + << " changed from " << oldpar->getVal() << " to " + << par->getVal() << endl ; + } + } + + } + + // Update reference list + *_initConstParamList = *_constParamList ; + + // Synchronize MINUIT with function state + // Handle floatParamList + for(index= 0; index < _floatParamList->getSize(); index++) { + RooRealVar *par= dynamic_cast(_floatParamList->at(index)) ; + + if (!par) continue ; + + Double_t pstep(0) ; + Double_t pmin(0) ; + Double_t pmax(0) ; + + if(!par->isConstant()) { + + // Verify that floating parameter is indeed of type RooRealVar + if (!par->IsA()->InheritsFrom(RooRealVar::Class())) { + oocoutW(_context,Minimization) << "RooGaussMinimizerFcn::fit: Error, non-constant parameter " + << par->GetName() + << " is not of type RooRealVar, skipping" << endl ; + _floatParamList->remove(*par); + index--; + _nDim--; + continue ; + } + + // Set the limits, if not infinite + if (par->hasMin() ) + pmin = par->getMin(); + if (par->hasMax() ) + pmax = par->getMax(); + + // Calculate step size + pstep = par->getError(); + if(pstep <= 0) { + // Floating parameter without error estitimate + if (par->hasMin() && par->hasMax()) { + pstep= 0.1*(pmax-pmin); + + // Trim default choice of error if within 2 sigma of limit + if (pmax - par->getVal() < 2*pstep) { + pstep = (pmax - par->getVal())/2 ; + } else if (par->getVal() - pmin < 2*pstep) { + pstep = (par->getVal() - pmin )/2 ; + } + + // If trimming results in zero error, restore default + if (pstep==0) { + pstep= 0.1*(pmax-pmin); + } + + } else { + pstep=1 ; + } + if(verbose) { + oocoutW(_context,Minimization) << "RooGaussMinimizerFcn::synchronize: WARNING: no initial error estimate available for " + << par->GetName() << ": using " << pstep << endl; + } + } + } else { + pmin = par->getVal() ; + pmax = par->getVal() ; + } + + // new parameter + if (index>=Int_t(parameters.size())) { + + if (par->hasMin() && par->hasMax()) { + parameters.push_back(ROOT::Fit::ParameterSettings(par->GetName(), + par->getVal(), + pstep, + pmin,pmax)); + } + else { + parameters.push_back(ROOT::Fit::ParameterSettings(par->GetName(), + par->getVal(), + pstep)); + if (par->hasMin() ) + parameters.back().SetLowerLimit(pmin); + else if (par->hasMax() ) + parameters.back().SetUpperLimit(pmax); + } + + continue; + + } + + Bool_t oldFixed = parameters[index].IsFixed(); + Double_t oldVar = parameters[index].Value(); + Double_t oldVerr = parameters[index].StepSize(); + Double_t oldVlo = parameters[index].LowerLimit(); + Double_t oldVhi = parameters[index].UpperLimit(); + + if (par->isConstant() && !oldFixed) { + + // Parameter changes floating -> constant : update only value if necessary + if (oldVar!=par->getVal()) { + parameters[index].SetValue(par->getVal()); + if (verbose) { + oocoutI(_context,Minimization) << "RooGaussMinimizerFcn::synchronize: value of parameter " + << par->GetName() << " changed from " << oldVar + << " to " << par->getVal() << endl ; + } + } + parameters[index].Fix(); + constStatChange=kTRUE ; + if (verbose) { + oocoutI(_context,Minimization) << "RooGaussMinimizerFcn::synchronize: parameter " + << par->GetName() << " is now fixed." << endl ; + } + + } else if (par->isConstant() && oldFixed) { + + // Parameter changes constant -> constant : update only value if necessary + if (oldVar!=par->getVal()) { + parameters[index].SetValue(par->getVal()); + constValChange=kTRUE ; + + if (verbose) { + oocoutI(_context,Minimization) << "RooGaussMinimizerFcn::synchronize: value of fixed parameter " + << par->GetName() << " changed from " << oldVar + << " to " << par->getVal() << endl ; + } + } + + } else { + // Parameter changes constant -> floating + if (!par->isConstant() && oldFixed) { + parameters[index].Release(); + constStatChange=kTRUE ; + + if (verbose) { + oocoutI(_context,Minimization) << "RooGaussMinimizerFcn::synchronize: parameter " + << par->GetName() << " is now floating." << endl ; + } + } + + // Parameter changes constant -> floating : update all if necessary + if (oldVar!=par->getVal() || oldVlo!=pmin || oldVhi != pmax || oldVerr!=pstep) { + parameters[index].SetValue(par->getVal()); + parameters[index].SetStepSize(pstep); + if (par->hasMin() && par->hasMax() ) + parameters[index].SetLimits(pmin,pmax); + else if (par->hasMin() ) + parameters[index].SetLowerLimit(pmin); + else if (par->hasMax() ) + parameters[index].SetUpperLimit(pmax); + } + + // Inform user about changes in verbose mode + if (verbose) { + // if ierr<0, par was moved from the const list and a message was already printed + + if (oldVar!=par->getVal()) { + oocoutI(_context,Minimization) << "RooGaussMinimizerFcn::synchronize: value of parameter " + << par->GetName() << " changed from " << oldVar << " to " + << par->getVal() << endl ; + } + if (oldVlo!=pmin || oldVhi!=pmax) { + oocoutI(_context,Minimization) << "RooGaussMinimizerFcn::synchronize: limits of parameter " + << par->GetName() << " changed from [" << oldVlo << "," << oldVhi + << "] to [" << pmin << "," << pmax << "]" << endl ; + } + + // If oldVerr=0, then parameter was previously fixed + if (oldVerr!=pstep && oldVerr!=0) { + oocoutI(_context,Minimization) << "RooGaussMinimizerFcn::synchronize: error/step size of parameter " + << par->GetName() << " changed from " << oldVerr << " to " << pstep << endl ; + } + } + } + } + + if (optConst) { + if (constStatChange) { + + RooAbsReal::setEvalErrorLoggingMode(RooAbsReal::CollectErrors) ; + + oocoutI(_context,Minimization) << "RooGaussMinimizerFcn::synchronize: set of constant parameters changed, rerunning const optimizer" << endl ; + _funct->constOptimizeTestStatistic(RooAbsArg::ConfigChange) ; + } else if (constValChange) { + oocoutI(_context,Minimization) << "RooGaussMinimizerFcn::synchronize: constant parameter values changed, rerunning const optimizer" << endl ; + _funct->constOptimizeTestStatistic(RooAbsArg::ValueChange) ; + } + + RooAbsReal::setEvalErrorLoggingMode(RooAbsReal::PrintErrors) ; + + } + + updateFloatVec() ; + + return 0 ; + +} + +Double_t RooGaussMinimizerFcn::GetPdfParamVal(Int_t index) +{ + // Access PDF parameter value by ordinal index (needed by MINUIT) + + return ((RooRealVar*)_floatParamList->at(index))->getVal() ; +} + +Double_t RooGaussMinimizerFcn::GetPdfParamErr(Int_t index) +{ + // Access PDF parameter error by ordinal index (needed by MINUIT) + return ((RooRealVar*)_floatParamList->at(index))->getError() ; +} + + +void RooGaussMinimizerFcn::SetPdfParamErr(Int_t index, Double_t value) +{ + // Modify PDF parameter error by ordinal index (needed by MINUIT) + + ((RooRealVar*)_floatParamList->at(index))->setError(value) ; +} + + + +void RooGaussMinimizerFcn::ClearPdfParamAsymErr(Int_t index) +{ + // Modify PDF parameter error by ordinal index (needed by MINUIT) + + ((RooRealVar*)_floatParamList->at(index))->removeAsymError() ; +} + + +void RooGaussMinimizerFcn::SetPdfParamErr(Int_t index, Double_t loVal, Double_t hiVal) +{ + // Modify PDF parameter error by ordinal index (needed by MINUIT) + + ((RooRealVar*)_floatParamList->at(index))->setAsymError(loVal,hiVal) ; +} + + +void RooGaussMinimizerFcn::BackProp(const ROOT::Fit::FitResult &results) +{ + // Transfer MINUIT fit results back into RooFit objects + + for (Int_t index= 0; index < _nDim; index++) { + Double_t value = results.Value(index); + SetPdfParamVal(index, value); + + // Set the parabolic error + Double_t err = results.Error(index); + SetPdfParamErr(index, err); + + Double_t eminus = results.LowerError(index); + Double_t eplus = results.UpperError(index); + + if(eplus > 0 || eminus < 0) { + // Store the asymmetric error, if it is available + SetPdfParamErr(index, eminus,eplus); + } else { + // Clear the asymmetric error + ClearPdfParamAsymErr(index) ; + } + } + +} + +Bool_t RooGaussMinimizerFcn::SetLogFile(const char* inLogfile) +{ + // Change the file name for logging of a RooGaussMinimizer of all MINUIT steppings + // through the parameter space. If inLogfile is null, the current log file + // is closed and logging is stopped. + + if (_logfile) { + oocoutI(_context,Minimization) << "RooGaussMinimizerFcn::setLogFile: closing previous log file" << endl ; + _logfile->close() ; + delete _logfile ; + _logfile = 0 ; + } + _logfile = new ofstream(inLogfile) ; + if (!_logfile->good()) { + oocoutI(_context,Minimization) << "RooGaussMinimizerFcn::setLogFile: cannot open file " << inLogfile << endl ; + _logfile->close() ; + delete _logfile ; + _logfile= 0; + } + + return kFALSE ; + +} + + +void RooGaussMinimizerFcn::ApplyCovarianceMatrix(TMatrixDSym& V) +{ + // Apply results of given external covariance matrix. i.e. propagate its errors + // to all RRV parameter representations and give this matrix instead of the + // HESSE matrix at the next save() call + + for (Int_t i=0 ; i<_nDim ; i++) { + // Skip fixed parameters + if (_floatParamList->at(i)->isConstant()) { + continue ; + } + SetPdfParamErr(i, sqrt(V(i,i))) ; + } + +} + + +Bool_t RooGaussMinimizerFcn::SetPdfParamVal(const Int_t &index, const Double_t &value) const +{ + //RooRealVar* par = (RooRealVar*)_floatParamList->at(index); + RooRealVar* par = (RooRealVar*)_floatParamVec[index] ; + + if (par->getVal()!=value) { + if (_verbose) cout << par->GetName() << "=" << value << ", " ; + + par->setVal(value); + return kTRUE; + } + + return kFALSE; +} + + + +//////////////////////////////////////////////////////////////////////////////// + +void RooGaussMinimizerFcn::updateFloatVec() +{ + _floatParamVec.clear() ; + RooFIter iter = _floatParamList->fwdIterator() ; + RooAbsArg* arg ; + _floatParamVec = std::vector(_floatParamList->getSize()) ; + Int_t i(0) ; + while((arg=iter.next())) { + _floatParamVec[i++] = arg ; + } +} + + + +double RooGaussMinimizerFcn::DoEval(const double *x) const +{ + + // Set the parameter values for this iteration + for (int index = 0; index < _nDim; index++) { + if (_logfile && _logfile->is_open()) (*_logfile) << x[index] << " " ; + SetPdfParamVal(index,x[index]); + } + + // Calculate the function for these parameters + RooAbsReal::setHideOffset(kFALSE) ; + double fvalue = _funct->getVal(); + RooAbsReal::setHideOffset(kTRUE) ; + + if (!std::isfinite(fvalue) || RooAbsReal::numEvalErrors() > 0 || fvalue > 1e30) { + + if (_printEvalErrors>=0) { + + if (_doEvalErrorWall) { + oocoutW(_context,Minimization) << "RooGaussMinimizerFcn: Minimized function has error status." << endl + << "Returning maximum FCN so far (" << _maxFCN + << ") to force MIGRAD to back out of this region. Error log follows" << endl ; + } else { + oocoutW(_context,Minimization) << "RooGaussMinimizerFcn: Minimized function has error status but is ignored" << endl ; + } + + TIterator* iter = _floatParamList->createIterator() ; + RooRealVar* var ; + Bool_t first(kTRUE) ; + ooccoutW(_context,Minimization) << "Parameter values: " ; + while((var=(RooRealVar*)iter->Next())) { + if (first) { first = kFALSE ; } else ooccoutW(_context,Minimization) << ", " ; + ooccoutW(_context,Minimization) << var->GetName() << "=" << var->getVal() ; + } + delete iter ; + ooccoutW(_context,Minimization) << endl ; + + RooAbsReal::printEvalErrors(ooccoutW(_context,Minimization),_printEvalErrors) ; + ooccoutW(_context,Minimization) << endl ; + } + + if (_doEvalErrorWall) { + fvalue = _maxFCN+1 ; + } + + RooAbsReal::clearEvalErrorLog() ; + _numBadNLL++ ; + } else if (fvalue>_maxFCN) { + _maxFCN = fvalue ; + } + + // Optional logging + if (_logfile && _logfile->is_open()) + (*_logfile) << setprecision(15) << fvalue << setprecision(4) << endl; + if (_verbose) { + cout << "\nprevFCN" << (_funct->isOffsetting()?"-offset":"") << " = " << setprecision(10) + << fvalue << setprecision(4) << " " ; + cout.flush() ; + } + + _evalCounter++ ; + cout<< "func eval "<is_open()) (*_logfile) << x[index] << " " ; + SetPdfParamVal(index,x[index]); + } + + // Calculate the function for these parameters + RooAbsReal::setHideOffset(kFALSE) ; // EGP TODO: check whether this is necessary + + ///// EGP TODO: REPLACE BELOW DERIVATIVE CALCULATION WITH THE FANCY MINUIT TYPE STUFF + double dx = max(1e-5 * x[icoord], 1e-8); + double fvalue_0 = _funct->getVal(); + + if (_logfile) (*_logfile) << x[icoord] << " " ; + SetPdfParamVal(icoord,x[icoord] + dx); + + double fvalue_dx = _funct->getVal(); + + double derivative_i_value = (fvalue_dx - fvalue_0) / dx; //######## OI THIS IS WHERE WE COMPUTE THE GRADIENT#### + ///// EGP TODO: REPLACE ABOVE DERIVATIVE CALCULATION WITH THE FANCY MINUIT TYPE STUFF + + RooAbsReal::setHideOffset(kTRUE) ; // EGP TODO: check whether this is necessary + + // EGP TODO: decide whether to do error handling and logging, like in DoEval + + // EGP TOOO: update this when changing the derivative algorithm + // Count the function calls necessary for this derivative and use that. + // Except when the derivative itself calls DoEval where the counter is already updated! + _evalCounter += 2; + + cout << "grad value " << derivative_i_value << endl; + return derivative_i_value; +} + + +#endif + diff --git a/roofit/roofitcore/src/RooGlobalFunc.cxx b/roofit/roofitcore/src/RooGlobalFunc.cxx index 22ff1a0c459c3..60002d2c6ec30 100644 --- a/roofit/roofitcore/src/RooGlobalFunc.cxx +++ b/roofit/roofitcore/src/RooGlobalFunc.cxx @@ -167,6 +167,8 @@ namespace RooFit { RooCmdArg Extended(Bool_t flag) { return RooCmdArg("Extended",flag,0,0,0,0,0,0,0) ; } RooCmdArg DataError(Int_t etype) { return RooCmdArg("DataError",(Int_t)etype,0,0,0,0,0,0,0) ; } RooCmdArg NumCPU(Int_t nCPU, Int_t interleave) { return RooCmdArg("NumCPU",nCPU,interleave,0,0,0,0,0,0) ; } + RooCmdArg CPUAffinity(Bool_t flag) { return RooCmdArg("CPUAffinity",flag,0,0,0,0,0,0,0); } + RooCmdArg BatchMode(bool flag) { return RooCmdArg("BatchMode", flag); } /// Integrate the PDF over bins. Improves accuracy for binned fits. Switch off using `0.` as argument. \see RooAbsPdf::fitTo(). RooCmdArg IntegrateBins(double precision) { return RooCmdArg("IntegrateBins", 0, 0, precision); } @@ -209,8 +211,10 @@ namespace RooFit { RooCmdArg Minos(Bool_t flag) { return RooCmdArg("Minos",flag,0,0,0,0,0,0,0) ; } RooCmdArg Minos(const RooArgSet& minosArgs) { return RooCmdArg("Minos",kTRUE,0,0,0,0,0,&minosArgs,0) ; } RooCmdArg Minos(RooArgSet && minosArgs) { return Minos(RooCmdArg::take(std::move(minosArgs))); } - RooCmdArg ConditionalObservables(const RooArgSet& set) { return RooCmdArg("ProjectedObservables",0,0,0,0,0,0,0,0,0,0,&set) ; } - RooCmdArg ProjectedObservables(const RooArgSet& set) { return RooCmdArg("ProjectedObservables",0,0,0,0,0,0,0,0,0,0,&set) ; } + RooCmdArg ConditionalObservables(const RooArgSet& set) { return RooCmdArg("ProjectedObservables",0,0,0,0,0,0,&set) ; } + RooCmdArg ConditionalObservables(RooArgSet && set) { return ConditionalObservables(RooCmdArg::take(std::move(set))) ; } + RooCmdArg ProjectedObservables(const RooArgSet& set) { return RooCmdArg("ProjectedObservables",0,0,0,0,0,0,&set) ; } + RooCmdArg ProjectedObservables(RooArgSet && set) { return ProjectedObservables(RooCmdArg::take(std::move(set))) ; } RooCmdArg SplitRange(Bool_t flag) { return RooCmdArg("SplitRange",flag,0,0,0,0,0,0,0) ; } RooCmdArg SumCoefRange(const char* rangeName) { return RooCmdArg("SumCoefRange",0,0,0,0,rangeName,0,0,0) ; } RooCmdArg Constrain(const RooArgSet& params) { return RooCmdArg("Constrain",0,0,0,0,0,0,0,0,0,0,¶ms) ; } diff --git a/roofit/roofitcore/src/RooGradMinimizerFcn.cxx b/roofit/roofitcore/src/RooGradMinimizerFcn.cxx new file mode 100644 index 0000000000000..02349b43590c5 --- /dev/null +++ b/roofit/roofitcore/src/RooGradMinimizerFcn.cxx @@ -0,0 +1,368 @@ +/***************************************************************************** + * Project: RooFit * + * Package: RooFitCore * + * @(#)root/roofitcore:$Id$ + * Authors: * + * AL, Alfio Lazzaro, INFN Milan, alfio.lazzaro@mi.infn.it * + * PB, Patrick Bos, NL eScience Center, p.bos@esciencecenter.nl * + * VC, Vince Croft, DIANA / NYU, vincent.croft@cern.ch * + * * + * * + * Redistribution and use in source and binary forms, * + * with or without modification, are permitted according to the terms * + * listed in LICENSE (http://roofit.sourceforge.net/license.txt) * + *****************************************************************************/ + +#ifndef __ROOFIT_NOROOGRADMINIMIZER + +////////////////////////////////////////////////////////////////////////////// +// +// RooGradMinimizerFcn is am interface class to the ROOT::Math function +// for minization. See RooGradMinimizer.cxx for more information. +// + +#include + +#include "RooFit.h" + +#include "Riostream.h" + +#include "TIterator.h" +#include "TClass.h" + +#include "RooAbsArg.h" +#include "RooAbsPdf.h" +#include "RooArgSet.h" +#include "RooRealVar.h" +#include "RooAbsRealLValue.h" +#include "RooMsgService.h" + +#include "RooMinimizer.h" +#include "RooGradMinimizerFcn.h" + +#include "Fit/Fitter.h" +#include "Math/Minimizer.h" +//#include "Minuit2/MnMachinePrecision.h" + +#include // std::equal + +RooGradMinimizerFcn::RooGradMinimizerFcn(RooAbsReal *funct, RooMinimizer *context, bool verbose) + : RooAbsMinimizerFcn(RooArgList(*funct->getParameters(RooArgSet())), context, verbose), + _grad(get_nDim()), _grad_params(get_nDim()), _funct(funct), + has_been_calculated(get_nDim()) +{ + // TODO: added "parameters" after rewrite in april 2020, check if correct + auto parameters = _context->fitter()->Config().ParamsSettings(); + synchronize_parameter_settings(parameters, kTRUE, verbose); + synchronize_gradient_parameter_settings(parameters); + set_strategy(ROOT::Math::MinimizerOptions::DefaultStrategy()); + set_error_level(ROOT::Math::MinimizerOptions::DefaultErrorDef()); +} + +RooGradMinimizerFcn::RooGradMinimizerFcn(const RooGradMinimizerFcn &other) + : RooAbsMinimizerFcn(other), _grad(other._grad), _grad_params(other._grad_params), _gradf(other._gradf), _funct(other._funct), + has_been_calculated(other.has_been_calculated), none_have_been_calculated(other.none_have_been_calculated) +{ +} + +ROOT::Math::IMultiGradFunction *RooGradMinimizerFcn::Clone() const +{ + return new RooGradMinimizerFcn(*this); +} + +void RooGradMinimizerFcn::synchronize_gradient_parameter_settings( + std::vector ¶meter_settings) const +{ + _gradf.SetInitialGradient(_context->getMultiGenFcn(), parameter_settings, _grad); +} + +//////////////////////////////////////////////////////////////////////////////// + +#define DEBUG_STREAM(var) << " " #var "=" << var +#include +#include + +double RooGradMinimizerFcn::DoEval(const double *x) const +{ + Bool_t parameters_changed = kFALSE; + + // Set the parameter values for this iteration + for (unsigned index = 0; index < NDim(); index++) { + // also check whether the function was already evaluated for this set of parameters + parameters_changed |= SetPdfParamVal(index, x[index]); + } + +// std::cout << "RooGradMinimizerFcn::DoEval @ PID" << getpid() << ": " DEBUG_STREAM(parameters_changed); + + // Calculate the function for these parameters + RooAbsReal::setHideOffset(kFALSE); + double fvalue = _funct->getVal(); + RooAbsReal::setHideOffset(kTRUE); + +// std::cout DEBUG_STREAM(fvalue) << std::endl; + + if (!parameters_changed) { + return fvalue; + } + + if (!std::isfinite(fvalue) || RooAbsReal::numEvalErrors() > 0 || fvalue > 1e30) { + + if (_printEvalErrors >= 0) { + + if (_doEvalErrorWall) { + oocoutW(static_cast(nullptr), Eval) + << "RooGradMinimizerFcn: Minimized function has error status." << std::endl + << "Returning maximum FCN so far (" << _maxFCN + << ") to force MIGRAD to back out of this region. Error log follows" << std::endl; + } else { + oocoutW(static_cast(nullptr), Eval) + << "RooGradMinimizerFcn: Minimized function has error status but is ignored" << std::endl; + } + + TIterator *iter = _floatParamList->createIterator(); + RooRealVar *var; + Bool_t first(kTRUE); + ooccoutW(static_cast(nullptr), Eval) << "Parameter values: "; + while ((var = (RooRealVar *)iter->Next())) { + if (first) { + first = kFALSE; + } else + ooccoutW(static_cast(nullptr), Eval) << ", "; + ooccoutW(static_cast(nullptr), Eval) << var->GetName() << "=" << var->getVal(); + } + delete iter; + ooccoutW(static_cast(nullptr), Eval) << std::endl; + + RooAbsReal::printEvalErrors(ooccoutW(static_cast(nullptr), Eval), _printEvalErrors); + ooccoutW(static_cast(nullptr), Eval) << std::endl; + } + + if (_doEvalErrorWall) { + fvalue = _maxFCN + 1; + } + + RooAbsReal::clearEvalErrorLog(); + _numBadNLL++; + } else if (fvalue > _maxFCN) { + _maxFCN = fvalue; + } + + // Optional logging + if (_verbose) { + std::cout << "\nprevFCN" << (_funct->isOffsetting() ? "-offset" : "") << " = " << std::setprecision(10) << fvalue + << std::setprecision(4) << " "; + std::cout.flush(); + } + + _evalCounter++; + //#ifndef NDEBUG + // std::cout << "RooGradMinimizerFcn " << this << " evaluations (in DoEval): " << _evalCounter << + // std::endl; + //#endif + return fvalue; +} + +void RooGradMinimizerFcn::reset_has_been_calculated_flags() const +{ + for (auto it = has_been_calculated.begin(); it != has_been_calculated.end(); ++it) { + *it = false; + } + none_have_been_calculated = true; +} + +bool RooGradMinimizerFcn::sync_parameter(double x, std::size_t ix) const +{ + bool parameter_has_changed = (_grad_params[ix] != x); + + if (parameter_has_changed) { + _grad_params[ix] = x; + // Set the parameter values for this iteration + // TODO: this is already done in DoEval as well; find efficient way to do only once + SetPdfParamVal(ix, x); + + if (!none_have_been_calculated) { + reset_has_been_calculated_flags(); + } + } + + return parameter_has_changed; +} + +bool RooGradMinimizerFcn::sync_parameters(const double *x) const +{ + bool has_been_synced = false; + + for (std::size_t ix = 0; ix < NDim(); ++ix) { + bool parameter_has_changed = (_grad_params[ix] != x[ix]); + + if (parameter_has_changed) { + _grad_params[ix] = x[ix]; + // Set the parameter values for this iteration + // TODO: this is already done in DoEval as well; find efficient way to do only once + SetPdfParamVal(ix, x[ix]); + } + + has_been_synced |= parameter_has_changed; + } + + if (has_been_synced) { + reset_has_been_calculated_flags(); + } + + return has_been_synced; +} + +void RooGradMinimizerFcn::run_derivator(unsigned int i_component) const +{ + // check whether the derivative was already calculated for this set of parameters + if (!has_been_calculated[i_component]) { + // Calculate the derivative etc for these parameters + _grad[i_component] = + _gradf.partial_derivative(_context->getMultiGenFcn(), _grad_params.data(), + _context->fitter()->Config().ParamsSettings(), i_component, _grad[i_component]); + has_been_calculated[i_component] = true; + none_have_been_calculated = false; + } +} + +double RooGradMinimizerFcn::DoDerivative(const double *x, unsigned int i_component) const +{ + sync_parameters(x); + run_derivator(i_component); + return _grad[i_component].derivative; +} + +bool RooGradMinimizerFcn::hasG2ndDerivative() const +{ + return true; +} + +bool RooGradMinimizerFcn::hasGStepSize() const +{ + return true; +} + +double RooGradMinimizerFcn::DoSecondDerivative(const double *x, unsigned int i_component) const +{ + sync_parameters(x); + run_derivator(i_component); + return _grad[i_component].second_derivative; +} + +double RooGradMinimizerFcn::DoStepSize(const double *x, unsigned int i_component) const +{ + sync_parameters(x); + run_derivator(i_component); + return _grad[i_component].step_size; +} + +bool RooGradMinimizerFcn::returnsInMinuit2ParameterSpace() const +{ + return true; +} + +unsigned int RooGradMinimizerFcn::NDim() const +{ + return get_nDim(); +} + +//////////////////////////////////////////////////////////////////////////////// + +void RooGradMinimizerFcn::set_strategy(int istrat) +{ + assert(istrat >= 0); + ROOT::Minuit2::MnStrategy strategy(static_cast(istrat)); + + set_step_tolerance(strategy.GradientStepTolerance()); + set_grad_tolerance(strategy.GradientTolerance()); + set_ncycles(strategy.GradientNCycles()); +} + +Bool_t +RooGradMinimizerFcn::Synchronize(std::vector ¶meters, Bool_t optConst, Bool_t verbose) +{ + Bool_t returnee = synchronize_parameter_settings(parameters, optConst, verbose); + synchronize_gradient_parameter_settings(parameters); + set_strategy(_context->fitter()->Config().MinimizerOptions().Strategy()); + set_error_level(_context->fitter()->Config().MinimizerOptions().ErrorDef()); + return returnee; +} + +void RooGradMinimizerFcn::optimizeConstantTerms(bool constStatChange, bool constValChange) +{ + if (constStatChange) { + + RooAbsReal::setEvalErrorLoggingMode(RooAbsReal::CollectErrors); + + oocoutI(static_cast(nullptr), Eval) + << "RooGradMinimizerFcn::::synchronize: set of constant parameters changed, rerunning const optimizer" + << std::endl; + _funct->constOptimizeTestStatistic(RooAbsArg::ConfigChange, true); + } else if (constValChange) { + oocoutI(static_cast(nullptr), Eval) + << "RooGradMinimizerFcn::::synchronize: constant parameter values changed, rerunning const optimizer" + << std::endl; + _funct->constOptimizeTestStatistic(RooAbsArg::ValueChange, true); + } + + RooAbsReal::setEvalErrorLoggingMode(RooAbsReal::PrintErrors); +} + +void RooGradMinimizerFcn::set_step_tolerance(double step_tolerance) const +{ + _gradf.set_step_tolerance(step_tolerance); +} +void RooGradMinimizerFcn::set_grad_tolerance(double grad_tolerance) const +{ + _gradf.set_grad_tolerance(grad_tolerance); +} +void RooGradMinimizerFcn::set_ncycles(unsigned int ncycles) const +{ + _gradf.set_ncycles(ncycles); +} +void RooGradMinimizerFcn::set_error_level(double error_level) const +{ + _gradf.set_error_level(error_level); +} + +std::string RooGradMinimizerFcn::getFunctionName() const +{ + return _funct->GetName(); +} + +std::string RooGradMinimizerFcn::getFunctionTitle() const +{ + return _funct->GetTitle(); +} + +void RooGradMinimizerFcn::setOptimizeConst(Int_t flag) +{ + RooAbsReal::setEvalErrorLoggingMode(RooAbsReal::CollectErrors); + + if (_optConst && !flag) { + if (_context->getPrintLevel() > -1) + oocoutI(_context, Minimization) << "RooGradMinimizerFcn::setOptimizeConst: deactivating const optimization" << std::endl; + _funct->constOptimizeTestStatistic(RooAbsArg::DeActivate, true); + _optConst = flag; + } else if (!_optConst && flag) { + if (_context->getPrintLevel() > -1) + oocoutI(_context, Minimization) << "RooGradMinimizerFcn::setOptimizeConst: activating const optimization" << std::endl; + _funct->constOptimizeTestStatistic(RooAbsArg::Activate, flag > 1); + _optConst = flag; + } else if (_optConst && flag) { + if (_context->getPrintLevel() > -1) + oocoutI(_context, Minimization) << "RooGradMinimizerFcn::setOptimizeConst: const optimization already active" << std::endl; + } else { + if (_context->getPrintLevel() > -1) + oocoutI(_context, Minimization) << "RooGradMinimizerFcn::setOptimizeConst: const optimization wasn't active" << std::endl; + } + + RooAbsReal::setEvalErrorLoggingMode(RooAbsReal::PrintErrors); +} + +void RooGradMinimizerFcn::Gradient(const double *x, double *grad) const +{ + ROOT::Math::IMultiGradFunction::Gradient(x, grad); +} + +#endif diff --git a/roofit/roofitcore/src/RooHelpers.cxx b/roofit/roofitcore/src/RooHelpers.cxx index c348c21962f03..66bcf25af91e6 100644 --- a/roofit/roofitcore/src/RooHelpers.cxx +++ b/roofit/roofitcore/src/RooHelpers.cxx @@ -20,6 +20,7 @@ #include "RooDataHist.h" #include "RooDataSet.h" #include "RooAbsRealLValue.h" +#include "RooArgList.h" #include "TClass.h" @@ -265,4 +266,36 @@ bool checkIfRangesOverlap(RooAbsPdf const& pdf, RooAbsData const& data, std::vec } +/// Create a string with all sorted names of RooArgSet elements separated by colons. +/// \param[in] arg argSet The input RooArgSet. +std::string getColonSeparatedNameString(RooArgSet const& argSet) { + + RooArgList tmp(argSet); + tmp.sort(); + + std::string content; + for(auto const& arg : tmp) { + content += arg->GetName(); + content += ":"; + } + if(!content.empty()) { + content.pop_back(); + } + return content; +} + + +/// Construct a RooArgSet of objects in a RooArgSet whose names match to those +/// in the names string. +/// \param[in] arg argSet The input RooArgSet. +/// \param[in] arg names The names of the objects to select in a colon-separated string. +RooArgSet selectFromArgSet(RooArgSet const& argSet, std::string const& names) { + RooArgSet output; + for(auto const& name : tokenise(names, ":")) { + if(auto arg = argSet.find(name.c_str())) output.add(*arg); + } + return output; +} + + } diff --git a/roofit/roofitcore/src/RooHistFunc.cxx b/roofit/roofitcore/src/RooHistFunc.cxx index d6ac6bbbf677c..ba77be4935f9f 100644 --- a/roofit/roofitcore/src/RooHistFunc.cxx +++ b/roofit/roofitcore/src/RooHistFunc.cxx @@ -35,6 +35,7 @@ discrete dimensions and may have negative values. #include "RooCategory.h" #include "RooWorkspace.h" #include "RooHistPdf.h" +#include "RooHelpers.h" #include "TError.h" @@ -501,7 +502,8 @@ Bool_t RooHistFunc::areIdentical(const RooDataHist& dh1, const RooDataHist& dh2) dh2.get(i) ; if (fabs(dh1.weight()-dh2.weight())>1e-8) return kFALSE ; } - if (!(RooNameSet(*dh1.get())==RooNameSet(*dh2.get()))) return kFALSE ; + using RooHelpers::getColonSeparatedNameString; + if (getColonSeparatedNameString(*dh1.get()) != getColonSeparatedNameString(*dh2.get())) return kFALSE ; return kTRUE ; } diff --git a/roofit/roofitcore/src/RooJsonListFile.cxx b/roofit/roofitcore/src/RooJsonListFile.cxx new file mode 100644 index 0000000000000..f8df77a9dfe0f --- /dev/null +++ b/roofit/roofitcore/src/RooJsonListFile.cxx @@ -0,0 +1,40 @@ +#include "RooJsonListFile.h" + +RooJsonListFile::RooJsonListFile(const std::string & filename) : + _member_index(0) { + open(filename); +} + +void RooJsonListFile::open(const std::string & filename) { + // do not use ios::app for opening out! + // app moves put pointer to end of file before each write, which makes seekp useless. + // See http://en.cppreference.com/w/cpp/io/basic_filebuf/open + _out.open(filename, std::ios_base::in | std::ios_base::out); // "mode r+" + if (!_out.is_open()) { + _out.clear(); + // new file + _out.open(filename, std::ios_base::out); // "mode w" + _out << "[\n"; + } else { + // existing file that, presumably, has been closed with close_json_list() and thus ends with "\n]". + _out.seekp(-2, std::ios_base::end); + _out << ",\n"; + } +} + +RooJsonListFile::~RooJsonListFile() { + _out.seekp(-2, std::ios_base::end); + _out << "\n]"; +} + +unsigned long RooJsonListFile::_next_member_index() { + auto current_index = _member_index; + _member_index = (_member_index + 1) % _member_names.size(); + return current_index; +} + +RooJsonListFile& RooJsonListFile::add_member_name(const std::string &name) { + _member_names.push_back(name); + + return *this; +} \ No newline at end of file diff --git a/roofit/roofitcore/src/RooLinkedList.cxx b/roofit/roofitcore/src/RooLinkedList.cxx index 703ac820ab23e..d00ac3fef9c30 100644 --- a/roofit/roofitcore/src/RooLinkedList.cxx +++ b/roofit/roofitcore/src/RooLinkedList.cxx @@ -31,7 +31,6 @@ Use RooAbsCollection derived objects for public use #include "RooFit.h" #include "RooLinkedListIter.h" -#include "RooHashTable.h" #include "RooAbsArg.h" #include "RooMsgService.h" diff --git a/roofit/roofitcore/src/RooMinimizer.cxx b/roofit/roofitcore/src/RooMinimizer.cxx index 12c1368381371..8373fa8740ef8 100644 --- a/roofit/roofitcore/src/RooMinimizer.cxx +++ b/roofit/roofitcore/src/RooMinimizer.cxx @@ -1,11 +1,13 @@ /***************************************************************************** * Project: RooFit * * Package: RooFitCore * - * @(#)root/roofitcore:$Id$ + * File: $Id$ * Authors: * - * WV, Wouter Verkerke, UC Santa Barbara, verkerke@slac.stanford.edu * - * DK, David Kirkby, UC Irvine, dkirkby@uci.edu * - * AL, Alfio Lazzaro, INFN Milan, alfio.lazzaro@mi.infn.it * + * WV, Wouter Verkerke, UC Santa Barbara, verkerke@slac.stanford.edu * + * DK, David Kirkby, UC Irvine, dkirkby@uci.edu * + * AL, Alfio Lazzaro, INFN Milan, alfio.lazzaro@mi.infn.it * + * PB, Patrick Bos, NL eScience Center, p.bos@esciencecenter.nl * + * * * * * Redistribution and use in source and binary forms, * * with or without modification, are permitted according to the terms * @@ -20,7 +22,7 @@ RooMinimizer is a wrapper class around ROOT::Fit:Fitter that provides a seamless interface between the minimizer functionality and the native RooFit interface. -By default the Minimizer is MINUIT. +By default the Minimizer is MINUIT2. RooMinimizer can minimize any RooAbsReal function with respect to its parameters. Usual choices for minimization are RooNLLVar and RooChi2Var @@ -36,20 +38,24 @@ Various methods are available to control verbosity, profiling, automatic PDF optimization. **/ -#ifndef __ROOFIT_NOROOMINIMIZER #include "RooFit.h" +#include "Riostream.h" #include "TClass.h" -#include #include +#include +#include +#include +#include "TH1.h" #include "TH2.h" #include "TMarker.h" #include "TGraph.h" #include "Fit/FitConfig.h" #include "TStopwatch.h" +#include "TDirectory.h" #include "TMatrixDSym.h" #include "RooArgSet.h" @@ -58,13 +64,10 @@ automatic PDF optimization. #include "RooAbsRealLValue.h" #include "RooRealVar.h" #include "RooAbsPdf.h" -#include "RooSentinel.h" -#include "RooMsgService.h" #include "RooPlot.h" - -#include "RooMinimizer.h" #include "RooFitResult.h" +#include "RooMinimizer.h" #include "Math/Minimizer.h" @@ -74,8 +77,7 @@ char* operator+( streampos&, char* ); using namespace std; -ClassImp(RooMinimizer); -; +//ClassImp(RooMinimizer); ROOT::Fit::Fitter *RooMinimizer::_theFitter = 0 ; @@ -87,81 +89,35 @@ ROOT::Fit::Fitter *RooMinimizer::_theFitter = 0 ; void RooMinimizer::cleanup() { - if (_theFitter) { - delete _theFitter ; - _theFitter =0 ; - } + if (_theFitter) { + delete _theFitter ; + _theFitter =0 ; + } } //////////////////////////////////////////////////////////////////////////////// -/// Construct MINUIT interface to given function. Function can be anything, -/// but is typically a -log(likelihood) implemented by RooNLLVar or a chi^2 -/// (implemented by RooChi2Var). Other frequent use cases are a RooAddition -/// of a RooNLLVar plus a penalty or constraint term. This class propagates -/// all RooFit information (floating parameters, their values and errors) -/// to MINUIT before each MINUIT call and propagates all MINUIT information -/// back to the RooFit object at the end of each call (updated parameter -/// values, their (asymmetric errors) etc. The default MINUIT error level -/// for HESSE and MINOS error analysis is taken from the defaultErrorLevel() -/// value of the input function. - -RooMinimizer::RooMinimizer(RooAbsReal& function) -{ - RooSentinel::activate() ; - - // Store function reference - _extV = 0 ; - _func = &function ; - _optConst = kFALSE ; - _verbose = kFALSE ; - _profile = kFALSE ; - _profileStart = kFALSE ; - _printLevel = 1 ; - _minimizerType = "Minuit"; // default minimizer - - if (_theFitter) delete _theFitter ; - _theFitter = new ROOT::Fit::Fitter; - _fcn = new RooMinimizerFcn(_func,this,_verbose); - _theFitter->Config().SetMinimizer(_minimizerType.c_str()); - setEps(1.0); // default tolerance - // default max number of calls - _theFitter->Config().MinimizerOptions().SetMaxIterations(500*_fcn->NDim()); - _theFitter->Config().MinimizerOptions().SetMaxFunctionCalls(500*_fcn->NDim()); - - // Shut up for now - setPrintLevel(-1) ; - - // Use +0.5 for 1-sigma errors - setErrorLevel(_func->defaultErrorLevel()) ; - - // Declare our parameters to MINUIT - _fcn->Synchronize(_theFitter->Config().ParamsSettings(), - _optConst,_verbose) ; +/// Constructor - // Now set default verbosity - if (RooMsgService::instance().silentMode()) { - setPrintLevel(-1) ; - } else { - setPrintLevel(1) ; - } +RooMinimizer::RooMinimizer(RooAbsReal &function) : +RooMinimizer::RooMinimizer(function, static_cast(nullptr)) +{ } - //////////////////////////////////////////////////////////////////////////////// /// Destructor RooMinimizer::~RooMinimizer() { - if (_extV) { - delete _extV ; - } + if (_extV) { + delete _extV ; + } - if (_fcn) { - delete _fcn; - } + if (_fcn) { + delete _fcn; + } } @@ -175,19 +131,19 @@ RooMinimizer::~RooMinimizer() void RooMinimizer::setStrategy(Int_t istrat) { - _theFitter->Config().MinimizerOptions().SetStrategy(istrat); + _theFitter->Config().MinimizerOptions().SetStrategy(istrat); } //////////////////////////////////////////////////////////////////////////////// -/// Change maximum number of MINUIT iterations +/// Change maximum number of MINUIT iterations /// (RooMinimizer default 500 * #parameters) -void RooMinimizer::setMaxIterations(Int_t n) +void RooMinimizer::setMaxIterations(Int_t n) { - _theFitter->Config().MinimizerOptions().SetMaxIterations(n); + _theFitter->Config().MinimizerOptions().SetMaxIterations(n); } @@ -197,9 +153,9 @@ void RooMinimizer::setMaxIterations(Int_t n) /// Change maximum number of likelihood function calss from MINUIT /// (RooMinimizer default 500 * #parameters) -void RooMinimizer::setMaxFunctionCalls(Int_t n) +void RooMinimizer::setMaxFunctionCalls(Int_t n) { - _theFitter->Config().MinimizerOptions().SetMaxFunctionCalls(n); + _theFitter->Config().MinimizerOptions().SetMaxFunctionCalls(n); } @@ -213,7 +169,7 @@ void RooMinimizer::setMaxFunctionCalls(Int_t n) void RooMinimizer::setErrorLevel(Double_t level) { - _theFitter->Config().MinimizerOptions().SetErrorDef(level); + _theFitter->Config().MinimizerOptions().SetErrorDef(level); } @@ -224,47 +180,45 @@ void RooMinimizer::setErrorLevel(Double_t level) void RooMinimizer::setEps(Double_t eps) { - _theFitter->Config().MinimizerOptions().SetTolerance(eps); + _theFitter->Config().MinimizerOptions().SetTolerance(eps); } -//////////////////////////////////////////////////////////////////////////////// -/// Enable internal likelihood offsetting for enhanced numeric precision - -void RooMinimizer::setOffsetting(Bool_t flag) -{ - _func->enableOffsetting(flag) ; -} +//////////////////////////////////////////////////////////////////////////////// +/// Choose the minimzer algorithm. +// forward declaration (necessary for avoiding circular dependency problems) +class RooGradMinimizerFcn; -//////////////////////////////////////////////////////////////////////////////// -/// Choose the minimiser algorithm. void RooMinimizer::setMinimizerType(const char* type) { - _minimizerType = type; + if (dynamic_cast(_fcn) && strcmp(type, "Minuit2") != 0) { + throw std::invalid_argument("In RooMinimizer::setMinimizerType: only Minuit2 is supported when using RooGradMinimizerFcn!"); + } + _minimizerType = type; } //////////////////////////////////////////////////////////////////////////////// -/// Return underlying ROOT fitter object +/// Return underlying ROOT fitter object ROOT::Fit::Fitter* RooMinimizer::fitter() { - return _theFitter ; + return _theFitter ; } //////////////////////////////////////////////////////////////////////////////// -/// Return underlying ROOT fitter object +/// Return underlying ROOT fitter object -const ROOT::Fit::Fitter* RooMinimizer::fitter() const +const ROOT::Fit::Fitter* RooMinimizer::fitter() const { - return _theFitter ; + return _theFitter ; } @@ -282,26 +236,56 @@ const ROOT::Fit::Fitter* RooMinimizer::fitter() const RooFitResult* RooMinimizer::fit(const char* options) { - TString opts(options) ; - opts.ToLower() ; + TString opts(options) ; + opts.ToLower() ; + + // Initial configuration + if (opts.Contains("v")) setVerbose(1) ; + if (opts.Contains("t")) setProfile(1) ; + if (opts.Contains("l")) setLogFile(Form("%s.log", _fcn->getFunctionName().c_str())) ; + if (opts.Contains("c")) optimizeConst(1) ; + + // Fitting steps + if (opts.Contains("0")) setStrategy(0) ; + migrad() ; + if (opts.Contains("0")) setStrategy(1) ; + if (opts.Contains("h")||!opts.Contains("m")) hesse() ; + if (!opts.Contains("m")) minos() ; + + return (opts.Contains("r")) ? save() : 0 ; +} - // Initial configuration - if (opts.Contains("v")) setVerbose(1) ; - if (opts.Contains("t")) setProfile(1) ; - if (opts.Contains("l")) setLogFile(Form("%s.log",_func->GetName())) ; - if (opts.Contains("c")) optimizeConst(1) ; - // Fitting steps - if (opts.Contains("0")) setStrategy(0) ; - migrad() ; - if (opts.Contains("0")) setStrategy(1) ; - if (opts.Contains("h")||!opts.Contains("m")) hesse() ; - if (!opts.Contains("m")) minos() ; - - return (opts.Contains("r")) ? save() : 0 ; -} +bool RooMinimizer::fitFcn() const { + bool ret; + switch (_fcnMode) { + case FcnMode::classic: { + ret = _theFitter->FitFCN(*static_cast(_fcn)); + break; + } + case FcnMode::gradient: { + auto thing = dynamic_cast(_fcn); + if (thing != nullptr) { + ret = _theFitter->FitFCN(*thing); + } else { + throw std::logic_error("In RooMinimizer::fitFcn: Minimizer fcnMode was set to gradient, but fit function type does not match RooGradMinimizerFcn!"); + } + break; + } + case FcnMode::generic_wrapper: { + auto thing = dynamic_cast(_fcn); + if (thing != nullptr) { + ret = _theFitter->FitFCN(*thing); + } else { + throw std::logic_error("In RooMinimizer::fitFcn: Minimizer fcnMode was set to generic_wrapper, but fit function type does not match RooFit::TestStatistics::MinuitFcnGrad!"); + } + break; + } + } + return ret; +} //////////////////////////////////////////////////////////////////////////////// @@ -312,7 +296,7 @@ RooFitResult* RooMinimizer::fit(const char* options) Int_t RooMinimizer::minimize(const char* type, const char* alg) { _fcn->Synchronize(_theFitter->Config().ParamsSettings(), - _optConst,_verbose) ; + _fcn->getOptConst(),_verbose) ; _minimizerType = type; _theFitter->Config().SetMinimizer(type,alg); @@ -321,7 +305,7 @@ Int_t RooMinimizer::minimize(const char* type, const char* alg) RooAbsReal::setEvalErrorLoggingMode(RooAbsReal::CollectErrors) ; RooAbsReal::clearEvalErrorLog() ; - bool ret = _theFitter->FitFCN(*_fcn); + bool ret = fitFcn(); _status = ((ret) ? _theFitter->Result().Status() : -1); RooAbsReal::setEvalErrorLoggingMode(RooAbsReal::PrintErrors) ; @@ -341,16 +325,18 @@ Int_t RooMinimizer::minimize(const char* type, const char* alg) /// propagated back the RooRealVars representing /// the floating parameters in the MINUIT operation. +// TODO: this function's body could be replaced by one line: `minimize(_minimizerType.c_str(),"migrad");`, except for the saveSTATUS call... + Int_t RooMinimizer::migrad() { _fcn->Synchronize(_theFitter->Config().ParamsSettings(), - _optConst,_verbose) ; + _fcn->getOptConst(),_verbose) ; profileStart() ; RooAbsReal::setEvalErrorLoggingMode(RooAbsReal::CollectErrors) ; RooAbsReal::clearEvalErrorLog() ; _theFitter->Config().SetMinimizer(_minimizerType.c_str(),"migrad"); - bool ret = _theFitter->FitFCN(*_fcn); + bool ret = fitFcn(); _status = ((ret) ? _theFitter->Result().Status() : -1); RooAbsReal::setEvalErrorLoggingMode(RooAbsReal::PrintErrors) ; @@ -372,32 +358,31 @@ Int_t RooMinimizer::migrad() Int_t RooMinimizer::hesse() { - if (_theFitter->GetMinimizer()==0) { - coutW(Minimization) << "RooMinimizer::hesse: Error, run Migrad before Hesse!" - << endl ; - _status = -1; - } - else { + if (_theFitter->GetMinimizer()==0) { + coutW(Minimization) << "RooMinimizer::hesse: Error, run Migrad before Hesse!" + << endl ; + _status = -1; + } + else { + + _fcn->Synchronize(_theFitter->Config().ParamsSettings(), _fcn->getOptConst(), _verbose); + profileStart() ; + RooAbsReal::setEvalErrorLoggingMode(RooAbsReal::CollectErrors) ; + RooAbsReal::clearEvalErrorLog() ; - _fcn->Synchronize(_theFitter->Config().ParamsSettings(), - _optConst,_verbose) ; - profileStart() ; - RooAbsReal::setEvalErrorLoggingMode(RooAbsReal::CollectErrors) ; - RooAbsReal::clearEvalErrorLog() ; + _theFitter->Config().SetMinimizer(_minimizerType.c_str()); + bool ret = _theFitter->CalculateHessErrors(); + _status = ((ret) ? _theFitter->Result().Status() : -1); - _theFitter->Config().SetMinimizer(_minimizerType.c_str()); - bool ret = _theFitter->CalculateHessErrors(); - _status = ((ret) ? _theFitter->Result().Status() : -1); + RooAbsReal::setEvalErrorLoggingMode(RooAbsReal::PrintErrors) ; + profileStop() ; + _fcn->BackProp(_theFitter->Result()); - RooAbsReal::setEvalErrorLoggingMode(RooAbsReal::PrintErrors) ; - profileStop() ; - _fcn->BackProp(_theFitter->Result()); + saveStatus("HESSE",_status) ; - saveStatus("HESSE",_status) ; - - } + } - return _status ; + return _status ; } @@ -409,32 +394,31 @@ Int_t RooMinimizer::hesse() Int_t RooMinimizer::minos() { - if (_theFitter->GetMinimizer()==0) { - coutW(Minimization) << "RooMinimizer::minos: Error, run Migrad before Minos!" - << endl ; - _status = -1; - } - else { + if (_theFitter->GetMinimizer()==0) { + coutW(Minimization) << "RooMinimizer::minos: Error, run Migrad before Minos!" + << endl ; + _status = -1; + } + else { + + _fcn->Synchronize(_theFitter->Config().ParamsSettings(), _fcn->getOptConst(), _verbose); + profileStart() ; + RooAbsReal::setEvalErrorLoggingMode(RooAbsReal::CollectErrors) ; + RooAbsReal::clearEvalErrorLog() ; - _fcn->Synchronize(_theFitter->Config().ParamsSettings(), - _optConst,_verbose) ; - profileStart() ; - RooAbsReal::setEvalErrorLoggingMode(RooAbsReal::CollectErrors) ; - RooAbsReal::clearEvalErrorLog() ; - - _theFitter->Config().SetMinimizer(_minimizerType.c_str()); - bool ret = _theFitter->CalculateMinosErrors(); - _status = ((ret) ? _theFitter->Result().Status() : -1); + _theFitter->Config().SetMinimizer(_minimizerType.c_str()); + bool ret = _theFitter->CalculateMinosErrors(); + _status = ((ret) ? _theFitter->Result().Status() : -1); - RooAbsReal::setEvalErrorLoggingMode(RooAbsReal::PrintErrors) ; - profileStop() ; - _fcn->BackProp(_theFitter->Result()); + RooAbsReal::setEvalErrorLoggingMode(RooAbsReal::PrintErrors) ; + profileStop() ; + _fcn->BackProp(_theFitter->Result()); - saveStatus("MINOS",_status) ; + saveStatus("MINOS",_status) ; - } + } - return _status ; + return _status ; } @@ -447,53 +431,52 @@ Int_t RooMinimizer::minos() Int_t RooMinimizer::minos(const RooArgSet& minosParamList) { - if (_theFitter->GetMinimizer()==0) { - coutW(Minimization) << "RooMinimizer::minos: Error, run Migrad before Minos!" - << endl ; - _status = -1; - } - else if (minosParamList.getSize()>0) { - - _fcn->Synchronize(_theFitter->Config().ParamsSettings(), - _optConst,_verbose) ; - profileStart() ; - RooAbsReal::setEvalErrorLoggingMode(RooAbsReal::CollectErrors) ; - RooAbsReal::clearEvalErrorLog() ; - - // get list of parameters for Minos - TIterator* aIter = minosParamList.createIterator() ; - RooAbsArg* arg ; - std::vector paramInd; - while((arg=(RooAbsArg*)aIter->Next())) { - RooAbsArg* par = _fcn->GetFloatParamList()->find(arg->GetName()); - if (par && !par->isConstant()) { - Int_t index = _fcn->GetFloatParamList()->index(par); - paramInd.push_back(index); + if (_theFitter->GetMinimizer()==0) { + coutW(Minimization) << "RooMinimizer::minos: Error, run Migrad before Minos!" + << endl ; + _status = -1; + } + else if (minosParamList.getSize()>0) { + + _fcn->Synchronize(_theFitter->Config().ParamsSettings(), _fcn->getOptConst(), _verbose); + profileStart() ; + RooAbsReal::setEvalErrorLoggingMode(RooAbsReal::CollectErrors) ; + RooAbsReal::clearEvalErrorLog() ; + + // get list of parameters for Minos + TIterator* aIter = minosParamList.createIterator() ; + RooAbsArg* arg ; + std::vector paramInd; + while((arg=(RooAbsArg*)aIter->Next())) { + RooAbsArg* par = _fcn->GetFloatParamList()->find(arg->GetName()); + if (par && !par->isConstant()) { + Int_t index = _fcn->GetFloatParamList()->index(par); + paramInd.push_back(index); + } } - } - delete aIter ; + delete aIter ; - if (paramInd.size()) { - // set the parameter indeces - _theFitter->Config().SetMinosErrors(paramInd); + if (paramInd.size()) { + // set the parameter indeces + _theFitter->Config().SetMinosErrors(paramInd); - _theFitter->Config().SetMinimizer(_minimizerType.c_str()); - bool ret = _theFitter->CalculateMinosErrors(); - _status = ((ret) ? _theFitter->Result().Status() : -1); - // to avoid that following minimization computes automatically the Minos errors - _theFitter->Config().SetMinosErrors(kFALSE); + _theFitter->Config().SetMinimizer(_minimizerType.c_str()); + bool ret = _theFitter->CalculateMinosErrors(); + _status = ((ret) ? _theFitter->Result().Status() : -1); + // to avoid that following minimization computes automatically the Minos errors + _theFitter->Config().SetMinosErrors(kFALSE); - } + } - RooAbsReal::setEvalErrorLoggingMode(RooAbsReal::PrintErrors) ; - profileStop() ; - _fcn->BackProp(_theFitter->Result()); + RooAbsReal::setEvalErrorLoggingMode(RooAbsReal::PrintErrors) ; + profileStop() ; + _fcn->BackProp(_theFitter->Result()); - saveStatus("MINOS",_status) ; + saveStatus("MINOS",_status) ; - } + } - return _status ; + return _status ; } @@ -506,23 +489,22 @@ Int_t RooMinimizer::minos(const RooArgSet& minosParamList) Int_t RooMinimizer::seek() { - _fcn->Synchronize(_theFitter->Config().ParamsSettings(), - _optConst,_verbose) ; - profileStart() ; - RooAbsReal::setEvalErrorLoggingMode(RooAbsReal::CollectErrors) ; - RooAbsReal::clearEvalErrorLog() ; + _fcn->Synchronize(_theFitter->Config().ParamsSettings(), _fcn->getOptConst(), _verbose); + profileStart() ; + RooAbsReal::setEvalErrorLoggingMode(RooAbsReal::CollectErrors) ; + RooAbsReal::clearEvalErrorLog() ; - _theFitter->Config().SetMinimizer(_minimizerType.c_str(),"seek"); - bool ret = _theFitter->FitFCN(*_fcn); - _status = ((ret) ? _theFitter->Result().Status() : -1); + _theFitter->Config().SetMinimizer(_minimizerType.c_str(),"seek"); + bool ret = fitFcn(); + _status = ((ret) ? _theFitter->Result().Status() : -1); - RooAbsReal::setEvalErrorLoggingMode(RooAbsReal::PrintErrors) ; - profileStop() ; - _fcn->BackProp(_theFitter->Result()); + RooAbsReal::setEvalErrorLoggingMode(RooAbsReal::PrintErrors) ; + profileStop() ; + _fcn->BackProp(_theFitter->Result()); - saveStatus("SEEK",_status) ; + saveStatus("SEEK",_status) ; - return _status ; + return _status ; } @@ -535,23 +517,22 @@ Int_t RooMinimizer::seek() Int_t RooMinimizer::simplex() { - _fcn->Synchronize(_theFitter->Config().ParamsSettings(), - _optConst,_verbose) ; - profileStart() ; - RooAbsReal::setEvalErrorLoggingMode(RooAbsReal::CollectErrors) ; - RooAbsReal::clearEvalErrorLog() ; + _fcn->Synchronize(_theFitter->Config().ParamsSettings(), _fcn->getOptConst(), _verbose); + profileStart() ; + RooAbsReal::setEvalErrorLoggingMode(RooAbsReal::CollectErrors) ; + RooAbsReal::clearEvalErrorLog() ; - _theFitter->Config().SetMinimizer(_minimizerType.c_str(),"simplex"); - bool ret = _theFitter->FitFCN(*_fcn); - _status = ((ret) ? _theFitter->Result().Status() : -1); + _theFitter->Config().SetMinimizer(_minimizerType.c_str(),"simplex"); + bool ret = fitFcn(); + _status = ((ret) ? _theFitter->Result().Status() : -1); - RooAbsReal::setEvalErrorLoggingMode(RooAbsReal::PrintErrors) ; - profileStop() ; - _fcn->BackProp(_theFitter->Result()); + RooAbsReal::setEvalErrorLoggingMode(RooAbsReal::PrintErrors) ; + profileStop() ; + _fcn->BackProp(_theFitter->Result()); - saveStatus("SEEK",_status) ; - - return _status ; + saveStatus("SEEK",_status) ; + + return _status ; } @@ -564,23 +545,22 @@ Int_t RooMinimizer::simplex() Int_t RooMinimizer::improve() { - _fcn->Synchronize(_theFitter->Config().ParamsSettings(), - _optConst,_verbose) ; - profileStart() ; - RooAbsReal::setEvalErrorLoggingMode(RooAbsReal::CollectErrors) ; - RooAbsReal::clearEvalErrorLog() ; + _fcn->Synchronize(_theFitter->Config().ParamsSettings(), _fcn->getOptConst(), _verbose); + profileStart() ; + RooAbsReal::setEvalErrorLoggingMode(RooAbsReal::CollectErrors) ; + RooAbsReal::clearEvalErrorLog() ; - _theFitter->Config().SetMinimizer(_minimizerType.c_str(),"migradimproved"); - bool ret = _theFitter->FitFCN(*_fcn); - _status = ((ret) ? _theFitter->Result().Status() : -1); + _theFitter->Config().SetMinimizer(_minimizerType.c_str(),"migradimproved"); + bool ret = fitFcn(); + _status = ((ret) ? _theFitter->Result().Status() : -1); - RooAbsReal::setEvalErrorLoggingMode(RooAbsReal::PrintErrors) ; - profileStop() ; - _fcn->BackProp(_theFitter->Result()); + RooAbsReal::setEvalErrorLoggingMode(RooAbsReal::PrintErrors) ; + profileStop() ; + _fcn->BackProp(_theFitter->Result()); - saveStatus("IMPROVE",_status) ; + saveStatus("IMPROVE",_status) ; - return _status ; + return _status ; } @@ -590,10 +570,10 @@ Int_t RooMinimizer::improve() Int_t RooMinimizer::setPrintLevel(Int_t newLevel) { - Int_t ret = _printLevel ; - _theFitter->Config().MinimizerOptions().SetPrintLevel(newLevel+1); - _printLevel = newLevel+1 ; - return ret ; + Int_t ret = _printLevel ; + _theFitter->Config().MinimizerOptions().SetPrintLevel(newLevel+1); + _printLevel = newLevel+1 ; + return ret ; } //////////////////////////////////////////////////////////////////////////////// @@ -602,24 +582,7 @@ Int_t RooMinimizer::setPrintLevel(Int_t newLevel) void RooMinimizer::optimizeConst(Int_t flag) { - RooAbsReal::setEvalErrorLoggingMode(RooAbsReal::CollectErrors) ; - - if (_optConst && !flag){ - if (_printLevel>-1) coutI(Minimization) << "RooMinimizer::optimizeConst: deactivating const optimization" << endl ; - _func->constOptimizeTestStatistic(RooAbsArg::DeActivate) ; - _optConst = flag ; - } else if (!_optConst && flag) { - if (_printLevel>-1) coutI(Minimization) << "RooMinimizer::optimizeConst: activating const optimization" << endl ; - _func->constOptimizeTestStatistic(RooAbsArg::Activate,flag>1) ; - _optConst = flag ; - } else if (_optConst && flag) { - if (_printLevel>-1) coutI(Minimization) << "RooMinimizer::optimizeConst: const optimization already active" << endl ; - } else { - if (_printLevel>-1) coutI(Minimization) << "RooMinimizer::optimizeConst: const optimization wasn't active" << endl ; - } - - RooAbsReal::setEvalErrorLoggingMode(RooAbsReal::PrintErrors) ; - + _fcn->setOptimizeConst(flag); } @@ -641,8 +604,8 @@ RooFitResult* RooMinimizer::save(const char* userName, const char* userTitle) } TString name,title ; - name = userName ? userName : Form("%s", _func->GetName()) ; - title = userTitle ? userTitle : Form("%s", _func->GetTitle()) ; + name = userName ? userName : Form("%s", _fcn->getFunctionName().c_str()) ; + title = userTitle ? userTitle : Form("%s", _fcn->getFunctionTitle().c_str()) ; RooFitResult* fitRes = new RooFitResult(name,title) ; // Move eventual fixed parameters in floatList to constList @@ -715,97 +678,97 @@ RooFitResult* RooMinimizer::save(const char* userName, const char* userTitle) /// See ROOT::Math::Minimizer::ErrorDef(). RooPlot* RooMinimizer::contour(RooRealVar& var1, RooRealVar& var2, - Double_t n1, Double_t n2, Double_t n3, - Double_t n4, Double_t n5, Double_t n6, unsigned int npoints) + Double_t n1, Double_t n2, Double_t n3, + Double_t n4, Double_t n5, Double_t n6, unsigned int npoints) { - RooArgList* params = _fcn->GetFloatParamList() ; - RooArgList* paramSave = (RooArgList*) params->snapshot() ; - - // Verify that both variables are floating parameters of PDF - Int_t index1= _fcn->GetFloatParamList()->index(&var1); - if(index1 < 0) { - coutE(Minimization) << "RooMinimizer::contour(" << GetName() - << ") ERROR: " << var1.GetName() - << " is not a floating parameter of " - << _func->GetName() << endl ; - return 0; - } - - Int_t index2= _fcn->GetFloatParamList()->index(&var2); - if(index2 < 0) { - coutE(Minimization) << "RooMinimizer::contour(" << GetName() - << ") ERROR: " << var2.GetName() - << " is not a floating parameter of PDF " - << _func->GetName() << endl ; - return 0; - } - - // create and draw a frame - RooPlot* frame = new RooPlot(var1,var2) ; - - // draw a point at the current parameter values - TMarker *point= new TMarker(var1.getVal(), var2.getVal(), 8); - frame->addObject(point) ; - - // check first if a inimizer is available. If not means - // the minimization is not done , so do it - if (_theFitter->GetMinimizer()==0) { - coutW(Minimization) << "RooMinimizer::contour: Error, run Migrad before contours!" - << endl ; - return frame; - } - - - // remember our original value of ERRDEF - Double_t errdef= _theFitter->GetMinimizer()->ErrorDef(); - - Double_t n[6] ; - n[0] = n1 ; n[1] = n2 ; n[2] = n3 ; n[3] = n4 ; n[4] = n5 ; n[5] = n6 ; - - for (Int_t ic = 0 ; ic<6 ; ic++) { - if(n[ic] > 0) { - - // set the value corresponding to an n1-sigma contour - _theFitter->GetMinimizer()->SetErrorDef(n[ic]*n[ic]*errdef); - - // calculate and draw the contour - Double_t *xcoor = new Double_t[npoints+1]; - Double_t *ycoor = new Double_t[npoints+1]; - bool ret = _theFitter->GetMinimizer()->Contour(index1,index2,npoints,xcoor,ycoor); - - if (!ret) { - coutE(Minimization) << "RooMinimizer::contour(" - << GetName() - << ") ERROR: MINUIT did not return a contour graph for n=" - << n[ic] << endl ; - } else { - xcoor[npoints] = xcoor[0]; - ycoor[npoints] = ycoor[0]; - TGraph* graph = new TGraph(npoints+1,xcoor,ycoor); - - graph->SetName(Form("contour_%s_n%f",_func->GetName(),n[ic])) ; - graph->SetLineStyle(ic+1) ; - graph->SetLineWidth(2) ; - graph->SetLineColor(kBlue) ; - frame->addObject(graph,"L") ; + RooArgList* params = _fcn->GetFloatParamList() ; + RooArgList* paramSave = (RooArgList*) params->snapshot() ; + + // Verify that both variables are floating parameters of PDF + Int_t index1= _fcn->GetFloatParamList()->index(&var1); + if(index1 < 0) { + coutE(Minimization) << "RooMinimizer::contour(" << GetName() + << ") ERROR: " << var1.GetName() + << " is not a floating parameter of " + << _fcn->getFunctionName() << endl ; + return 0; + } + + Int_t index2= _fcn->GetFloatParamList()->index(&var2); + if(index2 < 0) { + coutE(Minimization) << "RooMinimizer::contour(" << GetName() + << ") ERROR: " << var2.GetName() + << " is not a floating parameter of PDF " + << _fcn->getFunctionName() << endl ; + return 0; + } + + // create and draw a frame + RooPlot* frame = new RooPlot(var1,var2) ; + + // draw a point at the current parameter values + TMarker *point= new TMarker(var1.getVal(), var2.getVal(), 8); + frame->addObject(point) ; + + // check first if a inimizer is available. If not means + // the minimization is not done , so do it + if (_theFitter->GetMinimizer()==0) { + coutW(Minimization) << "RooMinimizer::contour: Error, run Migrad before contours!" + << endl ; + return frame; + } + + + // remember our original value of ERRDEF + Double_t errdef= _theFitter->GetMinimizer()->ErrorDef(); + + Double_t n[6] ; + n[0] = n1 ; n[1] = n2 ; n[2] = n3 ; n[3] = n4 ; n[4] = n5 ; n[5] = n6 ; + + for (Int_t ic = 0 ; ic<6 ; ic++) { + if(n[ic] > 0) { + + // set the value corresponding to an n1-sigma contour + _theFitter->GetMinimizer()->SetErrorDef(n[ic]*n[ic]*errdef); + + // calculate and draw the contour + Double_t *xcoor = new Double_t[npoints+1]; + Double_t *ycoor = new Double_t[npoints+1]; + bool ret = _theFitter->GetMinimizer()->Contour(index1,index2,npoints,xcoor,ycoor); + + if (!ret) { + coutE(Minimization) << "RooMinimizer::contour(" + << GetName() + << ") ERROR: MINUIT did not return a contour graph for n=" + << n[ic] << endl ; + } else { + xcoor[npoints] = xcoor[0]; + ycoor[npoints] = ycoor[0]; + TGraph* graph = new TGraph(npoints+1,xcoor,ycoor); + + graph->SetName(Form("contour_%s_n%f", _fcn->getFunctionName().c_str(), n[ic])); + graph->SetLineStyle(ic+1) ; + graph->SetLineWidth(2) ; + graph->SetLineColor(kBlue) ; + frame->addObject(graph,"L") ; + } + + delete [] xcoor; + delete [] ycoor; } + } - delete [] xcoor; - delete [] ycoor; - } - } + // restore the original ERRDEF + _theFitter->Config().MinimizerOptions().SetErrorDef(errdef); - // restore the original ERRDEF - _theFitter->Config().MinimizerOptions().SetErrorDef(errdef); + // restore parameter values + *params = *paramSave ; + delete paramSave ; - // restore parameter values - *params = *paramSave ; - delete paramSave ; - - return frame ; + return frame ; } @@ -815,11 +778,11 @@ RooPlot* RooMinimizer::contour(RooRealVar& var1, RooRealVar& var2, void RooMinimizer::profileStart() { - if (_profile) { - _timer.Start() ; - _cumulTimer.Start(_profileStart?kFALSE:kTRUE) ; - _profileStart = kTRUE ; - } + if (_profile) { + _timer.Start() ; + _cumulTimer.Start(_profileStart?kFALSE:kTRUE) ; + _profileStart = kTRUE ; + } } @@ -828,16 +791,85 @@ void RooMinimizer::profileStart() void RooMinimizer::profileStop() { - if (_profile) { - _timer.Stop() ; - _cumulTimer.Stop() ; - coutI(Minimization) << "Command timer: " ; _timer.Print() ; - coutI(Minimization) << "Session timer: " ; _cumulTimer.Print() ; - } + if (_profile) { + _timer.Stop() ; + _cumulTimer.Stop() ; + coutI(Minimization) << "Command timer: " ; _timer.Print() ; + coutI(Minimization) << "Session timer: " ; _cumulTimer.Print() ; + } +} + + +ROOT::Math::IMultiGenFunction* RooMinimizer::getFitterMultiGenFcn() const +{ + return fitter()->GetFCN(); +} + + +ROOT::Math::IMultiGenFunction* RooMinimizer::getMultiGenFcn() const +{ + if (getFitterMultiGenFcn()) { + return getFitterMultiGenFcn(); + } else { + switch (_fcnMode) { + case FcnMode::classic: { + return static_cast(static_cast(_fcn)); + } + case FcnMode::gradient: { + auto thing = dynamic_cast(_fcn); + if (thing != nullptr) { + return static_cast(thing); + } else { + throw std::logic_error("In RooMinimizer::fitterFcn: Minimizer fcnMode was set to gradient, but fit function type does not match RooGradMinimizerFcn!"); + } + } + case FcnMode::generic_wrapper: { + auto thing = dynamic_cast(_fcn); + if (thing != nullptr) { + return static_cast(thing); + } else { + throw std::logic_error("In RooMinimizer::fitterFcn: Minimizer fcnMode was set to generic_wrapper, but fit function type does not match RooFit::TestStatistics::MinuitFcnGrad!"); + } + } + } + } } +const RooAbsMinimizerFcn *RooMinimizer::fitterFcn() const +{ + if (getFitterMultiGenFcn()) { + switch (_fcnMode) { + case FcnMode::classic: { + return static_cast(static_cast(getFitterMultiGenFcn())); + } + case FcnMode::gradient: { + auto thing = dynamic_cast(getFitterMultiGenFcn()); + if (thing != nullptr) { + return static_cast(thing); + } else { + throw std::logic_error("In RooMinimizer::fitterFcn: Minimizer fcnMode was set to gradient, but fit function type does not match RooGradMinimizerFcn!"); + } + } + case FcnMode::generic_wrapper: { + auto thing = dynamic_cast(getFitterMultiGenFcn()); + if (thing != nullptr) { + return static_cast(thing); + } else { + throw std::logic_error("In RooMinimizer::fitterFcn: Minimizer fcnMode was set to generic_wrapper, but fit function type does not match RooFit::TestStatistics::MinuitFcnGrad!"); + } + } + } + } else { + return _fcn; + } +} +RooAbsMinimizerFcn *RooMinimizer::fitterFcn() +{ + // to avoid code duplication, we just reuse the const function and cast constness away + return const_cast( static_cast(*this).fitterFcn() ); +} //////////////////////////////////////////////////////////////////////////////// @@ -847,8 +879,8 @@ void RooMinimizer::profileStop() void RooMinimizer::applyCovarianceMatrix(TMatrixDSym& V) { - _extV = (TMatrixDSym*) V.Clone() ; - _fcn->ApplyCovarianceMatrix(*_extV); + _extV = (TMatrixDSym*) V.Clone() ; + _fcn->ApplyCovarianceMatrix(*_extV); } @@ -959,5 +991,20 @@ RooFitResult* RooMinimizer::lastMinuitFit(const RooArgList& varList) return res; } +Int_t RooMinimizer::getPrintLevel() const +{ + return _printLevel; +} + +void RooMinimizer::set_function_parameter_value(std::size_t ix, double value) const { + fitterFcn()->SetPdfParamVal(ix, value); +} -#endif +void RooMinimizer::enable_likelihood_offsetting(bool flag) { + auto minuit_fcn_grad = dynamic_cast(fitterFcn()); + if (minuit_fcn_grad != nullptr) { + minuit_fcn_grad->enable_likelihood_offsetting(flag); + } else { + throw std::logic_error("cannot enable likelihood offsetting through minimizer on old style likelihood, please switch to new MinuitFcnGrad based RooMinimizer"); + } +} diff --git a/roofit/roofitcore/src/RooMinimizerFcn.cxx b/roofit/roofitcore/src/RooMinimizerFcn.cxx index 84b6f7ef144b8..072d1618dd5b0 100644 --- a/roofit/roofitcore/src/RooMinimizerFcn.cxx +++ b/roofit/roofitcore/src/RooMinimizerFcn.cxx @@ -28,6 +28,7 @@ #include "RooAbsRealLValue.h" #include "RooMsgService.h" #include "RooMinimizer.h" +#include "RooGaussMinimizer.h" #include "RooNaNPacker.h" #include "TClass.h" @@ -40,81 +41,18 @@ using namespace std; RooMinimizerFcn::RooMinimizerFcn(RooAbsReal *funct, RooMinimizer* context, bool verbose) : - _funct(funct), _context(context), - // Reset the *largest* negative log-likelihood value we have seen so far - _maxFCN(-std::numeric_limits::infinity()), _numBadNLL(0), - _printEvalErrors(10), - _nDim(0), _logfile(0), - _verbose(verbose) -{ - - // Examine parameter list - RooArgSet* paramSet = _funct->getParameters(RooArgSet()); - RooArgList paramList(*paramSet); - delete paramSet; - - _floatParamList = (RooArgList*) paramList.selectByAttrib("Constant",kFALSE); - if (_floatParamList->getSize()>1) { - _floatParamList->sort(); - } - _floatParamList->setName("floatParamList"); - - _constParamList = (RooArgList*) paramList.selectByAttrib("Constant",kTRUE); - if (_constParamList->getSize()>1) { - _constParamList->sort(); - } - _constParamList->setName("constParamList"); - - // Remove all non-RooRealVar parameters from list (MINUIT cannot handle them) - for (unsigned int i = 0; i < _floatParamList->size(); ) { // Note: Counting loop, since removing from collection! - const RooAbsArg* arg = (*_floatParamList).at(i); - if (!arg->IsA()->InheritsFrom(RooAbsRealLValue::Class())) { - oocoutW(_context,Minimization) << "RooMinimizerFcn::RooMinimizerFcn: removing parameter " - << arg->GetName() << " from list because it is not of type RooRealVar" << endl; - _floatParamList->remove(*arg); - } else { - ++i; - } - } - - _nDim = _floatParamList->getSize(); + RooAbsMinimizerFcn(*funct->getParameters(RooArgSet()), context, verbose), _funct(funct) +{} - // Save snapshot of initial lists - _initFloatParamList = (RooArgList*) _floatParamList->snapshot(kFALSE) ; - _initConstParamList = (RooArgList*) _constParamList->snapshot(kFALSE) ; -} - - -RooMinimizerFcn::RooMinimizerFcn(const RooMinimizerFcn& other) : ROOT::Math::IBaseFunctionMultiDim(other), - _funct(other._funct), - _context(other._context), - _maxFCN(other._maxFCN), - _funcOffset(other._funcOffset), - _recoverFromNaNStrength(other._recoverFromNaNStrength), - _numBadNLL(other._numBadNLL), - _printEvalErrors(other._printEvalErrors), - _evalCounter(other._evalCounter), - _nDim(other._nDim), - _logfile(other._logfile), - _doEvalErrorWall(other._doEvalErrorWall), - _verbose(other._verbose) -{ - _floatParamList = new RooArgList(*other._floatParamList) ; - _constParamList = new RooArgList(*other._constParamList) ; - _initFloatParamList = (RooArgList*) other._initFloatParamList->snapshot(kFALSE) ; - _initConstParamList = (RooArgList*) other._initConstParamList->snapshot(kFALSE) ; -} +RooMinimizerFcn::RooMinimizerFcn(const RooMinimizerFcn& other) : RooAbsMinimizerFcn(other), ROOT::Math::IBaseFunctionMultiDim(other), + _funct(other._funct) +{} RooMinimizerFcn::~RooMinimizerFcn() -{ - delete _floatParamList; - delete _initFloatParamList; - delete _constParamList; - delete _initConstParamList; -} +{} ROOT::Math::IBaseFunctionMultiDim* RooMinimizerFcn::Clone() const @@ -122,374 +60,44 @@ ROOT::Math::IBaseFunctionMultiDim* RooMinimizerFcn::Clone() const return new RooMinimizerFcn(*this) ; } - -/// Internal function to synchronize TMinimizer with current -/// information in RooAbsReal function parameters -Bool_t RooMinimizerFcn::Synchronize(std::vector& parameters, - Bool_t optConst, Bool_t verbose) +void RooMinimizerFcn::setOptimizeConst(Int_t flag) { - Bool_t constValChange(kFALSE) ; - Bool_t constStatChange(kFALSE) ; - - Int_t index(0) ; - - // Handle eventual migrations from constParamList -> floatParamList - for(index= 0; index < _constParamList->getSize() ; index++) { - - RooRealVar *par= dynamic_cast(_constParamList->at(index)) ; - if (!par) continue ; - - RooRealVar *oldpar= dynamic_cast(_initConstParamList->at(index)) ; - if (!oldpar) continue ; - - // Test if constness changed - if (!par->isConstant()) { - - // Remove from constList, add to floatList - _constParamList->remove(*par) ; - _floatParamList->add(*par) ; - _initFloatParamList->addClone(*oldpar) ; - _initConstParamList->remove(*oldpar) ; - constStatChange=kTRUE ; - _nDim++ ; - - if (verbose) { - oocoutI(_context,Minimization) << "RooMinimizerFcn::synchronize: parameter " - << par->GetName() << " is now floating." << endl ; - } - } - - // Test if value changed - if (par->getVal()!= oldpar->getVal()) { - constValChange=kTRUE ; - if (verbose) { - oocoutI(_context,Minimization) << "RooMinimizerFcn::synchronize: value of constant parameter " - << par->GetName() - << " changed from " << oldpar->getVal() << " to " - << par->getVal() << endl ; - } - } - - } - - // Update reference list - *_initConstParamList = *_constParamList ; - - // Synchronize MINUIT with function state - // Handle floatParamList - for(index= 0; index < _floatParamList->getSize(); index++) { - RooRealVar *par= dynamic_cast(_floatParamList->at(index)) ; - - if (!par) continue ; - - Double_t pstep(0) ; - Double_t pmin(0) ; - Double_t pmax(0) ; - - if(!par->isConstant()) { - - // Verify that floating parameter is indeed of type RooRealVar - if (!par->IsA()->InheritsFrom(RooRealVar::Class())) { - oocoutW(_context,Minimization) << "RooMinimizerFcn::fit: Error, non-constant parameter " - << par->GetName() - << " is not of type RooRealVar, skipping" << endl ; - _floatParamList->remove(*par); - index--; - _nDim--; - continue ; - } - // make sure the parameter are in dirty state to enable - // a real NLL computation when the minimizer calls the function the first time - // (see issue #7659) - par->setValueDirty(); - - // Set the limits, if not infinite - if (par->hasMin() ) - pmin = par->getMin(); - if (par->hasMax() ) - pmax = par->getMax(); - - // Calculate step size - pstep = par->getError(); - if(pstep <= 0) { - // Floating parameter without error estitimate - if (par->hasMin() && par->hasMax()) { - pstep= 0.1*(pmax-pmin); - - // Trim default choice of error if within 2 sigma of limit - if (pmax - par->getVal() < 2*pstep) { - pstep = (pmax - par->getVal())/2 ; - } else if (par->getVal() - pmin < 2*pstep) { - pstep = (par->getVal() - pmin )/2 ; - } - - // If trimming results in zero error, restore default - if (pstep==0) { - pstep= 0.1*(pmax-pmin); - } - - } else { - pstep=1 ; - } - if(verbose) { - oocoutW(_context,Minimization) << "RooMinimizerFcn::synchronize: WARNING: no initial error estimate available for " - << par->GetName() << ": using " << pstep << endl; - } - } - } else { - pmin = par->getVal() ; - pmax = par->getVal() ; - } - - // new parameter - if (index>=Int_t(parameters.size())) { - - if (par->hasMin() && par->hasMax()) { - parameters.push_back(ROOT::Fit::ParameterSettings(par->GetName(), - par->getVal(), - pstep, - pmin,pmax)); - } - else { - parameters.push_back(ROOT::Fit::ParameterSettings(par->GetName(), - par->getVal(), - pstep)); - if (par->hasMin() ) - parameters.back().SetLowerLimit(pmin); - else if (par->hasMax() ) - parameters.back().SetUpperLimit(pmax); - } - - continue; - - } - - Bool_t oldFixed = parameters[index].IsFixed(); - Double_t oldVar = parameters[index].Value(); - Double_t oldVerr = parameters[index].StepSize(); - Double_t oldVlo = parameters[index].LowerLimit(); - Double_t oldVhi = parameters[index].UpperLimit(); - - if (par->isConstant() && !oldFixed) { - - // Parameter changes floating -> constant : update only value if necessary - if (oldVar!=par->getVal()) { - parameters[index].SetValue(par->getVal()); - if (verbose) { - oocoutI(_context,Minimization) << "RooMinimizerFcn::synchronize: value of parameter " - << par->GetName() << " changed from " << oldVar - << " to " << par->getVal() << endl ; - } - } - parameters[index].Fix(); - constStatChange=kTRUE ; - if (verbose) { - oocoutI(_context,Minimization) << "RooMinimizerFcn::synchronize: parameter " - << par->GetName() << " is now fixed." << endl ; - } - - } else if (par->isConstant() && oldFixed) { - - // Parameter changes constant -> constant : update only value if necessary - if (oldVar!=par->getVal()) { - parameters[index].SetValue(par->getVal()); - constValChange=kTRUE ; - - if (verbose) { - oocoutI(_context,Minimization) << "RooMinimizerFcn::synchronize: value of fixed parameter " - << par->GetName() << " changed from " << oldVar - << " to " << par->getVal() << endl ; - } - } - - } else { - // Parameter changes constant -> floating - if (!par->isConstant() && oldFixed) { - parameters[index].Release(); - constStatChange=kTRUE ; - - if (verbose) { - oocoutI(_context,Minimization) << "RooMinimizerFcn::synchronize: parameter " - << par->GetName() << " is now floating." << endl ; - } - } - - // Parameter changes constant -> floating : update all if necessary - if (oldVar!=par->getVal() || oldVlo!=pmin || oldVhi != pmax || oldVerr!=pstep) { - parameters[index].SetValue(par->getVal()); - parameters[index].SetStepSize(pstep); - if (par->hasMin() && par->hasMax() ) - parameters[index].SetLimits(pmin,pmax); - else if (par->hasMin() ) - parameters[index].SetLowerLimit(pmin); - else if (par->hasMax() ) - parameters[index].SetUpperLimit(pmax); - } - - // Inform user about changes in verbose mode - if (verbose) { - // if ierr<0, par was moved from the const list and a message was already printed - - if (oldVar!=par->getVal()) { - oocoutI(_context,Minimization) << "RooMinimizerFcn::synchronize: value of parameter " - << par->GetName() << " changed from " << oldVar << " to " - << par->getVal() << endl ; - } - if (oldVlo!=pmin || oldVhi!=pmax) { - oocoutI(_context,Minimization) << "RooMinimizerFcn::synchronize: limits of parameter " - << par->GetName() << " changed from [" << oldVlo << "," << oldVhi - << "] to [" << pmin << "," << pmax << "]" << endl ; - } - - // If oldVerr=0, then parameter was previously fixed - if (oldVerr!=pstep && oldVerr!=0) { - oocoutI(_context,Minimization) << "RooMinimizerFcn::synchronize: error/step size of parameter " - << par->GetName() << " changed from " << oldVerr << " to " << pstep << endl ; - } - } - } - } - - if (optConst) { - if (constStatChange) { - - RooAbsReal::setEvalErrorLoggingMode(RooAbsReal::CollectErrors) ; - - oocoutI(_context,Minimization) << "RooMinimizerFcn::synchronize: set of constant parameters changed, rerunning const optimizer" << endl ; - _funct->constOptimizeTestStatistic(RooAbsArg::ConfigChange) ; - } else if (constValChange) { - oocoutI(_context,Minimization) << "RooMinimizerFcn::synchronize: constant parameter values changed, rerunning const optimizer" << endl ; - _funct->constOptimizeTestStatistic(RooAbsArg::ValueChange) ; - } - - RooAbsReal::setEvalErrorLoggingMode(RooAbsReal::PrintErrors) ; - - } - - return 0 ; - -} - -/// Modify PDF parameter error by ordinal index (needed by MINUIT) -void RooMinimizerFcn::SetPdfParamErr(Int_t index, Double_t value) -{ - static_cast(_floatParamList->at(index))->setError(value); -} - -/// Modify PDF parameter error by ordinal index (needed by MINUIT) -void RooMinimizerFcn::ClearPdfParamAsymErr(Int_t index) -{ - static_cast(_floatParamList->at(index))->removeAsymError(); + RooAbsReal::setEvalErrorLoggingMode(RooAbsReal::CollectErrors); + + if (_optConst && !flag) { + if (_context->getPrintLevel() > -1) + oocoutI(_context, Minimization) << "RooMinimizerFcn::setOptimizeConst: deactivating const optimization" << endl; + _funct->constOptimizeTestStatistic(RooAbsArg::DeActivate, true); + _optConst = flag; + } else if (!_optConst && flag) { + if (_context->getPrintLevel() > -1) + oocoutI(_context, Minimization) << "RooMinimizerFcn::setOptimizeConst: activating const optimization" << endl; + _funct->constOptimizeTestStatistic(RooAbsArg::Activate, flag > 1); + _optConst = flag; + } else if (_optConst && flag) { + if (_context->getPrintLevel() > -1) + oocoutI(_context, Minimization) << "RooMinimizerFcn::setOptimizeConst: const optimization already active" << endl; + } else { + if (_context->getPrintLevel() > -1) + oocoutI(_context, Minimization) << "RooMinimizerFcn::setOptimizeConst: const optimization wasn't active" << endl; + } + + RooAbsReal::setEvalErrorLoggingMode(RooAbsReal::PrintErrors); } -/// Modify PDF parameter error by ordinal index (needed by MINUIT) -void RooMinimizerFcn::SetPdfParamErr(Int_t index, Double_t loVal, Double_t hiVal) -{ - static_cast(_floatParamList->at(index))->setAsymError(loVal,hiVal); -} +void RooMinimizerFcn::optimizeConstantTerms(bool constStatChange, bool constValChange) { + if (constStatChange) { -/// Transfer MINUIT fit results back into RooFit objects. -void RooMinimizerFcn::BackProp(const ROOT::Fit::FitResult &results) -{ - for (Int_t index= 0; index < _nDim; index++) { - Double_t value = results.Value(index); - SetPdfParamVal(index, value); - - // Set the parabolic error - Double_t err = results.Error(index); - SetPdfParamErr(index, err); - - Double_t eminus = results.LowerError(index); - Double_t eplus = results.UpperError(index); - - if(eplus > 0 || eminus < 0) { - // Store the asymmetric error, if it is available - SetPdfParamErr(index, eminus,eplus); - } else { - // Clear the asymmetric error - ClearPdfParamAsymErr(index) ; - } - } -} - -/// Change the file name for logging of a RooMinimizer of all MINUIT steppings -/// through the parameter space. If inLogfile is null, the current log file -/// is closed and logging is stopped. -Bool_t RooMinimizerFcn::SetLogFile(const char* inLogfile) -{ - if (_logfile) { - oocoutI(_context,Minimization) << "RooMinimizerFcn::setLogFile: closing previous log file" << endl ; - _logfile->close() ; - delete _logfile ; - _logfile = 0 ; - } - _logfile = new ofstream(inLogfile) ; - if (!_logfile->good()) { - oocoutI(_context,Minimization) << "RooMinimizerFcn::setLogFile: cannot open file " << inLogfile << endl ; - _logfile->close() ; - delete _logfile ; - _logfile= 0; - } - - return kFALSE ; -} - -/// Apply results of given external covariance matrix. i.e. propagate its errors -/// to all RRV parameter representations and give this matrix instead of the -/// HESSE matrix at the next save() call -void RooMinimizerFcn::ApplyCovarianceMatrix(TMatrixDSym& V) -{ - for (Int_t i=0 ; i<_nDim ; i++) { - // Skip fixed parameters - if (_floatParamList->at(i)->isConstant()) { - continue ; - } - SetPdfParamErr(i, sqrt(V(i,i))) ; - } - -} - -/// Set value of parameter i. -Bool_t RooMinimizerFcn::SetPdfParamVal(int index, double value) const -{ - auto par = static_cast(&(*_floatParamList)[index]); - - if (par->getVal()!=value) { - if (_verbose) cout << par->GetName() << "=" << value << ", " ; - - par->setVal(value); - return kTRUE; - } - - return kFALSE; -} - - -/// Print information about why evaluation failed. -/// Using _printEvalErrors, the number of errors printed can be steered. -/// Negative values disable printing. -void RooMinimizerFcn::printEvalErrors() const { - if (_printEvalErrors < 0) - return; - - std::ostringstream msg; - if (_doEvalErrorWall) { - msg << "RooMinimizerFcn: Minimized function has error status." << endl - << "Returning maximum FCN so far (" << _maxFCN - << ") to force MIGRAD to back out of this region. Error log follows.\n"; - } else { - msg << "RooMinimizerFcn: Minimized function has error status but is ignored.\n"; - } + RooAbsReal::setEvalErrorLoggingMode(RooAbsReal::CollectErrors) ; - msg << "Parameter values: " ; - for (const auto par : *_floatParamList) { - auto var = static_cast(par); - msg << "\t" << var->GetName() << "=" << var->getVal() ; - } - msg << std::endl; + oocoutI(_context,Minimization) << "RooMinimizerFcn::optimizeConstantTerms: set of constant parameters changed, rerunning const optimizer" << endl ; + _funct->constOptimizeTestStatistic(RooAbsArg::ConfigChange, true) ; + } else if (constValChange) { + oocoutI(_context,Minimization) << "RooMinimizerFcn::optimizeConstantTerms: constant parameter values changed, rerunning const optimizer" << endl ; + _funct->constOptimizeTestStatistic(RooAbsArg::ValueChange, true) ; + } - RooAbsReal::printEvalErrors(msg, _printEvalErrors); - ooccoutW(_context,Minimization) << msg.str() << endl; + RooAbsReal::setEvalErrorLoggingMode(RooAbsReal::PrintErrors) ; } @@ -497,7 +105,7 @@ void RooMinimizerFcn::printEvalErrors() const { double RooMinimizerFcn::DoEval(const double *x) const { // Set the parameter values for this iteration - for (int index = 0; index < _nDim; index++) { + for (unsigned index = 0; index < _nDim; index++) { if (_logfile) (*_logfile) << x[index] << " " ; SetPdfParamVal(index,x[index]); } @@ -541,4 +149,14 @@ double RooMinimizerFcn::DoEval(const double *x) const { return fvalue; } +std::string RooMinimizerFcn::getFunctionName() const +{ + return _funct->GetName(); +} + +std::string RooMinimizerFcn::getFunctionTitle() const +{ + return _funct->GetTitle(); +} + #endif diff --git a/roofit/roofitcore/src/RooNLLVar.cxx b/roofit/roofitcore/src/RooNLLVar.cxx index 540647bb463c2..ba43c3ca9657f 100644 --- a/roofit/roofitcore/src/RooNLLVar.cxx +++ b/roofit/roofitcore/src/RooNLLVar.cxx @@ -59,6 +59,7 @@ namespace { cfg.addCoefRangeName = RooCmdConfig::decodeStringOnTheFly("RooNLLVar::RooNLLVar","AddCoefRange",0,"",args...); cfg.nCPU = RooCmdConfig::decodeIntOnTheFly("RooNLLVar::RooNLLVar","NumCPU",0,1,args...); cfg.interleave = RooFit::BulkPartition; + cfg.CPUAffinity = static_cast(RooCmdConfig::decodeIntOnTheFly("RooNLLVar::RooNLLVar","CPUAffinity",0,1,args...)); cfg.verbose = static_cast(RooCmdConfig::decodeIntOnTheFly("RooNLLVar::RooNLLVar","Verbose",0,1,args...)); cfg.splitCutRange = static_cast(RooCmdConfig::decodeIntOnTheFly("RooNLLVar::RooNLLVar","SplitRange",0,0,args...)); cfg.cloneInputData = static_cast(RooCmdConfig::decodeIntOnTheFly("RooNLLVar::RooNLLVar","CloneData",0,1,args...)); diff --git a/roofit/roofitcore/src/RooNormSetCache.cxx b/roofit/roofitcore/src/RooNormSetCache.cxx index 2bfba9e3380a6..d5932765458b3 100644 --- a/roofit/roofitcore/src/RooNormSetCache.cxx +++ b/roofit/roofitcore/src/RooNormSetCache.cxx @@ -35,6 +35,7 @@ during their lifetime. #include "RooNormSetCache.h" #include "RooArgSet.h" +#include "RooHelpers.h" ClassImp(RooNormSetCache); ; @@ -122,7 +123,6 @@ Bool_t RooNormSetCache::autoCache(const RooAbsArg* self, const RooArgSet* set1, } // B - Check if dependents(set1/set2) are compatible with current cache - RooNameSet nset1d, nset2d; // cout << "RooNormSetCache::autoCache set1 = " << (set1?*set1:RooArgSet()) << " set2 = " << (set2?*set2:RooArgSet()) << endl; // if (set1) set1->Print("v"); @@ -140,10 +140,11 @@ Bool_t RooNormSetCache::autoCache(const RooAbsArg* self, const RooArgSet* set1, // cout << "RooNormSetCache::autoCache set1d = " << *set1d << " set2 = " << *set2d << endl; - nset1d.refill(*set1d); - nset2d.refill(*set2d); + using RooHelpers::getColonSeparatedNameString; - if (nset1d == _name1 && nset2d == _name2 && _set2RangeName == set2RangeName) { + if ( getColonSeparatedNameString(*set1d) == _name1 + && getColonSeparatedNameString(*set2d) == _name2 + && _set2RangeName == set2RangeName) { // Compatible - Add current set1/2 to cache add(set1,set2); @@ -156,8 +157,8 @@ Bool_t RooNormSetCache::autoCache(const RooAbsArg* self, const RooArgSet* set1, if (doRefill) { clear(); add(set1,set2); - _name1.refill(*set1d); - _name2.refill(*set2d); + _name1 = getColonSeparatedNameString(*set1d); + _name2 = getColonSeparatedNameString(*set2d); // cout << "RooNormSetCache::autoCache() _name1 refilled from " << *set1d << " to " ; _name1.printValue(cout) ; cout << endl; // cout << "RooNormSetCache::autoCache() _name2 refilled from " << *set2d << " to " ; _name2.printValue(cout) ; cout << endl; _set2RangeName = (TNamed*) set2RangeName; diff --git a/roofit/roofitcore/src/RooProdPdf.cxx b/roofit/roofitcore/src/RooProdPdf.cxx index dc719c26921df..48705d5498097 100644 --- a/roofit/roofitcore/src/RooProdPdf.cxx +++ b/roofit/roofitcore/src/RooProdPdf.cxx @@ -1787,21 +1787,15 @@ Double_t RooProdPdf::analyticalIntegralWN(Int_t code, const RooArgSet* normSet, // If cache has been sterilized, revive this slot if (cache==0) { - RooArgSet* vars = getParameters(RooArgSet()) ; - RooArgSet* nset = _cacheMgr.nameSet1ByIndex(code-1)->select(*vars) ; - RooArgSet* iset = _cacheMgr.nameSet2ByIndex(code-1)->select(*vars) ; + std::unique_ptr vars{getParameters(RooArgSet())} ; + RooArgSet nset = _cacheMgr.selectFromSet1(*vars, code-1) ; + RooArgSet iset = _cacheMgr.selectFromSet2(*vars, code-1) ; - Int_t code2 = getPartIntList(nset, iset, rangeName) ; - - delete vars ; + Int_t code2 = getPartIntList(&nset, &iset, rangeName) ; // preceding call to getPartIntList guarantees non-null return // coverity[NULL_RETURNS] - cache = (CacheElem*) _cacheMgr.getObj(nset,iset,&code2,rangeName) ; - - delete nset ; - delete iset ; - + cache = (CacheElem*) _cacheMgr.getObj(&nset,&iset,&code2,rangeName) ; } Double_t val = calculate(*cache,kTRUE) ; @@ -2240,13 +2234,12 @@ void RooProdPdf::setCacheAndTrackHints(RooArgSet& trackNodes) RooArgSet* pdf_nset = findPdfNSet((RooAbsPdf&)(*parg)) ; if (pdf_nset) { // Check if conditional normalization is specified + using RooHelpers::getColonSeparatedNameString; if (string("nset")==pdf_nset->GetName() && pdf_nset->getSize()>0) { - RooNameSet n(*pdf_nset) ; - parg->setStringAttribute("CATNormSet",n.content()) ; + parg->setStringAttribute("CATNormSet",getColonSeparatedNameString(*pdf_nset).c_str()) ; } if (string("cset")==pdf_nset->GetName()) { - RooNameSet c(*pdf_nset) ; - parg->setStringAttribute("CATCondSet",c.content()) ; + parg->setStringAttribute("CATCondSet",getColonSeparatedNameString(*pdf_nset).c_str()) ; } } else { coutW(Optimization) << "RooProdPdf::setCacheAndTrackHints(" << GetName() << ") WARNING product pdf does not specify a normalization set for component " << parg->GetName() << endl ; diff --git a/roofit/roofitcore/src/RooProduct.cxx b/roofit/roofitcore/src/RooProduct.cxx index a2d0a3a70ae5e..9190b98a00f26 100644 --- a/roofit/roofitcore/src/RooProduct.cxx +++ b/roofit/roofitcore/src/RooProduct.cxx @@ -309,8 +309,8 @@ Double_t RooProduct::analyticalIntegral(Int_t code, const char* rangeName) const if (cache==0) { // cache got sterilized, trigger repopulation of this slot, then try again... std::unique_ptr vars( getParameters(RooArgSet()) ); - std::unique_ptr iset( _cacheMgr.nameSet2ByIndex(code-1)->select(*vars) ); - Int_t code2 = getPartIntList(iset.get(),rangeName)+1; + RooArgSet iset = _cacheMgr.selectFromSet2(*vars, code-1); + Int_t code2 = getPartIntList(&iset,rangeName)+1; assert(code==code2); // must have revived the right (sterilized) slot... return analyticalIntegral(code2,rangeName); } diff --git a/roofit/roofitcore/src/RooProjectedPdf.cxx b/roofit/roofitcore/src/RooProjectedPdf.cxx index 0366e267507b0..bc008b20e6802 100644 --- a/roofit/roofitcore/src/RooProjectedPdf.cxx +++ b/roofit/roofitcore/src/RooProjectedPdf.cxx @@ -187,24 +187,17 @@ Double_t RooProjectedPdf::analyticalIntegralWN(Int_t code, const RooArgSet* /*no CacheElem *cache = (CacheElem*) _cacheMgr.getObjByIndex(code-1) ; if (cache) { - Double_t ret= cache->_projection->getVal() ; - return ret ; + return cache->_projection->getVal() ; } else { - RooArgSet* vars = getParameters(RooArgSet()) ; + std::unique_ptr vars{getParameters(RooArgSet())} ; vars->add(intobs) ; - RooArgSet* iset = _cacheMgr.nameSet1ByIndex(code-1)->select(*vars) ; - RooArgSet* nset = _cacheMgr.nameSet2ByIndex(code-1)->select(*vars) ; + RooArgSet iset = _cacheMgr.selectFromSet1(*vars, code-1) ; + RooArgSet nset = _cacheMgr.selectFromSet2(*vars, code-1) ; - Int_t code2(-1) ; - const RooAbsReal* proj = getProjection(iset,nset,rangeName,code2) ; - - delete vars ; - delete nset ; - delete iset ; - - Double_t ret = proj->getVal() ; - return ret ; + int code2 = -1 ; + + return getProjection(&iset,&nset,rangeName,code2)->getVal() ; } } diff --git a/roofit/roofitcore/src/RooRealIntegral.cxx b/roofit/roofitcore/src/RooRealIntegral.cxx index b4c67254be5d4..5dffedd0c2449 100644 --- a/roofit/roofitcore/src/RooRealIntegral.cxx +++ b/roofit/roofitcore/src/RooRealIntegral.cxx @@ -50,6 +50,9 @@ integration is performed in the various implementations of the RooAbsIntegrator #include "RooHelpers.h" #include "ROOT/RMakeUnique.hxx" +#include "RooTimer.h" +// getpid and getppid: +#include "unistd.h" #include "TClass.h" @@ -77,7 +80,8 @@ RooRealIntegral::RooRealIntegral() : _numIntegrand(0), _rangeName(0), _params(0), - _cacheNum(kFALSE) + _cacheNum(kFALSE), + _timeNumInt(kFALSE) { TRACE_CREATE } @@ -567,10 +571,42 @@ RooRealIntegral::RooRealIntegral(const char *name, const char *title, _sumCat.addOwned(*sumCat) ; } + // activate timing on numerical integrals + if (RooTrace::time_numInts()) { + activateTimingNumInts(); + } + TRACE_CREATE } +void RooRealIntegral::activateTimingNumInts() { + // activate timing on numerical integrals +// const RooAbsArg& pdfNode = _function.arg(); + const RooAbsPdf& pdfNode = dynamic_cast(_function.arg()); + // TODO: check whether indeed only RooAbsPdf _functions have integral components + Bool_t timing_flag = pdfNode.num_int_timing_flag(); + + RooFIter ni_iter = _intList.fwdIterator(); + while (RooAbsArg *normint = ni_iter.next()) { + // Integral expressions can be composite objects (in case of disjoint normalization ranges) + // Therefore: retrieve list of branch nodes of integral expression + RooArgList bi; + normint->branchNodeServerList(&bi); + RooFIter ib_iter = bi.fwdIterator(); + RooAbsArg *inode; + while ((inode = ib_iter.next())) { + // If a RooRealIntegal component is found... + if (inode->IsA() == RooRealIntegral::Class()) { + // Retrieve the number of real dimensions that is integrated numerically, + RooRealIntegral *rri = (RooRealIntegral *) inode; + rri->setNumIntTiming(timing_flag); + } + } + } +} + + //////////////////////////////////////////////////////////////////////////////// /// Set appropriate cache operation mode for integral depending on cache operation @@ -702,7 +738,7 @@ Bool_t RooRealIntegral::initNumIntegrator() const RooRealIntegral::RooRealIntegral(const RooRealIntegral& other, const char* name) : RooAbsReal(other,name), _valid(other._valid), - _respectCompSelect(other._respectCompSelect), + _respectCompSelect(other._respectCompSelect), _sumList("!sumList",this,other._sumList), _intList("!intList",this,other._intList), _anaList("!anaList",this,other._anaList), @@ -718,7 +754,8 @@ RooRealIntegral::RooRealIntegral(const RooRealIntegral& other, const char* name) _numIntegrand(0), _rangeName(other._rangeName), _params(0), - _cacheNum(kFALSE) + _cacheNum(kFALSE), + _timeNumInt(other._timeNumInt) { _funcNormSet = other._funcNormSet ? (RooArgSet*)other._funcNormSet->snapshot(kFALSE) : 0 ; @@ -732,6 +769,9 @@ RooRealIntegral::RooRealIntegral(const RooRealIntegral& other, const char* name) other._intList.snapshot(_saveInt) ; other._sumList.snapshot(_saveSum) ; + // activate timing on numerical integrals + activateTimingNumInts(); + TRACE_CREATE } @@ -819,8 +859,6 @@ Double_t RooRealIntegral::getValV(const RooArgSet* nset) const - - //////////////////////////////////////////////////////////////////////////////// /// Perform the integration and return the result @@ -832,7 +870,7 @@ Double_t RooRealIntegral::evaluate() const switch (_intOperMode) { case Hybrid: - { + { // Cache numeric integrals in >1d expensive object cache RooDouble* cacheVal(0) ; if ((_cacheNum && _intList.getSize()>0) || _intList.getSize()>=_cacheAllNDim) { @@ -845,40 +883,40 @@ Double_t RooRealIntegral::evaluate() const } else { - // Find any function dependents that are AClean + // Find any function dependents that are AClean // and switch them temporarily to ADirty Bool_t origState = inhibitDirty() ; setDirtyInhibit(kTRUE) ; - + // try to initialize our numerical integration engine if(!(_valid= initNumIntegrator())) { coutE(Integration) << ClassName() << "::" << GetName() << ":evaluate: cannot initialize numerical integrator" << endl; return 0; } - - // Save current integral dependent values + + // Save current integral dependent values _saveInt = _intList ; _saveSum = _sumList ; - + // Evaluate sum/integral retVal = sum() ; - - + + // This must happen BEFORE restoring dependents, otherwise no dirty state propagation in restore step setDirtyInhibit(origState) ; - + // Restore integral dependent values _intList=_saveInt ; _sumList=_saveSum ; - + // Cache numeric integrals in >1d expensive object cache if ((_cacheNum && _intList.getSize()>0) || _intList.getSize()>=_cacheAllNDim) { RooDouble* val = new RooDouble(retVal) ; expensiveObjectCache().registerObject(_function.arg().GetName(),GetName(),*val,parameters()) ; // cout << "### caching value of integral" << GetName() << " in " << &expensiveObjectCache() << endl ; } - + } break ; } @@ -1148,3 +1186,11 @@ Int_t RooRealIntegral::getCacheAllNumeric() { return _cacheAllNDim ; } + +void RooRealIntegral::setNumIntTiming(Bool_t flag) { + Int_t numIntDim = this->numIntRealVars().getSize(); + // .. and activate timing if numeric integration occurs + if (numIntDim > 0) { + _timeNumInt = flag; + } +} \ No newline at end of file diff --git a/roofit/roofitcore/src/RooRealMPFE.cxx b/roofit/roofitcore/src/RooRealMPFE.cxx index 178d97b25510e..c30088ff7853b 100644 --- a/roofit/roofitcore/src/RooRealMPFE.cxx +++ b/roofit/roofitcore/src/RooRealMPFE.cxx @@ -48,7 +48,7 @@ For general multiprocessing in ROOT, please refer to the TProcessExecutor class. #include "RooFit.h" #ifndef _WIN32 -#include "BidirMMapPipe.h" +#include #endif #include @@ -62,16 +62,31 @@ For general multiprocessing in ROOT, please refer to the TProcessExecutor class. #include "RooMsgService.h" #include "RooNLLVar.h" #include "RooTrace.h" +#include "RooConstVar.h" +#include "RooRealIntegral.h" +#include "RooTaskSpec.h" #include "TSystem.h" +// for cpu affinity +#if !defined(__APPLE__) && !defined(_WIN32) +#include +#endif + +#include + RooMPSentinel RooRealMPFE::_sentinel ; +// timing +#include "RooTimer.h" +// getpid and getppid: +#include "unistd.h" + using namespace std; using namespace RooFit; ClassImp(RooRealMPFE); - ; +; //////////////////////////////////////////////////////////////////////////////// @@ -79,19 +94,24 @@ ClassImp(RooRealMPFE); /// asynchronously in a separate process. If calcInline is true the value of 'arg' /// is calculate synchronously in the current process. -RooRealMPFE::RooRealMPFE(const char *name, const char *title, RooAbsReal& arg, Bool_t calcInline) : - RooAbsReal(name,title), - _state(Initialize), - _arg("arg","arg",this,arg), - _vars("vars","vars",this), - _calcInProgress(kFALSE), - _verboseClient(kFALSE), - _verboseServer(kFALSE), - _inlineMode(calcInline), - _remoteEvalErrorLoggingState(RooAbsReal::PrintErrors), - _pipe(0), - _updateMaster(0), - _retrieveDispatched(kFALSE), _evalCarry(0.) +RooRealMPFE::RooRealMPFE(const char *name, const char *title, RooAbsReal& arg, Int_t inSetNum, Int_t inNumSets, + Bool_t calcInline) : + RooAbsReal(name,title), + _state(Initialize), + _arg("arg","arg",this,arg), + _vars("vars","vars",this), + _calcInProgress(kFALSE), + _useTaskSpec(kTRUE), + _verboseClient(kFALSE), + _verboseServer(kFALSE), + _inlineMode(calcInline), + _remoteEvalErrorLoggingState(RooAbsReal::PrintErrors), + _pipe(0), + _updateMaster(0), + _retrieveDispatched(kFALSE), + _evalCarry(0.), + _setNum(inSetNum), + _numSets(inNumSets) { #ifdef _WIN32 _inlineMode = kTRUE; @@ -108,19 +128,24 @@ RooRealMPFE::RooRealMPFE(const char *name, const char *title, RooAbsReal& arg, B /// this instance will create its own server processes RooRealMPFE::RooRealMPFE(const RooRealMPFE& other, const char* name) : - RooAbsReal(other, name), - _state(Initialize), - _arg("arg",this,other._arg), - _vars("vars",this,other._vars), - _calcInProgress(kFALSE), - _verboseClient(other._verboseClient), - _verboseServer(other._verboseServer), - _inlineMode(other._inlineMode), - _forceCalc(other._forceCalc), - _remoteEvalErrorLoggingState(other._remoteEvalErrorLoggingState), - _pipe(0), - _updateMaster(0), - _retrieveDispatched(kFALSE), _evalCarry(other._evalCarry) + RooAbsReal(other, name), + _state(Initialize), + _arg("arg",this,other._arg), + _vars("vars",this,other._vars), + _calcInProgress(kFALSE), + _useTaskSpec(kTRUE), + _verboseClient(other._verboseClient), + _verboseServer(other._verboseServer), + _inlineMode(other._inlineMode), + _forceCalc(other._forceCalc), + _remoteEvalErrorLoggingState(other._remoteEvalErrorLoggingState), + _pipe(0), + _updateMaster(0), + _retrieveDispatched(kFALSE), + _evalCarry(other._evalCarry), + _setNum(other._setNum), + _numSets(other._numSets) + { initVars() ; _sentinel.add(*this) ; @@ -135,6 +160,8 @@ RooRealMPFE::~RooRealMPFE() { if (_state==Client) standby(); _sentinel.remove(*this); + +// delete _components; } @@ -144,6 +171,8 @@ RooRealMPFE::~RooRealMPFE() void RooRealMPFE::initVars() { +// std::cout <<"initialising variables of a RooMPFE"< 0) { +// _initTiming(); +// } if (_pipe->isChild()) { // Start server loop - RooTrace::callgrind_zero() ; +// RooTrace::callgrind_zero() ; _state = Server ; serverLoop(); // Kill server at end of service if (_verboseServer) ccoutD(Minimization) << "RooRealMPFE::initialize(" << - GetName() << ") server process terminating" << endl ; + GetName() << ") server process terminating" << endl ; delete _arg.absArg(); delete _pipe; _exit(0) ; } else { + if (_useTaskSpec){ +// cout<<"sending arg"<pidOtherEnd() << endl ; + GetName() << ") successfully forked server process " << + _pipe->pidOtherEnd() << endl ; _state = Client ; _calcInProgress = kFALSE ; } @@ -221,181 +259,525 @@ void RooRealMPFE::initialize() } +//////////////////////////////////////////////////////////////////////////////// +/// Open the timing file and initialize its field names +void RooRealMPFE::_initTiming() { + std::stringstream proc_id_ss; + + if (_setNum < 0 || _setNum >= _numSets) { + // _setNum not a valid number, setting subprocess output file ID to PID + proc_id_ss << getpid(); + } else { + // otherwise use the ppid combined with the setnum + proc_id_ss << getppid() << "_sub" << _setNum; + } + + std::string proc_id = proc_id_ss.str(); + + if (_pipe->isChild()) { + if (RooTimer::timing_outfiles.size() < 1) { + RooTimer::timing_outfiles.emplace_back(); + } + // on server (slave process) + switch (RooTrace::timing_flag) { + case 9: { + stringstream filename_ss; + filename_ss << "timing_RRMPFE_serverloop_while_p" << proc_id << ".json"; + RooTimer::timing_outfiles[0].open(filename_ss.str().c_str()); + std::string names[3] = {"RRMPFE_serverloop_while_wall_s", "pid", "ppid"}; + RooTimer::timing_outfiles[0].set_member_names(names, names + 3); + break; + } + default: { + // no server-side timing, do nothing + break; + } + } + } else { + // on client (master process) + while (static_cast(RooTimer::timing_outfiles.size()) < _numSets) { + RooTimer::timing_outfiles.emplace_back(); + } + + switch (RooTrace::timing_flag) { + case 4: { + stringstream filename_ss; + filename_ss << "timing_RRMPFE_evaluate_full_p" << proc_id << ".json"; + RooTimer::timing_outfiles[_setNum].open(filename_ss.str().c_str()); + std::string names[2] = {"RRMPFE_evaluate_wall_s", "pid"}; + RooTimer::timing_outfiles[_setNum].set_member_names(names, names + 2); + break; + } + + case 5: { + stringstream filename_ss; + filename_ss << "timing_wall_RRMPFE_evaluate_client_p" << proc_id << ".json"; + RooTimer::timing_outfiles[_setNum].open(filename_ss.str().c_str()); + std::string names[4] = {"time s", "cpu/wall", "segment", "pid"}; + RooTimer::timing_outfiles[_setNum].set_member_names(names, names + 4); + break; + } + + case 6: { + stringstream filename_ss; + filename_ss << "timing_cpu_RRMPFE_evaluate_client_p" << proc_id << ".json"; + RooTimer::timing_outfiles[_setNum].open(filename_ss.str().c_str()); + std::string names[4] = {"time s", "cpu/wall", "segment", "pid"}; + RooTimer::timing_outfiles[_setNum].set_member_names(names, names + 4); + break; + } + + case 7: { + stringstream filename_ss; + + if (_state==Initialize) { + filename_ss << "timing_RRMPFE_calculate_initialize_p" << proc_id << ".json"; + RooTimer::timing_outfiles[_setNum].open(filename_ss.str().c_str()); + std::string names[2] = {"RRMPFE_calculate_initialize_wall_s", "pid"}; + RooTimer::timing_outfiles[_setNum].set_member_names(names, names + 2); + } + + if (_state==Inline) { + filename_ss << "timing_RRMPFE_calculate_inline_p" << proc_id << ".json"; + RooTimer::timing_outfiles[_setNum].open(filename_ss.str().c_str()); + std::string names[2] = {"RRMPFE_calculate_inline_wall_s", "pid"}; + RooTimer::timing_outfiles[_setNum].set_member_names(names, names + 2); + } + + if (_state==Client) { + filename_ss << "timing_RRMPFE_calculate_client_p" << proc_id << ".json"; + RooTimer::timing_outfiles[_setNum].open(filename_ss.str().c_str()); + std::string names[2] = {"RRMPFE_calculate_client_wall_s", "pid"}; + RooTimer::timing_outfiles[_setNum].set_member_names(names, names + 2); + } + break; + } + + case 10: { + // no writing to file, do nothing + break; + } + + default: { + // no client-side timing, do nothing + break; + } + } + } + +} + + +//////////////////////////////////////////////////////////////////////////////// +/// Set the cpu affinity of the server process to a specific cpu. + +void RooRealMPFE::setCpuAffinity(int cpu) { + Message msg = SetCpuAffinity; + *_pipe << msg << cpu; +} + + +//////////////////////////////////////////////////////////////////////////////// +/// Pipe stream operators for timing type and TaskSpec. +namespace RooFit { + using WallClock = std::chrono::system_clock; + using TimePoint = WallClock::time_point; + using Duration = WallClock::duration; + + BidirMMapPipe& operator<<(BidirMMapPipe& bipe, const TimePoint& wall) { + Duration::rep const ns = wall.time_since_epoch().count(); + bipe.write(&ns, sizeof(ns)); + return bipe; + } + + BidirMMapPipe& operator<<(BidirMMapPipe& bipe, const RooTaskSpec::Task& Task) { +// cout<<"passing Task out"<::const_iterator task = TaskSpec.tasks.begin(), end = TaskSpec.tasks.end(); task != end; ++task){ + bipe << *task; + } + return bipe; + } + + BidirMMapPipe& operator>>(BidirMMapPipe& bipe, TimePoint& wall) { + Duration::rep ns; + bipe.read(&ns, sizeof(ns)); + Duration const d(ns); + wall = TimePoint(d); + + return bipe; + } + + // BidirMMapPipe& operator>>(BidirMMapPipe& bipe, const RooTaskSpec::Task& Task) { + // const char *name = Task.name; + // bipe.read(&name, sizeof(name)); + // return bipe; + // } + BidirMMapPipe& operator>>(BidirMMapPipe& bipe, RooTaskSpec::Task& Task) { + bipe >> Task.name; + return bipe; + } +} + + +//////////////////////////////////////////////////////////////////////////////// +/// Set use of RooTaskSpec. + +void RooRealMPFE::setTaskSpec() { +// cout<<"Setting TaskSpec!"<(_arg.absArg()); + RooTaskSpec taskspecification = RooTaskSpec(tmp); + Message msg = TaskSpec; +// cout<<"Got task spec "<< endl; +// tmp->Print(); // WARNING: don't print MPFE values before they're fully initialized! Or make them dirty again afterwards. + *_pipe << msg << *taskspecification.tasks.begin(); + //for (std::list::const_iterator task = taskspecification.tasks.begin(), end = taskspecification.tasks.end(); task != end; ++task){ + // cout << "This task is " << task->name <find(name.c_str()); +// +// cout << "component: " << component <getSize() << endl << endl; +// +// RooFIter iter = _components->fwdIterator(); +// RooAbsArg* node; +// int i = 0; +// while((node = iter.next())) { +// cout << "name of component " << i << ": " << node->GetName() << endl << endl; +// ++i; +// } +// return component; +//} + //////////////////////////////////////////////////////////////////////////////// /// Server loop of remote processes. This function will return /// only when an incoming TERMINATE message is received. -void RooRealMPFE::serverLoop() -{ +void RooRealMPFE::serverLoop() { #ifndef _WIN32 - int msg ; + RooWallTimer timer; - Int_t idx, index, numErrors ; - Double_t value ; - Bool_t isConst ; + int msg; - clearEvalErrorLog() ; + Int_t idx, index, numErrors; + Double_t value; + Bool_t isConst; + + clearEvalErrorLog(); + + while (*_pipe && !_pipe->eof()) { + if (RooTrace::timing_flag == 9) { + timer.start(); + } - while(*_pipe && !_pipe->eof()) { *_pipe >> msg; + if (Terminate == msg) { - if (_verboseServer) cout << "RooRealMPFE::serverLoop(" << GetName() - << ") IPC fromClient> Terminate" << endl; + if (_verboseServer) + cout << "RooRealMPFE::serverLoop(" << GetName() + << ") IPC fromClient> Terminate" << endl; // send terminate acknowledged to client *_pipe << msg << BidirMMapPipe::flush; break; } switch (msg) { - case SendReal: - { - *_pipe >> idx >> value >> isConst; - if (_verboseServer) cout << "RooRealMPFE::serverLoop(" << GetName() - << ") IPC fromClient> SendReal [" << idx << "]=" << value << endl ; - RooRealVar* rvar = (RooRealVar*)_vars.at(idx) ; - rvar->setVal(value) ; - if (rvar->isConstant() != isConst) { - rvar->setConstant(isConst) ; - } + case SendReal: { + *_pipe >> idx >> value >> isConst; + if (_verboseServer) + cout << "RooRealMPFE::serverLoop(" << GetName() + << ") IPC fromClient> SendReal [" << idx << "]=" << value << endl; + RooRealVar *rvar = (RooRealVar *) _vars.at(idx); + rvar->setVal(value); + if (rvar->isConstant() != isConst) { + rvar->setConstant(isConst); + } } - break ; - - case SendCat: - { - *_pipe >> idx >> index; - if (_verboseServer) cout << "RooRealMPFE::serverLoop(" << GetName() - << ") IPC fromClient> SendCat [" << idx << "]=" << index << endl ; - ((RooCategory*)_vars.at(idx))->setIndex(index) ; + break; + + case SendCat: { + *_pipe >> idx >> index; + if (_verboseServer) + cout << "RooRealMPFE::serverLoop(" << GetName() + << ") IPC fromClient> SendCat [" << idx << "]=" << index << endl; + ((RooCategory *) _vars.at(idx))->setIndex(index); } - break ; - - case Calculate: - if (_verboseServer) cout << "RooRealMPFE::serverLoop(" << GetName() - << ") IPC fromClient> Calculate" << endl ; - _value = _arg ; - break ; - - case CalculateNoOffset: - if (_verboseServer) cout << "RooRealMPFE::serverLoop(" << GetName() - << ") IPC fromClient> Calculate" << endl ; - - RooAbsReal::setHideOffset(kFALSE) ; - _value = _arg ; - RooAbsReal::setHideOffset(kTRUE) ; - break ; - - case Retrieve: - { - if (_verboseServer) cout << "RooRealMPFE::serverLoop(" << GetName() - << ") IPC fromClient> Retrieve" << endl ; - msg = ReturnValue; - numErrors = numEvalErrors(); - *_pipe << msg << _value << getCarry() << numErrors; - - if (_verboseServer) cout << "RooRealMPFE::serverLoop(" << GetName() - << ") IPC toClient> ReturnValue " << _value << " NumError " << numErrors << endl ; - - if (numErrors) { - // Loop over errors - std::string objidstr; - { - ostringstream oss2; - // Format string with object identity as this cannot be evaluated on the other side - oss2 << "PID" << gSystem->GetPid() << "/"; - printStream(oss2,kName|kClassName|kArgs,kInline); - objidstr = oss2.str(); - } - std::map > >::const_iterator iter = evalErrorIter(); - const RooAbsArg* ptr = 0; - for (int i = 0; i < numEvalErrorItems(); ++i) { - list::const_iterator iter2 = iter->second.second.begin(); - for (; iter->second.second.end() != iter2; ++iter2) { - ptr = iter->first; - *_pipe << ptr << iter2->_msg << iter2->_srvval << objidstr; - if (_verboseServer) cout << "RooRealMPFE::serverLoop(" << GetName() - << ") IPC toClient> sending error log Arg " << iter->first << " Msg " << iter2->_msg << endl ; - } - } - // let other end know that we're done with the list of errors - ptr = 0; - *_pipe << ptr; - // Clear error list on local side - clearEvalErrorLog(); - } - *_pipe << BidirMMapPipe::flush; + break; + + case Calculate: + if (_verboseServer) + cout << "RooRealMPFE::serverLoop(" << GetName() + << ") IPC fromClient> Calculate" << endl; + _value = _arg; + break; + + case CalculateNoOffset: + if (_verboseServer) + cout << "RooRealMPFE::serverLoop(" << GetName() + << ") IPC fromClient> Calculate" << endl; + + RooAbsReal::setHideOffset(kFALSE); + _value = _arg; + RooAbsReal::setHideOffset(kTRUE); + break; + + case Retrieve: { + if (_verboseServer) + cout << "RooRealMPFE::serverLoop(" << GetName() + << ") IPC fromClient> Retrieve" << endl; + msg = ReturnValue; + numErrors = numEvalErrors(); + *_pipe << msg << _value << getCarry() << numErrors; + + if (_verboseServer) + cout << "RooRealMPFE::serverLoop(" << GetName() + << ") IPC toClient> ReturnValue " << _value << " NumError " << numErrors << endl; + + if (numErrors) { + // Loop over errors + std::string objidstr; + { + ostringstream oss2; + // Format string with object identity as this cannot be evaluated on the other side + oss2 << "PID" << gSystem->GetPid() << "/"; + printStream(oss2, kName | kClassName | kArgs, kInline); + objidstr = oss2.str(); + } + std::map > > ::const_iterator + iter = evalErrorIter(); + const RooAbsArg *ptr = 0; + for (int i = 0; i < numEvalErrorItems(); ++i) { + list::const_iterator iter2 = iter->second.second.begin(); + for (; iter->second.second.end() != iter2; ++iter2) { + ptr = iter->first; + *_pipe << ptr << iter2->_msg << iter2->_srvval << objidstr; + if (_verboseServer) + cout << "RooRealMPFE::serverLoop(" << GetName() + << ") IPC toClient> sending error log Arg " << iter->first << " Msg " << iter2->_msg << endl; + } + } + // let other end know that we're done with the list of errors + ptr = 0; + *_pipe << ptr; + // Clear error list on local side + clearEvalErrorLog(); + } + *_pipe << BidirMMapPipe::flush; + } + break; + + case ConstOpt: { + Bool_t doTrack; + int code; + *_pipe >> code >> doTrack; + if (_verboseServer) + cout << "RooRealMPFE::serverLoop(" << GetName() + << ") IPC fromClient> ConstOpt " << code << " doTrack = " << (doTrack ? "T" : "F") << endl; + ((RooAbsReal &) _arg.arg()).constOptimizeTestStatistic(static_cast(code), doTrack); + break; } - break; - case ConstOpt: - { - Bool_t doTrack ; - int code; - *_pipe >> code >> doTrack; - if (_verboseServer) cout << "RooRealMPFE::serverLoop(" << GetName() - << ") IPC fromClient> ConstOpt " << code << " doTrack = " << (doTrack?"T":"F") << endl ; - ((RooAbsReal&)_arg.arg()).constOptimizeTestStatistic(static_cast(code),doTrack) ; - break ; + case Verbose: { + Bool_t flag; + *_pipe >> flag; + if (_verboseServer) + cout << "RooRealMPFE::serverLoop(" << GetName() + << ") IPC fromClient> Verbose " << (flag ? 1 : 0) << endl; + _verboseServer = flag; } + break; + + + case ApplyNLLW2: { + Bool_t flag; + *_pipe >> flag; + if (_verboseServer) + cout << "RooRealMPFE::serverLoop(" << GetName() + << ") IPC fromClient> ApplyNLLW2 " << (flag ? 1 : 0) << endl; - case Verbose: - { - Bool_t flag ; - *_pipe >> flag; - if (_verboseServer) cout << "RooRealMPFE::serverLoop(" << GetName() - << ") IPC fromClient> Verbose " << (flag?1:0) << endl ; - _verboseServer = flag ; + // Do application of weight-squared here + doApplyNLLW2(flag); } - break ; + break; + case EnableOffset: { + Bool_t flag; + *_pipe >> flag; + if (_verboseServer) + cout << "RooRealMPFE::serverLoop(" << GetName() + << ") IPC fromClient> EnableOffset " << (flag ? 1 : 0) << endl; - case ApplyNLLW2: - { - Bool_t flag ; - *_pipe >> flag; - if (_verboseServer) cout << "RooRealMPFE::serverLoop(" << GetName() - << ") IPC fromClient> ApplyNLLW2 " << (flag?1:0) << endl ; + // Enable likelihoof offsetting here + ((RooAbsReal &) _arg.arg()).enableOffsetting(flag); + } + break; + + case LogEvalError: { + int iflag2; + *_pipe >> iflag2; + RooAbsReal::ErrorLoggingMode flag2 = static_cast(iflag2); + RooAbsReal::setEvalErrorLoggingMode(flag2); + if (_verboseServer) + cout << "RooRealMPFE::serverLoop(" << GetName() + << ") IPC fromClient> LogEvalError flag = " << flag2 << endl; + } + break; + + + case SetCpuAffinity: { + int cpu; + *_pipe >> cpu; + +#if defined(__APPLE__) + if (_verboseServer) + std::cout << "WARNING: CPU affinity cannot be set on macOS, continuing..." << std::endl; +#elif defined(_WIN32) + if (_verboseServer) + std::cout << "WARNING: CPU affinity setting not implemented on Windows, continuing..." << std::endl; +#else + cpu_set_t mask; + // zero all bits in mask + CPU_ZERO(&mask); + // set correct bit + CPU_SET(cpu, &mask); + /* sched_setaffinity returns 0 in success */ + + if (_verboseServer) { + if (sched_setaffinity(0, sizeof(mask), &mask) == -1) { + std::cout << "WARNING: Could not set CPU affinity, continuing..." << std::endl; + } else { + std::cout << "CPU affinity set to cpu " << cpu << " in server process " << getpid() << std::endl; + } + } +#endif - // Do application of weight-squared here - doApplyNLLW2(flag) ; + break; } - break ; - case EnableOffset: - { - Bool_t flag ; - *_pipe >> flag; - if (_verboseServer) cout << "RooRealMPFE::serverLoop(" << GetName() - << ") IPC fromClient> EnableOffset " << (flag?1:0) << endl ; + case TaskSpec: { - // Enable likelihoof offsetting here - ((RooAbsReal&)_arg.arg()).enableOffsetting(flag) ; + RooTaskSpec::Task taskspecification; + // cout << *_pipe << endl; + //RooTaskSpec taskspecification; + *_pipe >> taskspecification; +// std::cout << "EEEEEE TaskSpec'd "<< taskspecification.name <> iflag2; - RooAbsReal::ErrorLoggingMode flag2 = static_cast(iflag2); - RooAbsReal::setEvalErrorLoggingMode(flag2) ; - if (_verboseServer) cout << "RooRealMPFE::serverLoop(" << GetName() - << ") IPC fromClient> LogEvalError flag = " << flag2 << endl ; + + + case EnableTimingNumInts: { + // This must be done server-side, otherwise you have to copy all timing flags to server manually anyway + // FIXME: make this more general than just RooAbsTestStatistic (when needed) + dynamic_cast(_arg.absArg())->_setNumIntTimingInPdfs(); + break; } - break ; - default: - if (_verboseServer) cout << "RooRealMPFE::serverLoop(" << GetName() - << ") IPC fromClient> Unknown message (code = " << msg << ")" << endl ; - break ; + case DisableTimingNumInts: { + dynamic_cast(_arg.absArg())->_setNumIntTimingInPdfs(kFALSE); + break; + } + + + case MeasureCommunicationTime: { + // Measure end time asap, since time of arrival at this case block is what we need to measure + // communication overhead, i.e. time between sending message and corresponding action taken. + // Defining the end time variable can reasonably be called overhead too, since many other + // messages also define a piped-message-receiving variable. + TimePoint comm_wallclock_begin, comm_wallclock_end; + comm_wallclock_end = WallClock::now(); + + *_pipe >> comm_wallclock_begin; + + std::cout << "client to server communication overhead timing:" << std::endl; + std::cout << "comm_wallclock_begin: " << std::chrono::duration_cast(comm_wallclock_begin.time_since_epoch()).count() << std::endl; + std::cout << "comm_wallclock_end: " << std::chrono::duration_cast(comm_wallclock_end.time_since_epoch()).count() << std::endl; + + double comm_wallclock_s = std::chrono::duration_cast(comm_wallclock_end - comm_wallclock_begin).count() / 1.e9; + + std::cout << "comm_wallclock (seconds): " << comm_wallclock_s << std::endl; + + comm_wallclock_begin = WallClock::now(); + *_pipe << comm_wallclock_begin << BidirMMapPipe::flush; + + break; + } + + case RetrieveTimings: { + Bool_t clear_timings; + *_pipe >> clear_timings; + *_pipe << static_cast(RooTrace::objectTiming.size()) << BidirMMapPipe::flush; + for (auto it = RooTrace::objectTiming.begin(); it != RooTrace::objectTiming.end(); ++it) { + std::string name = it->first; + double timing_s = it->second; + *_pipe << name << timing_s << BidirMMapPipe::flush; + } + if (clear_timings == kTRUE) { + RooTrace::objectTiming.clear(); + } + + break; + } + + case GetPID: { + *_pipe << getpid() << BidirMMapPipe::flush; + break; + } + + + default: + if (_verboseServer) + cout << "RooRealMPFE::serverLoop(" << GetName() + << ") IPC fromClient> Unknown message (code = " << msg << ")" << endl; + break; + } + + // end timing + if (RooTrace::timing_flag == 9) { + timer.stop(); + RooTimer::timing_outfiles[0] << timer.timing_s() << getpid() << getppid(); } + + if (Terminate == msg) { + if (_verboseServer) + cout << "RooRealMPFE::serverLoop(" << GetName() + << ") Terminate from inside loop itself" << endl; + break; + } + } #endif // _WIN32 } +//////////////////////////////////////////////////////////////////////////////// + +void RooRealMPFE::setTimingNumInts(Bool_t flag) { + if (flag == kTRUE) { + *_pipe << EnableTimingNumInts; + } else { + *_pipe << DisableTimingNumInts; + } +} + //////////////////////////////////////////////////////////////////////////////// /// Client-side function that instructs server process to start @@ -405,23 +787,51 @@ void RooRealMPFE::serverLoop() void RooRealMPFE::calculate() const { + RooWallTimer timer; // Start asynchronous calculation of arg value if (_state==Initialize) { // cout << "RooRealMPFE::calculate(" << GetName() << ") initializing" << endl ; + if (RooTrace::timing_flag == 7) { + timer.start(); + } + const_cast(this)->initialize() ; + + if (RooTrace::timing_flag == 7) { + timer.stop(); + RooTimer::timing_outfiles[_setNum] << timer.timing_s() << getpid(); + } } // Inline mode -- Calculate value now if (_state==Inline) { // cout << "RooRealMPFE::calculate(" << GetName() << ") performing Inline calculation NOW" << endl ; + if (RooTrace::timing_flag == 7) { + timer.start(); + } + _value = _arg ; clearValueDirty() ; + + if (RooTrace::timing_flag == 7) { + timer.stop(); + RooTimer::timing_outfiles[_setNum] << timer.timing_s() << getpid(); + } } #ifndef _WIN32 // Compare current value of variables with saved values and send changes to server if (_state==Client) { + // timing stuff + if (RooTrace::timing_flag == 7) { + timer.start(); + } + + if (RooTrace::timing_flag == 10) { + _time_communication_overhead(); + } + // cout << "RooRealMPFE::calculate(" << GetName() << ") state is Client trigger remote calculation" << endl ; Int_t i(0) ; RooFIter viter = _vars.fwdIterator() ; @@ -435,40 +845,40 @@ void RooRealMPFE::calculate() const //Bool_t valChanged = !(*var==*saveVar) ; Bool_t valChanged,constChanged ; if (!_updateMaster) { - valChanged = !var->isIdentical(*saveVar,kTRUE) ; - constChanged = (var->isConstant() != saveVar->isConstant()) ; - _valueChanged[i] = valChanged ; - _constChanged[i] = constChanged ; + valChanged = !var->isIdentical(*saveVar,kTRUE) ; + constChanged = (var->isConstant() != saveVar->isConstant()) ; + _valueChanged[i] = valChanged ; + _constChanged[i] = constChanged ; } else { - valChanged = _updateMaster->_valueChanged[i] ; - constChanged = _updateMaster->_constChanged[i] ; + valChanged = _updateMaster->_valueChanged[i] ; + constChanged = _updateMaster->_constChanged[i] ; } if ( valChanged || constChanged || _forceCalc) { - //cout << "RooRealMPFE::calculate(" << GetName() << " variable " << var->GetName() << " changed " << endl ; - if (_verboseClient) cout << "RooRealMPFE::calculate(" << GetName() - << ") variable " << _vars.at(i)->GetName() << " changed" << endl ; - if (constChanged) { - ((RooRealVar*)saveVar)->setConstant(var->isConstant()) ; - } - saveVar->copyCache(var) ; - - // send message to server - if (dynamic_cast(var)) { - int msg = SendReal ; - Double_t val = ((RooAbsReal*)var)->getVal() ; - Bool_t isC = var->isConstant() ; - *_pipe << msg << i << val << isC; - - if (_verboseServer) cout << "RooRealMPFE::calculate(" << GetName() - << ") IPC toServer> SendReal [" << i << "]=" << val << (isC?" (Constant)":"") << endl ; - } else if (dynamic_cast(var)) { - int msg = SendCat ; - UInt_t idx = ((RooAbsCategory*)var)->getCurrentIndex() ; - *_pipe << msg << i << idx; - if (_verboseServer) cout << "RooRealMPFE::calculate(" << GetName() - << ") IPC toServer> SendCat [" << i << "]=" << idx << endl ; - } + //cout << "RooRealMPFE::calculate(" << GetName() << " variable " << var->GetName() << " changed " << endl ; + if (_verboseClient) cout << "RooRealMPFE::calculate(" << GetName() + << ") variable " << _vars.at(i)->GetName() << " changed" << endl ; + if (constChanged) { + ((RooRealVar*)saveVar)->setConstant(var->isConstant()) ; + } + saveVar->copyCache(var) ; + + // send message to server + if (dynamic_cast(var)) { + int msg = SendReal ; + Double_t val = ((RooAbsReal*)var)->getVal() ; + Bool_t isC = var->isConstant() ; + *_pipe << msg << i << val << isC; + + if (_verboseServer) cout << "RooRealMPFE::calculate(" << GetName() + << ") IPC toServer> SendReal [" << i << "]=" << val << (isC?" (Constant)":"") << endl ; + } else if (dynamic_cast(var)) { + int msg = SendCat ; + UInt_t idx = ((RooAbsCategory*)var)->getCurrentIndex() ; + *_pipe << msg << i << idx; + if (_verboseServer) cout << "RooRealMPFE::calculate(" << GetName() + << ") IPC toServer> SendCat [" << i << "]=" << idx << endl ; + } } i++ ; } @@ -476,7 +886,7 @@ void RooRealMPFE::calculate() const int msg = hideOffset() ? Calculate : CalculateNoOffset; *_pipe << msg; if (_verboseServer) cout << "RooRealMPFE::calculate(" << GetName() - << ") IPC toServer> Calculate " << endl ; + << ") IPC toServer> Calculate " << endl ; // Clear dirty state and mark that calculation request was dispatched clearValueDirty() ; @@ -486,12 +896,18 @@ void RooRealMPFE::calculate() const msg = Retrieve ; *_pipe << msg << BidirMMapPipe::flush; if (_verboseServer) cout << "RooRealMPFE::evaluate(" << GetName() - << ") IPC toServer> Retrieve " << endl ; + << ") IPC toServer> Retrieve " << endl ; _retrieveDispatched = kTRUE ; + // end timing + if (RooTrace::timing_flag == 7) { + timer.stop(); + RooTimer::timing_outfiles[_setNum] << timer.timing_s() << getpid(); + } + } else if (_state!=Inline) { cout << "RooRealMPFE::calculate(" << GetName() - << ") ERROR not in Client or Inline mode" << endl ; + << ") ERROR not in Client or Inline mode" << endl ; } @@ -537,14 +953,31 @@ Double_t RooRealMPFE::getValV(const RooArgSet* /*nset*/) const Double_t RooRealMPFE::evaluate() const { + RooWallTimer wtimer, wtimer_before, wtimer_retrieve, wtimer_after; + RooCPUTimer ctimer, ctimer_before, ctimer_retrieve, ctimer_after; + + if (RooTrace::timing_flag == 4) { + wtimer.start(); + } + // Retrieve value of arg Double_t return_value = 0; if (_state==Inline) { return_value = _arg ; } else if (_state==Client) { #ifndef _WIN32 + if (RooTrace::timing_flag == 5) { + wtimer.start(); + wtimer_before.start(); + } + if (RooTrace::timing_flag == 6) { + ctimer.start(); + ctimer_before.start(); + } + bool needflush = false; - int msg; + int msg_i; + Message msg; Double_t value; // If current error loggin state is not the same as remote state @@ -552,17 +985,17 @@ Double_t RooRealMPFE::evaluate() const if (evalErrorLoggingMode() != _remoteEvalErrorLoggingState) { msg = LogEvalError ; RooAbsReal::ErrorLoggingMode flag = evalErrorLoggingMode() ; - *_pipe << msg << flag; + *_pipe << static_cast(msg) << flag; needflush = true; _remoteEvalErrorLoggingState = evalErrorLoggingMode() ; } if (!_retrieveDispatched) { msg = Retrieve ; - *_pipe << msg; + *_pipe << static_cast(msg); needflush = true; if (_verboseServer) cout << "RooRealMPFE::evaluate(" << GetName() - << ") IPC toServer> Retrieve " << endl ; + << ") IPC toServer> Retrieve " << endl ; } if (needflush) *_pipe << BidirMMapPipe::flush; _retrieveDispatched = kFALSE ; @@ -570,30 +1003,49 @@ Double_t RooRealMPFE::evaluate() const Int_t numError; - *_pipe >> msg >> value >> _evalCarry >> numError; + if (RooTrace::timing_flag == 5) { + wtimer_before.stop(); + wtimer_retrieve.start(); + } + if (RooTrace::timing_flag == 6) { + ctimer_before.stop(); + ctimer_retrieve.start(); + } + + *_pipe >> msg_i >> value >> _evalCarry >> numError; + msg = static_cast(msg_i); + + if (RooTrace::timing_flag == 5) { + wtimer_retrieve.stop(); + wtimer_after.start(); + } + if (RooTrace::timing_flag == 6) { + ctimer_retrieve.stop(); + ctimer_after.start(); + } if (msg!=ReturnValue) { cout << "RooRealMPFE::evaluate(" << GetName() - << ") ERROR: unexpected message from server process: " << msg << endl ; + << ") ERROR: unexpected message from server process: " << msg << endl ; return 0 ; } if (_verboseServer) cout << "RooRealMPFE::evaluate(" << GetName() - << ") IPC fromServer> ReturnValue " << value << endl ; + << ") IPC fromServer> ReturnValue " << value << endl ; if (_verboseServer) cout << "RooRealMPFE::evaluate(" << GetName() - << ") IPC fromServer> NumErrors " << numError << endl ; + << ") IPC fromServer> NumErrors " << numError << endl ; if (numError) { // Retrieve remote errors and feed into local error queue char *msgbuf1 = 0, *msgbuf2 = 0, *msgbuf3 = 0; RooAbsArg *ptr = 0; while (true) { - *_pipe >> ptr; - if (!ptr) break; - *_pipe >> msgbuf1 >> msgbuf2 >> msgbuf3; - if (_verboseServer) cout << "RooRealMPFE::evaluate(" << GetName() - << ") IPC fromServer> retrieving error log Arg " << ptr << " Msg " << msgbuf1 << endl ; + *_pipe >> ptr; + if (!ptr) break; + *_pipe >> msgbuf1 >> msgbuf2 >> msgbuf3; + if (_verboseServer) cout << "RooRealMPFE::evaluate(" << GetName() + << ") IPC fromServer> retrieving error log Arg " << ptr << " Msg " << msgbuf1 << endl ; - logEvalError(reinterpret_cast(ptr),msgbuf3,msgbuf1,msgbuf2) ; + logEvalError(reinterpret_cast(ptr),msgbuf3,msgbuf1,msgbuf2) ; } std::free(msgbuf1); std::free(msgbuf2); @@ -603,9 +1055,37 @@ Double_t RooRealMPFE::evaluate() const // Mark end of calculation in progress _calcInProgress = kFALSE ; return_value = value ; + + + if (RooTrace::timing_flag == 5) { + wtimer_after.stop(); + wtimer.stop(); + + RooTimer::timing_outfiles[_setNum] << wtimer.timing_s() << "wall" << "all" << getpid(); + RooTimer::timing_outfiles[_setNum] << wtimer_before.timing_s() << "wall" << "before_retrieve" << getpid(); + RooTimer::timing_outfiles[_setNum] << wtimer_retrieve.timing_s() << "wall" << "retrieve" << getpid(); + RooTimer::timing_outfiles[_setNum] << wtimer_after.timing_s() << "wall" << "after_retrieve" << getpid(); + } + + if (RooTrace::timing_flag == 6) { + ctimer_after.stop(); + ctimer.stop(); + + RooTimer::timing_outfiles[_setNum] << ctimer.timing_s() << "cpu" << "all" << getpid(); + RooTimer::timing_outfiles[_setNum] << ctimer_before.timing_s() << "cpu" << "before_retrieve" << getpid(); + RooTimer::timing_outfiles[_setNum] << ctimer_retrieve.timing_s() << "cpu" << "retrieve" << getpid(); + RooTimer::timing_outfiles[_setNum] << ctimer_after.timing_s() << "cpu" << "after_retrieve" << getpid(); + } + + #endif // _WIN32 } + if (RooTrace::timing_flag == 4) { + wtimer.stop(); + RooTimer::timing_outfiles[_setNum] << wtimer.timing_s() << getpid(); + } + return return_value; } @@ -623,21 +1103,21 @@ void RooRealMPFE::standby() if (_pipe->good()) { // Terminate server process ; if (_verboseServer) cout << "RooRealMPFE::standby(" << GetName() - << ") IPC toServer> Terminate " << endl; + << ") IPC toServer> Terminate " << endl; int msg = Terminate; *_pipe << msg << BidirMMapPipe::flush; // read handshake msg = 0; *_pipe >> msg; if (Terminate != msg || 0 != _pipe->close()) { - std::cerr << "In " << __func__ << "(" << __FILE__ ", " << __LINE__ << - "): Server shutdown failed." << std::endl; + std::cerr << "In " << __func__ << "(" << __FILE__ ", " << __LINE__ << + "): Server shutdown failed." << std::endl; } } else { if (_verboseServer) { - std::cerr << "In " << __func__ << "(" << __FILE__ ", " << - __LINE__ << "): Pipe has already shut down, not sending " - "Terminate to server." << std::endl; + std::cerr << "In " << __func__ << "(" << __FILE__ ", " << + __LINE__ << "): Pipe has already shut down, not sending " + "Terminate to server." << std::endl; } } // Close pipes @@ -665,7 +1145,7 @@ void RooRealMPFE::constOptimizeTestStatistic(ConstOpCode opcode, Bool_t doAlsoTr int op = opcode; *_pipe << msg << op << doAlsoTracking; if (_verboseServer) cout << "RooRealMPFE::constOptimize(" << GetName() - << ") IPC toServer> ConstOpt " << opcode << endl ; + << ") IPC toServer> ConstOpt " << opcode << endl ; initVars() ; } @@ -689,7 +1169,7 @@ void RooRealMPFE::setVerbose(Bool_t clientFlag, Bool_t serverFlag) int msg = Verbose ; *_pipe << msg << serverFlag; if (_verboseServer) cout << "RooRealMPFE::setVerbose(" << GetName() - << ") IPC toServer> Verbose " << (serverFlag?1:0) << endl ; + << ") IPC toServer> Verbose " << (serverFlag?1:0) << endl ; } #endif // _WIN32 _verboseClient = clientFlag ; _verboseServer = serverFlag ; @@ -707,7 +1187,7 @@ void RooRealMPFE::applyNLLWeightSquared(Bool_t flag) int msg = ApplyNLLW2 ; *_pipe << msg << flag; if (_verboseServer) cout << "RooRealMPFE::applyNLLWeightSquared(" << GetName() - << ") IPC toServer> ApplyNLLW2 " << (flag?1:0) << endl ; + << ") IPC toServer> ApplyNLLW2 " << (flag?1:0) << endl ; } #endif // _WIN32 doApplyNLLW2(flag) ; @@ -736,11 +1216,88 @@ void RooRealMPFE::enableOffsetting(Bool_t flag) int msg = EnableOffset ; *_pipe << msg << flag; if (_verboseServer) cout << "RooRealMPFE::enableOffsetting(" << GetName() - << ") IPC toServer> EnableOffset " << (flag?1:0) << endl ; + << ") IPC toServer> EnableOffset " << (flag?1:0) << endl ; } #endif // _WIN32 ((RooAbsReal&)_arg.arg()).enableOffsetting(flag) ; } +std::map RooRealMPFE::collectTimingsFromServer(Bool_t clear_timings) const { + std::map server_timings; + + *_pipe << RetrieveTimings << clear_timings << BidirMMapPipe::flush; + + unsigned long numTimings; + *_pipe >> numTimings; + + for (unsigned long i = 0; i < numTimings; ++i) { + std::string name; + double timing_s; + *_pipe >> name >> timing_s; + server_timings.insert({name, timing_s}); + } + + return server_timings; +} + +pid_t RooRealMPFE::getPIDFromServer() const { + *_pipe << GetPID << BidirMMapPipe::flush; + pid_t pid; + *_pipe >> pid; + return pid; +} + +void RooRealMPFE::_time_communication_overhead() const { + // test communication overhead timing + TimePoint comm_wallclock_begin_c2s, comm_wallclock_begin_s2c, comm_wallclock_end_s2c; + // ... from client to server + comm_wallclock_begin_c2s = WallClock::now(); + *_pipe << MeasureCommunicationTime << comm_wallclock_begin_c2s << BidirMMapPipe::flush; + // ... and from server to client + *_pipe >> comm_wallclock_begin_s2c; + comm_wallclock_end_s2c = WallClock::now(); + + std::cout << "server to client communication overhead timing:" << std::endl; + std::cout << "comm_wallclock_begin: " << std::chrono::duration_cast(comm_wallclock_begin_s2c.time_since_epoch()).count() << std::endl; + std::cout << "comm_wallclock_end: " << std::chrono::duration_cast(comm_wallclock_end_s2c.time_since_epoch()).count() << std::endl; + + double comm_wallclock_s = std::chrono::duration_cast(comm_wallclock_end_s2c - comm_wallclock_begin_s2c).count() / 1.e9; + + std::cout << "comm_wallclock (seconds): " << comm_wallclock_s << std::endl; +} + + +std::ostream& operator<<(std::ostream& out, const RooRealMPFE::Message value){ + const char* s = 0; +#define PROCESS_VAL(p) case(p): s = #p; break; + switch(value){ + PROCESS_VAL(RooRealMPFE::SendReal); + PROCESS_VAL(RooRealMPFE::SendCat); + PROCESS_VAL(RooRealMPFE::Calculate); + PROCESS_VAL(RooRealMPFE::Retrieve); + PROCESS_VAL(RooRealMPFE::ReturnValue); + PROCESS_VAL(RooRealMPFE::Terminate); + PROCESS_VAL(RooRealMPFE::ConstOpt); + PROCESS_VAL(RooRealMPFE::Verbose); + PROCESS_VAL(RooRealMPFE::LogEvalError); + PROCESS_VAL(RooRealMPFE::ApplyNLLW2); + PROCESS_VAL(RooRealMPFE::EnableOffset); + PROCESS_VAL(RooRealMPFE::CalculateNoOffset); + PROCESS_VAL(RooRealMPFE::SetCpuAffinity); + PROCESS_VAL(RooRealMPFE::TaskSpec); + PROCESS_VAL(RooRealMPFE::MeasureCommunicationTime); + PROCESS_VAL(RooRealMPFE::RetrieveTimings); + PROCESS_VAL(RooRealMPFE::EnableTimingNumInts); + PROCESS_VAL(RooRealMPFE::DisableTimingNumInts); + PROCESS_VAL(RooRealMPFE::GetPID); + default: { + s = "unknown Message!"; + break; + } + } +#undef PROCESS_VAL + + return out << s; +} diff --git a/roofit/roofitcore/src/RooRealSumFunc.cxx b/roofit/roofitcore/src/RooRealSumFunc.cxx index cbff7435b6486..3f78020acd47f 100644 --- a/roofit/roofitcore/src/RooRealSumFunc.cxx +++ b/roofit/roofitcore/src/RooRealSumFunc.cxx @@ -356,10 +356,10 @@ Double_t RooRealSumFunc::analyticalIntegralWN(Int_t code, const RooArgSet *normS // "RooRealSumFunc("<") // << ": reviving cache "<< endl; std::unique_ptr vars(getParameters(RooArgSet())); - std::unique_ptr iset(_normIntMgr.nameSet2ByIndex(code - 1)->select(*vars)); - std::unique_ptr nset(_normIntMgr.nameSet1ByIndex(code - 1)->select(*vars)); + RooArgSet iset = _normIntMgr.selectFromSet2(*vars, code - 1); + RooArgSet nset = _normIntMgr.selectFromSet1(*vars, code - 1); RooArgSet dummy; - Int_t code2 = getAnalyticalIntegralWN(*iset, dummy, nset.get(), rangeName); + Int_t code2 = getAnalyticalIntegralWN(iset, dummy, &nset, rangeName); assert(code == code2); // must have revived the right (sterilized) slot... (void)code2; cache = (CacheElem *)_normIntMgr.getObjByIndex(code - 1); diff --git a/roofit/roofitcore/src/RooRealSumPdf.cxx b/roofit/roofitcore/src/RooRealSumPdf.cxx index 67d3291fbae9b..fde4464ea4e15 100644 --- a/roofit/roofitcore/src/RooRealSumPdf.cxx +++ b/roofit/roofitcore/src/RooRealSumPdf.cxx @@ -369,10 +369,10 @@ Double_t RooRealSumPdf::analyticalIntegralWN(Int_t code, const RooArgSet* normSe if (cache==0) { // revive the (sterilized) cache //cout << "RooRealSumPdf("<") << ": reviving cache "<< endl; std::unique_ptr vars( getParameters(RooArgSet()) ); - std::unique_ptr iset( _normIntMgr.nameSet2ByIndex(code-1)->select(*vars) ); - std::unique_ptr nset( _normIntMgr.nameSet1ByIndex(code-1)->select(*vars) ); + RooArgSet iset = _normIntMgr.selectFromSet2(*vars, code-1); + RooArgSet nset = _normIntMgr.selectFromSet1(*vars, code-1); RooArgSet dummy; - Int_t code2 = getAnalyticalIntegralWN(*iset,dummy,nset.get(),rangeName); + Int_t code2 = getAnalyticalIntegralWN(iset,dummy,&nset,rangeName); R__ASSERT(code==code2); // must have revived the right (sterilized) slot... cache = (CacheElem*) _normIntMgr.getObjByIndex(code-1) ; R__ASSERT(cache!=0); diff --git a/roofit/roofitcore/src/RooRealVar.cxx b/roofit/roofitcore/src/RooRealVar.cxx index c3970d93afc9d..f3fe3dbe2b8c5 100644 --- a/roofit/roofitcore/src/RooRealVar.cxx +++ b/roofit/roofitcore/src/RooRealVar.cxx @@ -254,11 +254,16 @@ RooSpan RooRealVar::getValues(RooBatchCompute::RunContext& inputDa /// Set value of variable to 'value'. If 'value' is outside /// range of object, clip value into range +#include // getpid, pid_t + + void RooRealVar::setVal(Double_t value) { Double_t clipValue ; inRange(value,0,&clipValue) ; +// printf("setVal: %s %p on PID %d\n", GetName(), this, getpid()); + if (clipValue != _value) { setValueDirty() ; _value = clipValue; diff --git a/roofit/roofitcore/src/RooTaskSpec.cxx b/roofit/roofitcore/src/RooTaskSpec.cxx new file mode 100644 index 0000000000000..d2b232c7edb73 --- /dev/null +++ b/roofit/roofitcore/src/RooTaskSpec.cxx @@ -0,0 +1,122 @@ +/**************************************************************************** + * Project: RooFit * + * Package: RooFitCore * + * @(#)root/roofitcore:$Id$ + * Authors: * + * WV, Wouter Verkerke, UC Santa Barbara, verkerke@slac.stanford.edu * + * DK, David Kirkby, UC Irvine, dkirkby@uci.edu * + * * + * Copyright (c) 2000-2005, Regents of the University of California * + * and Stanford University. All rights reserved. * + * * + * Redistribution and use in source and binary forms, * + * with or without modification, are permitted according to the terms * + * listed in LICENSE (http://roofit.sourceforge.net/license.txt) * + *****************************************************************************/ +/** +\file RooTaskSpec.cxx +\class RooTaskSpec +\ingroup Roofitcore + +RooTaskSpec is a precursor to an upgrade of the the multi-processor front-end for +parallel calculation of RooAbsReal objects. The RooTaskSpec should return a table +containing the information that will be passed to the RooRealMPFE process. The +first calculation that is returned is the case being calculated since Binned and +Unbinned likelihood calcualtions need to be optimised for multiprocessing in very +different ways. +Several cases are possible: +Either the return value of createNLL() is a RooAbsTestStatisic or a RooAddition of +RooAbsTestStatistics and/or a RooConstraintSum. +Case 1: return value is a RooAbsTestStatistic -> this class returns the tools to +analyse the components of this likelihood. +Case 2: Multiple return values. The first of which is always a RooAbsTestStatistic +followed by either more RooAbsTestStatistics or a RooConstraintSum which also +signifies the last term. Here we only consider the first term since the others +represent odd and rare test cases. +ToDo: Simple use demonstration. +**/ + + +#include "RooTaskSpec.h" +// #include "Riostream.h" +// #include "RooFit.h" +// #include +#include "RooAbsTestStatistic.h" +#include "RooAddition.h" +#include "RooAbsData.h" + +using namespace std; +using namespace RooFit; + +//ClassImp(RooTaskSpec); +RooTaskSpec::RooTaskSpec(){}; + +RooTaskSpec::RooTaskSpec(RooAbsTestStatistic* rats_nll){ +// cout << " NLL is a RooAbsTestStatistic (Case 1)" << endl ; +// rats_nll->Print(); // WARNING: don't print MPFE values before they're fully initialized! Or make them dirty again afterwards. + _fit_case = 1; + _initialise(rats_nll); +} + +RooTaskSpec::RooTaskSpec(RooAbsReal* nll){ +// cout <<"starting case2"<(nll) ; + if (ra) { +// cout <<"yes ra, printing RooAddition"<Print(); +// cout <<"printed"<(ra->list().at(0)) ; + if (!rats) { +// cout << "ERROR: NLL is a RooAddition, but first element of addition is not a RooAbsTestStatistic!" << endl ; + _fit_case = 0; +// cout << "It is a "<list().at(0)<Print(); + } +} + + +void RooTaskSpec::_initialise (RooAbsTestStatistic* rats){ + // Check if nll is a AbsTestStatistic + if (rats->numSimultaneous()==0){ + // _set.add(_fill_task(0, rats)); + tasks.push_back(_fill_task(0, rats)); + } else { + for (Int_t i=0 ; i < rats->numSimultaneous() ; i++) { +// cout << "SimComponent #" << i << " = " ; + rats->simComponents()[i]->Print() ; + RooAbsTestStatistic* comp = (RooAbsTestStatistic*) rats->simComponents()[i] ; + tasks.push_back(_fill_task(i, comp)); + } + } +} + +RooTaskSpec::Task RooTaskSpec::_fill_task(Int_t n, RooAbsTestStatistic* rats){ + Bool_t b = rats->function().getAttribute("BinnedLikelihood") ; + Task t; + t.id = n; + t.binned = b; + if (b) { + t.name = rats->function().GetName(); + t.entries = rats->data().numEntries(); + // cout << "Binned Likelihood has probability model named " << rats->function().GetName() + // << " and a binned dataset with " << rats->data().numEntries() + // << " bins with a total event count of " << rats->data().sumEntries() << endl ; + } else { + t.name = rats->function().GetName(); + t.entries = rats->data().numEntries(); + + // cout << "Unbinned likelihood has probability density model named " << rats->function().GetName() + // << " and an dataset with " << rats->data().numEntries() + // << " events with a weight of " << rats->data().sumEntries() << endl ; + } return t; +} + diff --git a/roofit/roofitcore/src/RooTimer.cxx b/roofit/roofitcore/src/RooTimer.cxx new file mode 100644 index 0000000000000..54a2615605c83 --- /dev/null +++ b/roofit/roofitcore/src/RooTimer.cxx @@ -0,0 +1,48 @@ +#include "RooTimer.h" +#include "RooTrace.h" + + +double RooTimer::timing_s() { + return _timing_s; +} + +void RooTimer::set_timing_s(double timing_s) { + _timing_s = timing_s; +} + +void RooTimer::store_timing_in_RooTrace(const std::string &name) { + RooTrace::objectTiming[name] = _timing_s; // subscript operator overwrites existing values, insert does not +} + +std::vector RooTimer::timing_outfiles; +std::vector RooTimer::timings; + + +RooWallTimer::RooWallTimer() { + start(); +} + +void RooWallTimer::start() { + _timing_begin = std::chrono::high_resolution_clock::now(); +} + +void RooWallTimer::stop() { + _timing_end = std::chrono::high_resolution_clock::now(); + set_timing_s(std::chrono::duration_cast(_timing_end - _timing_begin).count() / 1.e9); +} + + +RooCPUTimer::RooCPUTimer() { + start(); +} + +void RooCPUTimer::start() { + clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &_timing_begin); +} + +void RooCPUTimer::stop() { + clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &_timing_end); + long long timing_end_nsec = _timing_end.tv_nsec + 1000000000 * _timing_end.tv_sec; // 1'000'000'000 in c++14 + long long timing_begin_nsec = _timing_begin.tv_nsec + 1000000000 * _timing_begin.tv_sec; // 1'000'000'000 in c++14 + set_timing_s((timing_end_nsec - timing_begin_nsec) / 1.e9); +} \ No newline at end of file diff --git a/roofit/roofitcore/src/RooTrace.cxx b/roofit/roofitcore/src/RooTrace.cxx index dbdd71a8a5661..92512d0505f22 100644 --- a/roofit/roofitcore/src/RooTrace.cxx +++ b/roofit/roofitcore/src/RooTrace.cxx @@ -311,3 +311,32 @@ void RooTrace::callgrind_dump() { ooccoutD((TObject*)0,Tracing) << "RooTrace::callgrind_dump()" << endl ; } + +//////////////////////////////////////////////////////////////////////////////// +/// static map used to store timings of named objects +std::map RooTrace::objectTiming; + +//////////////////////////////////////////////////////////////////////////////// +/// Flag to switch on timers used for performance benchmarks. Use at your own peril! +/// +/// 1: not used in code, used in scripts to time the entire minimization +/// 2: timing_RATS_evaluate_full +/// 3: timing_RATS_evaluate_mpmaster_perCPU +/// 4: timing_RRMPFE_evaluate_full +/// 5: timing_wall_RRMPFE_evaluate_client +/// 6: timing_cpu_RRMPFE_evaluate_client +/// 7: timing_RRMPFE_calculate_initialize +/// 8: timing_RRMPFE_serverloop_p +/// 9: timing_RRMPFE_serverloop_while_p +/// 10: time communication overhead +int RooTrace::timing_flag = 0; + +Bool_t RooTrace::time_numInts() { + return _time_numInts; +} + +void RooTrace::set_time_numInts(Bool_t flag) { + _time_numInts = flag; +} + +Bool_t RooTrace::_time_numInts = kFALSE; \ No newline at end of file diff --git a/roofit/roofitcore/src/RooVectorDataStore.cxx b/roofit/roofitcore/src/RooVectorDataStore.cxx index 01837396f8f3d..0d431c2b1be17 100644 --- a/roofit/roofitcore/src/RooVectorDataStore.cxx +++ b/roofit/roofitcore/src/RooVectorDataStore.cxx @@ -39,7 +39,6 @@ which returns spans pointing directly to the data. #include "RooFormulaVar.h" #include "RooRealVar.h" #include "RooCategory.h" -#include "RooNameSet.h" #include "RooHistError.h" #include "RooTrace.h" #include "RooHelpers.h" @@ -1008,7 +1007,6 @@ void RooVectorDataStore::cacheArgs(const RooAbsArg* owner, RooArgSet& newVarSet, std::vector argObsList ; // Now need to attach branch buffers of clones - RooArgSet *anset(0), *acset(0) ; for (const auto arg : cloneSet) { arg->attachToVStore(*newCache) ; @@ -1019,20 +1017,17 @@ void RooVectorDataStore::cacheArgs(const RooAbsArg* owner, RooArgSet& newVarSet, const char* catNset = arg->getStringAttribute("CATNormSet") ; if (catNset) { // cout << "RooVectorDataStore::cacheArgs() cached node " << arg->GetName() << " has a normalization set specification CATNormSet = " << catNset << endl ; - RooNameSet rns ; - rns.setNameList(catNset) ; - anset = rns.select(nset?*nset:RooArgSet()) ; - normSet = (RooArgSet*) anset->selectCommon(*argObs) ; + + RooArgSet anset = RooHelpers::selectFromArgSet(nset ? *nset : RooArgSet{}, catNset); + normSet = (RooArgSet*) anset.selectCommon(*argObs) ; } const char* catCset = arg->getStringAttribute("CATCondSet") ; if (catCset) { // cout << "RooVectorDataStore::cacheArgs() cached node " << arg->GetName() << " has a conditional observable set specification CATCondSet = " << catCset << endl ; - RooNameSet rns ; - rns.setNameList(catCset) ; - acset = rns.select(nset?*nset:RooArgSet()) ; - - argObs->remove(*acset,kTRUE,kTRUE) ; + + RooArgSet acset = RooHelpers::selectFromArgSet(nset ? *nset : RooArgSet{}, catCset); + argObs->remove(acset,kTRUE,kTRUE) ; normSet = argObs ; } diff --git a/roofit/roofitcore/src/TestStatistics/LikelihoodGradientJob.cxx b/roofit/roofitcore/src/TestStatistics/LikelihoodGradientJob.cxx new file mode 100644 index 0000000000000..fbcb40750645f --- /dev/null +++ b/roofit/roofitcore/src/TestStatistics/LikelihoodGradientJob.cxx @@ -0,0 +1,446 @@ +/* + * Project: RooFit + * Authors: + * PB, Patrick Bos, Netherlands eScience Center, p.bos@esciencecenter.nl + * + * Copyright (c) 2016-2020, Netherlands eScience Center + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +#include +#include + +#include +#include "RooMsgService.h" +#include "RooFit/MultiProcess/JobManager.h" +#include "RooFit/MultiProcess/Messenger.h" +#include "RooFit/MultiProcess/Queue.h" +#include "RooMinimizer.h" + +namespace RooFit { +namespace TestStatistics { + +LikelihoodGradientJob::LikelihoodGradientJob(std::shared_ptr likelihood, std::shared_ptr calculation_is_clean, std::size_t N_dim, + RooMinimizer *minimizer) + : LikelihoodGradientWrapper(std::move(likelihood), std::move(calculation_is_clean), N_dim, minimizer), _grad(N_dim) +{ + // Note to future maintainers: take care when storing the minimizer_fcn pointer. The + // RooAbsMinimizerFcn subclasses may get cloned inside MINUIT, which means the pointer + // should also somehow be updated in this class. +// N_tasks = minimizer_fcn->get_nDim(); + N_tasks = N_dim; + completed_task_ids.reserve(N_tasks); + minuit_internal_x_.reserve(N_dim); + // TODO: make sure that the full gradients are sent back so that the + // derivator will depart from correct state next step everywhere! +} + +LikelihoodGradientJob::LikelihoodGradientJob(const LikelihoodGradientJob &other) + : LikelihoodGradientWrapper(other), _grad(other._grad), _gradf(other._gradf), N_tasks(other.N_tasks), + completed_task_ids(other.completed_task_ids), minuit_internal_x_(other.minuit_internal_x_) +{ +} + +LikelihoodGradientJob *LikelihoodGradientJob::clone() const +{ + return new LikelihoodGradientJob(*this); +} + +void LikelihoodGradientJob::synchronize_parameter_settings( + ROOT::Math::IMultiGenFunction *function, const std::vector ¶meter_settings) +{ + _gradf.SetInitialGradient(function, parameter_settings, _grad); +} + +void LikelihoodGradientJob::synchronize_with_minimizer(const ROOT::Math::MinimizerOptions &options) +{ + set_strategy(options.Strategy()); + set_error_level(options.ErrorDef()); +} + +void LikelihoodGradientJob::set_strategy(int istrat) +{ + assert(istrat >= 0); + ROOT::Minuit2::MnStrategy strategy(static_cast(istrat)); + + set_step_tolerance(strategy.GradientStepTolerance()); + set_grad_tolerance(strategy.GradientTolerance()); + set_ncycles(strategy.GradientNCycles()); +} + +void LikelihoodGradientJob::set_step_tolerance(double step_tolerance) const +{ + _gradf.set_step_tolerance(step_tolerance); +} + +void LikelihoodGradientJob::set_grad_tolerance(double grad_tolerance) const +{ + _gradf.set_grad_tolerance(grad_tolerance); +} + +void LikelihoodGradientJob::set_ncycles(unsigned int ncycles) const +{ + _gradf.set_ncycles(ncycles); +} + +void LikelihoodGradientJob::set_error_level(double error_level) const +{ + _gradf.set_error_level(error_level); +} + +/////////////////////////////////////////////////////////////////////////////// +/// Job overrides: + +void LikelihoodGradientJob::evaluate_task(std::size_t task) +{ +// RooWallTimer timer; +// RooCPUTimer ctimer; + run_derivator(task); +// ctimer.stop(); +// timer.stop(); +// oocxcoutD((TObject *)nullptr, Benchmarking1) +// << "worker_id: " << get_manager()->process_manager().worker_id() << ", task: " << task +// << ", partial derivative time: " << timer.timing_s() << "s -- cputime: " << ctimer.timing_s() << "s" << std::endl; +} + +void LikelihoodGradientJob::update_real(std::size_t ix, double val, bool /*is_constant*/) +{ + if (get_manager()->process_manager().is_worker()) { + // ix is defined in "flat" FunctionGradient space ix_dim * size + ix_component +// std::cout << "on worker, ix = " << ix << ", ix / _minimizer->getNPar() = " << ix / _minimizer->getNPar() << ", ix % _minimizer->getNPar() = " << ix % _minimizer->getNPar() << std::endl; + switch (ix / _minimizer->getNPar()) { + case 0: { + _grad[ix % _minimizer->getNPar()].derivative = val; + break; + } + case 1: { + _grad[ix % _minimizer->getNPar()].second_derivative = val; + break; + } + case 2: { + _grad[ix % _minimizer->getNPar()].step_size = val; + break; + } + case 3: { + std::size_t ix_component = ix % _minimizer->getNPar(); + minuit_internal_x_[ix_component] = val; +// _minimizer->set_function_parameter_value(ix_component, val); // if we want to update this, we should send over external values! + break; + } + default: + std::stringstream ss; + ss << "ix = " << ix << " out of range in LikelihoodGradientJob::update_real! NPar = " << _minimizer->getNPar(); + throw std::runtime_error(ss.str()); + } + } +} + +void LikelihoodGradientJob::update_bool(std::size_t /*ix*/, bool /*value*/) {} + +// SYNCHRONIZATION FROM WORKERS TO MASTER + +//void LikelihoodGradientJob::send_back_task_result_from_worker(std::size_t task) +//{ +//// std::cout << "worker " << get_manager()->process_manager().worker_id() << " sends task result id=" << id +//// << " task=" << task << " grad=" << _grad[task].derivative << " g2=" << _grad[task].second_derivative +//// << " gstep=" << _grad[task].step_size << std::endl; +// get_manager()->messenger().send_from_worker_to_queue(_grad[task].derivative, _grad[task].second_derivative, _grad[task].step_size); +//} + +void LikelihoodGradientJob::send_back_task_result_from_worker(std::size_t task) +{ + task_result_t task_result {id, task, _grad[task]}; + zmq::message_t message(sizeof(task_result_t)); + memcpy(message.data(), &task_result, sizeof(task_result_t)); + get_manager()->messenger().send_from_worker_to_master(std::move(message)); +} + +void LikelihoodGradientJob::receive_task_result_on_queue(std::size_t task, std::size_t worker_id) +{ + completed_task_ids.push_back(task); + _grad[task].derivative = get_manager()->messenger().receive_from_worker_on_queue(worker_id); + _grad[task].second_derivative = get_manager()->messenger().receive_from_worker_on_queue(worker_id); + _grad[task].step_size = get_manager()->messenger().receive_from_worker_on_queue(worker_id); +// std::cout << "queue receives for job id " << id << " from worker " << worker_id << " task result task=" << task +// << " grad=" << _grad[task].derivative << " g2=" << _grad[task].second_derivative << " gstep=" << _grad[task].step_size +// << std::endl; +} + +void LikelihoodGradientJob::send_back_results_from_queue_to_master() +{ + get_manager()->messenger().send_from_queue_to_master(completed_task_ids.size()); +// std::cout << "sending from queue to master " << completed_task_ids.size() << " results: "; + for (auto task : completed_task_ids) { + get_manager()->messenger().send_from_queue_to_master(task, _grad[task].derivative, _grad[task].second_derivative, + _grad[task].step_size); +// std::cout << "\t" +// << "task=" << task << " grad=" << _grad[task].derivative << " g2=" << _grad[task].second_derivative +// << " gstep=" << _grad[task].step_size; + } +// std::cout << std::endl; +} + +void LikelihoodGradientJob::clear_results() +{ + completed_task_ids.clear(); +} + +void LikelihoodGradientJob::receive_results_on_master() +{ + auto get_time = []() { + return std::chrono::duration_cast( + std::chrono::high_resolution_clock::now().time_since_epoch()) + .count(); + }; + decltype(get_time()) t1, t2; + t1 = get_time(); + std::size_t N_completed_tasks = get_manager()->messenger().receive_from_queue_on_master(); + for (unsigned int sync_ix = 0u; sync_ix < N_completed_tasks; ++sync_ix) { + std::size_t task = get_manager()->messenger().receive_from_queue_on_master(); + _grad[task].derivative = get_manager()->messenger().receive_from_queue_on_master(); + _grad[task].second_derivative = get_manager()->messenger().receive_from_queue_on_master(); + _grad[task].step_size = get_manager()->messenger().receive_from_queue_on_master(); + } + t2 = get_time(); + printf("timestamps LikelihoodGradientJob::receive_results_on_master: %lld %lld\n", t1, t2); +} + +//bool LikelihoodGradientJob::receive_task_result_on_master() +//{ +// std::size_t task = get_manager()->messenger().receive_from_worker_on_master(); +// _grad[task] = get_manager()->messenger().receive_from_worker_on_master(); +// --N_tasks_at_workers; +// bool job_completed = (N_tasks_at_workers == 0); +// return job_completed; +//} + +bool LikelihoodGradientJob::receive_task_result_on_master(const zmq::message_t & message) +{ + auto result = message.data(); + _grad[result->task_id] = result->grad; + --N_tasks_at_workers; + bool job_completed = (N_tasks_at_workers == 0); + return job_completed; +} + + +// END SYNCHRONIZATION FROM WORKERS TO MASTER + +/////////////////////////////////////////////////////////////////////////////// +/// Calculation stuff (mostly duplicates of RooGradMinimizerFcn code): + +void LikelihoodGradientJob::run_derivator(unsigned int i_component) const +{ + // Calculate the derivative etc for these parameters +// auto parameter_values = _minimizer->get_function_parameter_values(); + _grad[i_component] = + _gradf.fast_partial_derivative(_minimizer->getMultiGenFcn(), + _minimizer->fitter()->Config().ParamsSettings(), i_component, _grad[i_component]); +} + +/////////////////////////////////////////////////////////////////////////////// +/// copy pasted and adapted from old MP::GradMinimizerFcn: + +//void LikelihoodGradientJob::update_workers_state() +//{ +// auto get_time = []() { +// return std::chrono::duration_cast( +// std::chrono::high_resolution_clock::now().time_since_epoch()) +// .count(); +// }; +// decltype(get_time()) t1, t2; +// t1 = get_time(); +// // TODO optimization: only send changed parameters (now sending all) +// std::size_t ix = 0; +// RooFit::MultiProcess::M2Q msg = RooFit::MultiProcess::M2Q::update_real; +// for (ix = 0; ix < static_cast(_minimizer->getNPar()); ++ix) { +// get_manager()->messenger().send_from_master_to_queue(msg, id, ix, _grad[ix].derivative, false); +// } +// for (ix = 0; ix < static_cast(_minimizer->getNPar()); ++ix) { +// get_manager()->messenger().send_from_master_to_queue(msg, id, ix + 1 * _minimizer->getNPar(), _grad[ix].second_derivative, +// false); +// } +// for (ix = 0; ix < static_cast(_minimizer->getNPar()); ++ix) { +// get_manager()->messenger().send_from_master_to_queue(msg, id, ix + 2 * _minimizer->getNPar(), _grad[ix].step_size, +// false); +// } +// +//// ix = 0; +//// auto parameter_values = _minimizer->get_function_parameter_values(); +//// for (auto ¶meter : parameter_values) { +// for (ix = 0; ix < static_cast(_minimizer->getNPar()); ++ix) { +// get_manager()->messenger().send_from_master_to_queue(msg, id, ix + 3 * _minimizer->getNPar(), /*parameter*/ minuit_internal_x_[ix], false); +//// ++ix; +// } +// t2 = get_time(); +// printf("timestamps LikelihoodGradientJob::update_workers_state: %lld %lld\n", t1, t2); +//} + +//void LikelihoodGradientJob::update_workers_state() +//{ +// // TODO optimization: only send changed parameters (now sending all) +// std::size_t ix; +// for (ix = 0; ix < static_cast(_minimizer->getNPar()); ++ix) { +// get_manager()->messenger().publish_from_master_to_workers(_grad[ix].derivative); +// } +// for (ix = 0; ix < static_cast(_minimizer->getNPar()); ++ix) { +// get_manager()->messenger().publish_from_master_to_workers(_grad[ix].second_derivative); +// } +// for (ix = 0; ix < static_cast(_minimizer->getNPar()); ++ix) { +// get_manager()->messenger().publish_from_master_to_workers(_grad[ix].step_size); +// } +// for (ix = 0; ix < static_cast(_minimizer->getNPar()); ++ix) { +// get_manager()->messenger().publish_from_master_to_workers(minuit_internal_x_[ix]); +// } +//} + +void LikelihoodGradientJob::update_workers_state() +{ + // TODO optimization: only send changed parameters (now sending all) + get_manager()->messenger().publish_from_master_to_workers(id); + zmq::message_t gradient_message(_grad.begin(), _grad.end()); + get_manager()->messenger().publish_from_master_to_workers(std::move(gradient_message)); + zmq::message_t minuit_internal_x_message(minuit_internal_x_.begin(), minuit_internal_x_.end()); + get_manager()->messenger().publish_from_master_to_workers(std::move(minuit_internal_x_message)); +} + + +void LikelihoodGradientJob::calculate_all() +{ +// auto get_time = []() { +// return std::chrono::duration_cast( +// std::chrono::high_resolution_clock::now().time_since_epoch()) +// .count(); +// }; +// decltype(get_time()) t1, t2; + +// std::cout << "BABBELBOX" << std::endl; + + if (get_manager()->process_manager().is_master()) { +// std::cout << "HAAAAAA" << std::endl; + // do Grad, G2 and Gstep here and then just return results from the + // separate functions below + + // update parameters and object states that changed since last calculation (or creation if first time) +// t1 = get_time(); + update_workers_state(); +// t2 = get_time(); + +// printf("wallclock [master] update_workers_state: %f\n", (t2 - t1) / 1.e9); + +// t1 = get_time(); + // master fills queue with tasks + for (std::size_t ix = 0; ix < N_tasks; ++ix) { + MultiProcess::JobTask job_task(id, ix); + get_manager()->queue().add(job_task); + } + N_tasks_at_workers = N_tasks; +// t2 = get_time(); + +// printf("wallclock [master] put job tasks in queue: %f\n", (t2 - t1) / 1.e9); + + // wait for task results back from workers to master (put into _grad) +// t1 = get_time(); + gather_worker_results(); +// t2 = get_time(); + +// printf("wallclock [master] gather_worker_results: %f\n", (t2 - t1) / 1.e9); + + calculation_is_clean->gradient = true; + calculation_is_clean->g2 = true; + calculation_is_clean->gstep = true; + } +} + +void LikelihoodGradientJob::fill_gradient(double *grad) +{ + if (get_manager()->process_manager().is_master()) { + if (!calculation_is_clean->gradient) { + calculate_all(); + } + + // TODO: maybe make a flag to avoid this copy operation, but maybe not worth the effort + // put the results from _grad into *grad + for (Int_t ix = 0; ix < _minimizer->getNPar(); ++ix) { + grad[ix] = _grad[ix].derivative; + } + } +} + +void LikelihoodGradientJob::fill_second_derivative(double *g2) +{ + if (get_manager()->process_manager().is_master()) { + if (!calculation_is_clean->g2) { + calculate_all(); + } + + // TODO: maybe make a flag to avoid this copy operation, but maybe not worth the effort + // put the results from _grad into *grad + for (Int_t ix = 0; ix < _minimizer->getNPar(); ++ix) { + g2[ix] = _grad[ix].second_derivative; + } + } +} + +void LikelihoodGradientJob::fill_step_size(double *gstep) +{ + if (get_manager()->process_manager().is_master()) { + if (!calculation_is_clean->gstep) { + calculate_all(); + } + + // TODO: maybe make a flag to avoid this copy operation, but maybe not worth the effort + // put the results from _grad into *grad + for (Int_t ix = 0; ix < _minimizer->getNPar(); ++ix) { + gstep[ix] = _grad[ix].step_size; + } + } +} + +void LikelihoodGradientJob::update_minuit_internal_parameter_values(const std::vector& minuit_internal_x) +{ + minuit_internal_x_ = minuit_internal_x; +} + +bool LikelihoodGradientJob::uses_minuit_internal_values() +{ + return true; +} + +void LikelihoodGradientJob::update_state() +{ +// std::size_t ix; +// +// for (ix = 0; ix < static_cast(_minimizer->getNPar()); ++ix) { +// _grad[ix].derivative = get_manager()->messenger().receive_from_master_on_worker(); +// } +// for (ix = 0; ix < static_cast(_minimizer->getNPar()); ++ix) { +// _grad[ix].second_derivative = get_manager()->messenger().receive_from_master_on_worker(); +// } +// for (ix = 0; ix < static_cast(_minimizer->getNPar()); ++ix) { +// _grad[ix].step_size = get_manager()->messenger().receive_from_master_on_worker(); +// } +// for (ix = 0; ix < static_cast(_minimizer->getNPar()); ++ix) { +// minuit_internal_x_[ix] = get_manager()->messenger().receive_from_master_on_worker(); +// } + + auto gradient_message = get_manager()->messenger().receive_from_master_on_worker(); + auto gradient_message_begin = gradient_message.data(); + auto gradient_message_end = gradient_message_begin + gradient_message.size()/sizeof(RooFit::MinuitDerivatorElement); + std::copy(gradient_message_begin, gradient_message_end, _grad.begin()); + + auto minuit_internal_x_message = get_manager()->messenger().receive_from_master_on_worker(); + auto minuit_internal_x_message_begin = minuit_internal_x_message.data(); + auto minuit_internal_x_message_end = minuit_internal_x_message_begin + minuit_internal_x_message.size()/sizeof(double); + std::copy(minuit_internal_x_message_begin, minuit_internal_x_message_end, minuit_internal_x_.begin()); + + _gradf.setup_differentiate(_minimizer->getMultiGenFcn(), minuit_internal_x_.data(), _minimizer->fitter()->Config().ParamsSettings()); +} + + +} // namespace TestStatistics +} // namespace RooFit diff --git a/roofit/roofitcore/src/TestStatistics/LikelihoodGradientWrapper.cxx b/roofit/roofitcore/src/TestStatistics/LikelihoodGradientWrapper.cxx new file mode 100644 index 0000000000000..8639878fdc6a4 --- /dev/null +++ b/roofit/roofitcore/src/TestStatistics/LikelihoodGradientWrapper.cxx @@ -0,0 +1,43 @@ +/* + * Project: RooFit + * Authors: + * PB, Patrick Bos, Netherlands eScience Center, p.bos@esciencecenter.nl + * + * Copyright (c) 2016-2020, Netherlands eScience Center + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +#include +#include "RooMinimizer.h" + +namespace RooFit { +namespace TestStatistics { + +LikelihoodGradientWrapper::LikelihoodGradientWrapper(std::shared_ptr likelihood, + std::shared_ptr calculation_is_clean, + std::size_t /*N_dim*/, RooMinimizer *minimizer) + : likelihood(std::move(likelihood)), _minimizer(minimizer), + calculation_is_clean(std::move(calculation_is_clean)) /*, _minimizer_fcn(minimizer_fcn)*/ +{ + // Note to future maintainers: take care when storing the minimizer_fcn pointer. The + // RooAbsMinimizerFcn subclasses may get cloned inside MINUIT, which means the pointer + // should also somehow be updated in this class. +} + +void LikelihoodGradientWrapper::synchronize_with_minimizer(const ROOT::Math::MinimizerOptions & /*options*/) {} + +void LikelihoodGradientWrapper::synchronize_parameter_settings( + const std::vector ¶meter_settings) +{ + synchronize_parameter_settings(_minimizer->getMultiGenFcn(), parameter_settings); +} + +void LikelihoodGradientWrapper::update_minuit_internal_parameter_values(const std::vector& /*minuit_internal_x*/) {} +void LikelihoodGradientWrapper::update_minuit_external_parameter_values(const std::vector& /*minuit_external_x*/) {} + +} // namespace TestStatistics +} // namespace RooFit diff --git a/roofit/roofitcore/src/TestStatistics/LikelihoodJob.cxx b/roofit/roofitcore/src/TestStatistics/LikelihoodJob.cxx new file mode 100644 index 0000000000000..68b3b6a62b3e3 --- /dev/null +++ b/roofit/roofitcore/src/TestStatistics/LikelihoodJob.cxx @@ -0,0 +1,254 @@ +/* + * Project: RooFit + * Authors: + * PB, Patrick Bos, Netherlands eScience Center, p.bos@esciencecenter.nl + * + * Copyright (c) 2016-2020, Netherlands eScience Center + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "RooRealVar.h" +#include + +namespace RooFit { +namespace TestStatistics { + + +LikelihoodJob::LikelihoodJob(std::shared_ptr likelihood, std::shared_ptr calculation_is_clean/*, RooMinimizer *minimizer*/) + : LikelihoodWrapper(std::move(likelihood), std::move(calculation_is_clean)/*, minimizer*/) +{ + init_vars(); + // determine likelihood type + if (dynamic_cast(likelihood_.get()) != nullptr) { + likelihood_type = LikelihoodType::unbinned; + } else if (dynamic_cast(likelihood_.get()) != nullptr) { + likelihood_type = LikelihoodType::binned; + } else if (dynamic_cast(likelihood_.get()) != nullptr) { + likelihood_type = LikelihoodType::sum; + } else if (dynamic_cast(likelihood_.get()) != nullptr) { + likelihood_type = LikelihoodType::subsidiary; + } else { + throw std::logic_error("in LikelihoodJob constructor: likelihood is not of a valid subclass!"); + } + // Note to future maintainers: take care when storing the minimizer_fcn pointer. The + // RooAbsMinimizerFcn subclasses may get cloned inside MINUIT, which means the pointer + // should also somehow be updated in this class. +} + +LikelihoodJob* LikelihoodJob::clone() const { + return new LikelihoodJob(*this); +} + + +// This is a separate function (instead of just in ctor) for historical reasons. +// Its predecessor RooRealMPFE::initVars() was used from multiple ctors, but also +// from RooRealMPFE::constOptimizeTestStatistic at the end, which makes sense, +// because it might change the set of variables. We may at some point want to do +// this here as well. +void LikelihoodJob::init_vars() { + // Empty current lists + _vars.removeAll() ; + _saveVars.removeAll() ; + + // Retrieve non-constant parameters + auto vars = std::make_unique(*likelihood_->getParameters()); // TODO: make sure this is the right list of parameters, compare to original implementation in RooRealMPFE.cxx + RooArgList varList(*vars); + + // Save in lists + _vars.add(varList); + _saveVars.addClone(varList); +} + + +void LikelihoodJob::update_real(std::size_t ix, double val, bool is_constant) { + if (get_manager()->process_manager().is_master()) { + auto msg = RooFit::MultiProcess::M2Q::update_real; + get_manager()->messenger().send_from_master_to_queue(msg, id, ix, val, is_constant); + } else if (get_manager()->process_manager().is_worker()) { + RooRealVar *rvar = (RooRealVar *) _vars.at(ix); + rvar->setVal(static_cast(val)); + if (rvar->isConstant() != is_constant) { + rvar->setConstant(static_cast(is_constant)); + } + } else { + throw std::logic_error("LikelihoodJob::update_real only implemented on master and worker processes."); + } +} + + +void LikelihoodJob::update_bool(std::size_t ix, bool value) { + if (get_manager()->process_manager().is_master()) { + auto msg = RooFit::MultiProcess::M2Q::update_bool; + get_manager()->messenger().send_from_queue_to_master(msg, ix, value); + } else if (get_manager()->process_manager().is_worker()) { + switch(ix) { + case 0: { + LikelihoodWrapper::enable_offsetting(value); + break; + } + default: { + throw std::logic_error("LikelihoodJob::update_bool only supports ix = 0!"); + break; + } + } + } else { + throw std::logic_error("LikelihoodJob::update_bool only implemented on worker processes."); + } +} + + +void LikelihoodJob::update_parameters() { + if (get_manager()->process_manager().is_master()) { + for (std::size_t ix = 0u; ix < static_cast(_vars.getSize()); ++ix) { + bool valChanged = !_vars[ix].isIdentical(_saveVars[ix], kTRUE); + bool constChanged = (_vars[ix].isConstant() != _saveVars[ix].isConstant()); + + if (valChanged || constChanged) { + if (constChanged) { + ((RooRealVar *) &_saveVars[ix])->setConstant(_vars[ix].isConstant()); + } + // TODO: Check with Wouter why he uses copyCache in MPFE; makes it very difficult to extend, because copyCache is protected (so must be friend). Moved setting value to if-block below. + // _saveVars[ix].copyCache(&_vars[ix]); + + // send message to queue (which will relay to workers) + RooAbsReal * rar_val = dynamic_cast(&_vars[ix]); + if (rar_val) { + Double_t val = rar_val->getVal(); + dynamic_cast(&_saveVars[ix])->setVal(val); + Bool_t isC = _vars[ix].isConstant(); + update_real(ix, val, isC); + } + } + } + } +} + + +double LikelihoodJob::return_result() const { + return result; +} + +void LikelihoodJob::evaluate() { + if (get_manager()->process_manager().is_master()) { + // update parameters that changed since last calculation (or creation if first time) + update_parameters(); + + // master fills queue with tasks + for (std::size_t ix = 0; ix < get_manager()->process_manager().N_workers(); ++ix) { + get_manager()->queue().add({id, ix}); + } + N_tasks_at_workers = get_manager()->process_manager().N_workers(); + + // wait for task results back from workers to master + gather_worker_results(); + + // put the results in vectors for calling sum_of_kahan_sums (TODO: make a map-friendly sum_of_kahan_sums) +// std::vector results_vec, carrys_vec; +// for (auto const &item : results) { +// results_vec.emplace_back(item.second); +// carrys_vec.emplace_back(carrys[item.first]); +// } +// +// // sum task results +// std::tie(result, carry) = sum_of_kahan_sums(results_vec, carrys_vec); + std::tie(result, carry) = sum_of_kahan_sums(results, carrys); + apply_offsetting(result, carry); + } +} + +// --- RESULT LOGISTICS --- + +//void LikelihoodJob::send_back_task_result_from_worker(std::size_t task) { +// get_manager()->messenger().send_from_worker_to_queue(id, task, result, carry); +//} + +void LikelihoodJob::send_back_task_result_from_worker(std::size_t /*task*/) { + get_manager()->messenger().send_from_worker_to_master(result, carry); +} + +void LikelihoodJob::receive_task_result_on_queue(std::size_t task, std::size_t worker_id) { + result = get_manager()->messenger().receive_from_worker_on_queue(worker_id); + carry = get_manager()->messenger().receive_from_worker_on_queue(worker_id); + results[task] = result; + carrys[task] = carry; +} + +void LikelihoodJob::send_back_results_from_queue_to_master() { + get_manager()->messenger().send_from_queue_to_master(results.size()); + for (auto const &item : results) { + get_manager()->messenger().send_from_queue_to_master(item.first, item.second, carrys[item.first]); + } +} + +bool LikelihoodJob::receive_task_result_on_master(const zmq::message_t & /*message*/) { + std::size_t task = get_manager()->messenger().receive_from_worker_on_master(); + results[task] = get_manager()->messenger().receive_from_worker_on_master(); + carrys[task] = get_manager()->messenger().receive_from_worker_on_master(); + --N_tasks_at_workers; + bool job_completed = (N_tasks_at_workers == 0); + return job_completed; +} + +void LikelihoodJob::clear_results() { + // empty results caches + results.clear(); + carrys.clear(); +} + +void LikelihoodJob::receive_results_on_master() { + std::size_t N_job_tasks = get_manager()->messenger().receive_from_queue_on_master(); + for (std::size_t task_ix = 0ul; task_ix < N_job_tasks; ++task_ix) { + std::size_t task_id = get_manager()->messenger().receive_from_queue_on_master(); + results[task_id] = get_manager()->messenger().receive_from_queue_on_master(); + carrys[task_id] = get_manager()->messenger().receive_from_queue_on_master(); + } +} + +// --- END OF RESULT LOGISTICS --- + +void LikelihoodJob::evaluate_task(std::size_t task) { + assert(get_manager()->process_manager().is_worker()); + + std::size_t N_events = likelihood_->numDataEntries(); + + // used to have multiple modes here, but only kept "bulk" mode; dropped interleaved, single_event and all_events from old MultiProcess::NLLVar + std::size_t first = N_events * task / get_manager()->process_manager().N_workers(); + std::size_t last = N_events * (task + 1) / get_manager()->process_manager().N_workers(); + + switch (likelihood_type) { + case LikelihoodType::unbinned: + case LikelihoodType::binned: { + result = likelihood_->evaluate_partition({static_cast(first)/N_events, static_cast(last)/N_events}, 0, 0); + break; + } + default: { + throw std::logic_error("in LikelihoodJob::evaluate_task: likelihood types other than binned and unbinned not yet implemented!"); + break; + } + } + carry = likelihood_->get_carry(); +} + +void LikelihoodJob::enable_offsetting(bool flag) { + update_bool(0, flag); + LikelihoodWrapper::enable_offsetting(flag); +} + +} +} diff --git a/roofit/roofitcore/src/TestStatistics/LikelihoodSerial.cxx b/roofit/roofitcore/src/TestStatistics/LikelihoodSerial.cxx new file mode 100644 index 0000000000000..e8c2e63a8a73a --- /dev/null +++ b/roofit/roofitcore/src/TestStatistics/LikelihoodSerial.cxx @@ -0,0 +1,129 @@ +/* + * Project: RooFit + * Authors: + * PB, Patrick Bos, Netherlands eScience Center, p.bos@esciencecenter.nl + * + * Copyright (c) 2016-2020, Netherlands eScience Center + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "RooRealVar.h" +#include + +namespace RooFit { +namespace TestStatistics { + +LikelihoodSerial::LikelihoodSerial(std::shared_ptr likelihood, std::shared_ptr calculation_is_clean/*, + RooMinimizer *minimizer*/) + : LikelihoodWrapper(std::move(likelihood), std::move(calculation_is_clean)/*, minimizer*/) +{ + init_vars(); + // determine likelihood type + if (dynamic_cast(likelihood_.get()) != nullptr) { + likelihood_type = LikelihoodType::unbinned; + } else if (dynamic_cast(likelihood_.get()) != nullptr) { + likelihood_type = LikelihoodType::binned; + } else if (dynamic_cast(likelihood_.get()) != nullptr) { + likelihood_type = LikelihoodType::sum; + } else if (dynamic_cast(likelihood_.get()) != nullptr) { + likelihood_type = LikelihoodType::subsidiary; + } else { + throw std::logic_error("in LikelihoodSerial constructor: _likelihood is not of a valid subclass!"); + } + // Note to future maintainers: take care when storing the minimizer_fcn pointer. The + // RooAbsMinimizerFcn subclasses may get cloned inside MINUIT, which means the pointer + // should also somehow be updated in this class. +} + +LikelihoodSerial *LikelihoodSerial::clone() const +{ + return new LikelihoodSerial(*this); +} + +// This is a separate function (instead of just in ctor) for historical reasons. +// Its predecessor RooRealMPFE::initVars() was used from multiple ctors, but also +// from RooRealMPFE::constOptimizeTestStatistic at the end, which makes sense, +// because it might change the set of variables. We may at some point want to do +// this here as well. +void LikelihoodSerial::init_vars() +{ + // Empty current lists + _vars.removeAll(); + _saveVars.removeAll(); + + // Retrieve non-constant parameters + auto vars = std::make_unique( + *likelihood_->getParameters()); // TODO: make sure this is the right list of parameters, compare to original + // implementation in RooRealMPFE.cxx + +// std::cout << "vars size: " << vars->getSize() << std::endl; +// auto iter = vars->fwdIterator(); +// RooAbsArg* var; +// int ix = 0; +// while((var = iter.next())) { +// printf("LikelihoodSerial::init_vars var %d = %s %p\n", ix, var->GetName(), var); +// ++ix; +// } + + RooArgList varList(*vars); + + // Save in lists + _vars.add(varList); + _saveVars.addClone(varList); +} + +void LikelihoodSerial::evaluate() { +// std::size_t N_events = likelihood_->numDataEntries(); + + switch (likelihood_type) { + case LikelihoodType::unbinned: + case LikelihoodType::binned: { + result = likelihood_->evaluate_partition({0, 1}, 0, 0); + carry = likelihood_->get_carry(); + break; + } + case LikelihoodType::sum: { + result = likelihood_->evaluate_partition({0, 1}, 0, likelihood_->get_N_components()); + carry = likelihood_->get_carry(); + // TODO: this normalization part below came from RooOptTestStatistic::evaluate, probably this just means you need to do the normalization on master only when doing parallel calculation. Make sure of this! In any case, it is currently not relevant, because the norm term is 1 by default and is only overridden for the RooDataWeightAverage class. +// // Only apply global normalization if SimMaster doesn't have MP master +// if (numSets() == 1) { +// const Double_t norm = globalNormalization(); +// result /= norm; +// carry /= norm; +// } + break; + } + default: { + throw std::logic_error("in LikelihoodSerial::evaluate_task: likelihood types other than binned, unbinned and simultaneous not yet implemented!"); + break; + } + } + + apply_offsetting(result, carry); +} + +double LikelihoodSerial::return_result() const +{ + return result; +} + +} // namespace TestStatistics +} // namespace RooFit \ No newline at end of file diff --git a/roofit/roofitcore/src/TestStatistics/LikelihoodWrapper.cxx b/roofit/roofitcore/src/TestStatistics/LikelihoodWrapper.cxx new file mode 100644 index 0000000000000..f6c7fe8ec38c8 --- /dev/null +++ b/roofit/roofitcore/src/TestStatistics/LikelihoodWrapper.cxx @@ -0,0 +1,170 @@ +/* + * Project: RooFit + * Authors: + * PB, Patrick Bos, Netherlands eScience Center, p.bos@esciencecenter.nl + * + * Copyright (c) 2016-2020, Netherlands eScience Center + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +#include +#include // need complete type for likelihood->... +#include +#include +#include // need complete type for dynamic cast + +namespace RooFit { +namespace TestStatistics { + +LikelihoodWrapper::LikelihoodWrapper(std::shared_ptr likelihood, + std::shared_ptr calculation_is_clean/*, + RooMinimizer *minimizer*/) + : likelihood_(std::move(likelihood)),/* minimizer_(minimizer),*/ + calculation_is_clean_(std::move(calculation_is_clean)) /*, minimizer_fcn_(minimizer_fcn)*/ +{ + // Note to future maintainers: take care when storing the minimizer_fcn pointer. The + // RooAbsMinimizerFcn subclasses may get cloned inside MINUIT, which means the pointer + // should also somehow be updated in this class. +} + +void LikelihoodWrapper::synchronize_with_minimizer(const ROOT::Math::MinimizerOptions & /*options*/) {} + +void LikelihoodWrapper::constOptimizeTestStatistic(RooAbsArg::ConstOpCode opcode, bool doAlsoTrackingOpt) +{ + likelihood_->constOptimizeTestStatistic(opcode, doAlsoTrackingOpt); +} + +void LikelihoodWrapper::synchronize_parameter_settings( + const std::vector & /*parameter_settings*/) +{ +} + +std::string LikelihoodWrapper::GetName() const +{ + return likelihood_->GetName(); +} +std::string LikelihoodWrapper::GetTitle() const +{ + return likelihood_->GetTitle(); +} +double LikelihoodWrapper::defaultErrorLevel() const +{ + return likelihood_->defaultErrorLevel(); +} +bool LikelihoodWrapper::is_offsetting() const +{ + return do_offset_; +} + +double LikelihoodWrapper::offset() +{ + return offset_; +} + +double LikelihoodWrapper::offset_carry() +{ + return offset_carry_; +} + +void LikelihoodWrapper::enable_offsetting(bool flag) +{ + do_offset_ = flag; + // Clear offset if feature is disabled so that it is recalculated next time it is enabled + if (!do_offset_) { + offset_ = 0; + offset_carry_ = 0; + } +} + +void LikelihoodWrapper::set_offsetting_mode(OffsettingMode mode) +{ + offsetting_mode_ = mode; + if (is_offsetting()) { + oocoutI(static_cast(nullptr), Minimization) << "LikelihoodWrapper::set_offsetting_mode(" << GetName() << "): changed offsetting mode while offsetting was enabled; resetting offset values" << std::endl; + offset_ = 0; + offset_carry_ = 0; + } +} + +void LikelihoodWrapper::apply_offsetting(double ¤t_value, double &carry) +{ + if (do_offset_) { + + // If no offset is stored enable this feature now + if (offset_ == 0 && current_value != 0) { + offset_ = current_value; + offset_carry_ = carry; + if (offsetting_mode_ == OffsettingMode::legacy) { + auto sum_likelihood = dynamic_cast(likelihood_.get()); + if (sum_likelihood != nullptr) { + double subsidiary_value, subsidiary_carry; + std::tie(subsidiary_value, subsidiary_carry) = sum_likelihood->get_subsidiary_value(); + // "undo" the addition of the subsidiary value to emulate legacy behavior + offset_ -= subsidiary_value; + offset_carry_ -= subsidiary_carry; + // then add 0 in Kahan summation way to make sure the carry gets taken up into the value if it should be + double y = 0 - offset_carry_; + double t = offset_ + y; + offset_carry_ = (t - offset_) - y; + offset_ = t; + // also set carry to this value, again to emulate legacy behavior + carry = offset_carry_; + } + } + oocoutI(static_cast(nullptr), Minimization) + << "LikelihoodWrapper::apply_offsetting(" << GetName() << "): Likelihood offset now set to " << offset_ + << std::endl; + } + + // Subtract offset + // old method: +// { +// double y = -offset_ - (carry + offset_carry_); +// double t = current_value + y; +// carry = (t - current_value) - y; +// current_value = t; +// } + // Jonas method: + { + double new_value = current_value - offset_; + double new_carry = carry - offset_carry_; + // then add 0 in Kahan summation way to make sure the carry gets taken up into the value if it should be + double y = 0 - new_carry; + double t = new_value + y; + carry = (t - new_value) - y; + current_value = t; + } + } +} + +/// When calculating an unbinned likelihood with square weights applied, a different offset +/// is necessary. Similar situations may ask for a separate offset as well. This function +/// switches between the two sets of offset values. +void LikelihoodWrapper::swap_offsets() +{ + std::swap(offset_, offset_save_); + std::swap(offset_carry_, offset_carry_save_); +} + +void LikelihoodWrapper::set_apply_weight_squared(bool flag) +{ + RooUnbinnedL *unbinned_likelihood = dynamic_cast(likelihood_.get()); + if (unbinned_likelihood == nullptr) { + throw std::logic_error("LikelihoodWrapper::set_apply_weight_squared can only be used on unbinned likelihoods, but the wrapped likelihood_ member is not a RooUnbinnedL!"); + } + bool flag_was_changed = unbinned_likelihood->set_apply_weight_squared(flag); + + if (flag_was_changed) { + swap_offsets(); + } +} + +void LikelihoodWrapper::update_minuit_internal_parameter_values(const std::vector& /*minuit_internal_x*/) {} +void LikelihoodWrapper::update_minuit_external_parameter_values(const std::vector& /*minuit_external_x*/) {} + +} // namespace TestStatistics +} // namespace RooFit diff --git a/roofit/roofitcore/src/TestStatistics/MinuitFcnGrad.cxx b/roofit/roofitcore/src/TestStatistics/MinuitFcnGrad.cxx new file mode 100644 index 0000000000000..db4e70e3bd1cb --- /dev/null +++ b/roofit/roofitcore/src/TestStatistics/MinuitFcnGrad.cxx @@ -0,0 +1,339 @@ +/* + * Project: RooFit + * Authors: + * PB, Patrick Bos, Netherlands eScience Center, p.bos@esciencecenter.nl + * + * Copyright (c) 2016-2020, Netherlands eScience Center + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ + +#include "RooMsgService.h" +#include "RooAbsPdf.h" +#include "TestStatistics/MinuitFcnGrad.h" +#include "RooMinimizer.h" + +#define DEBUG_STREAM(var) << " " #var "=" << var +#include +#include + +namespace RooFit { +namespace TestStatistics { + +//MinuitFcnGrad::MinuitFcnGrad(const MinuitFcnGrad &other) +// : RooAbsMinimizerFcn(other), likelihood(other.likelihood->clone()), gradient(other.gradient->clone()){}; + +// IMultiGradFunction overrides necessary for Minuit: DoEval, Gradient, G2ndDerivative and GStepSize +// The likelihood and gradient wrappers do the actual calculations. + +double MinuitFcnGrad::DoEval(const double *x) const +{ +// auto get_time = []() { +// return std::chrono::duration_cast( +// std::chrono::high_resolution_clock::now().time_since_epoch()) +// .count(); +// }; +// decltype(get_time()) t1 = get_time(), t2 = 0, t3 = 0, t4 = 0; + + Bool_t parameters_changed = sync_parameter_values_from_minuit_calls(x, false); +// t2 = get_time(); + +// std::cout << "MinuitFcnGrad::DoEval @ PID" << getpid() << ": " DEBUG_STREAM(parameters_changed); + + // Calculate the function for these parameters +// RooAbsReal::setHideOffset(kFALSE); + likelihood->evaluate(); +// t3 = get_time(); + double fvalue = likelihood->return_result(); +// t4 = get_time(); + calculation_is_clean->likelihood = true; +// RooAbsReal::setHideOffset(kTRUE); + +// std::cout DEBUG_STREAM(fvalue) << std::endl; + +// printf("wallclock [worker] DoEval parts: 1. %f 2. %f 3. %f\n", (t2 - t1) / 1.e9, (t3 - t2) / 1.e9, (t4 - t3) / 1.e9); + + if (!parameters_changed) { + return fvalue; + } + + if (!std::isfinite(fvalue) || RooAbsReal::numEvalErrors() > 0 || fvalue > 1e30) { + + if (_printEvalErrors >= 0) { + + if (_doEvalErrorWall) { + oocoutW(static_cast(nullptr), Eval) + << "RooGradMinimizerFcn: Minimized function has error status." << std::endl + << "Returning maximum FCN so far (" << _maxFCN + << ") to force MIGRAD to back out of this region. Error log follows" << std::endl; + } else { + oocoutW(static_cast(nullptr), Eval) + << "RooGradMinimizerFcn: Minimized function has error status but is ignored" << std::endl; + } + + TIterator *iter = _floatParamList->createIterator(); + RooRealVar *var; + Bool_t first(kTRUE); + ooccoutW(static_cast(nullptr), Eval) << "Parameter values: "; + while ((var = (RooRealVar *)iter->Next())) { + if (first) { + first = kFALSE; + } else + ooccoutW(static_cast(nullptr), Eval) << ", "; + ooccoutW(static_cast(nullptr), Eval) << var->GetName() << "=" << var->getVal(); + } + delete iter; + ooccoutW(static_cast(nullptr), Eval) << std::endl; + + RooAbsReal::printEvalErrors(ooccoutW(static_cast(nullptr), Eval), _printEvalErrors); + ooccoutW(static_cast(nullptr), Eval) << std::endl; + } + + if (_doEvalErrorWall) { + fvalue = _maxFCN + 1; + } + + RooAbsReal::clearEvalErrorLog(); + _numBadNLL++; + } else if (fvalue > _maxFCN) { + _maxFCN = fvalue; + } + + // Optional logging + if (_verbose) { + std::cout << "\nprevFCN" << (likelihood->is_offsetting() ? "-offset" : "") << " = " << std::setprecision(10) + << fvalue << std::setprecision(4) << " "; + std::cout.flush(); + } + + _evalCounter++; + //#ifndef NDEBUG + // std::cout << "RooGradMinimizerFcn " << this << " evaluations (in DoEval): " << _evalCounter << + // std::endl; + //#endif + return fvalue; +} + +/// Minuit calls (via FcnAdapters etc) DoEval or Gradient/G2ndDerivative/GStepSize with a set of parameters x. +/// This function syncs these values to the proper places in RooFit. +/// +/// The first twist, and reason this function is more complicated than one may imagine, is that Minuit internally uses a +/// transformed parameter space to account for parameter boundaries. Whether we receive these Minuit "internal" +/// parameter values or "regular"/untransformed RooFit parameter space values depends on the situation. +/// - The values that arrive here via DoEval are always "normal" parameter values, since Minuit transforms these +/// back into regular space before passing to DoEval (see MnUserFcn::operator() which wraps the Fcn(Gradient)Base +/// in ModularFunctionMinimizer::Minimize and is used for direct function calls from that point on in the minimizer). +/// These can thus always be safely synced with this function's RooFit parameters using SetPdfParamVal. +/// - The values that arrive here via Gradient/G2ndDerivative/GStepSize will be in internal coordinates if that is +/// what this class expects, and indeed this is the case for MinuitFcnGrad's current implementation. This is +/// communicated to Minuit via MinuitFcnGrad::returnsInMinuit2ParameterSpace. Inside Minuit, that function determines +/// whether this class's gradient calculator is wrapped inside a AnalyticalGradientCalculator, to which Minuit passes +/// "external" parameter values, or as an ExternalInternalGradientCalculator, which gets "internal" parameter values. +/// Long story short: when MinuitFcnGrad::returnsInMinuit2ParameterSpace() returns true, Minuit will pass "internal" +/// values to Gradient/G2ndDerivative/GStepSize. These cannot be synced with this function's RooFit parameters using +/// SetPdfParamVal, unless a manual transformation step is performed in advance. However, they do need to be passed +/// on to the gradient calculator, since indeed we expect values there to be in "internal" space. However, this is +/// calculator dependent. Note that in the current MinuitFcnGrad implementation we do not actually allow for +/// calculators in "external" (i.e. regular RooFit parameter space) values, since +/// MinuitFcnGrad::returnsInMinuit2ParameterSpace is hardcoded to true. This should in a future version be changed so +/// that the calculator (the wrapper) is queried for this information. +/// Because some gradient calculators may also use the regular RooFit parameters (e.g. for calculating the likelihood's +/// value itself), this information is also passed on to the gradient wrapper. Vice versa, when updated "internal" +/// parameters are passed to Gradient/G2ndDerivative/GStepSize, the likelihood may be affected as well. Even though a +/// transformation from internal to "external" may be necessary before the values can be used, the likelihood can at +/// least log that its parameter values are possibly no longer in sync with those of the gradient. +/// +/// The second twist is that the Minuit external parameters may still be different from the ones used in RooFit. This +/// happens when Minuit tries out values that lay outside the RooFit parameter's range(s). RooFit's setVal (called +/// inside SetPdfParamVal) then clips the RooAbsArg's value to one of the range limits, instead of setting it to the +/// value Minuit intended. When this happens, i.e. sync_parameter_values_from_minuit_calls is called with +/// minuit_internal = false and the values do not match the previous values stored in minuit_internal_x_ *but* the +/// values after SetPdfParamVal did not get set to the intended value, the minuit_internal_roofit_x_mismatch_ flag is +/// set. This information can be used by calculators, if desired, for instance when a calculator does not want to make +/// use of the range information in the RooAbsArg parameters. +bool MinuitFcnGrad::sync_parameter_values_from_minuit_calls(const double *x, bool minuit_internal) const +{ + bool a_parameter_has_been_updated = false; + if (minuit_internal) { + if (!returnsInMinuit2ParameterSpace()) { + throw std::logic_error("Updating Minuit-internal parameters only makes sense for (gradient) calculators that are defined in Minuit-internal parameter space."); + } + + for (std::size_t ix = 0; ix < NDim(); ++ix) { + bool parameter_changed = (x[ix] != minuit_internal_x_[ix]); + if (parameter_changed) { + minuit_internal_x_[ix] = x[ix]; + } + a_parameter_has_been_updated |= parameter_changed; + } + + if(a_parameter_has_been_updated) { + calculation_is_clean->set_all(false); + likelihood->update_minuit_internal_parameter_values(minuit_internal_x_); + gradient->update_minuit_internal_parameter_values(minuit_internal_x_); + } + } else { + bool a_parameter_is_mismatched = false; + + for (std::size_t ix = 0; ix < NDim(); ++ix) { + // Note: the return value of SetPdfParamVal does not always mean that the parameter's value in the RooAbsReal changed since last + // time! If the value was out of range bin, setVal was still called, but the value was not updated. + SetPdfParamVal(ix, x[ix]); + minuit_external_x_[ix] = x[ix]; + // The above is why we need minuit_external_x_. The minuit_external_x_ vector can also be passed to + // LikelihoodWrappers, if needed, but typically they will make use of the RooFit parameters directly. However, + // we log in the flag below whether they are different so that calculators can use this information. + bool parameter_changed = (x[ix] != minuit_external_x_[ix]); + a_parameter_has_been_updated |= parameter_changed; + a_parameter_is_mismatched |= (((RooRealVar *)_floatParamList->at(ix))->getVal() != minuit_external_x_[ix]); + } + + minuit_internal_roofit_x_mismatch_ = a_parameter_is_mismatched; + + if(a_parameter_has_been_updated) { + calculation_is_clean->set_all(false); + likelihood->update_minuit_external_parameter_values(minuit_external_x_); + gradient->update_minuit_external_parameter_values(minuit_external_x_); + } + } + return a_parameter_has_been_updated; +} + + +void MinuitFcnGrad::Gradient(const double *x, double *grad) const +{ + sync_parameter_values_from_minuit_calls(x, returnsInMinuit2ParameterSpace()); + gradient->fill_gradient(grad); +} + +void MinuitFcnGrad::G2ndDerivative(const double *x, double *g2) const +{ + sync_parameter_values_from_minuit_calls(x, returnsInMinuit2ParameterSpace()); + gradient->fill_second_derivative(g2); +} + +void MinuitFcnGrad::GStepSize(const double *x, double *gstep) const +{ + sync_parameter_values_from_minuit_calls(x, returnsInMinuit2ParameterSpace()); + gradient->fill_step_size(gstep); +} + +ROOT::Math::IMultiGradFunction *MinuitFcnGrad::Clone() const +{ + return new MinuitFcnGrad(*this); +} + +double MinuitFcnGrad::DoDerivative(const double * /*x*/, unsigned int /*icoord*/) const +{ + throw std::runtime_error("MinuitFcnGrad::DoDerivative is not implemented, please use Gradient instead."); +} + +double MinuitFcnGrad::DoSecondDerivative(const double * /*x*/, unsigned int /*icoord*/) const +{ + throw std::runtime_error("MinuitFcnGrad::DoSecondDerivative is not implemented, please use G2ndDerivative instead."); +} + +double MinuitFcnGrad::DoStepSize(const double * /*x*/, unsigned int /*icoord*/) const +{ + throw std::runtime_error("MinuitFcnGrad::DoStepSize is not implemented, please use GStepSize instead."); +} + +bool MinuitFcnGrad::hasG2ndDerivative() const +{ + return true; +} + +bool MinuitFcnGrad::hasGStepSize() const +{ + return true; +} + +unsigned int MinuitFcnGrad::NDim() const +{ + return _nDim; +} + +bool MinuitFcnGrad::returnsInMinuit2ParameterSpace() const +{ + return gradient->uses_minuit_internal_values(); +} + +void MinuitFcnGrad::optimizeConstantTerms(bool constStatChange, bool constValChange) +{ + if (constStatChange) { + + RooAbsReal::setEvalErrorLoggingMode(RooAbsReal::CollectErrors); + + oocoutI(static_cast(nullptr), Eval) + << "MinuitFcnGrad::synchronize: set of constant parameters changed, rerunning const optimizer" << std::endl; + likelihood->constOptimizeTestStatistic(RooAbsArg::ConfigChange, true); + } else if (constValChange) { + oocoutI(static_cast(nullptr), Eval) + << "MinuitFcnGrad::synchronize: constant parameter values changed, rerunning const optimizer" << std::endl; + likelihood->constOptimizeTestStatistic(RooAbsArg::ValueChange, true); + } + + RooAbsReal::setEvalErrorLoggingMode(RooAbsReal::PrintErrors); +} + +Bool_t +MinuitFcnGrad::Synchronize(std::vector ¶meters, Bool_t optConst, Bool_t verbose) +{ + Bool_t returnee = synchronize_parameter_settings(parameters, optConst, verbose); + likelihood->synchronize_parameter_settings(parameters); + gradient->synchronize_parameter_settings(parameters); + + likelihood->synchronize_with_minimizer(_context->fitter()->Config().MinimizerOptions()); + gradient->synchronize_with_minimizer(_context->fitter()->Config().MinimizerOptions()); + return returnee; +} + +std::string MinuitFcnGrad::getFunctionName() const +{ + return likelihood->GetName(); +} + +std::string MinuitFcnGrad::getFunctionTitle() const +{ + return likelihood->GetTitle(); +} + +void MinuitFcnGrad::setOptimizeConst(Int_t flag) +{ + RooAbsReal::setEvalErrorLoggingMode(RooAbsReal::CollectErrors); + + if (_optConst && !flag) { + if (_context->getPrintLevel() > -1) + oocoutI(_context, Minimization) << "MinuitFcnGrad::setOptimizeConst: deactivating const optimization" + << std::endl; + likelihood->constOptimizeTestStatistic(RooAbsArg::DeActivate, true); + _optConst = flag; + } else if (!_optConst && flag) { + if (_context->getPrintLevel() > -1) + oocoutI(_context, Minimization) << "MinuitFcnGrad::setOptimizeConst: activating const optimization" + << std::endl; + likelihood->constOptimizeTestStatistic(RooAbsArg::Activate, flag > 1); + _optConst = flag; + } else if (_optConst && flag) { + if (_context->getPrintLevel() > -1) + oocoutI(_context, Minimization) << "MinuitFcnGrad::setOptimizeConst: const optimization already active" + << std::endl; + } else { + if (_context->getPrintLevel() > -1) + oocoutI(_context, Minimization) << "MinuitFcnGrad::setOptimizeConst: const optimization wasn't active" + << std::endl; + } + + RooAbsReal::setEvalErrorLoggingMode(RooAbsReal::PrintErrors); +} + +void MinuitFcnGrad::enable_likelihood_offsetting(bool flag) { + likelihood->enable_offsetting(flag); +} + +} // namespace TestStatistics +} // namespace RooFit diff --git a/roofit/roofitcore/src/TestStatistics/RooAbsL.cxx b/roofit/roofitcore/src/TestStatistics/RooAbsL.cxx new file mode 100644 index 0000000000000..7ca288d8f0ea1 --- /dev/null +++ b/roofit/roofitcore/src/TestStatistics/RooAbsL.cxx @@ -0,0 +1,485 @@ +/* + * Project: RooFit + * Authors: + * PB, Patrick Bos, Netherlands eScience Center, p.bos@esciencecenter.nl + * + * Copyright (c) 2016-2020, Netherlands eScience Center + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +#include +#include +#include "RooAbsPdf.h" +#include "RooAbsData.h" + +#include + +// for dynamic casts in init_clones: +#include "RooAbsRealLValue.h" +#include "RooRealVar.h" +#include "RooDataHist.h" + +// other stuff in init_clones: +#include "RooErrorHandler.h" +#include "RooMsgService.h" + +// concrete classes in getParameters (testing, remove later) +#include "RooRealSumPdf.h" +#include "RooProdPdf.h" + +namespace RooFit { +namespace TestStatistics { + +// static function +bool RooAbsL::is_extended(RooAbsPdf* pdf, Extended extended) +{ + bool return_value; + switch (extended) { + case RooAbsL::Extended::No: { + return_value = false; + break; + } + case RooAbsL::Extended::Yes: { + return_value = true; + break; + } + case RooAbsL::Extended::Auto: { + return_value = ((pdf->extendMode() == RooAbsPdf::CanBeExtended || pdf->extendMode() == RooAbsPdf::MustBeExtended)) + ? true + : false; + break; + } + } + return return_value; +} + +// private ctor +RooAbsL::RooAbsL(std::shared_ptr pdf, std::shared_ptr data, + std::size_t N_events, std::size_t N_components, Extended extended) + : pdf_(std::move(pdf)), data_(std::move(data)), N_events(N_events), N_components(N_components) +{ + // std::unique_ptr obs {pdf->getObservables(*data)}; + // data->attachBuffers(*obs); + extended_ = is_extended(pdf_.get(), extended); + if (extended == Extended::Auto) { + if (extended_) { + oocoutI((TObject *)nullptr, Minimization) + << "in RooAbsL ctor: p.d.f. provides expected number of events, including extended term in likelihood." + << std::endl; + } + } +} + +// this constructor clones the pdf/data and owns those cloned copies +RooAbsL::RooAbsL(RooAbsL::ClonePdfData in, std::size_t N_events, std::size_t N_components, Extended extended) + : RooAbsL(std::shared_ptr(static_cast(in.pdf->cloneTree())), + std::shared_ptr(static_cast(in.data->Clone())), N_events, N_components, extended) +{ + init_clones(*in.pdf, *in.data); +} + +// this constructor does not clone pdf/data and uses the shared_ptr aliasing constructor to make it non-owning +RooAbsL::RooAbsL(RooAbsPdf *inpdf, RooAbsData *indata, std::size_t N_events, std::size_t N_components, + Extended extended) + : RooAbsL({std::shared_ptr(nullptr), inpdf}, {std::shared_ptr(nullptr), indata}, N_events, N_components, extended) +{} + + +RooAbsL::RooAbsL(const RooAbsL &other) + : pdf_(other.pdf_), data_(other.data_), N_events(other.N_events), + N_components(other.N_components), extended_(other.extended_), sim_count_(other.sim_count_), eval_carry_(other.eval_carry_) +{ + // it can never be one, since we just copied the shared_ptr; if it is, something really weird is going on; also they must be equal (usually either zero or two) + assert((pdf_.use_count() != 1) && (data_.use_count() != 1) && (pdf_.use_count() == data_.use_count())); + // TODO: use aliasing ctor in initialization list, and then check in body here whether pdf and data were clones; if so, they need to be cloned again (and init_clones called on them) + if ((pdf_.use_count() > 1) && (data_.use_count() > 1)) { + pdf_.reset(static_cast(other.pdf_->cloneTree())); + data_.reset(static_cast(other.data_->Clone())); + init_clones(*other.pdf_, *other.data_); + } +} + +void RooAbsL::init_clones(RooAbsPdf &inpdf, RooAbsData &indata) +{ + // RooArgSet obs(*indata.get()) ; + // obs.remove(projDeps,kTRUE,kTRUE) ; + + // ****************************************************************** + // *** PART 1 *** Clone incoming pdf, attach to each other * + // ****************************************************************** + + // moved to ctor + // pdf = static_cast(inpdf->cloneTree()); + + // Attach FUNC to data set + auto _funcObsSet = pdf_->getObservables(indata); + + if (pdf_->getAttribute("BinnedLikelihood")) { + pdf_->setAttribute("BinnedLikelihoodActive"); + } + + // Reattach FUNC to original parameters + std::unique_ptr origParams{inpdf.getParameters(indata)}; + pdf_->recursiveRedirectServers(*origParams); + + // Mark all projected dependents as such + // if (projDeps.getSize()>0) { + // RooArgSet *projDataDeps = (RooArgSet*) _funcObsSet->selectCommon(projDeps) ; + // projDataDeps->setAttribAll("projectedDependent") ; + // delete projDataDeps ; + // } + + // TODO: do we need this here? Or in RooSumL? + // // If PDF is a RooProdPdf (with possible constraint terms) + // // analyze pdf for actual parameters (i.e those in unconnected constraint terms should be + // // ignored as here so that the test statistic will not be recalculated if those + // // are changed + // RooProdPdf* pdfWithCons = dynamic_cast(pdf) ; + // if (pdfWithCons) { + // + // RooArgSet* connPars = pdfWithCons->getConnectedParameters(*indata.get()) ; + // // Add connected parameters as servers + // _paramSet.removeAll() ; + // _paramSet.add(*connPars) ; + // delete connPars ; + // + // } else { + // // Add parameters as servers + // _paramSet.add(*origParams) ; + // } + + // Store normalization set + _normSet.reset((RooArgSet *)indata.get()->snapshot(kFALSE)); + + // Expand list of observables with any observables used in parameterized ranges + RooAbsArg *realDep; + RooFIter iter = _funcObsSet->fwdIterator(); + while ((realDep = iter.next())) { + RooAbsRealLValue *realDepRLV = dynamic_cast(realDep); + if (realDepRLV && realDepRLV->isDerived()) { + RooArgSet tmp2; + realDepRLV->leafNodeServerList(&tmp2, 0, kTRUE); + _funcObsSet->add(tmp2, kTRUE); + } + } + + // ****************************************************************** + // *** PART 2 *** Clone and adjust incoming data, attach to PDF * + // ****************************************************************** + + // Check if the fit ranges of the dependents in the data and in the FUNC are consistent + const RooArgSet *dataDepSet = indata.get(); + iter = _funcObsSet->fwdIterator(); + RooAbsArg *arg; + while ((arg = iter.next())) { + + // Check that both dataset and function argument are of type RooRealVar + RooRealVar *realReal = dynamic_cast(arg); + if (!realReal) { + continue; + } + RooRealVar *datReal = dynamic_cast(dataDepSet->find(realReal->GetName())); + if (!datReal) { + continue; + } + + // Check that range of observables in pdf is equal or contained in range of observables in data + + if (!realReal->getBinning().lowBoundFunc() && realReal->getMin() < (datReal->getMin() - 1e-6)) { + oocoutE((TObject *)0, InputArguments) << "RooAbsL: ERROR minimum of FUNC observable " << arg->GetName() << "(" + << realReal->getMin() << ") is smaller than that of " << arg->GetName() + << " in the dataset (" << datReal->getMin() << ")" << std::endl; + RooErrorHandler::softAbort(); + return; + } + + if (!realReal->getBinning().highBoundFunc() && realReal->getMax() > (datReal->getMax() + 1e-6)) { + oocoutE((TObject *)0, InputArguments) + << "RooAbsL: ERROR maximum of FUNC observable " << arg->GetName() << " is larger than that of " + << arg->GetName() << " in the dataset" << std::endl; + RooErrorHandler::softAbort(); + return; + } + } + + // // Copy data and strip entries lost by adjusted fit range, data ranges will be copied from realDepSet ranges + // if (rangeName && strlen(rangeName)) { + // data = ((RooAbsData &)indata).reduce(RooFit::SelectVars(*_funcObsSet), RooFit::CutRange(rangeName)); + // // cout << "RooAbsOptTestStatistic: reducing dataset to fit in range named " << rangeName << " resulting + // // dataset has " << data->sumEntries() << " events" << endl ; + // } else { + + // moved to ctor + // data = static_cast(indata.Clone()); + + // } + // _ownData = kTRUE; + + // ****************************************************************** + // *** PART 3 *** Make adjustments for fit ranges, if specified * + // ****************************************************************** + + // RooArgSet *origObsSet = inpdf.getObservables(indata); + // RooArgSet *dataObsSet = (RooArgSet *)data->get(); + // if (rangeName && strlen(rangeName)) { + // cxcoutI(Fitting) << "RooAbsOptTestStatistic::ctor(" << GetName() + // << ") constructing test statistic for sub-range named " << rangeName << endl; + // // cout << "now adjusting observable ranges to requested fit range" << endl ; + // + // // Adjust FUNC normalization ranges to requested fitRange, store original ranges for RooAddPdf coefficient + // // interpretation + // iter = _funcObsSet->fwdIterator(); + // while ((arg = iter.next())) { + // + // RooRealVar *realObs = dynamic_cast(arg); + // if (realObs) { + // + // // If no explicit range is given for RooAddPdf coefficients, create explicit named range equivalent to + // // original observables range + // if (!(addCoefRangeName && strlen(addCoefRangeName))) { + // realObs->setRange(Form("NormalizationRangeFor%s", rangeName), realObs->getMin(), realObs->getMax()); + // // cout << "RAOTS::ctor() setting range " << Form("NormalizationRangeFor%s",rangeName) << " on + // // observable " + // // << realObs->GetName() << " to [" << realObs->getMin() << "," << realObs->getMax() << "]" + // << + // // endl ; + // } + // + // // Adjust range of function observable to those of given named range + // realObs->setRange(realObs->getMin(rangeName), realObs->getMax(rangeName)); + // // cout << "RAOTS::ctor() setting normalization range on observable " + // // << realObs->GetName() << " to [" << realObs->getMin() << "," << realObs->getMax() << "]" << + // endl + // // ; + // + // // Adjust range of data observable to those of given named range + // RooRealVar *dataObs = (RooRealVar *)dataObsSet->find(realObs->GetName()); + // dataObs->setRange(realObs->getMin(rangeName), realObs->getMax(rangeName)); + // + // // Keep track of list of fit ranges in string attribute fit range of original p.d.f. + // if (!_splitRange) { + // const char *origAttrib = inpdf.getStringAttribute("fitrange"); + // if (origAttrib) { + // inpdf.setStringAttribute("fitrange", Form("%s,fit_%s", origAttrib, GetName())); + // } else { + // inpdf.setStringAttribute("fitrange", Form("fit_%s", GetName())); + // } + // RooRealVar *origObs = (RooRealVar *)origObsSet->find(arg->GetName()); + // if (origObs) { + // origObs->setRange(Form("fit_%s", GetName()), realObs->getMin(rangeName), + // realObs->getMax(rangeName)); + // } + // } + // } + // } + // } + // delete origObsSet; + + // If dataset is binned, activate caching of bins that are invalid because they're outside the + // updated range definition (WVE need to add virtual interface here) + RooDataHist *tmph = dynamic_cast(data_.get()); + if (tmph) { + tmph->cacheValidEntries(); + } + + // // Fix RooAddPdf coefficients to original normalization range + // if (rangeName && strlen(rangeName)) { + // + // // WVE Remove projected dependents from normalization + // pdf->fixAddCoefNormalization(*data->get(), kFALSE); + // + // if (addCoefRangeName && strlen(addCoefRangeName)) { + // cxcoutI(Fitting) << "RooAbsOptTestStatistic::ctor(" << GetName() + // << ") fixing interpretation of coefficients of any RooAddPdf component to range " + // << addCoefRangeName << endl; + // pdf->fixAddCoefRange(addCoefRangeName, kFALSE); + // } else { + // cxcoutI(Fitting) << "RooAbsOptTestStatistic::ctor(" << GetName() + // << ") fixing interpretation of coefficients of any RooAddPdf to full domain of + // observables " + // << endl; + // pdf->fixAddCoefRange(Form("NormalizationRangeFor%s", rangeName), kFALSE); + // } + // } + + // This is deferred from part 2 - but must happen after part 3 - otherwise invalid bins cannot be properly marked in + // cacheValidEntries + data_->attachBuffers(*_funcObsSet); + // TODO: we pass event count to the ctor in the subclasses currently, because it's split into components and events + // now + // setEventCount(data->numEntries()); + + // ********************************************************************* + // *** PART 4 *** Adjust normalization range for projected observables * + // ********************************************************************* + + // // Remove projected dependents from normalization set + // if (projDeps.getSize() > 0) { + // + // _projDeps = (RooArgSet *)projDeps.snapshot(kFALSE); + // + // // RooArgSet* tobedel = (RooArgSet*) _normSet->selectCommon(*_projDeps) ; + // _normSet->remove(*_projDeps, kTRUE, kTRUE); + // + // // // Delete owned projected dependent copy in _normSet + // // TIterator* ii = tobedel->createIterator() ; + // // RooAbsArg* aa ; + // // while((aa=(RooAbsArg*)ii->Next())) { + // // delete aa ; + // // } + // // delete ii ; + // // delete tobedel ; + // + // // Mark all projected dependents as such + // RooArgSet *projDataDeps = (RooArgSet *)_funcObsSet->selectCommon(*_projDeps); + // projDataDeps->setAttribAll("projectedDependent"); + // delete projDataDeps; + // } + + // coutI(Optimization) + // << "RooAbsOptTestStatistic::ctor(" << GetName() + // << ") optimizing internal clone of p.d.f for likelihood evaluation." + // << "Lazy evaluation and associated change tracking will disabled for all nodes that depend on observables" + // << endl; + + // ********************************************************************* + // *** PART 4 *** Finalization and activation of optimization * + // ********************************************************************* + + //_origFunc = _func ; + //_origData = _data ; + + // // Redirect pointers of base class to clone + // _func = pdf ; + // _data = data ; + + // TODO: why this call? + // pdf->getVal(_normSet); + + // cout << "ROATS::ctor(" << GetName() << ") funcClone structure dump BEFORE opt" << endl ; + // pdf->Print("t") ; + + // optimizeCaching() ; + + // cout << "ROATS::ctor(" << GetName() << ") funcClone structure dump AFTER opt" << endl ; + // pdf->Print("t") ; + + // optimization steps (copied from ROATS::optimizeCaching) + + pdf_->getVal(_normSet.get()); + // Set value caching mode for all nodes that depend on any of the observables to ADirty + pdf_->optimizeCacheMode(*_funcObsSet); + // Disable propagation of dirty state flags for observables + data_->setDirtyProp(kFALSE); + + // Disable reading of observables that are not used + data_->optimizeReadingWithCaching(*pdf_, RooArgSet(), RooArgSet()) ; + +} + +RooArgSet *RooAbsL::getParameters() +{ + auto ding = pdf_->getParameters(*data_); + return ding; + +// // *** START HERE +// // WVE HACK determine if we have a RooRealSumPdf and then treat it like a binned likelihood +// RooAbsPdf *binnedPdf = 0; +// if (pdf_->getAttribute("BinnedLikelihood") && pdf_->IsA()->InheritsFrom(RooRealSumPdf::Class())) { +// // Simplest case: top-level of component is a RRSP +// binnedPdf = pdf_.get(); +// } else if (pdf_->IsA()->InheritsFrom(RooProdPdf::Class())) { +// // Default case: top-level pdf is a product of RRSP and other pdfs +// RooFIter iter = ((RooProdPdf *)pdf_.get())->pdfList().fwdIterator(); +// RooAbsArg *component; +// while ((component = iter.next())) { +// if (component->getAttribute("BinnedLikelihood") && +// component->IsA()->InheritsFrom(RooRealSumPdf::Class())) { +// binnedPdf = (RooAbsPdf *)component; +// } +// if (component->getAttribute("MAIN_MEASUREMENT")) { +// // not really a binned pdf, but this prevents a (potentially) long list of subsidiary measurements to +// // be passed to the slave calculator +// binnedPdf = (RooAbsPdf *)component; +// } +// } +// } +// // WVE END HACK +// +// std::unique_ptr actualParams {binnedPdf ? binnedPdf->getParameters(data_.get()) : pdf_->getParameters(data_.get())}; +// RooArgSet* selTargetParams = (RooArgSet *)ding->selectCommon(*actualParams); +// +// std::cout << "RooAbsL::getParameters:" << std::endl; +// selTargetParams->Print("v"); +// +// return selTargetParams; +} + +void RooAbsL::constOptimizeTestStatistic(RooAbsArg::ConstOpCode opcode, bool doAlsoTrackingOpt) +{ + // to be further implemented, this is just a first test implementation + if (opcode == RooAbsArg::Activate) { + ConstantTermsOptimizer::enable_constant_terms_optimization(pdf_.get(), _normSet.get(), data_.get(), doAlsoTrackingOpt); + } +} + +std::string RooAbsL::GetName() const +{ + std::string output("likelihood of pdf "); + output.append(pdf_->GetName()); + return output; +} + +std::string RooAbsL::GetTitle() const +{ + std::string output("likelihood of pdf "); + output.append(pdf_->GetTitle()); + return output; +} + +double RooAbsL::defaultErrorLevel() const +{ + return 0.5; +} +std::size_t RooAbsL::numDataEntries() const +{ + return static_cast(data_->numEntries()); +} + +void RooAbsL::optimize_pdf() +{ + // TODO: implement +} + +std::size_t RooAbsL::get_N_events() const +{ + return N_events; +} + +std::size_t RooAbsL::get_N_components() const +{ + return N_components; +} + +double RooAbsL::get_carry() const +{ + return eval_carry_; +} + +bool RooAbsL::is_extended() const +{ + return extended_; +} + +void RooAbsL::set_sim_count(std::size_t value) +{ + sim_count_ = value; +} + + +} // namespace TestStatistics +} // namespace RooFit diff --git a/roofit/roofitcore/src/TestStatistics/RooBinnedL.cxx b/roofit/roofitcore/src/TestStatistics/RooBinnedL.cxx new file mode 100644 index 0000000000000..178fd33106ac6 --- /dev/null +++ b/roofit/roofitcore/src/TestStatistics/RooBinnedL.cxx @@ -0,0 +1,172 @@ +/***************************************************************************** + * Project: RooFit * + * Package: RooFitCore * + * @(#)root/roofitcore:$Id$ + * Authors: * + * WV, Wouter Verkerke, UC Santa Barbara, verkerke@slac.stanford.edu * + * DK, David Kirkby, UC Irvine, dkirkby@uci.edu * + * PB, Patrick Bos, NL eScience Center, p.bos@esciencecenter.nl * + * * + * Copyright (c) 2000-2020, Regents of the University of California * + * and Stanford University. All rights reserved. * + * * + * Redistribution and use in source and binary forms, * + * with or without modification, are permitted according to the terms * + * listed in LICENSE (http://roofit.sourceforge.net/license.txt) * + *****************************************************************************/ + +/** +\file RooBinnedL.cxx +\class RooBinnedL +\ingroup Roofitcore + +Class RooBinnedL implements a a -log(likelihood) calculation from a dataset +and a PDF. The NLL is calculated as +

+ Sum[data] -log( pdf(x_data) )
+
+In extended mode, a (Nexpect - Nobserved*log(NExpected) term is added +**/ + +#include "TMath.h" + +#include "RooAbsData.h" +#include "RooAbsPdf.h" +#include "RooAbsDataStore.h" +#include "RooRealSumPdf.h" +#include "RooRealVar.h" + +#include + +namespace RooFit { +namespace TestStatistics { + +RooBinnedL::RooBinnedL(RooAbsPdf* pdf, RooAbsData* data) : + RooAbsL(RooAbsL::ClonePdfData{pdf, data}, data->numEntries(), 1) +{ + // pdf must be a RooRealSumPdf representing a yield vector for a binned likelihood calculation + if (!dynamic_cast(pdf)) { + throw std::logic_error("RooBinnedL can only be created from pdf of type RooRealSumPdf!"); + } + + // Retrieve and cache bin widths needed to convert unnormalized binned pdf values back to yields + + // The Active label will disable pdf integral calculations + pdf->setAttribute("BinnedLikelihoodActive"); + + RooArgSet *obs = pdf->getObservables(data); + if (obs->getSize() != 1) { + throw std::logic_error("RooBinnedL can only be created from combination of pdf and data which has exactly one observable!"); + } else { + RooRealVar *var = (RooRealVar *)obs->first(); + std::list *boundaries = pdf->binBoundaries(*var, var->getMin(), var->getMax()); + std::list::iterator biter = boundaries->begin(); + _binw.resize(boundaries->size() - 1); + Double_t lastBound = (*biter); + ++biter; + int ibin = 0; + while (biter != boundaries->end()) { + _binw[ibin] = (*biter) - lastBound; + lastBound = (*biter); + ibin++; + ++biter; + } + } +} + +////////////////////////////////////////////////////////////////////////////////// +///// Calculate and return likelihood on subset of data from firstEvent to lastEvent +///// processed with a step size of 'stepSize'. If this an extended likelihood and +///// and the zero event is processed the extended term is added to the return +///// likelihood. +// +double RooBinnedL::evaluate_partition(Section bins, std::size_t /*components_begin*/, + std::size_t /*components_end*/) +{ + // Throughout the calculation, we use Kahan's algorithm for summing to + // prevent loss of precision - this is a factor four more expensive than + // straight addition, but since evaluating the PDF is usually much more + // expensive than that, we tolerate the additional cost... + Double_t result(0), carry(0); + + // cout << "RooNLLVar::evaluatePartition(" << GetName() << ") projDeps = " << (_projDeps?*_projDeps:RooArgSet()) << + // endl ; + +// data->store()->recalculateCache(_projDeps, firstEvent, lastEvent, stepSize, (_binnedPdf?kFALSE:kTRUE)); + // TODO: check when we might need _projDeps (it seems to be mostly empty); ties in with TODO below + data_->store()->recalculateCache(nullptr, bins.begin(N_events), bins.end(N_events), 1, kFALSE); + + Double_t sumWeight(0), sumWeightCarry(0); + + for (std::size_t i = bins.begin(N_events); i < bins.end(N_events); ++i) { + + data_->get(i); + + if (!data_->valid()) + continue; + + Double_t eventWeight = data_->weight(); + + // Calculate log(Poisson(N|mu) for this bin + Double_t N = eventWeight; + Double_t mu = pdf_->getVal() * _binw[i]; + // cout << "RooNLLVar::binnedL(" << GetName() << ") N=" << N << " mu = " << mu << endl ; + + if (mu <= 0 && N > 0) { + + // Catch error condition: data present where zero events are predicted +// logEvalError(Form("Observed %f events in bin %d with zero event yield", N, i)); + // TODO: check if using regular stream vs logEvalError error gathering is ok + oocoutI(static_cast(nullptr), Minimization) + << "Observed " << N << " events in bin " << i << " with zero event yield" << std::endl; + + } else if (fabs(mu) < 1e-10 && fabs(N) < 1e-10) { + + // Special handling of this case since log(Poisson(0,0)=0 but can't be calculated with usual log-formula + // since log(mu)=0. No update of result is required since term=0. + + } else { + + Double_t term = -1 * (-mu + N * log(mu) - TMath::LnGamma(N + 1)); + + // Kahan summation of sumWeight + Double_t y = eventWeight - sumWeightCarry; + Double_t t = sumWeight + y; + sumWeightCarry = (t - sumWeight) - y; + sumWeight = t; + + // Kahan summation of result + y = term - carry; + t = result + y; + carry = (t - result) - y; + result = t; + } + } + + + // If part of simultaneous PDF normalize probability over + // number of simultaneous PDFs: -sum(log(p/n)) = -sum(log(p)) + N*log(n) + if (sim_count_ > 1) { + Double_t y = sumWeight * log(1.0 * sim_count_) - carry; + Double_t t = result + y; + carry = (t - result) - y; + result = t; + } + + // timer.Stop() ; + // cout << "RooNLLVar::evalPart(" << GetName() << ") SET=" << _setNum << " first=" << firstEvent << ", last=" << + // lastEvent << ", step=" << stepSize << ") result = " << result << " CPU = " << timer.CpuTime() << endl ; + + // At the end of the first full calculation, wire the caches + if (_first) { + _first = false; + pdf_->wireAllCaches(); + } + + eval_carry_ = carry; + return result; +} + + +} // namespace TestStatistics +} // namespace RooFit diff --git a/roofit/roofitcore/src/TestStatistics/RooRealL.cxx b/roofit/roofitcore/src/TestStatistics/RooRealL.cxx new file mode 100644 index 0000000000000..b276b5bffdbad --- /dev/null +++ b/roofit/roofitcore/src/TestStatistics/RooRealL.cxx @@ -0,0 +1,70 @@ +/* + * Project: RooFit + * Authors: + * PB, Patrick Bos, Netherlands eScience Center, p.bos@esciencecenter.nl + * + * Copyright (c) 2016-2020, Netherlands eScience Center + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +#include +#include + +//ClassImp(RooFit::TestStatistics::RooRealL); + +namespace RooFit { +namespace TestStatistics { + +RooRealL::RooRealL(const char *name, const char *title, std::shared_ptr likelihood) + : RooAbsReal(name, title), likelihood_(std::move(likelihood)), + vars_proxy_("varsProxy", "proxy set of parameters", this) +{ + vars_proxy_.add(*likelihood_->getParameters()); +} + +RooRealL::RooRealL(const RooRealL &other, const char *name) + : RooAbsReal(other, name), likelihood_(other.likelihood_), vars_proxy_("varsProxy", "proxy set of parameters", this) +{ + vars_proxy_.add(*likelihood_->getParameters()); +} + +double RooRealL::globalNormalization() const +{ + // Default value of global normalization factor is 1.0 + return 1.0; +} + +double RooRealL::get_carry() const +{ + return eval_carry; +} + +Double_t RooRealL::evaluate() const +{ + // Evaluate as straight FUNC + std::size_t last_component = likelihood_->get_N_components(); + + Double_t ret = likelihood_->evaluate_partition({0, 1}, 0, last_component); + + const Double_t norm = globalNormalization(); + ret /= norm; + eval_carry = likelihood_->get_carry() / norm; + + return ret; +} + +TObject *RooRealL::clone(const char *newname) const +{ + return new RooRealL(*this, newname); +} + +Double_t RooRealL::defaultErrorLevel() const +{ + return 0.5; +} +} // namespace TestStatistics +} // namespace RooFit diff --git a/roofit/roofitcore/src/TestStatistics/RooSubsidiaryL.cxx b/roofit/roofitcore/src/TestStatistics/RooSubsidiaryL.cxx new file mode 100644 index 0000000000000..171c357f18ab1 --- /dev/null +++ b/roofit/roofitcore/src/TestStatistics/RooSubsidiaryL.cxx @@ -0,0 +1,83 @@ +/* + * Project: RooFit + * Authors: + * PB, Patrick Bos, Netherlands eScience Center, p.bos@esciencecenter.nl + * + * Copyright (c) 2016-2020, Netherlands eScience Center + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +#include +#include +#include // for dynamic cast +#include + +namespace RooFit { +namespace TestStatistics { + +RooSubsidiaryL::RooSubsidiaryL(const std::string& parent_pdf_name, const RooArgSet &pdfs, const RooArgSet ¶meter_set) + : RooAbsL(nullptr, nullptr, 0, 0, RooAbsL::Extended::No), parent_pdf_name_(parent_pdf_name) +{ + std::unique_ptr inputIter{pdfs.createIterator()}; + RooAbsArg *comp; + while ((comp = (RooAbsArg *)inputIter->Next())) { + if (!dynamic_cast(comp)) { + oocoutE((TObject *)0, InputArguments) << "RooSubsidiaryL::ctor(" << GetName() << ") ERROR: component " + << comp->GetName() << " is not of type RooAbsPdf" << std::endl; + RooErrorHandler::softAbort(); + } + subsidiary_pdfs_.add(*comp); + } + parameter_set_.add(parameter_set); +} + +double +RooSubsidiaryL::evaluate_partition(RooAbsL::Section events, std::size_t /*components_begin*/, std::size_t /*components_end*/) +{ + if (events.begin_fraction != 0 || events.end_fraction != 1) { + oocoutW((TObject *)0, InputArguments) << "RooSubsidiaryL::evaluate_partition can only calculate everything, so " + "section should be {0,1}, but it's not!" + << std::endl; + } + + double sum = 0, carry = 0; + RooAbsReal *comp; + RooFIter setIter1 = subsidiary_pdfs_.fwdIterator(); + + while ((comp = (RooAbsReal *)setIter1.next())) { + double term = -((RooAbsPdf *)comp)->getLogVal(¶meter_set_); + std::tie(sum, carry) = kahan_add(sum, term, carry); + } + eval_carry_ = carry; + + return sum; +} + +RooArgSet *RooSubsidiaryL::getParameters() +{ + return ¶meter_set_; +} +std::string RooSubsidiaryL::GetName() const +{ + return std::string("subsidiary_pdf_of_") + parent_pdf_name_; +} +std::string RooSubsidiaryL::GetTitle() const +{ + return std::string("Subsidiary PDF set of simultaneous PDF ") + parent_pdf_name_; +} +std::size_t RooSubsidiaryL::numDataEntries() const +{ + // function only used in LikelihoodJob::evaluate, but this class must always be evaluated over Section(0,1), so not useful + return 0; +} + +void RooSubsidiaryL::constOptimizeTestStatistic(RooAbsArg::ConstOpCode /*opcode*/, bool /*doAlsoTrackingOpt*/) { + // do nothing, there's no dataset here to cache +} + +} // namespace TestStatistics +} // namespace RooFit diff --git a/roofit/roofitcore/src/TestStatistics/RooSumL.cxx b/roofit/roofitcore/src/TestStatistics/RooSumL.cxx new file mode 100644 index 0000000000000..d3faf343aa7c9 --- /dev/null +++ b/roofit/roofitcore/src/TestStatistics/RooSumL.cxx @@ -0,0 +1,91 @@ +/* + * Project: RooFit + * Authors: + * PB, Patrick Bos, Netherlands eScience Center, p.bos@esciencecenter.nl + * + * Copyright (c) 2016-2020, Netherlands eScience Center + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +#include +#include +#include + +#include // min, max + +namespace RooFit { +namespace TestStatistics { + +// Note: components must be passed with std::move, otherwise it cannot be moved into the RooSumL because of the unique_ptr! +RooSumL::RooSumL(RooAbsPdf *pdf, RooAbsData *data, std::vector> components, RooAbsL::Extended extended) + : RooAbsL(pdf, data, + data->numEntries(), // TODO: this may be misleading, because components in reality will have their own N_events... + components.size(), extended), components_(std::move(components)) +{} + + +bool RooSumL::processEmptyDataSets() const +{ + // TODO: check whether this is correct! This is copied the implementation of the RooNLLVar override; the + // implementation in RooAbsTestStatistic always returns true + return extended_; +} + +double RooSumL::evaluate_partition(Section events, std::size_t components_begin, std::size_t components_end) +{ + // Evaluate specified range of owned GOF objects + double ret = 0; + + // from RooAbsOptTestStatistic::combinedValue (which is virtual, so could be different for non-RooNLLVar!): + eval_carry_ = 0; + for (std::size_t ix = components_begin; ix < components_end; ++ix) { + // TODO: make sure we only calculate over events in the sub-range that the caller asked for + // std::size_t component_events_begin = std::max(events_begin, components_[ix]->get_N_events()) // THIS + // WON'T WORK, we need to somehow allow evaluate_partition to take in separate event ranges for all + // components... + + double y = components_[ix]->evaluate_partition(events, 0, 0); + +// if (dynamic_cast(components_[ix].get()) != nullptr) { +// printf("subsidiary component %d = %f\n", ix, y); +// } + + eval_carry_ += components_[ix]->get_carry(); + y -= eval_carry_; + double t = ret + y; + eval_carry_ = (t - ret) - y; + ret = t; + } + + // Note: compared to the RooAbsTestStatistic implementation that this was taken from, we leave out Hybrid and + // SimComponents interleaving support here, this should be implemented by calculator, if desired. + + return ret; +} + +// note: this assumes there is only one subsidiary component! +std::tuple RooSumL::get_subsidiary_value() +{ + // iterate in reverse, because the subsidiary component is usually at the end: + for (auto component = components_.rbegin(); component != components_.rend(); ++component) { + if (dynamic_cast((*component).get()) != nullptr) { + double value = (*component)->evaluate_partition({0, 1}, 0, 0); + double carry = (*component)->get_carry(); + return {value, carry}; + } + } + return {0, 0}; +} + +void RooSumL::constOptimizeTestStatistic(RooAbsArg::ConstOpCode opcode, bool doAlsoTrackingOpt) { + for (auto& component : components_) { + component->constOptimizeTestStatistic(opcode, doAlsoTrackingOpt); + } +} + +} // namespace TestStatistics +} // namespace RooFit diff --git a/roofit/roofitcore/src/TestStatistics/RooUnbinnedL.cxx b/roofit/roofitcore/src/TestStatistics/RooUnbinnedL.cxx new file mode 100644 index 0000000000000..692a9393e30b4 --- /dev/null +++ b/roofit/roofitcore/src/TestStatistics/RooUnbinnedL.cxx @@ -0,0 +1,193 @@ +/***************************************************************************** + * Project: RooFit * + * Package: RooFitCore * + * @(#)root/roofitcore:$Id$ + * Authors: * + * WV, Wouter Verkerke, UC Santa Barbara, verkerke@slac.stanford.edu * + * DK, David Kirkby, UC Irvine, dkirkby@uci.edu * + * PB, Patrick Bos, NL eScience Center, p.bos@esciencecenter.nl * + * * + * Copyright (c) 2000-2020, Regents of the University of California * + * and Stanford University. All rights reserved. * + * * + * Redistribution and use in source and binary forms, * + * with or without modification, are permitted according to the terms * + * listed in LICENSE (http://roofit.sourceforge.net/license.txt) * + *****************************************************************************/ + +/** +\file RooUnbinnedL.cxx +\class RooUnbinnedL +\ingroup Roofitcore + +Class RooUnbinnedL implements a a -log(likelihood) calculation from a dataset +and a PDF. The NLL is calculated as +
+ Sum[data] -log( pdf(x_data) )
+
+In extended mode, a (Nexpect - Nobserved*log(NExpected) term is added +**/ + +#include "RooAbsData.h" +#include "RooAbsPdf.h" +#include "RooAbsDataStore.h" + +#include + +namespace RooFit { +namespace TestStatistics { + +RooUnbinnedL::RooUnbinnedL(RooAbsPdf *pdf, RooAbsData *data, + RooAbsL::Extended extended) + : RooAbsL(RooAbsL::ClonePdfData{pdf, data}, data->numEntries(), 1, extended) +{} + +RooUnbinnedL::RooUnbinnedL(const RooUnbinnedL &other) + : RooAbsL(other), apply_weight_squared(other.apply_weight_squared), _first(other._first) +{} + + +bool RooUnbinnedL::processEmptyDataSets() const +{ + return extended_; +} + +////////////////////////////////////////////////////////////////////////////////// + +/// Returns true if value was changed, false otherwise. +bool RooUnbinnedL::set_apply_weight_squared(bool flag) +{ + if (apply_weight_squared != flag) { + apply_weight_squared = flag; + return true; + } + // setValueDirty(); + return false; +} + +////////////////////////////////////////////////////////////////////////////////// +///// Calculate and return likelihood on subset of data from firstEvent to lastEvent +///// processed with a step size of 'stepSize'. If this an extended likelihood and +///// and the zero event is processed the extended term is added to the return +///// likelihood. +// +double RooUnbinnedL::evaluate_partition(Section events, + std::size_t /*components_begin*/, std::size_t /*components_end*/) +{ + // Throughout the calculation, we use Kahan's algorithm for summing to + // prevent loss of precision - this is a factor four more expensive than + // straight addition, but since evaluating the PDF is usually much more + // expensive than that, we tolerate the additional cost... + Double_t result(0), carry(0); + + // cout << "RooNLLVar::evaluatePartition(" << GetName() << ") projDeps = " << (_projDeps?*_projDeps:RooArgSet()) << + // endl ; + + // data->store()->recalculateCache(_projDeps, firstEvent, lastEvent, stepSize, (_binnedPdf?kFALSE:kTRUE)); + // TODO: check when we might need _projDeps (it seems to be mostly empty); ties in with TODO below + data_->store()->recalculateCache(nullptr, events.begin(N_events), events.end(N_events), 1, kTRUE); + + Double_t sumWeight(0), sumWeightCarry(0); + + for (std::size_t i = events.begin(N_events); i < events.end(N_events); ++i) { + data_->get(i); + if (!data_->valid()) { + continue; + } + + Double_t eventWeight = data_->weight(); + if (0. == eventWeight * eventWeight) { + continue; + } + if (apply_weight_squared) { + eventWeight = data_->weightSquared(); + } + + Double_t term = -eventWeight * pdf_->getLogVal(_normSet.get()); + // TODO: _normSet should be modified if _projDeps is non-null, connected to TODO above + + Double_t y = eventWeight - sumWeightCarry; + Double_t t = sumWeight + y; + sumWeightCarry = (t - sumWeight) - y; + sumWeight = t; + + y = term - carry; + t = result + y; + carry = (t - result) - y; + result = t; + } + + // include the extended maximum likelihood term, if requested + if (extended_) { + if (apply_weight_squared) { + + // Calculate sum of weights-squared here for extended term + Double_t sumW2(0), sumW2carry(0); + for (Int_t i = 0; i < data_->numEntries(); i++) { + data_->get(i); + Double_t y = data_->weightSquared() - sumW2carry; + Double_t t = sumW2 + y; + sumW2carry = (t - sumW2) - y; + sumW2 = t; + } + + Double_t expected = pdf_->expectedEvents(data_->get()); + + // Adjust calculation of extended term with W^2 weighting: adjust poisson such that + // estimate of Nexpected stays at the same value, but has a different variance, rescale + // both the observed and expected count of the Poisson with a factor sum[w] / sum[w^2] which is + // the effective weight of the Poisson term. + // i.e. change Poisson(Nobs = sum[w]| Nexp ) --> Poisson( sum[w] * sum[w] / sum[w^2] | Nexp * sum[w] / + // sum[w^2] ) weighted by the effective weight sum[w^2]/ sum[w] in the likelihood. Since here we compute + // the likelihood with the weight square we need to multiply by the square of the effective weight expectedW + // = expected * sum[w] / sum[w^2] : effective expected entries observedW = sum[w] * sum[w] / sum[w^2] : + // effective observed entries The extended term for the likelihood weighted by the square of the weight will + // be then: + // (sum[w^2]/ sum[w] )^2 * expectedW - (sum[w^2]/ sum[w] )^2 * observedW * log (expectedW) and this is + // using the previous expressions for expectedW and observedW + // sum[w^2] / sum[w] * expected - sum[w^2] * log (expectedW) + // and since the weights are constants in the likelihood we can use log(expected) instead of log(expectedW) + + Double_t expectedW2 = expected * sumW2 / data_->sumEntries(); + Double_t extra = expectedW2 - sumW2 * log(expected); + + // Double_t y = pdf->extendedTerm(sumW2, data->get()) - carry; + + Double_t y = extra - carry; + + Double_t t = result + y; + carry = (t - result) - y; + result = t; + } else { + Double_t y = pdf_->extendedTerm(data_->sumEntries(), data_->get()) - carry; + Double_t t = result + y; + carry = (t - result) - y; + result = t; + } + } + + // If part of simultaneous PDF normalize probability over + // number of simultaneous PDFs: -sum(log(p/n)) = -sum(log(p)) + N*log(n) + if (sim_count_ > 1) { + Double_t y = sumWeight * log(1.0 * sim_count_) - carry; + Double_t t = result + y; + carry = (t - result) - y; + result = t; + } + + // timer.Stop() ; + // cout << "RooNLLVar::evalPart(" << GetName() << ") SET=" << _setNum << " first=" << firstEvent << ", last=" << + // lastEvent << ", step=" << stepSize << ") result = " << result << " CPU = " << timer.CpuTime() << endl ; + + // At the end of the first full calculation, wire the caches + if (_first) { + _first = false; + pdf_->wireAllCaches(); + } + + eval_carry_ = carry; + return result; +} + +} // namespace TestStatistics +} // namespace RooFit diff --git a/roofit/roofitcore/src/TestStatistics/kahan_sum.cxx b/roofit/roofitcore/src/TestStatistics/kahan_sum.cxx new file mode 100644 index 0000000000000..b744e610d52e7 --- /dev/null +++ b/roofit/roofitcore/src/TestStatistics/kahan_sum.cxx @@ -0,0 +1,29 @@ +/* + * Project: RooFit + * Authors: + * PB, Patrick Bos, Netherlands eScience Center, p.bos@esciencecenter.nl + * + * Copyright (c) 2016-2021, Netherlands eScience Center + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ + +#include + +namespace RooFit { + +std::tuple kahan_add(double sum, double additive, double carry) +{ + double y = additive - carry; + double t = sum + y; + carry = (t - sum) - y; + sum = t; + + return {sum, carry}; +} + +} \ No newline at end of file diff --git a/roofit/roofitcore/src/TestStatistics/likelihood_builders.cxx b/roofit/roofitcore/src/TestStatistics/likelihood_builders.cxx new file mode 100644 index 0000000000000..325927b4422ae --- /dev/null +++ b/roofit/roofitcore/src/TestStatistics/likelihood_builders.cxx @@ -0,0 +1,315 @@ +/* + * Project: RooFit + * Authors: + * PB, Patrick Bos, Netherlands eScience Center, p.bos@esciencecenter.nl + * + * Copyright (c) 2016-2021, Netherlands eScience Center + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace RooFit { +namespace TestStatistics { + +std::unique_ptr build_constraints(RooAbsPdf *pdf, RooAbsData *data, + ConstrainedParameters constrained_parameters, ExternalConstraints external_constraints, + GlobalObservables global_observables, std::string global_observables_tag) +{ + // BEGIN CONSTRAINT COLLECTION; copied from RooAbsPdf::createNLL + + Bool_t doStripDisconnected = kFALSE; + // If no explicit list of parameters to be constrained is specified apply default algorithm + // All terms of RooProdPdfs that do not contain observables and share a parameters with one or more + // terms that do contain observables are added as constraints. +#ifndef NDEBUG + bool did_default_constraint_algo = false; + std::size_t N_default_constraints = 0; +#endif + if (constrained_parameters.set.getSize() == 0) { + std::unique_ptr default_constraints{pdf->getParameters(*data, kFALSE)}; + constrained_parameters.set.add(*default_constraints); + doStripDisconnected = kTRUE; +#ifndef NDEBUG + did_default_constraint_algo = true; + N_default_constraints = default_constraints->getSize(); +#endif + } +#ifndef NDEBUG + if (did_default_constraint_algo) { + assert(N_default_constraints == constrained_parameters.set.getSize()); + } +#endif + + // Collect internal and external constraint specifications + RooArgSet allConstraints; + + if (!global_observables_tag.empty()) { + std::cout << "DEBUG: global_observables_tag > 0" << std::endl; + if (global_observables.set.getSize() > 0) { + global_observables.set.removeAll(); + } + std::unique_ptr allVars {pdf->getVariables()}; + global_observables.set.add(*dynamic_cast(allVars->selectByAttrib(global_observables_tag.c_str(), kTRUE))); + oocoutI((TObject*)nullptr, Minimization) << "User-defined specification of global observables definition with tag named '" << global_observables_tag << "'" << std::endl; + } else if (global_observables.set.getSize() == 0) { + // neither global_observables nor global_observables_tag was given - try if a default tag is defined in the head node + const char* defGlobObsTag = pdf->getStringAttribute("DefaultGlobalObservablesTag"); + if (defGlobObsTag) { + oocoutI((TObject*)nullptr, Minimization) << "p.d.f. provides built-in specification of global observables definition with tag named '" << defGlobObsTag << "'" << std::endl; + std::unique_ptr allVars {pdf->getVariables()}; + global_observables.set.add(*dynamic_cast(allVars->selectByAttrib(defGlobObsTag, kTRUE))); + } + } + + // EGP: removed workspace (RooAbsPdf::_myws) based stuff for now; TODO: reconnect this class to workspaces + + if (constrained_parameters.set.getSize() > 0) { + std::unique_ptr constraints{pdf->getAllConstraints(*data->get(), constrained_parameters.set, doStripDisconnected)}; + allConstraints.add(*constraints); + } + if (external_constraints.set.getSize() > 0) { + allConstraints.add(external_constraints.set); + } + + std::unique_ptr subsidiary_likelihood; + // Include constraints, if any, in likelihood + if (allConstraints.getSize() > 0) { + + oocoutI((TObject*) nullptr, Minimization) << " Including the following contraint terms in minimization: " << allConstraints << std::endl; + if (global_observables.set.getSize() > 0) { + oocoutI((TObject*) nullptr, Minimization) << "The following global observables have been defined: " << global_observables.set << std::endl; + } + std::string name("likelihood for pdf "); + name += pdf->GetName(); + subsidiary_likelihood = std::make_unique(name, allConstraints, + (global_observables.set.getSize() > 0) ? global_observables.set : constrained_parameters.set); + } + + // END CONSTRAINT COLLECTION; copied from RooAbsPdf::createNLL + + return subsidiary_likelihood; +} + + +std::shared_ptr build_simultaneous_likelihood(RooAbsPdf *pdf, RooAbsData *data, RooAbsL::Extended extended, + ConstrainedParameters constrained_parameters, ExternalConstraints external_constraints, + GlobalObservables global_observables, std::string global_observables_tag) { + auto sim_pdf = dynamic_cast(pdf); + if (sim_pdf == nullptr) { + throw std::logic_error("Can only build RooSumL from RooSimultaneous pdf!"); + } + + // the rest of this function is an adaptation of RooAbsTestStatistic::initSimMode: + + RooAbsCategoryLValue &simCat = (RooAbsCategoryLValue &)sim_pdf->indexCat(); + + // FIXME: process_empty_data_sets was previously member function processEmptyDataSets(), which returned extended_ for RooSumL (previously named RooSimultaneousL). See RooAbsL and RooSumL/RooUnbinnedL implementations. I think this should work here, but be careful in other subclasses! + bool process_empty_data_sets = RooAbsL::is_extended(pdf, extended); + + TString simCatName(simCat.GetName()); + // Note: important not to use cloned dataset here (possible when this code is run in Roo[...]L ctor), use the + // original one (which is data_ in Roo[...]L ctors, but data here) + std::unique_ptr dsetList{data->split(simCat, process_empty_data_sets)}; + if (!dsetList) { + throw std::logic_error("build_simultaneous_likelihood ERROR, index category of simultaneous pdf is missing in dataset, aborting"); + } + + // Count number of used states + std::size_t N_components = 0; + + RooCatType *type; + std::unique_ptr catIter{simCat.typeIterator()}; + while ((type = (RooCatType *)catIter->Next())) { + // Retrieve the PDF for this simCat state + RooAbsPdf *component_pdf = sim_pdf->getPdf(type->GetName()); + auto dset = (RooAbsData *)dsetList->FindObject(type->GetName()); + + if (component_pdf && dset && (0. != dset->sumEntries() || process_empty_data_sets)) { + ++N_components; + } + } + + // Allocate arrays + std::vector> components; + components.reserve(N_components); + // _gofSplitMode.resize(N_components); // not used, Hybrid mode only, see below + + // Create array of regular fit contexts, containing subset of data and single fitCat PDF + catIter->Reset(); + std::size_t n = 0; + while ((type = (RooCatType *)catIter->Next())) { + // Retrieve the PDF for this simCat state + RooAbsPdf *component_pdf = sim_pdf->getPdf(type->GetName()); + auto dset = (RooAbsData *)dsetList->FindObject(type->GetName()); + + if (component_pdf && dset && (0. != dset->sumEntries() || process_empty_data_sets)) { + ooccoutI((TObject *)nullptr, Fitting) + << "RooSumL: creating slave calculator #" << n << " for state " << type->GetName() << " (" + << dset->numEntries() << " dataset entries)" << std::endl; + + // *** START HERE + // WVE HACK determine if we have a RooRealSumPdf and then treat it like a binned likelihood + RooAbsPdf *binnedPdf = 0; + Bool_t binnedL = kFALSE; + if (component_pdf->getAttribute("BinnedLikelihood") && + component_pdf->IsA()->InheritsFrom(RooRealSumPdf::Class())) { + // Simplest case: top-level of component is a RRSP + binnedPdf = component_pdf; + binnedL = kTRUE; + } else if (component_pdf->IsA()->InheritsFrom(RooProdPdf::Class())) { + // Default case: top-level pdf is a product of RRSP and other pdfs + RooFIter iter = ((RooProdPdf *)component_pdf)->pdfList().fwdIterator(); + RooAbsArg *component; + while ((component = iter.next())) { + if (component->getAttribute("BinnedLikelihood") && + component->IsA()->InheritsFrom(RooRealSumPdf::Class())) { + binnedPdf = (RooAbsPdf *)component; + binnedL = kTRUE; + } + if (component->getAttribute("MAIN_MEASUREMENT")) { + // not really a binned pdf, but this prevents a (potentially) long list of subsidiary measurements to + // be passed to the slave calculator + binnedPdf = (RooAbsPdf *)component; + } + } + } + // WVE END HACK + // Below here directly pass binnedPdf instead of PROD(binnedPdf,constraints) as constraints are evaluated + // elsewhere anyway and omitting them reduces model complexity and associated handling/cloning times + // if (_splitRange && rangeName) { + // _gofArray[n] = + // create(type->GetName(), type->GetName(), (binnedPdf ? *binnedPdf : *component_pdf), *dset, + // *projDeps, + // Form("%s_%s", rangeName, type->GetName()), addCoefRangeName, _nCPU * (_mpinterl ? -1 : + // 1), _mpinterl, _CPUAffinity, _verbose, _splitRange, binnedL); + // } else { + // _gofArray[n] = + // create(type->GetName(), type->GetName(), (binnedPdf ? *binnedPdf : *component_pdf), *dset, + // *projDeps, rangeName, + // addCoefRangeName, _nCPU, _mpinterl, _CPUAffinity, _verbose, _splitRange, binnedL); + if (binnedL) { + components.push_back(std::make_unique((binnedPdf ? binnedPdf : component_pdf), dset)); + } else { + components.push_back(std::make_unique((binnedPdf ? binnedPdf : component_pdf), dset)); + } + // } + components.back()->set_sim_count(N_components); + // *** END HERE + + // TODO: left out Hybrid mode for now, evaluate later whether to reinclude (also then change + // evaluate_partition) + // // Fill per-component split mode with Bulk Partition for now so that Auto will map to bulk-splitting + // of all components if (_mpinterl==RooFit::Hybrid) { + // if (dset->numEntries()<10) { + // //cout << "RAT::initSim("<< GetName() << ") MP mode is auto, setting split mode for component + // "<< n << " to SimComponents"<< endl ; _gofSplitMode[n] = RooFit::SimComponents; + // _gofArray[n]->_mpinterl = RooFit::SimComponents; + // } else { + // //cout << "RAT::initSim("<< GetName() << ") MP mode is auto, setting split mode for component + // "<< n << " to BulkPartition"<< endl ; _gofSplitMode[n] = RooFit::BulkPartition; + // _gofArray[n]->_mpinterl = RooFit::BulkPartition; + // } + // } + // + // Servers may have been redirected between instantiation and (deferred) initialization + + std::unique_ptr actualParams{binnedPdf ? binnedPdf->getParameters(dset) + : component_pdf->getParameters(dset)}; + std::unique_ptr selTargetParams{(RooArgSet *)pdf->getParameters(*data)->selectCommon(*actualParams)}; + + // TODO: I don't think we have to redirect servers, because our classes make no use of those, but we should + // make sure. Do we need to reset the parameter set instead? + // components_.back()->recursiveRedirectServers(*selTargetParams); + assert(selTargetParams->equals(*components.back()->getParameters())); + + ++n; + } else { + if ((!dset || (0. != dset->sumEntries() && !process_empty_data_sets)) && component_pdf) { + ooccoutD((TObject *)nullptr, Fitting) << "RooSumL: state " << type->GetName() + << " has no data entries, no slave calculator created" << std::endl; + } + } + } + oocoutI((TObject *)nullptr, Fitting) << "RooSumL: created " << n << " slave calculators." << std::endl; + + std::unique_ptr subsidiary = build_constraints(pdf, data, constrained_parameters, external_constraints, global_observables, global_observables_tag); + if (subsidiary) { + components.push_back(std::move(subsidiary)); + } + + return std::make_shared(pdf, data, std::move(components), extended); +} + +// delegating convenience overloads +std::shared_ptr build_simultaneous_likelihood(RooAbsPdf* pdf, RooAbsData* data, ConstrainedParameters constrained_parameters) +{ + return build_simultaneous_likelihood(pdf, data, RooAbsL::Extended::Auto, constrained_parameters); +} +std::shared_ptr build_simultaneous_likelihood(RooAbsPdf* pdf, RooAbsData* data, ExternalConstraints external_constraints) +{ + return build_simultaneous_likelihood(pdf, data, RooAbsL::Extended::Auto, {}, external_constraints); +} +std::shared_ptr build_simultaneous_likelihood(RooAbsPdf* pdf, RooAbsData* data, GlobalObservables global_observables) +{ + return build_simultaneous_likelihood(pdf, data, RooAbsL::Extended::Auto, {}, {}, global_observables); +} +std::shared_ptr build_simultaneous_likelihood(RooAbsPdf* pdf, RooAbsData* data, std::string global_observables_tag) +{ + return build_simultaneous_likelihood(pdf, data, RooAbsL::Extended::Auto, {}, {}, {}, global_observables_tag); +} +std::shared_ptr build_simultaneous_likelihood(RooAbsPdf* pdf, RooAbsData* data, ConstrainedParameters constrained_parameters, GlobalObservables global_observables) +{ + return build_simultaneous_likelihood(pdf, data, RooAbsL::Extended::Auto, constrained_parameters, {}, global_observables); +} + + +std::shared_ptr build_unbinned_constrained_likelihood(RooAbsPdf *pdf, RooAbsData *data, RooAbsL::Extended extended, + ConstrainedParameters constrained_parameters, ExternalConstraints external_constraints, + GlobalObservables global_observables, std::string global_observables_tag) { + std::vector> components; + components.reserve(2); + components.push_back(std::make_unique(pdf, data, extended)); + components.push_back(build_constraints(pdf, data, constrained_parameters, external_constraints, global_observables, global_observables_tag)); + return std::make_shared(pdf, data, std::move(components), extended); +} + +// delegating convenience overloads +std::shared_ptr build_unbinned_constrained_likelihood(RooAbsPdf* pdf, RooAbsData* data, ConstrainedParameters constrained_parameters) +{ + return build_unbinned_constrained_likelihood(pdf, data, RooAbsL::Extended::Auto, constrained_parameters); +} +std::shared_ptr build_unbinned_constrained_likelihood(RooAbsPdf* pdf, RooAbsData* data, ExternalConstraints external_constraints) +{ + return build_unbinned_constrained_likelihood(pdf, data, RooAbsL::Extended::Auto, {}, external_constraints); +} +std::shared_ptr build_unbinned_constrained_likelihood(RooAbsPdf* pdf, RooAbsData* data, GlobalObservables global_observables) +{ + return build_unbinned_constrained_likelihood(pdf, data, RooAbsL::Extended::Auto, {}, {}, global_observables); +} +std::shared_ptr build_unbinned_constrained_likelihood(RooAbsPdf* pdf, RooAbsData* data, std::string global_observables_tag) +{ + return build_unbinned_constrained_likelihood(pdf, data, RooAbsL::Extended::Auto, {}, {}, {}, global_observables_tag); +} + +} +} + diff --git a/roofit/roofitcore/src/TestStatistics/optimization.cxx b/roofit/roofitcore/src/TestStatistics/optimization.cxx new file mode 100644 index 0000000000000..fe4fac4f75add --- /dev/null +++ b/roofit/roofitcore/src/TestStatistics/optimization.cxx @@ -0,0 +1,163 @@ +/* + * Project: RooFit + * Authors: + * PB, Patrick Bos, Netherlands eScience Center, p.bos@esciencecenter.nl + * + * Copyright (c) 2016-2021, Netherlands eScience Center + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ + +#include + +#include +#include // complete type for dynamic cast +#include +#include +#include + +namespace RooFit { +namespace TestStatistics { + +RooArgSet ConstantTermsOptimizer::requiredExtraObservables() +{ + // TODO: the RooAbsOptTestStatistics::requiredExtraObservables() call this code was copied + // from was overloaded for RooXYChi2Var only; implement different options when necessary + return RooArgSet(); +} + +void ConstantTermsOptimizer::enable_constant_terms_optimization(RooAbsReal *function, RooArgSet *norm_set, RooAbsData *dataset, + bool applyTrackingOpt) +{ + // Trigger create of all object caches now in nodes that have deferred object creation + // so that cache contents can be processed immediately + function->getVal(norm_set); + + // Apply tracking optimization here. Default strategy is to track components + // of RooAddPdfs and RooRealSumPdfs. If these components are a RooProdPdf + // or a RooProduct respectively, track the components of these products instead + // of the product term + RooArgSet trackNodes; + + // Add safety check here - applyTrackingOpt will only be applied if present + // dataset is constructed in terms of a RooVectorDataStore + if (applyTrackingOpt) { + if (!dynamic_cast(dataset->store())) { + oocoutW((TObject *)nullptr, Optimization) + << "enable_constant_terms_optimization(function: " << function->GetName() + << ", dataset: " << dataset->GetName() + << ") WARNING Cache-and-track optimization (Optimize level 2) is only available for datasets" + << " implemented in terms of RooVectorDataStore - ignoring this option for current dataset" << std::endl; + applyTrackingOpt = kFALSE; + } + } + + if (applyTrackingOpt) { + RooArgSet branches; + function->branchNodeServerList(&branches); + RooFIter iter = branches.fwdIterator(); + RooAbsArg *arg; + while ((arg = iter.next())) { + arg->setCacheAndTrackHints(trackNodes); + } + // Do not set CacheAndTrack on constant expressions + auto constNodes = (RooArgSet *)trackNodes.selectByAttrib("Constant", kTRUE); + trackNodes.remove(*constNodes); + delete constNodes; + + // Set CacheAndTrack flag on all remaining nodes + trackNodes.setAttribAll("CacheAndTrack", kTRUE); + } + + // Find all nodes that depend exclusively on constant parameters + RooArgSet cached_nodes; + + function->findConstantNodes(*dataset->get(), cached_nodes); + + // Cache constant nodes with dataset - also cache entries corresponding to zero-weights in data when using + // BinnedLikelihood + // NOTE: we pass nullptr as cache-owner here, because we don't use the cacheOwner() anywhere in TestStatistics + // TODO: make sure this (nullptr) is always correct + dataset->cacheArgs(nullptr, cached_nodes, norm_set, !function->getAttribute("BinnedLikelihood")); + + // Put all cached nodes in AClean value caching mode so that their evaluate() is never called + TIterator *cIter = cached_nodes.createIterator(); + RooAbsArg *cacheArg; + while ((cacheArg = (RooAbsArg *)cIter->Next())) { + cacheArg->setOperMode(RooAbsArg::AClean); + } + delete cIter; + + RooArgSet *constNodes = (RooArgSet *)cached_nodes.selectByAttrib("ConstantExpressionCached", kTRUE); + RooArgSet actualTrackNodes(cached_nodes); + actualTrackNodes.remove(*constNodes); + if (constNodes->getSize() > 0) { + if (constNodes->getSize() < 20) { + oocoutI((TObject*)nullptr, Minimization) + << " The following expressions have been identified as constant and will be precalculated and cached: " + << *constNodes << std::endl; + } else { + oocoutI((TObject*)nullptr, Minimization) << " A total of " << constNodes->getSize() + << " expressions have been identified as constant and will be precalculated and cached." + << std::endl; + } + } + if (actualTrackNodes.getSize() > 0) { + if (actualTrackNodes.getSize() < 20) { + oocoutI((TObject*)nullptr, Minimization) << " The following expressions will be evaluated in cache-and-track mode: " + << actualTrackNodes << std::endl; + } else { + oocoutI((TObject*)nullptr, Minimization) << " A total of " << constNodes->getSize() + << " expressions will be evaluated in cache-and-track-mode." << std::endl; + } + } + delete constNodes; + + // Disable reading of observables that are no longer used + dataset->optimizeReadingWithCaching(*function, cached_nodes, requiredExtraObservables()); + + // _optimized = kTRUE; +} + +void ConstantTermsOptimizer::disable_constant_terms_optimization(RooAbsReal *function, RooArgSet *norm_set, RooArgSet *observables, + RooAbsData *dataset) +{ + // Delete the cache + dataset->resetCache(); + + // Reactivate all tree branches + dataset->setArgStatus(*dataset->get(), kTRUE); + + // Reset all nodes to ADirty + optimize_caching(function, norm_set, observables, dataset); + + // Disable propagation of dirty state flags for observables + dataset->setDirtyProp(kFALSE); + + // _cachedNodes.removeAll(); + + // _optimized = kFALSE; +} + +void ConstantTermsOptimizer::optimize_caching(RooAbsReal *function, RooArgSet *norm_set, RooArgSet *observables, RooAbsData *dataset) +{ + // Trigger create of all object caches now in nodes that have deferred object creation + // so that cache contents can be processed immediately + function->getVal(norm_set); + + // Set value caching mode for all nodes that depend on any of the observables to ADirty + function->optimizeCacheMode(*observables); + + // Disable propagation of dirty state flags for observables + dataset->setDirtyProp(kFALSE); + + // Disable reading of observables that are not used + dataset->optimizeReadingWithCaching(*function, RooArgSet(), requiredExtraObservables()) ; +} + +} // namespace TestStatistics +} // namespace RooFit \ No newline at end of file diff --git a/roofit/roofitcore/src/TestStatistics/optional_parameter_types.cxx b/roofit/roofitcore/src/TestStatistics/optional_parameter_types.cxx new file mode 100644 index 0000000000000..260fd3e51d511 --- /dev/null +++ b/roofit/roofitcore/src/TestStatistics/optional_parameter_types.cxx @@ -0,0 +1,27 @@ +/* + * Project: RooFit + * Authors: + * PB, Patrick Bos, Netherlands eScience Center, p.bos@esciencecenter.nl + * + * Copyright (c) 2016-2021, Netherlands eScience Center + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ + +#include + +namespace RooFit { +namespace TestStatistics { + +ConstrainedParameters::ConstrainedParameters(const RooArgSet ¶meters) : set(parameters) {} + +ExternalConstraints::ExternalConstraints(const RooArgSet &constraints) : set(constraints) {} + +GlobalObservables::GlobalObservables(const RooArgSet &observables) : set(observables) {} + +} +} \ No newline at end of file diff --git a/roofit/roofitcore/test/CMakeLists.txt b/roofit/roofitcore/test/CMakeLists.txt index 9c1d8f6c68fe5..fad3c966739ae 100644 --- a/roofit/roofitcore/test/CMakeLists.txt +++ b/roofit/roofitcore/test/CMakeLists.txt @@ -4,9 +4,13 @@ # For the licensing terms see $ROOTSYS/LICENSE. # For the list of contributors see $ROOTSYS/README/CREDITS. +# @author Patrick Bos, NL eScience Center, 2018 # @author Danilo Piparo CERN, 2018 ROOT_ADD_GTEST(simple simple.cxx LIBRARIES RooFitCore) +ROOT_ADD_GTEST(testRooGradMinimizer RooGradMinimizer.cxx LIBRARIES RooFitCore) +ROOT_ADD_GTEST(testBidirMMapPipe testBidirMMapPipe.cxx LIBRARIES RooFitCore m) +ROOT_ADD_GTEST(testRooCacheManager testRooCacheManager.cxx LIBRARIES RooFitCore) ROOT_ADD_GTEST(testWorkspace testWorkspace.cxx LIBRARIES RooFitCore RooFit RooStats) if(NOT MSVC OR win_broken_tests) ROOT_ADD_GTEST(testRooDataHist testRooDataHist.cxx LIBRARIES RooFitCore @@ -37,3 +41,10 @@ endif() ROOT_ADD_GTEST(testRooProductPdf testRooProductPdf.cxx LIBRARIES RooFitCore) ROOT_ADD_GTEST(testNaNPacker testNaNPacker.cxx LIBRARIES RooFitCore) +ROOT_ADD_GTEST(testMPFEnll MultiProcess/MPFEnll.cpp LIBRARIES RooFitCore) + +#find_package(ZeroMQ REQUIRED) + +ROOT_ADD_GTEST(testLikelihoodGradientJob TestStatistics/testLikelihoodGradientJob.cpp LIBRARIES RooFitMultiProcess RooFitCore RooFit RooStats m ${ZeroMQ_LIBRARY}) +ROOT_ADD_GTEST(testLikelihoodSerial TestStatistics/testLikelihoodSerial.cxx LIBRARIES RooFitCore RooFit m) +ROOT_ADD_GTEST(testRooRealL TestStatistics/RooRealL.cpp LIBRARIES RooFitCore RooFit) diff --git a/roofit/roofitcore/test/MultiProcess/MPFEnll.cpp b/roofit/roofitcore/test/MultiProcess/MPFEnll.cpp new file mode 100644 index 0000000000000..a010af10c32d1 --- /dev/null +++ b/roofit/roofitcore/test/MultiProcess/MPFEnll.cpp @@ -0,0 +1,79 @@ +/***************************************************************************** + * Project: RooFit * + * Package: RooFitCore * + * @(#)root/roofitcore:$Id$ + * Authors: * + * PB, Patrick Bos, NL eScience Center, p.bos@esciencecenter.nl * + * IP, Inti Pelupessy, NL eScience Center, i.pelupessy@esciencecenter.nl * + * * + * Redistribution and use in source and binary forms, * + * with or without modification, are permitted according to the terms * + * listed in LICENSE (http://roofit.sourceforge.net/license.txt) * + *****************************************************************************/ + +#include "gtest/gtest.h" +#include "../test_lib.h" + +TEST(MPFEnll, getVal) { + RooMsgService::instance().setGlobalKillBelow(RooFit::ERROR); + + // check whether MPFE produces the same results when using different NumCPU or mode. + // this defines the baseline against which we compare our MP NLL + RooRandom::randomGenerator()->SetSeed(3); + // N.B.: it passes on seeds 1 and 2 + + RooWorkspace w; + w.factory("Gaussian::g(x[-5,5],mu[0,-3,3],sigma[1])"); + auto x = w.var("x"); + RooAbsPdf *pdf = w.pdf("g"); + RooRealVar *mu = w.var("mu"); + RooDataSet *data = pdf->generate(RooArgSet(*x), 10000); + double results[4]; + + RooArgSet values = RooArgSet(*mu, *pdf); + + auto nll1 = pdf->createNLL(*data, RooFit::NumCPU(1)); + results[0] = nll1->getVal(); + delete nll1; + auto nll2 = pdf->createNLL(*data, RooFit::NumCPU(2)); + results[1] = nll2->getVal(); + delete nll2; + auto nll3 = pdf->createNLL(*data, RooFit::NumCPU(3)); + results[2] = nll3->getVal(); + delete nll3; + auto nll4 = pdf->createNLL(*data, RooFit::NumCPU(4)); + results[3] = nll4->getVal(); + delete nll4; + auto nll1b = pdf->createNLL(*data, RooFit::NumCPU(1)); + auto result1b = nll1b->getVal(); + delete nll1b; + auto nll2b = pdf->createNLL(*data, RooFit::NumCPU(2)); + auto result2b = nll2b->getVal(); + delete nll2b; + + auto nll1_mpfe = pdf->createNLL(*data, RooFit::NumCPU(-1)); + auto result1_mpfe = nll1_mpfe->getVal(); + delete nll1_mpfe; + + auto nll1_interleave = pdf->createNLL(*data, RooFit::NumCPU(1, 1)); + auto result_interleave1 = nll1_interleave->getVal(); + delete nll1_interleave; + auto nll2_interleave = pdf->createNLL(*data, RooFit::NumCPU(2, 1)); + auto result_interleave2 = nll2_interleave->getVal(); + delete nll2_interleave; + auto nll3_interleave = pdf->createNLL(*data, RooFit::NumCPU(3, 1)); + auto result_interleave3 = nll3_interleave->getVal(); + delete nll3_interleave; + + EXPECT_DOUBLE_EQ(Hex(results[0]), Hex(results[1])); + EXPECT_DOUBLE_EQ(Hex(results[0]), Hex(results[2])); + EXPECT_DOUBLE_EQ(Hex(results[0]), Hex(results[3])); + + EXPECT_DOUBLE_EQ(Hex(results[0]), Hex(result1b)); + EXPECT_DOUBLE_EQ(Hex(results[1]), Hex(result2b)); + EXPECT_DOUBLE_EQ(Hex(results[0]), Hex(result1_mpfe)); + + EXPECT_DOUBLE_EQ(Hex(results[0]), Hex(result_interleave1)); + EXPECT_DOUBLE_EQ(Hex(results[0]), Hex(result_interleave2)); + EXPECT_DOUBLE_EQ(Hex(results[0]), Hex(result_interleave3)); +} diff --git a/roofit/roofitcore/test/MultiProcess/VectorGradMinimizer.cpp b/roofit/roofitcore/test/MultiProcess/VectorGradMinimizer.cpp new file mode 100644 index 0000000000000..5078620f5858c --- /dev/null +++ b/roofit/roofitcore/test/MultiProcess/VectorGradMinimizer.cpp @@ -0,0 +1,306 @@ +/***************************************************************************** + * Project: RooFit * + * Package: RooFitCore * + * @(#)root/roofitcore:$Id$ + * Authors: * + * PB, Patrick Bos, NL eScience Center, p.bos@esciencecenter.nl * + * IP, Inti Pelupessy, NL eScience Center, i.pelupessy@esciencecenter.nl * + * VC, Vince Croft, DIANA / NYU, vincent.croft@cern.ch * + * * + * Redistribution and use in source and binary forms, * + * with or without modification, are permitted according to the terms * + * listed in LICENSE (http://roofit.sourceforge.net/license.txt) * + *****************************************************************************/ + +#include // runtime_error + +#include +#include +#include + +#include + +#include + +#include "gtest/gtest.h" +#include "../test_lib.h" // generate_1D_gaussian_pdf_nll + +class MPGradMinimizer : public ::testing::TestWithParam> {}; + +TEST_P(MPGradMinimizer, Gaussian1D) { + // do a minimization, but now using GradMinimizer and its MP version + + RooMsgService::instance().setGlobalKillBelow(RooFit::ERROR); + + // parameters + std::size_t NWorkers = std::get<0>(GetParam()); +// RooFit::MultiProcess::NLLVarTask mp_task_mode = std::get<1>(GetParam()); + std::size_t seed = std::get<1>(GetParam()); + + RooRandom::randomGenerator()->SetSeed(seed); + + RooWorkspace w = RooWorkspace(); + + std::unique_ptr nll; + std::unique_ptr values; + std::tie(nll, values) = generate_1D_gaussian_pdf_nll(w, 10000); + // when c++17 support arrives, change to this: +// auto [nll, values] = generate_1D_gaussian_pdf_nll(w, 10000); + RooRealVar *mu = w.var("mu"); + + RooArgSet *savedValues = dynamic_cast(values->snapshot()); + if (savedValues == nullptr) { + throw std::runtime_error("params->snapshot() cannot be casted to RooArgSet!"); + } + + // -------- + + RooMinimizer m0(*nll); + m0.setMinimizerType("Minuit2"); + + m0.setStrategy(0); + m0.setPrintLevel(-1); + + m0.migrad(); + + RooFitResult *m0result = m0.lastMinuitFit(); + double minNll0 = m0result->minNll(); + double edm0 = m0result->edm(); + double mu0 = mu->getVal(); + double muerr0 = mu->getError(); + + *values = *savedValues; + + RooFit::MultiProcess::GradMinimizer m1(*nll, NWorkers); + m1.setMinimizerType("Minuit2"); + + m1.setStrategy(0); + m1.setPrintLevel(-1); + + m1.migrad(); + + RooFitResult *m1result = m1.lastMinuitFit(); + double minNll1 = m1result->minNll(); + double edm1 = m1result->edm(); + double mu1 = mu->getVal(); + double muerr1 = mu->getError(); + + EXPECT_EQ(minNll0, minNll1); + EXPECT_EQ(mu0, mu1); + EXPECT_EQ(muerr0, muerr1); + EXPECT_EQ(edm0, edm1); + + m1.cleanup(); // necessary in tests to clean up global _theFitter +} + + +TEST(MPGradMinimizerDEBUGGING, DISABLED_Gaussian1DNominal) { +// std::size_t NWorkers = 1; + std::size_t seed = 1; + + RooRandom::randomGenerator()->SetSeed(seed); + + RooWorkspace w = RooWorkspace(); + + std::unique_ptr nll; + std::unique_ptr _; + std::tie(nll, _) = generate_1D_gaussian_pdf_nll(w, 10000); + + RooMinimizer m0(*nll); + m0.setMinimizerType("Minuit2"); + + m0.setStrategy(0); + m0.setPrintLevel(2); + + m0.migrad(); + m0.cleanup(); // necessary in tests to clean up global _theFitter +} + +TEST(MPGradMinimizerDEBUGGING, DISABLED_Gaussian1DMultiProcess) { + std::size_t NWorkers = 1; + std::size_t seed = 1; + + RooRandom::randomGenerator()->SetSeed(seed); + + RooWorkspace w = RooWorkspace(); + + std::unique_ptr nll; + std::unique_ptr values; + std::tie(nll, values) = generate_1D_gaussian_pdf_nll(w, 10000); + + RooFit::MultiProcess::GradMinimizer m1(*nll, NWorkers); + m1.setMinimizerType("Minuit2"); + + m1.setStrategy(0); + m1.setPrintLevel(2); + + m1.migrad(); + m1.cleanup(); // necessary in tests to clean up global _theFitter +} + + +TEST(MPGradMinimizer, RepeatMigrad) { + // do multiple minimizations using MP::GradMinimizer, testing breakdown and rebuild + + RooMsgService::instance().setGlobalKillBelow(RooFit::ERROR); + + // parameters + std::size_t NWorkers = 2; + std::size_t seed = 5; + + RooRandom::randomGenerator()->SetSeed(seed); + + RooWorkspace w = RooWorkspace(); + + std::unique_ptr nll; + std::unique_ptr values; + std::tie(nll, values) = generate_1D_gaussian_pdf_nll(w, 10000); + // when c++17 support arrives, change to this: +// auto [nll, values] = generate_1D_gaussian_pdf_nll(w, 10000); + + RooArgSet *savedValues = dynamic_cast(values->snapshot()); + if (savedValues == nullptr) { + throw std::runtime_error("params->snapshot() cannot be casted to RooArgSet!"); + } + + // -------- + + RooFit::MultiProcess::GradMinimizer m1(*nll, NWorkers); + + m1.setMinimizerType("Minuit2"); + + m1.setStrategy(0); + m1.setPrintLevel(-1); + + std::cout << "... running migrad first time ..." << std::endl; + m1.migrad(); + + std::cout << "... terminating TaskManager instance ..." << std::endl; + RooFit::MultiProcess::TaskManager::instance()->terminate(); + + *values = *savedValues; + + std::cout << "... running migrad second time ..." << std::endl; + m1.migrad(); + + std::cout << "... cleaning up minimizer ..." << std::endl; + m1.cleanup(); // necessary in tests to clean up global _theFitter +} + + +TEST_P(MPGradMinimizer, GaussianND) { + // do a minimization, but now using GradMinimizer and its MP version + + RooMsgService::instance().setGlobalKillBelow(RooFit::ERROR); + + // parameters + std::size_t NWorkers = std::get<0>(GetParam()); +// RooFit::MultiProcess::NLLVarTask mp_task_mode = std::get<1>(GetParam()); + std::size_t seed = std::get<1>(GetParam()); + + unsigned int N = 4; + + RooRandom::randomGenerator()->SetSeed(seed); + + RooWorkspace w = RooWorkspace(); + + std::unique_ptr nll; + std::unique_ptr values; + std::tie(nll, values) = generate_ND_gaussian_pdf_nll(w, N, 1000); + // when c++17 support arrives, change to this: +// auto [nll, all_values] = generate_ND_gaussian_pdf_nll(w, N, 1000); + + RooArgSet *savedValues = dynamic_cast(values->snapshot()); + if (savedValues == nullptr) { + throw std::runtime_error("params->snapshot() cannot be casted to RooArgSet!"); + } + + RooWallTimer wtimer; + + // -------- + + RooMinimizer m0(*nll); + m0.setMinimizerType("Minuit2"); + + m0.setStrategy(0); + m0.setPrintLevel(-1); + + wtimer.start(); + m0.migrad(); + wtimer.stop(); + std::cout << "\nwall clock time RooGradMinimizer.migrad (NWorkers = " + << NWorkers << ", seed = " << seed << "): " + << wtimer.timing_s() << " s" << std::endl; + + RooFitResult *m0result = m0.lastMinuitFit(); + double minNll0 = m0result->minNll(); + double edm0 = m0result->edm(); + double mean0[N]; + double std0[N]; + for (unsigned ix = 0; ix < N; ++ix) { + { + std::ostringstream os; + os << "m" << ix; + mean0[ix] = dynamic_cast(w.arg(os.str().c_str()))->getVal(); + } + { + std::ostringstream os; + os << "s" << ix; + std0[ix] = dynamic_cast(w.arg(os.str().c_str()))->getVal(); + } + } + + // -------- + + *values = *savedValues; + + // -------- + + RooFit::MultiProcess::GradMinimizer m1(*nll, NWorkers); + m1.setMinimizerType("Minuit2"); + + m1.setStrategy(0); + m1.setPrintLevel(-1); + + wtimer.start(); + m1.migrad(); + wtimer.stop(); + std::cout << "wall clock time MP::GradMinimizer.migrad (NWorkers = " + << NWorkers << ", seed = " << seed << "): " + << wtimer.timing_s() << " s\n" << std::endl; + + RooFitResult *m1result = m1.lastMinuitFit(); + double minNll1 = m1result->minNll(); + double edm1 = m1result->edm(); + double mean1[N]; + double std1[N]; + for (unsigned ix = 0; ix < N; ++ix) { + { + std::ostringstream os; + os << "m" << ix; + mean1[ix] = dynamic_cast(w.arg(os.str().c_str()))->getVal(); + } + { + std::ostringstream os; + os << "s" << ix; + std1[ix] = dynamic_cast(w.arg(os.str().c_str()))->getVal(); + } + } + + EXPECT_EQ(minNll0, minNll1); + EXPECT_EQ(edm0, edm1); + + for (unsigned ix = 0; ix < N; ++ix) { + EXPECT_EQ(mean0[ix], mean1[ix]); + EXPECT_EQ(std0[ix], std1[ix]); + } + + m1.cleanup(); // necessary in tests to clean up global _theFitter +} + + + +INSTANTIATE_TEST_SUITE_P(NworkersSeed, + MPGradMinimizer, + ::testing::Combine(::testing::Values(1,2,3), // number of workers + ::testing::Values(2,3))); // random seed diff --git a/roofit/roofitcore/test/MultiProcess/VectorNLL.cpp b/roofit/roofitcore/test/MultiProcess/VectorNLL.cpp new file mode 100644 index 0000000000000..c3f9f4ad99441 --- /dev/null +++ b/roofit/roofitcore/test/MultiProcess/VectorNLL.cpp @@ -0,0 +1,395 @@ +/***************************************************************************** + * Project: RooFit * + * Package: RooFitCore * + * @(#)root/roofitcore:$Id$ + * Authors: * + * PB, Patrick Bos, NL eScience Center, p.bos@esciencecenter.nl * + * IP, Inti Pelupessy, NL eScience Center, i.pelupessy@esciencecenter.nl * + * VC, Vince Croft, DIANA / NYU, vincent.croft@cern.ch * + * * + * Redistribution and use in source and binary forms, * + * with or without modification, are permitted according to the terms * + * listed in LICENSE (http://roofit.sourceforge.net/license.txt) * + *****************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gtest/gtest.h" +#include "../test_lib.h" // Hex + + +class MultiProcessVectorNLL : public ::testing::TestWithParam> {}; + + +TEST_P(MultiProcessVectorNLL, getVal) { + // Real-life test: calculate a NLL using event-based parallelization. This + // should replicate RooRealMPFE results. + RooRandom::randomGenerator()->SetSeed(std::get<2>(GetParam())); + RooWorkspace w; + w.factory("Gaussian::g(x[-5,5],mu[0,-3,3],sigma[1])"); + auto x = w.var("x"); + RooAbsPdf *pdf = w.pdf("g"); + RooDataSet *data = pdf->generate(RooArgSet(*x), 10000); + auto nll = pdf->createNLL(*data); + + auto nominal_result = nll->getVal(); + + std::size_t NumCPU = std::get<0>(GetParam()); + RooFit::MultiProcess::NLLVarTask mp_task_mode = std::get<1>(GetParam()); + + RooFit::MultiProcess::NLLVar nll_mp(NumCPU, mp_task_mode, *dynamic_cast(nll)); + + auto mp_result = nll_mp.getVal(); + + EXPECT_DOUBLE_EQ(Hex(nominal_result), Hex(mp_result)); + if (HasFailure()) { + std::cout << "failed test had parameters NumCPU = " << NumCPU << ", task_mode = " << mp_task_mode << ", seed = " << std::get<2>(GetParam()) << std::endl; + } +} + +void check_NLL_type(RooAbsReal *nll) { + if (dynamic_cast(nll) != nullptr) { + std::cout << "the NLL object is a RooAddition*..." << std::endl; + bool has_rooconstraintsum = false; + RooFIter nll_component_iter = nll->getComponents()->fwdIterator(); + RooAbsArg *nll_component; + while ((nll_component = nll_component_iter.next())) { + if (nll_component->IsA() == RooConstraintSum::Class()) { + has_rooconstraintsum = true; + break; + } else if (nll_component->IsA() != RooNLLVar::Class() && nll_component->IsA() != RooAddition::Class()) { + std::cerr << "... containing an unexpected component class: " << nll_component->ClassName() << std::endl; + throw std::runtime_error("RooAddition* type NLL object contains unexpected component class!"); + } + } + if (has_rooconstraintsum) { + std::cout << "...containing a RooConstraintSum component: " << nll_component->GetName() << std::endl; + } else { + std::cout << "...containing only RooNLLVar components." << std::endl; + } + } else if (dynamic_cast(nll) != nullptr) { + std::cout << "the NLL object is a RooNLLVar*" << std::endl; + } +} + + +void count_NLL_components(RooAbsReal *nll) { + if (dynamic_cast(nll) != nullptr) { + std::cout << "the NLL object is a RooAddition*..." << std::endl; + unsigned nll_component_count = 0; + RooFIter nll_component_iter = nll->getComponents()->fwdIterator(); + RooAbsArg *nll_component; + while ((nll_component = nll_component_iter.next())) { + if (nll_component->IsA() != RooNLLVar::Class()) { + ++nll_component_count; + } + } + std::cout << "...containing " << nll_component_count << " RooNLLVar components." << std::endl; + } else if (dynamic_cast(nll) != nullptr) { + std::cout << "the NLL object is a RooNLLVar*" << std::endl; + } +} + + +TEST_P(MultiProcessVectorNLL, getValRooAddition) { + RooMsgService::instance().setGlobalKillBelow(RooFit::ERROR); + + std::size_t NumCPU = std::get<0>(GetParam()); + + RooRandom::randomGenerator()->SetSeed(std::get<2>(GetParam())); + + RooWorkspace w; + w.factory("Gaussian::g(x[-10,10],mu[0,-3,3],sigma[1])"); + + RooRealVar *x = w.var("x"); + x->setRange("x_range",-3,0); + x->setRange("another_range",1,7); + + RooAbsPdf *pdf = w.pdf("g"); + RooDataSet *data = pdf->generate(*x, 10000); + + RooAbsReal *nll = pdf->createNLL(*data, RooFit::NumCPU(NumCPU), + RooFit::Range("x_range"), RooFit::Range("another_range")); + + check_NLL_type(nll); + count_NLL_components(nll); + + delete nll; + delete data; +} + + +TEST_P(MultiProcessVectorNLL, getValRooConstraintSumAddition) { + // modified from https://github.com/roofit-dev/rootbench/blob/43d12f33e8dac7af7d587b53a2804ddf6717e92f/root/roofit/roofit/RooFitASUM.cxx#L417 + + RooMsgService::instance().setGlobalKillBelow(RooFit::ERROR); + + int cpu = 1; + int bins = 10000; + + RooRealVar x("x","x",0,bins); + x.setBins(bins); +// Parameters + RooRealVar a0("a0","a0",0); + RooRealVar a1("a1","a1",1,0,2); + RooRealVar a2("a2","a2",0); + + RooPolynomial p0("p0","p0",x); + RooPolynomial p1("p1","p1",x,RooArgList(a0,a1,a2),0); + + RooDataHist *dh_bkg = p0.generateBinned(x, 1000000000); + RooDataHist *dh_sig = p1.generateBinned(x, 100000000); + dh_bkg->SetName("dh_bkg"); + dh_sig->SetName("dh_sig"); + + a1.setVal(2); + RooDataHist *dh_sig_up = p1.generateBinned(x, 1100000000); + dh_sig_up->SetName("dh_sig_up"); + a1.setVal(.5); + RooDataHist *dh_sig_down = p1.generateBinned(x, 900000000); + dh_sig_down->SetName("dh_sig_down"); + + RooWorkspace w = RooWorkspace("w"); + w.import(x); + w.import(*dh_sig); + w.import(*dh_bkg); + w.import(*dh_sig_up); + w.import(*dh_sig_down); + w.factory("HistFunc::hf_sig(x,dh_sig)"); + w.factory("HistFunc::hf_bkg(x,dh_bkg)"); + w.factory("HistFunc::hf_sig_up(x,dh_sig_up)"); + w.factory("HistFunc::hf_sig_down(x,dh_sig_down)"); + w.factory("PiecewiseInterpolation::pi_sig(hf_sig,hf_sig_down,hf_sig_up,alpha[-5,5])"); + + w.factory("ASUM::model(mu[1,0,5]*pi_sig,nu[1]*hf_bkg)"); + w.factory("Gaussian::constraint(alpha,0,1)"); + w.factory("PROD::model2(model,constraint)"); + + RooAbsPdf *pdf = w.pdf("model2"); + + RooDataHist *data = pdf->generateBinned(x, 1100000); + RooAbsReal *nll = pdf->createNLL(*data, RooFit::NumCPU(cpu, 0)); + + check_NLL_type(nll); + count_NLL_components(nll); + + delete nll; +} + +TEST_P(MultiProcessVectorNLL, setVal) { + // calculate the NLL twice with different parameters + + RooRandom::randomGenerator()->SetSeed(std::get<2>(GetParam())); + RooWorkspace w; + w.factory("Gaussian::g(x[-5,5],mu[0,-3,3],sigma[1])"); + auto x = w.var("x"); + RooAbsPdf *pdf = w.pdf("g"); + RooDataSet *data = pdf->generate(RooArgSet(*x), 10000); + auto nll = pdf->createNLL(*data); + + std::size_t NumCPU = std::get<0>(GetParam()); + RooFit::MultiProcess::NLLVarTask mp_task_mode = std::get<1>(GetParam()); + + RooFit::MultiProcess::NLLVar nll_mp(NumCPU, mp_task_mode, *dynamic_cast(nll)); + + // calculate first results + nll->getVal(); + nll_mp.getVal(); + + w.var("mu")->setVal(2); + + // calculate second results after parameter change + auto nominal_result2 = nll->getVal(); + auto mp_result2 = nll_mp.getVal(); + + EXPECT_DOUBLE_EQ(Hex(nominal_result2), Hex(mp_result2)); + if (HasFailure()) { + std::cout << "failed test had parameters NumCPU = " << NumCPU << ", task_mode = " << mp_task_mode << ", seed = " << std::get<2>(GetParam()) << std::endl; + } +} + + +INSTANTIATE_TEST_SUITE_P(NworkersModeSeed, + MultiProcessVectorNLL, + ::testing::Combine(::testing::Values(1,2,3), // number of workers + ::testing::Values(RooFit::MultiProcess::NLLVarTask::all_events, + RooFit::MultiProcess::NLLVarTask::single_event, + RooFit::MultiProcess::NLLVarTask::bulk_partition, + RooFit::MultiProcess::NLLVarTask::interleave), + ::testing::Values(2,3))); // random seed + + + +class NLLMultiProcessVsMPFE : public ::testing::TestWithParam> {}; + +TEST_P(NLLMultiProcessVsMPFE, getVal) { + // Compare our MP NLL to actual RooRealMPFE results using the same strategies. + + RooMsgService::instance().setGlobalKillBelow(RooFit::ERROR); + + // parameters + std::size_t NumCPU = std::get<0>(GetParam()); + RooFit::MultiProcess::NLLVarTask mp_task_mode = std::get<1>(GetParam()); + std::size_t seed = std::get<2>(GetParam()); + + RooRandom::randomGenerator()->SetSeed(seed); + + RooWorkspace w; + w.factory("Gaussian::g(x[-5,5],mu[0,-3,3],sigma[1])"); + auto x = w.var("x"); + RooAbsPdf *pdf = w.pdf("g"); + RooDataSet *data = pdf->generate(RooArgSet(*x), 10000); + + int mpfe_task_mode = 0; + if (mp_task_mode == RooFit::MultiProcess::NLLVarTask::interleave) { + mpfe_task_mode = 1; + } + + auto nll_mpfe = pdf->createNLL(*data, RooFit::NumCPU(NumCPU, mpfe_task_mode)); + + auto mpfe_result = nll_mpfe->getVal(); + + // create new nll without MPFE for creating nll_mp (an MPFE-enabled RooNLLVar interferes with MP::Vector's bipe use) + auto nll = pdf->createNLL(*data); + RooFit::MultiProcess::NLLVar nll_mp(NumCPU, mp_task_mode, *dynamic_cast(nll)); + + auto mp_result = nll_mp.getVal(); + + EXPECT_EQ(Hex(mpfe_result), Hex(mp_result)); + if (HasFailure()) { + std::cout << "failed test had parameters NumCPU = " << NumCPU << ", task_mode = " << mp_task_mode << ", seed = " << seed << std::endl; + } +} + + +TEST_P(NLLMultiProcessVsMPFE, minimize) { + // do a minimization (e.g. like in GradMinimizer_Gaussian1D test) + + RooMsgService::instance().setGlobalKillBelow(RooFit::ERROR); + + // TODO: see whether it performs adequately + + // parameters + std::size_t NumCPU = std::get<0>(GetParam()); + RooFit::MultiProcess::NLLVarTask mp_task_mode = std::get<1>(GetParam()); + std::size_t seed = std::get<2>(GetParam()); + + RooRandom::randomGenerator()->SetSeed(seed); + + RooWorkspace w = RooWorkspace(); + + w.factory("Gaussian::g(x[-5,5],mu[0,-3,3],sigma[1])"); + auto x = w.var("x"); + RooAbsPdf *pdf = w.pdf("g"); + RooRealVar *mu = w.var("mu"); + + RooDataSet *data = pdf->generate(RooArgSet(*x), 10000); + mu->setVal(-2.9); + + int mpfe_task_mode; + switch (mp_task_mode) { + case RooFit::MultiProcess::NLLVarTask::bulk_partition: { + mpfe_task_mode = 0; + break; + } + case RooFit::MultiProcess::NLLVarTask::interleave: { + mpfe_task_mode = 1; + break; + } + default: { + throw std::logic_error("can only compare bulk_partition and interleave strategies to MPFE NLL"); + } + } + + auto nll_mpfe = pdf->createNLL(*data, RooFit::NumCPU(NumCPU, mpfe_task_mode)); + auto nll_nominal = pdf->createNLL(*data); + RooFit::MultiProcess::NLLVar nll_mp(NumCPU, mp_task_mode, *dynamic_cast(nll_nominal)); + + // save initial values for the start of all minimizations + RooArgSet values = RooArgSet(*mu, *pdf); + + RooArgSet *savedValues = dynamic_cast(values.snapshot()); + if (savedValues == nullptr) { + throw std::runtime_error("params->snapshot() cannot be casted to RooArgSet!"); + } + + // -------- + + RooMinimizer m0(*nll_mpfe); + m0.setMinimizerType("Minuit2"); + + m0.setStrategy(0); + m0.setPrintLevel(-1); + + m0.migrad(); + + RooFitResult *m0result = m0.lastMinuitFit(); + double minNll0 = m0result->minNll(); + double edm0 = m0result->edm(); + double mu0 = mu->getVal(); + double muerr0 = mu->getError(); + + values = *savedValues; + + RooMinimizer m1(nll_mp); + m1.setMinimizerType("Minuit2"); + + m1.setStrategy(0); + m1.setPrintLevel(-1); + + m1.migrad(); + + RooFitResult *m1result = m1.lastMinuitFit(); + double minNll1 = m1result->minNll(); + double edm1 = m1result->edm(); + double mu1 = mu->getVal(); + double muerr1 = mu->getError(); + + EXPECT_EQ(minNll0, minNll1); + EXPECT_EQ(mu0, mu1); + EXPECT_EQ(muerr0, muerr1); + EXPECT_EQ(edm0, edm1); + + m1.cleanup(); // necessary in tests to clean up global _theFitter +} + + +INSTANTIATE_TEST_SUITE_P(NworkersModeSeed, + NLLMultiProcessVsMPFE, + ::testing::Combine(::testing::Values(2,3), // number of workers + ::testing::Values(RooFit::MultiProcess::NLLVarTask::bulk_partition, + RooFit::MultiProcess::NLLVarTask::interleave), + ::testing::Values(2,3))); // random seed + + +TEST(NLLMultiProcessVsMPFE, throwOnCreatingMPwithMPFE) { + // Using an MPFE-enabled NLL should throw when creating an MP NLL. + RooWorkspace w; + w.factory("Gaussian::g(x[-5,5],mu[0,-3,3],sigma[1])"); + auto x = w.var("x"); + RooAbsPdf *pdf = w.pdf("g"); + RooDataSet *data = pdf->generate(RooArgSet(*x), 10); + + RooAbsReal* nll_mpfe = pdf->createNLL(*data, RooFit::NumCPU(2)); + + EXPECT_THROW({ + RooFit::MultiProcess::NLLVar nll_mp(2, RooFit::MultiProcess::NLLVarTask::bulk_partition, *dynamic_cast(nll_mpfe)); + }, std::logic_error); + + delete nll_mpfe; +} diff --git a/roofit/roofitcore/test/MultiProcess_Vector.cxx b/roofit/roofitcore/test/MultiProcess_Vector.cxx new file mode 100644 index 0000000000000..b9891b9f87643 --- /dev/null +++ b/roofit/roofitcore/test/MultiProcess_Vector.cxx @@ -0,0 +1,186 @@ +/***************************************************************************** + * Project: RooFit * + * Package: RooFitCore * + * @(#)root/roofitcore:$Id$ + * Authors: * + * PB, Patrick Bos, NL eScience Center, p.bos@esciencecenter.nl * + * IP, Inti Pelupessy, NL eScience Center, i.pelupessy@esciencecenter.nl * + * VC, Vince Croft, DIANA / NYU, vincent.croft@cern.ch * + * * + * Redistribution and use in source and binary forms, * + * with or without modification, are permitted according to the terms * + * listed in LICENSE (http://roofit.sourceforge.net/license.txt) * + *****************************************************************************/ + +// MultiProcess back-end +//#include +#include +#include +#include + +#include // std::_Exit +#include +#include +#include +//#include +#include // accumulate +#include // for google test Combine in parameterized test + +#include +#include + +#include "gtest/gtest.h" +#include "test_lib.h" + +class xSquaredPlusBVectorSerial { + public: + xSquaredPlusBVectorSerial(double b, std::vector x_init) : + _b("b", "b", b), + x(std::move(x_init)), + result(x.size()) {} + + virtual void evaluate() { + // call evaluate_task for each task + for (std::size_t ix = 0; ix < x.size(); ++ix) { + result[ix] = std::pow(x[ix], 2) + _b.getVal(); + } + } + + std::vector get_result() { + evaluate(); + return result; + } + + protected: + RooRealVar _b; + std::vector x; + std::vector result; +}; + + +using RooFit::MultiProcess::JobTask; + +class xSquaredPlusBVectorParallel : public RooFit::MultiProcess::Vector { + public: + xSquaredPlusBVectorParallel(std::size_t NumCPU, double b_init, std::vector x_init) : + RooFit::MultiProcess::Vector(NumCPU, b_init, + x_init) // NumCPU stands for everything that defines the parallelization behaviour (number of cpu, strategy, affinity etc) + {} + + void evaluate() override { + if (get_manager()->is_master()) { + // master fills queue with tasks + for (std::size_t task_id = 0; task_id < x.size(); ++task_id) { + JobTask job_task(id, task_id); + get_manager()->to_queue(job_task); + } + waiting_for_queued_tasks = true; + + // wait for task results back from workers to master + gather_worker_results(); + + // put task results in desired container (same as used in serial class) + for (std::size_t task_id = 0; task_id < x.size(); ++task_id) { + result[task_id] = results[task_id]; + } + } + } + + + private: + void evaluate_task(std::size_t task) override { + assert(get_manager()->is_worker()); + result[task] = std::pow(x[task], 2) + _b.getVal(); + } + + double get_task_result(std::size_t task) override { + assert(get_manager()->is_worker()); + return result[task]; + } + +}; + +class MultiProcessVectorSingleJob : public ::testing::TestWithParam { + // You can implement all the usual fixture class members here. + // To access the test parameter, call GetParam() from class + // TestWithParam. +}; + + + + +TEST_P(MultiProcessVectorSingleJob, getResult) { + // Simple test case: calculate x^2 + b, where x is a vector. This case does + // both a simple calculation (squaring the input vector x) and represents + // handling of state updates in b. + std::vector x{0, 1, 2, 3}; + double b_initial = 3.; + + // start serial test + + xSquaredPlusBVectorSerial x_sq_plus_b(b_initial, x); + + auto y = x_sq_plus_b.get_result(); + std::vector y_expected{3, 4, 7, 12}; + + EXPECT_EQ(Hex(y[0]), Hex(y_expected[0])); + EXPECT_EQ(Hex(y[1]), Hex(y_expected[1])); + EXPECT_EQ(Hex(y[2]), Hex(y_expected[2])); + EXPECT_EQ(Hex(y[3]), Hex(y_expected[3])); + + std::size_t NumCPU = GetParam(); + + // start parallel test + + xSquaredPlusBVectorParallel x_sq_plus_b_parallel(NumCPU, b_initial, x); + + auto y_parallel = x_sq_plus_b_parallel.get_result(); + + EXPECT_EQ(Hex(y_parallel[0]), Hex(y_expected[0])); + EXPECT_EQ(Hex(y_parallel[1]), Hex(y_expected[1])); + EXPECT_EQ(Hex(y_parallel[2]), Hex(y_expected[2])); + EXPECT_EQ(Hex(y_parallel[3]), Hex(y_expected[3])); +} + + +INSTANTIATE_TEST_SUITE_P(NumberOfWorkerProcesses, + MultiProcessVectorSingleJob, + ::testing::Values(1,2,3)); + + +class MultiProcessVectorMultiJob : public ::testing::TestWithParam {}; + +TEST_P(MultiProcessVectorMultiJob, getResult) { + // Simple test case: calculate x^2 + b, where x is a vector. This case does + // both a simple calculation (squaring the input vector x) and represents + // handling of state updates in b. + std::vector x{0, 1, 2, 3}; + double b_initial = 3.; + + std::vector y_expected{3, 4, 7, 12}; + + std::size_t NumCPU = GetParam(); + + // define jobs + xSquaredPlusBVectorParallel x_sq_plus_b_parallel(NumCPU, b_initial, x); + xSquaredPlusBVectorParallel x_sq_plus_b_parallel2(NumCPU, b_initial + 1, x); + + // do stuff + auto y_parallel = x_sq_plus_b_parallel.get_result(); + auto y_parallel2 = x_sq_plus_b_parallel2.get_result(); + + EXPECT_EQ(Hex(y_parallel[0]), Hex(y_expected[0])); + EXPECT_EQ(Hex(y_parallel[1]), Hex(y_expected[1])); + EXPECT_EQ(Hex(y_parallel[2]), Hex(y_expected[2])); + EXPECT_EQ(Hex(y_parallel[3]), Hex(y_expected[3])); + + EXPECT_EQ(Hex(y_parallel2[0]), Hex(y_expected[0] + 1)); + EXPECT_EQ(Hex(y_parallel2[1]), Hex(y_expected[1] + 1)); + EXPECT_EQ(Hex(y_parallel2[2]), Hex(y_expected[2] + 1)); + EXPECT_EQ(Hex(y_parallel2[3]), Hex(y_expected[3] + 1)); +} + + +INSTANTIATE_TEST_SUITE_P(NumberOfWorkerProcesses, + MultiProcessVectorMultiJob, + ::testing::Values(2,1,3)); diff --git a/roofit/roofitcore/test/RooGradMinimizer.cxx b/roofit/roofitcore/test/RooGradMinimizer.cxx new file mode 100644 index 0000000000000..dca02b3d7f1b0 --- /dev/null +++ b/roofit/roofitcore/test/RooGradMinimizer.cxx @@ -0,0 +1,914 @@ +/***************************************************************************** + * Project: RooFit * + * Package: RooFitCore * + * @(#)root/roofitcore:$Id$ + * Authors: * + * PB, Patrick Bos, NL eScience Center, p.bos@esciencecenter.nl * + * * + * Redistribution and use in source and binary forms, * + * with or without modification, are permitted according to the terms * + * listed in LICENSE (http://roofit.sourceforge.net/license.txt) * + *****************************************************************************/ + +//#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "TFile.h" // for loading the workspace file +#include // remove redundant workspace files + +#include + +#include "gtest/gtest.h" +#include "test_lib.h" + +#include +#include // RooFit::ERROR + +TEST(GradMinimizer, Gaussian1D) +{ + RooMsgService::instance().setGlobalKillBelow(RooFit::ERROR); + + // produce the same random stuff every time + RooRandom::randomGenerator()->SetSeed(1); + + for (int i = 0; i < 10; ++i) { + RooWorkspace w = RooWorkspace(); + + std::unique_ptr nll; + std::unique_ptr values; + RooAbsPdf *pdf; + RooDataSet *data; + std::tie(nll, pdf, data, values) = generate_1D_gaussian_pdf_nll(w, 10000); + // when c++17 support arrives, change to this: + // auto [nll, values] = generate_1D_gaussian_pdf_nll(w, 10000); + RooRealVar *mu = w.var("mu"); + + RooArgSet *savedValues = dynamic_cast(values->snapshot()); + if (savedValues == nullptr) { + throw std::runtime_error("params->snapshot() cannot be casted to RooArgSet!"); + } + + // -------- + + RooWallTimer wtimer; + + // -------- + + RooMinimizer m0(*nll); + m0.setMinimizerType("Minuit2"); + + m0.setStrategy(0); + m0.setPrintLevel(-1); + + wtimer.start(); + m0.migrad(); + wtimer.stop(); + + RooFitResult *m0result = m0.lastMinuitFit(); + double minNll0 = m0result->minNll(); + double edm0 = m0result->edm(); + double mu0 = mu->getVal(); + double muerr0 = mu->getError(); + + *values = *savedValues; + + std::unique_ptr m1 = RooMinimizer::create(*nll); + m1->setMinimizerType("Minuit2"); + + m1->setStrategy(0); + m1->setPrintLevel(-1); + + wtimer.start(); + m1->migrad(); + wtimer.stop(); + + RooFitResult *m1result = m1->lastMinuitFit(); + double minNll1 = m1result->minNll(); + double edm1 = m1result->edm(); + double mu1 = mu->getVal(); + double muerr1 = mu->getError(); + + EXPECT_EQ(minNll0, minNll1); + EXPECT_EQ(mu0, mu1); + EXPECT_EQ(muerr0, muerr1); + EXPECT_EQ(edm0, edm1); + } +} + +TEST(GradMinimizerDebugging, DISABLED_Gaussian1DNominal) +{ + // produce the same random stuff every time + RooRandom::randomGenerator()->SetSeed(1); + + RooWorkspace w = RooWorkspace(); + + std::unique_ptr nll; + std::unique_ptr _; + RooAbsPdf *pdf; + RooDataSet *data; + std::tie(nll, pdf, data, _) = generate_1D_gaussian_pdf_nll(w, 10000); + // when c++17 support arrives, change to this: + // auto [nll, _] = generate_1D_gaussian_pdf_nll(w, 10000); + + RooMinimizer m0(*nll); + m0.setMinimizerType("Minuit2"); + + m0.setStrategy(0); + m0.setPrintLevel(100); + m0.setVerbose(kTRUE); + + m0.migrad(); +} + +TEST(GradMinimizerDebugging, DISABLED_Gaussian1DGradMinimizer) +{ + // produce the same random stuff every time + RooRandom::randomGenerator()->SetSeed(1); + + RooWorkspace w = RooWorkspace(); + + std::unique_ptr nll; + std::unique_ptr _; + RooAbsPdf *pdf; + RooDataSet *data; + std::tie(nll, pdf, data, _) = generate_1D_gaussian_pdf_nll(w, 10000); + // when c++17 support arrives, change to this: + // auto [nll, _] = generate_1D_gaussian_pdf_nll(w, 10000); + + std::unique_ptr m1 = RooMinimizer::create(*nll); + m1->setMinimizerType("Minuit2"); + + m1->setStrategy(0); + m1->setPrintLevel(100); + m1->setVerbose(kTRUE); + + m1->migrad(); +} + +/* +TEST(GradMinimizer, Gaussian2DVarToConst) { + RooMsgService::instance().setGlobalKillBelow(RooFit::ERROR); + + // produce the same random stuff every time + RooRandom::randomGenerator()->SetSeed(1); + + RooWorkspace w = RooWorkspace(); + + w.factory("Gaussian::g1(x[-5,5],mu1[0,-3,3],sigma1[1])"); + RooRealVar *mu1 = w.var("mu1"); + + w.factory("Gaussian::g2(x[-5,5],mu2[4,-3,12],sigma2[2.5])"); + RooRealVar *mu2 = w.var("mu2"); + +// RooArgSet pdf_set = w.allPdfs(); + + // create event counts for all pdfs +// RooArgSet count_set; + + // ... for the gaussians + RooRealVar N1("Nsig1", "#signal events component 1", 5000, 0., 20000); + w.import(N1); + RooRealVar N2("Nsig2", "#signal events component 2", 5000, 0., 20000); + w.import(N2); +// count_set.add(*w.arg("Nsig1")); +// count_set.add(*w.arg("Nsig2")); + + RooAddPdf sum("sum", "2 gaussians", w.allPdfs(), RooArgSet(N1, N2)); + + auto x = w.var("x"); + RooDataSet *data = sum.generate(RooArgSet(*x), 10000); + mu1->setVal(-2.9); + mu2->setVal(1); + + auto nll = sum.createNLL(*data); + + // save initial values for the start of all minimizations + RooArgSet values = RooArgSet(RooArgSet(*mu1, *mu2, N1, N2, sum, *nll), w.allPdfs(), "all values"); + + RooArgSet *savedValues = dynamic_cast(values.snapshot()); + if (savedValues == nullptr) { + throw std::runtime_error("params->snapshot() cannot be casted to RooArgSet!"); + } + + // -------- + + RooWallTimer wtimer; + + // -------- + + RooMinimizer m0(*nll); + m0.setMinimizerType("Minuit2"); + + m0.setStrategy(0); + m0.setPrintLevel(-1); + + wtimer.start(); + m0.migrad(); + wtimer.stop(); + + values = *savedValues; + mu1->setConstant(kTRUE); + + wtimer.start(); + m0.migrad(); + wtimer.stop(); + + RooFitResult *m0result = m0.lastMinuitFit(); + double minNll0 = m0result->minNll(); + double edm0 = m0result->edm(); + double mu1_0 = mu1->getVal(); + double muerr1_0 = mu1->getError(); + double mu2_0 = mu2->getVal(); + double muerr2_0 = mu2->getError(); + + values = *savedValues; + mu1->setConstant(kFALSE); + + RooMinimizer m1(*nll); + m1.setMinimizerType("Minuit2"); + + m1.setStrategy(0); + m1.setPrintLevel(-1); + + wtimer.start(); + m1.migrad(); + wtimer.stop(); + + values = *savedValues; + mu1->setConstant(kTRUE); + + wtimer.start(); + m1.migrad(); + wtimer.stop(); + + RooFitResult *m1result = m1.lastMinuitFit(); + double minNll1 = m1result->minNll(); + double edm1 = m1result->edm(); + double mu1_1 = mu1->getVal(); + double muerr1_1 = mu1->getError(); + double mu2_1 = mu2->getVal(); + double muerr2_1 = mu2->getError(); + + EXPECT_EQ(minNll0, minNll1); + EXPECT_EQ(mu1_0, mu1_1); + EXPECT_EQ(muerr1_0, muerr1_1); + EXPECT_EQ(mu2_0, mu2_1); + EXPECT_EQ(muerr2_0, muerr2_1); + EXPECT_EQ(edm0, edm1); +} + +TEST(GradMinimizer, Gaussian2DConstToVar) { + RooMsgService::instance().setGlobalKillBelow(RooFit::ERROR); + + // produce the same random stuff every time + RooRandom::randomGenerator()->SetSeed(1); + + RooWorkspace w = RooWorkspace(); + + w.factory("Gaussian::g1(x[-5,5],mu1[0,-3,3],sigma1[1])"); + RooRealVar *mu1 = w.var("mu1"); + + w.factory("Gaussian::g2(x[-5,5],mu2[4,-3,12],sigma2[2.5])"); + RooRealVar *mu2 = w.var("mu2"); + + RooArgSet pdf_set = w.allPdfs(); + + // create event counts for all pdfs + RooArgSet count_set; + + // ... for the gaussians + RooRealVar N1("Nsig1", "#signal events component 1", 5000, 0., 20000); + w.import(N1); + RooRealVar N2("Nsig2", "#signal events component 2", 5000, 0., 20000); + w.import(N2); + count_set.add(*w.arg("Nsig1")); + count_set.add(*w.arg("Nsig2")); + + RooAddPdf sum("sum", "2 gaussians", pdf_set, count_set); + + auto x = w.var("x"); + RooDataSet *data = sum.generate(RooArgSet(*x), 10000); + mu1->setVal(-2.9); + mu2->setVal(1); + + auto nll = sum.createNLL(*data); + + // save initial values for the start of all minimizations + RooArgSet values = RooArgSet(*mu1, *mu2, sum, *nll); + + RooArgSet *savedValues = dynamic_cast(values.snapshot()); + if (savedValues == nullptr) { + throw std::runtime_error("params->snapshot() cannot be casted to RooArgSet!"); + } + + // -------- + + RooWallTimer wtimer; + + // -------- + + RooMinimizer m0(*nll); + m0.setMinimizerType("Minuit2"); + + m0.setStrategy(0); + m0.setPrintLevel(-1); + + mu1->setConstant(kTRUE); + + wtimer.start(); + m0.migrad(); + wtimer.stop(); + + values = *savedValues; + mu1->setConstant(kFALSE); + + wtimer.start(); + m0.migrad(); + wtimer.stop(); + + RooFitResult *m0result = m0.lastMinuitFit(); + double minNll0 = m0result->minNll(); + double edm0 = m0result->edm(); + double mu1_0 = mu1->getVal(); + double muerr1_0 = mu1->getError(); + double mu2_0 = mu2->getVal(); + double muerr2_0 = mu2->getError(); + + values = *savedValues; + mu1->setConstant(kTRUE); + + RooMinimizer m1(*nll); + m1.setMinimizerType("Minuit2"); + + m1.setStrategy(0); + m1.setPrintLevel(-1); + + wtimer.start(); + m1.migrad(); + wtimer.stop(); + + values = *savedValues; + mu1->setConstant(kFALSE); + + wtimer.start(); + m1.migrad(); + wtimer.stop(); + + RooFitResult *m1result = m1.lastMinuitFit(); + double minNll1 = m1result->minNll(); + double edm1 = m1result->edm(); + double mu1_1 = mu1->getVal(); + double muerr1_1 = mu1->getError(); + double mu2_1 = mu2->getVal(); + double muerr2_1 = mu2->getError(); + + EXPECT_EQ(minNll0, minNll1); + EXPECT_EQ(mu1_0, mu1_1); + EXPECT_EQ(muerr1_0, muerr1_1); + EXPECT_EQ(mu2_0, mu2_1); + EXPECT_EQ(muerr2_0, muerr2_1); + EXPECT_EQ(edm0, edm1); +} +*/ + +TEST(GradMinimizer, GaussianND) +{ + // test RooMinimizer class with simple N-dimensional pdf + + RooMsgService::instance().setGlobalKillBelow(RooFit::ERROR); + + unsigned N = 5; + unsigned N_events = 1000; + // produce the same random stuff every time + RooRandom::randomGenerator()->SetSeed(1); + + RooWorkspace w("w", kFALSE); + + std::unique_ptr nll; + std::unique_ptr all_values; + RooAbsPdf *pdf; + RooDataSet *data; + std::tie(nll, pdf, data, all_values) = generate_ND_gaussian_pdf_nll(w, N, N_events); + // when c++17 support arrives, change to this: + // auto [nll, all_values] = generate_ND_gaussian_pdf_nll(w, N, N_events); + + // save initial values for the start of all minimizations + RooArgSet *savedValues = dynamic_cast(all_values->snapshot()); + if (savedValues == nullptr) { + throw std::runtime_error("params->snapshot() cannot be casted to RooArgSet!"); + } + + // -------- + + RooWallTimer wtimer; + + // -------- + + RooMinimizer m0(*(nll.get())); + m0.setMinimizerType("Minuit2"); + + m0.setStrategy(0); + m0.setPrintLevel(-1); + + wtimer.start(); + m0.migrad(); + wtimer.stop(); + + RooFitResult *m0result = m0.lastMinuitFit(); + double minNll0 = m0result->minNll(); + double edm0 = m0result->edm(); + double mean0[N]; + double std0[N]; + for (unsigned ix = 0; ix < N; ++ix) { + { + std::ostringstream os; + os << "m" << ix; + mean0[ix] = dynamic_cast(w.arg(os.str().c_str()))->getVal(); + } + { + std::ostringstream os; + os << "s" << ix; + std0[ix] = dynamic_cast(w.arg(os.str().c_str()))->getVal(); + } + } + + // -------- + + *all_values = *savedValues; + + // -------- + + std::unique_ptr m1 = RooMinimizer::create(*(nll.get())); + + m1->setStrategy(0); + m1->setPrintLevel(-1); + + wtimer.start(); + m1->migrad(); + wtimer.stop(); + + RooFitResult *m1result = m1->lastMinuitFit(); + double minNll1 = m1result->minNll(); + double edm1 = m1result->edm(); + double mean1[N]; + double std1[N]; + for (unsigned ix = 0; ix < N; ++ix) { + { + std::ostringstream os; + os << "m" << ix; + mean1[ix] = dynamic_cast(w.arg(os.str().c_str()))->getVal(); + } + { + std::ostringstream os; + os << "s" << ix; + std1[ix] = dynamic_cast(w.arg(os.str().c_str()))->getVal(); + } + } + + EXPECT_EQ(minNll0, minNll1); + EXPECT_EQ(edm0, edm1); + + for (unsigned ix = 0; ix < N; ++ix) { + EXPECT_EQ(mean0[ix], mean1[ix]); + EXPECT_EQ(std0[ix], std1[ix]); + } +} + +TEST(GradMinimizerReverse, GaussianND) +{ + // test RooMinimizer class with simple N-dimensional pdf + + RooMsgService::instance().setGlobalKillBelow(RooFit::ERROR); + + unsigned N = 5; + unsigned N_events = 1000; + // produce the same random stuff every time + RooRandom::randomGenerator()->SetSeed(1); + + RooWorkspace w("w", kFALSE); + + std::unique_ptr nll; + std::unique_ptr all_values; + RooAbsPdf *pdf; + RooDataSet *data; + std::tie(nll, pdf, data, all_values) = generate_ND_gaussian_pdf_nll(w, N, N_events); + // when c++17 support arrives, change to this: + // auto [nll, all_values] = generate_ND_gaussian_pdf_nll(w, N, N_events); + + // save initial values for the start of all minimizations + RooArgSet *savedValues = dynamic_cast(all_values->snapshot()); + if (savedValues == nullptr) { + throw std::runtime_error("params->snapshot() cannot be casted to RooArgSet!"); + } + + // -------- + + RooWallTimer wtimer; + + // -------- + + std::unique_ptr m0 = RooMinimizer::create(*nll); + + m0->setMinimizerType("Minuit2"); + + m0->setStrategy(0); + m0->setPrintLevel(-1); + + wtimer.start(); + m0->migrad(); + wtimer.stop(); + + RooFitResult *m0result = m0->lastMinuitFit(); + double minNll0 = m0result->minNll(); + double edm0 = m0result->edm(); + double mean0[N]; + double std0[N]; + for (unsigned ix = 0; ix < N; ++ix) { + { + std::ostringstream os; + os << "m" << ix; + mean0[ix] = dynamic_cast(w.arg(os.str().c_str()))->getVal(); + } + { + std::ostringstream os; + os << "s" << ix; + std0[ix] = dynamic_cast(w.arg(os.str().c_str()))->getVal(); + } + } + + // -------- + + *all_values = *savedValues; + + // -------- + + RooMinimizer m1(*nll); + m1.setMinimizerType("Minuit2"); + + m1.setStrategy(0); + m1.setPrintLevel(-1); + + wtimer.start(); + m1.migrad(); + wtimer.stop(); + + RooFitResult *m1result = m1.lastMinuitFit(); + double minNll1 = m1result->minNll(); + double edm1 = m1result->edm(); + double mean1[N]; + double std1[N]; + for (unsigned ix = 0; ix < N; ++ix) { + { + std::ostringstream os; + os << "m" << ix; + mean1[ix] = dynamic_cast(w.arg(os.str().c_str()))->getVal(); + } + { + std::ostringstream os; + os << "s" << ix; + std1[ix] = dynamic_cast(w.arg(os.str().c_str()))->getVal(); + } + } + + EXPECT_EQ(minNll0, minNll1); + EXPECT_EQ(edm0, edm1); + + for (unsigned ix = 0; ix < N; ++ix) { + EXPECT_EQ(mean0[ix], mean1[ix]); + EXPECT_EQ(std0[ix], std1[ix]); + } +} + +TEST(GradMinimizer, BranchingPDF) +{ + // test RooMinimizer class with an N-dimensional pdf that forms a tree of + // pdfs, where one subpdf is the parameter of a higher level pdf + + RooMsgService::instance().setGlobalKillBelow(RooFit::ERROR); + + int N_events = 1000; + // produce the same random stuff every time + RooRandom::randomGenerator()->SetSeed(1); + + RooWorkspace w("w", kFALSE); + + // 3rd level + w.factory("Gamma::ga0_0_1(k0_0_1[3,2,10],u[1,20],1,0)"); // leaf pdf + // Gamma(mu,N+1,1,0) ~ Pois(N,mu), so this is a "continuous Poissonian" + + // 2nd level that will be linked to from 3rd level + w.factory("Gamma::ga1_0(k1_0[4,2,10],z[1,20],1,0)"); // leaf pdf + + // rest of 3rd level + w.factory("Gaussian::g0_0_0(v[-10,10],m0_0_0[0.6,-10,10],ga1_0)"); // two branch pdf, one up a level to different 1st + // level branch + + // rest of 2nd level + w.factory("Gaussian::g0_0(g0_0_0,m0_0[6,-10,10],ga0_0_1)"); // branch pdf + + // 1st level + w.factory("Gaussian::g0(x[-10,10],g0_0,s0[3,0.1,10])"); // branch pdf + w.factory("Gaussian::g1(y[-10,10],m1[-2,-10,10],ga1_0)"); // branch pdf + RooArgSet level1_pdfs; + level1_pdfs.add(*w.arg("g0")); + level1_pdfs.add(*w.arg("g1")); + + // event counts for 1st level pdfs + RooRealVar N_g0("N_g0", "#events g0", N_events / 10, 0., 10 * N_events); + RooRealVar N_g1("N_g1", "#events g1", N_events / 10, 0., 10 * N_events); + w.import(N_g0); + w.import(N_g1); + // gather in count_set + RooArgSet level1_counts; + level1_counts.add(N_g0); + level1_counts.add(N_g1); + + // finally, sum the top level pdfs + RooAddPdf sum("sum", "gaussian tree", level1_pdfs, level1_counts); + + // gather observables + RooArgSet obs_set; + for (auto obs : {"x", "y", "z", "u", "v"}) { + obs_set.add(*w.arg(obs)); + } + + // --- Generate a toyMC sample from composite PDF --- + RooDataSet *data = sum.generate(obs_set, N_events); + + auto nll = sum.createNLL(*data); + + // gather all values of parameters, observables, pdfs and nll here for easy + // saving and restoring + RooArgSet some_values = RooArgSet(obs_set, w.allPdfs(), "some_values"); + RooArgSet most_values = RooArgSet(some_values, level1_counts, "most_values"); + most_values.add(*nll); + most_values.add(sum); + + RooArgSet *param_set = nll->getParameters(obs_set); + + RooArgSet all_values = RooArgSet(most_values, *param_set, "all_values"); + + // set parameter values randomly so that they actually need to do some fitting + auto it = all_values.fwdIterator(); + while (RooRealVar *val = dynamic_cast(it.next())) { + val->setVal(RooRandom::randomGenerator()->Uniform(val->getMin(), val->getMax())); + } + + // save initial values for the start of all minimizations + RooArgSet *savedValues = dynamic_cast(all_values.snapshot()); + if (savedValues == nullptr) { + throw std::runtime_error("params->snapshot() cannot be casted to RooArgSet!"); + } + + // write the workspace state to file, but remove it again if everything was + // successful (at the end of the test) + w.import(*data); + w.import(sum); + w.writeToFile("failed_testRooGradMinimizer_BranchingPDF_workspace.root"); + + // -------- + + RooWallTimer wtimer; + + // -------- + + RooMinimizer m0(*nll); + m0.setMinimizerType("Minuit2"); + + m0.setStrategy(0); + m0.setPrintLevel(-1); + + wtimer.start(); + m0.migrad(); + wtimer.stop(); + + RooFitResult *m0result = m0.lastMinuitFit(); + double minNll0 = m0result->minNll(); + double edm0 = m0result->edm(); + + double N_g0__0 = N_g0.getVal(); + double N_g1__0 = N_g1.getVal(); + double k0_0_1__0 = dynamic_cast(w.arg("k0_0_1"))->getVal(); + double k1_0__0 = dynamic_cast(w.arg("k1_0"))->getVal(); + double m0_0__0 = dynamic_cast(w.arg("m0_0"))->getVal(); + double m0_0_0__0 = dynamic_cast(w.arg("m0_0_0"))->getVal(); + double m1__0 = dynamic_cast(w.arg("m1"))->getVal(); + double s0__0 = dynamic_cast(w.arg("s0"))->getVal(); + + // -------- + + all_values = *savedValues; + + // -------- + + std::unique_ptr m1 = RooMinimizer::create(*nll); + + m1->setStrategy(0); + m1->setPrintLevel(-1); + + wtimer.start(); + m1->migrad(); + wtimer.stop(); + + RooFitResult *m1result = m1->lastMinuitFit(); + double minNll1 = m1result->minNll(); + double edm1 = m1result->edm(); + + EXPECT_EQ(minNll0, minNll1); + EXPECT_EQ(edm0, edm1); + + double N_g0__1 = N_g0.getVal(); + double N_g1__1 = N_g1.getVal(); + double k0_0_1__1 = dynamic_cast(w.arg("k0_0_1"))->getVal(); + double k1_0__1 = dynamic_cast(w.arg("k1_0"))->getVal(); + double m0_0__1 = dynamic_cast(w.arg("m0_0"))->getVal(); + double m0_0_0__1 = dynamic_cast(w.arg("m0_0_0"))->getVal(); + double m1__1 = dynamic_cast(w.arg("m1"))->getVal(); + double s0__1 = dynamic_cast(w.arg("s0"))->getVal(); + + EXPECT_EQ(N_g0__0, N_g0__1); + EXPECT_EQ(N_g1__0, N_g1__1); + EXPECT_EQ(k0_0_1__0, k0_0_1__1); + EXPECT_EQ(k1_0__0, k1_0__1); + EXPECT_EQ(m0_0__0, m0_0__1); + EXPECT_EQ(m0_0_0__0, m0_0_0__1); + EXPECT_EQ(m1__0, m1__1); + EXPECT_EQ(s0__0, s0__1); + + // N_g0 = 494.514 +/- 18.8621 (limited) + // N_g1 = 505.817 +/- 24.6705 (limited) + // k0_0_1 = 2.96883 +/- 0.00561152 (limited) + // k1_0 = 4.12068 +/- 0.0565994 (limited) + // m0_0 = 8.09563 +/- 1.30395 (limited) + // m0_0_0 = 0.411472 +/- 0.183239 (limited) + // m1 = -1.99988 +/- 0.00194089 (limited) + // s0 = 3.04623 +/- 0.0982477 (limited) + + if (!HasFailure()) { + if (remove("failed_testRooGradMinimizer_BranchingPDF_workspace.root") != 0) { + std::cout << "Failed to remove failed_testRooGradMinimizer_BranchingPDF_workspace.root workspace file, sorry. " + "There were no failures though, so manually remove at your leisure." + << std::endl; + } + } +} + +TEST(GradMinimizerDebugging, DISABLED_BranchingPDFLoadFromWorkspace) +{ + // test RooMinimizer class with an N-dimensional pdf that forms a tree of + // pdfs, where one subpdf is the parameter of a higher level pdf + + // This version of the BranchingPDF test loads the random parameters written + // to a workspace file by the original BranchingPDF test at some point. + + RooMsgService::instance().setGlobalKillBelow(RooFit::ERROR); + + TFile *f = new TFile("failed_testRooGradMinimizer_BranchingPDF_workspace.root"); + + // Retrieve workspace from file + RooWorkspace w = *static_cast(f->Get("w")); + + RooAddPdf sum = *static_cast(w.pdf("sum")); + RooDataSet *data = static_cast(w.data("")); + + auto nll = sum.createNLL(*data); + + RooArgSet all_values = w.allVars(); + // RooArgSet all_values; + // for (auto var_name : {"x", "y", "z", "u", "v", "ga0_0_1", "ga1_0", "g0_0_0", "g0_0", "g0", "g1", "N_g0", "N_g1", + // "nll_sum_sumData", "sum", "k0_0_1", "k1_0", "m0_0", "m0_0_0", "m1", "s0"}) { + // all_values.add(*w.arg(var_name)); + // } + + // save initial values for the start of all minimizations + RooArgSet *savedValues = dynamic_cast(all_values.snapshot()); + if (savedValues == nullptr) { + throw std::runtime_error("params->snapshot() cannot be casted to RooArgSet!"); + } + + // -------- + + RooWallTimer wtimer; + + // -------- + + all_values.Print("v"); + RooMinimizer m0(*nll); + m0.setMinimizerType("Minuit2"); + + m0.setStrategy(0); + m0.setPrintLevel(-1); + + wtimer.start(); + m0.migrad(); + wtimer.stop(); + + RooFitResult *m0result = m0.lastMinuitFit(); + double minNll0 = m0result->minNll(); + double edm0 = m0result->edm(); + + double N_g0__0 = dynamic_cast(w.arg("N_g0"))->getVal(); + double N_g1__0 = dynamic_cast(w.arg("N_g1"))->getVal(); + double k0_0_1__0 = dynamic_cast(w.arg("k0_0_1"))->getVal(); + double k1_0__0 = dynamic_cast(w.arg("k1_0"))->getVal(); + double m0_0__0 = dynamic_cast(w.arg("m0_0"))->getVal(); + double m0_0_0__0 = dynamic_cast(w.arg("m0_0_0"))->getVal(); + double m1__0 = dynamic_cast(w.arg("m1"))->getVal(); + double s0__0 = dynamic_cast(w.arg("s0"))->getVal(); + + all_values.Print("v"); + + // -------- + + all_values = *savedValues; + + // -------- + + all_values.Print("v"); + + std::unique_ptr m1 = RooMinimizer::create(*nll); + m1->setMinimizerType("Minuit2"); + + m1->setStrategy(0); + m1->setPrintLevel(-1); + + wtimer.start(); + m1->migrad(); + wtimer.stop(); + + RooFitResult *m1result = m1->lastMinuitFit(); + double minNll1 = m1result->minNll(); + double edm1 = m1result->edm(); + + EXPECT_EQ(minNll0, minNll1); + EXPECT_EQ(edm0, edm1); + + double N_g0__1 = dynamic_cast(w.arg("N_g0"))->getVal(); + double N_g1__1 = dynamic_cast(w.arg("N_g1"))->getVal(); + double k0_0_1__1 = dynamic_cast(w.arg("k0_0_1"))->getVal(); + double k1_0__1 = dynamic_cast(w.arg("k1_0"))->getVal(); + double m0_0__1 = dynamic_cast(w.arg("m0_0"))->getVal(); + double m0_0_0__1 = dynamic_cast(w.arg("m0_0_0"))->getVal(); + double m1__1 = dynamic_cast(w.arg("m1"))->getVal(); + double s0__1 = dynamic_cast(w.arg("s0"))->getVal(); + + EXPECT_EQ(N_g0__0, N_g0__1); + EXPECT_EQ(N_g1__0, N_g1__1); + EXPECT_EQ(k0_0_1__0, k0_0_1__1); + EXPECT_EQ(k1_0__0, k1_0__1); + EXPECT_EQ(m0_0__0, m0_0__1); + EXPECT_EQ(m0_0_0__0, m0_0_0__1); + EXPECT_EQ(m1__0, m1__1); + EXPECT_EQ(s0__0, s0__1); + + all_values.Print("v"); + + // N_g0 = 494.514 +/- 18.8621 (limited) + // N_g1 = 505.817 +/- 24.6705 (limited) + // k0_0_1 = 2.96883 +/- 0.00561152 (limited) + // k1_0 = 4.12068 +/- 0.0565994 (limited) + // m0_0 = 8.09563 +/- 1.30395 (limited) + // m0_0_0 = 0.411472 +/- 0.183239 (limited) + // m1 = -1.99988 +/- 0.00194089 (limited) + // s0 = 3.04623 +/- 0.0982477 (limited) +} + +TEST(GradMinimizerDebugging, DISABLED_BranchingPDFLoadFromWorkspaceNominal) +{ + // only run the nominal minimizer of the BranchingPDF test and print results + + TFile *f = new TFile("failed_testRooGradMinimizer_BranchingPDF_workspace.root"); + RooWorkspace w = *static_cast(f->Get("w")); + RooAddPdf sum = *static_cast(w.pdf("sum")); + RooDataSet *data = static_cast(w.data("")); + auto nll = sum.createNLL(*data); + + RooMinimizer m0(*nll); + m0.setMinimizerType("Minuit2"); + m0.setStrategy(0); + m0.migrad(); +} + +TEST(GradMinimizerDebugging, DISABLED_BranchingPDFLoadFromWorkspaceGradMinimizer) +{ + // only run the GradMinimizer from the BranchingPDF test and print results + + TFile *f = new TFile("failed_testRooGradMinimizer_BranchingPDF_workspace.root"); + RooWorkspace w = *static_cast(f->Get("w")); + RooAddPdf sum = *static_cast(w.pdf("sum")); + RooDataSet *data = static_cast(w.data("")); + auto nll = sum.createNLL(*data); + + std::unique_ptr m0 = RooMinimizer::create(*nll); + m0->setMinimizerType("Minuit2"); + m0->setStrategy(0); + m0->migrad(); +} diff --git a/roofit/roofitcore/test/TestStatistics/RooRealL.cpp b/roofit/roofitcore/test/TestStatistics/RooRealL.cpp new file mode 100644 index 0000000000000..9fcf0a96c7de7 --- /dev/null +++ b/roofit/roofitcore/test/TestStatistics/RooRealL.cpp @@ -0,0 +1,347 @@ +/***************************************************************************** + * Project: RooFit * + * Package: RooFitCore * + * @(#)root/roofitcore:$Id$ + * Authors: * + * PB, Patrick Bos, NL eScience Center, p.bos@esciencecenter.nl * + * IP, Inti Pelupessy, NL eScience Center, i.pelupessy@esciencecenter.nl * + * VC, Vince Croft, DIANA / NYU, vincent.croft@cern.ch * + * * + * Redistribution and use in source and binary forms, * + * with or without modification, are permitted according to the terms * + * listed in LICENSE (http://roofit.sourceforge.net/license.txt) * + *****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "TestStatistics/RooRealL.h" +#include "TestStatistics/RooUnbinnedL.h" + +#include "gtest/gtest.h" +#include "../test_lib.h" // Hex + +class RooRealL + : public ::testing::TestWithParam> { +}; + +TEST_P(RooRealL, getVal) +{ + // Real-life test: calculate a NLL using event-based parallelization. This + // should replicate RooRealMPFE results. + RooRandom::randomGenerator()->SetSeed(std::get<0>(GetParam())); + RooWorkspace w; + w.factory("Gaussian::g(x[-5,5],mu[0,-3,3],sigma[1])"); + auto x = w.var("x"); + RooAbsPdf *pdf = w.pdf("g"); + RooDataSet *data = pdf->generate(RooArgSet(*x), 10000); + auto nll = pdf->createNLL(*data); + + auto nominal_result = nll->getVal(); + +// std::size_t NumCPU = std::get<0>(GetParam()); +// RooFit::MultiProcess::NLLVarTask mp_task_mode = std::get<1>(GetParam()); + +// RooFit::MultiProcess::NLLVar nll_mp(NumCPU, mp_task_mode, *dynamic_cast(nll)); + RooFit::TestStatistics::RooRealL nll_new("nll_new", "new style NLL", std::make_shared(pdf, data)); + + auto mp_result = nll_new.getVal(); + + EXPECT_DOUBLE_EQ(Hex(nominal_result), Hex(mp_result)); +// if (HasFailure()) { +// std::cout << "failed test had parameters NumCPU = " << NumCPU << ", task_mode = " << mp_task_mode +// << ", seed = " << std::get<1>(GetParam()) << std::endl; +// } +} + +void check_NLL_type(RooAbsReal *nll) +{ + if (dynamic_cast(nll) != nullptr) { + std::cout << "the NLL object is a RooAddition*..." << std::endl; + bool has_rooconstraintsum = false; + RooFIter nll_component_iter = nll->getComponents()->fwdIterator(); + RooAbsArg *nll_component; + while ((nll_component = nll_component_iter.next())) { + if (nll_component->IsA() == RooConstraintSum::Class()) { + has_rooconstraintsum = true; + break; + } else if (nll_component->IsA() != RooNLLVar::Class() && nll_component->IsA() != RooAddition::Class()) { + std::cerr << "... containing an unexpected component class: " << nll_component->ClassName() << std::endl; + throw std::runtime_error("RooAddition* type NLL object contains unexpected component class!"); + } + } + if (has_rooconstraintsum) { + std::cout << "...containing a RooConstraintSum component: " << nll_component->GetName() << std::endl; + } else { + std::cout << "...containing only RooNLLVar components." << std::endl; + } + } else if (dynamic_cast(nll) != nullptr) { + std::cout << "the NLL object is a RooNLLVar*" << std::endl; + } +} + +void count_NLL_components(RooAbsReal *nll) +{ + if (dynamic_cast(nll) != nullptr) { + std::cout << "the NLL object is a RooAddition*..." << std::endl; + unsigned nll_component_count = 0; + RooFIter nll_component_iter = nll->getComponents()->fwdIterator(); + RooAbsArg *nll_component; + while ((nll_component = nll_component_iter.next())) { + if (nll_component->IsA() != RooNLLVar::Class()) { + ++nll_component_count; + } + } + std::cout << "...containing " << nll_component_count << " RooNLLVar components." << std::endl; + } else if (dynamic_cast(nll) != nullptr) { + std::cout << "the NLL object is a RooNLLVar*" << std::endl; + } +} + +TEST_P(RooRealL, getValRooAddition) +{ + RooMsgService::instance().setGlobalKillBelow(RooFit::ERROR); + + RooRandom::randomGenerator()->SetSeed(std::get<0>(GetParam())); + + RooWorkspace w; + w.factory("Gaussian::g(x[-10,10],mu[0,-3,3],sigma[1])"); + + RooRealVar *x = w.var("x"); + x->setRange("x_range", -3, 0); + x->setRange("another_range", 1, 7); + + RooAbsPdf *pdf = w.pdf("g"); + RooDataSet *data = pdf->generate(*x, 10000); + + RooAbsReal *nll = + pdf->createNLL(*data, RooFit::Range("x_range"), RooFit::Range("another_range")); + + check_NLL_type(nll); + count_NLL_components(nll); + + delete nll; + delete data; +} + +TEST_P(RooRealL, getValRooConstraintSumAddition) +{ + // modified from + // https://github.com/roofit-dev/rootbench/blob/43d12f33e8dac7af7d587b53a2804ddf6717e92f/root/roofit/roofit/RooFitASUM.cxx#L417 + + RooMsgService::instance().setGlobalKillBelow(RooFit::ERROR); + + int bins = 10000; + + RooRealVar x("x", "x", 0, bins); + x.setBins(bins); + // Parameters + RooRealVar a0("a0", "a0", 0); + RooRealVar a1("a1", "a1", 1, 0, 2); + RooRealVar a2("a2", "a2", 0); + + RooPolynomial p0("p0", "p0", x); + RooPolynomial p1("p1", "p1", x, RooArgList(a0, a1, a2), 0); + + RooDataHist *dh_bkg = p0.generateBinned(x, 1000000000); + RooDataHist *dh_sig = p1.generateBinned(x, 100000000); + dh_bkg->SetName("dh_bkg"); + dh_sig->SetName("dh_sig"); + + a1.setVal(2); + RooDataHist *dh_sig_up = p1.generateBinned(x, 1100000000); + dh_sig_up->SetName("dh_sig_up"); + a1.setVal(.5); + RooDataHist *dh_sig_down = p1.generateBinned(x, 900000000); + dh_sig_down->SetName("dh_sig_down"); + + RooWorkspace w = RooWorkspace("w"); + w.import(x); + w.import(*dh_sig); + w.import(*dh_bkg); + w.import(*dh_sig_up); + w.import(*dh_sig_down); + w.factory("HistFunc::hf_sig(x,dh_sig)"); + w.factory("HistFunc::hf_bkg(x,dh_bkg)"); + w.factory("HistFunc::hf_sig_up(x,dh_sig_up)"); + w.factory("HistFunc::hf_sig_down(x,dh_sig_down)"); + w.factory("PiecewiseInterpolation::pi_sig(hf_sig,hf_sig_down,hf_sig_up,alpha[-5,5])"); + + w.factory("ASUM::model(mu[1,0,5]*pi_sig,nu[1]*hf_bkg)"); + w.factory("Gaussian::constraint(alpha,0,1)"); + w.factory("PROD::model2(model,constraint)"); + + RooAbsPdf *pdf = w.pdf("model2"); + + RooDataHist *data = pdf->generateBinned(x, 1100000); + RooAbsReal *nll = pdf->createNLL(*data); + + check_NLL_type(nll); + count_NLL_components(nll); + + delete nll; +} + +TEST_P(RooRealL, setVal) +{ + // calculate the NLL twice with different parameters + + RooRandom::randomGenerator()->SetSeed(std::get<0>(GetParam())); + RooWorkspace w; + w.factory("Gaussian::g(x[-5,5],mu[0,-3,3],sigma[1])"); + auto x = w.var("x"); + RooAbsPdf *pdf = w.pdf("g"); + RooDataSet *data = pdf->generate(RooArgSet(*x), 10000); + auto nll = pdf->createNLL(*data); + + RooFit::TestStatistics::RooRealL nll_new("nll_new", "new style NLL", std::make_shared(pdf, data)); + + // calculate first results + auto nominal_result1 = nll->getVal(); + auto mp_result1 = nll_new.getVal(); + + std::cout << "nominal_result1 = " << nominal_result1 << ", mp_result1 = " << mp_result1 << std::endl; + + EXPECT_EQ(Hex(nominal_result1), Hex(mp_result1)); + + w.var("mu")->setVal(2); + + // calculate second results after parameter change + auto nominal_result2 = nll->getVal(); + auto mp_result2 = nll_new.getVal(); + + std::cout << "nominal_result2 = " << nominal_result2 << ", mp_result2 = " << mp_result2 << std::endl; + + EXPECT_EQ(Hex(nominal_result2), Hex(mp_result2)); + if (HasFailure()) { + std::cout << "failed test had seed = " << std::get<0>(GetParam()) << std::endl; + } +} + +INSTANTIATE_TEST_SUITE_P(NworkersModeSeed, RooRealL, + ::testing::Values(2, 3)); // random seed + +class RealLVsMPFE + : public ::testing::TestWithParam> { +}; + +TEST_P(RealLVsMPFE, getVal) +{ + // Compare our MP NLL to actual RooRealMPFE results using the same strategies. + + RooMsgService::instance().setGlobalKillBelow(RooFit::ERROR); + + // parameters +// RooFit::MultiProcess::NLLVarTask mp_task_mode = std::get<1>(GetParam()); + std::size_t seed = std::get<0>(GetParam()); + + RooRandom::randomGenerator()->SetSeed(seed); + + RooWorkspace w; + w.factory("Gaussian::g(x[-5,5],mu[0,-3,3],sigma[1])"); + auto x = w.var("x"); + RooAbsPdf *pdf = w.pdf("g"); + RooDataSet *data = pdf->generate(RooArgSet(*x), 10000); + + auto nll_mpfe = pdf->createNLL(*data); + + auto mpfe_result = nll_mpfe->getVal(); + + RooFit::TestStatistics::RooRealL nll_new("nll_new", "new style NLL", std::make_shared(pdf, data)); + + auto mp_result = nll_new.getVal(); + + EXPECT_EQ(Hex(mpfe_result), Hex(mp_result)); + if (HasFailure()) { + std::cout << "failed test had seed = " << std::get<0>(GetParam()) << std::endl; + } +} + +TEST_P(RealLVsMPFE, minimize) +{ + // do a minimization (e.g. like in GradMinimizer_Gaussian1D test) + + RooMsgService::instance().setGlobalKillBelow(RooFit::ERROR); + + // TODO: see whether it performs adequately + + // parameters + std::size_t seed = std::get<0>(GetParam()); + + RooRandom::randomGenerator()->SetSeed(seed); + + RooWorkspace w = RooWorkspace(); + + w.factory("Gaussian::g(x[-5,5],mu[0,-3,3],sigma[1])"); + auto x = w.var("x"); + RooAbsPdf *pdf = w.pdf("g"); + RooRealVar *mu = w.var("mu"); + + RooDataSet *data = pdf->generate(RooArgSet(*x), 10000); + mu->setVal(-2.9); + + auto nll_mpfe = pdf->createNLL(*data); + RooFit::TestStatistics::RooRealL nll_new("nll_new", "new style NLL", std::make_shared(pdf, data)); + + // save initial values for the start of all minimizations + RooArgSet values = RooArgSet(*mu, *pdf); + + RooArgSet *savedValues = dynamic_cast(values.snapshot()); + if (savedValues == nullptr) { + throw std::runtime_error("params->snapshot() cannot be casted to RooArgSet!"); + } + + // -------- + + RooMinimizer m0(*nll_mpfe); + m0.setMinimizerType("Minuit2"); + + m0.setStrategy(0); + m0.setPrintLevel(-1); + + m0.migrad(); + + RooFitResult *m0result = m0.lastMinuitFit(); + double minNll0 = m0result->minNll(); + double edm0 = m0result->edm(); + double mu0 = mu->getVal(); + double muerr0 = mu->getError(); + + values = *savedValues; + + RooMinimizer m1(nll_new); + m1.setMinimizerType("Minuit2"); + + m1.setStrategy(0); + m1.setPrintLevel(-1); + + m1.migrad(); + + RooFitResult *m1result = m1.lastMinuitFit(); + double minNll1 = m1result->minNll(); + double edm1 = m1result->edm(); + double mu1 = mu->getVal(); + double muerr1 = mu->getError(); + + EXPECT_EQ(minNll0, minNll1); + EXPECT_EQ(mu0, mu1); + EXPECT_EQ(muerr0, muerr1); + EXPECT_EQ(edm0, edm1); + + m1.cleanup(); // necessary in tests to clean up global _theFitter +} + +INSTANTIATE_TEST_SUITE_P(NworkersModeSeed, RealLVsMPFE, + ::testing::Values(2, 3)); // random seed diff --git a/roofit/roofitcore/test/TestStatistics/testLikelihoodGradientJob.cpp b/roofit/roofitcore/test/TestStatistics/testLikelihoodGradientJob.cpp new file mode 100644 index 0000000000000..bec26ed99318f --- /dev/null +++ b/roofit/roofitcore/test/TestStatistics/testLikelihoodGradientJob.cpp @@ -0,0 +1,583 @@ +/***************************************************************************** + * Project: RooFit * + * Package: RooFitCore * + * @(#)root/roofitcore:$Id$ + * Authors: * + * PB, Patrick Bos, NL eScience Center, p.bos@esciencecenter.nl * + * IP, Inti Pelupessy, NL eScience Center, i.pelupessy@esciencecenter.nl * + * VC, Vince Croft, DIANA / NYU, vincent.croft@cern.ch * + * * + * Redistribution and use in source and binary forms, * + * with or without modification, are permitted according to the terms * + * listed in LICENSE (http://roofit.sourceforge.net/license.txt) * + *****************************************************************************/ + +#include // runtime_error + +#include + +#include +#include +#include + +#include "RooDataHist.h" // complete type in Binned test +#include "RooCategory.h" // complete type in MultiBinnedConstraint test + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include // need to complete type for debugging + +#include "gtest/gtest.h" +#include "../test_lib.h" // generate_1D_gaussian_pdf_nll + + +class Environment : public testing::Environment { +public: + void SetUp() override { + RooMsgService::instance().setGlobalKillBelow(RooFit::ERROR); + } +}; + +testing::Environment* const test_env = + testing::AddGlobalTestEnvironment(new Environment); + + +class LikelihoodGradientJob : public ::testing::TestWithParam> { +}; + +TEST_P(LikelihoodGradientJob, Gaussian1D) +{ + // do a minimization, but now using GradMinimizer and its MP version + + // parameters + std::size_t NWorkers = std::get<0>(GetParam()); + // RooFit::MultiProcess::NLLVarTask mp_task_mode = std::get<1>(GetParam()); + std::size_t seed = std::get<1>(GetParam()); + + RooRandom::randomGenerator()->SetSeed(seed); + + RooWorkspace w = RooWorkspace(); + + std::unique_ptr nll; + std::unique_ptr values; + RooAbsPdf *pdf; + RooDataSet *data; + std::tie(nll, pdf, data, values) = generate_1D_gaussian_pdf_nll(w, 10000); + // when c++17 support arrives, change to this: + // auto [nll, pdf, data, values] = generate_1D_gaussian_pdf_nll(w, 10000); + RooRealVar *mu = w.var("mu"); + + RooArgSet *savedValues = dynamic_cast(values->snapshot()); + if (savedValues == nullptr) { + throw std::runtime_error("params->snapshot() cannot be casted to RooArgSet!"); + } + + // -------- + + std::unique_ptr m0 = RooMinimizer::create(*nll); + m0->setMinimizerType("Minuit2"); + + m0->setStrategy(0); +// m0->setVerbose(true); + m0->setPrintLevel(-1); + + m0->migrad(); + + RooFitResult *m0result = m0->lastMinuitFit(); + double minNll0 = m0result->minNll(); + double edm0 = m0result->edm(); + double mu0 = mu->getVal(); + double muerr0 = mu->getError(); + + *values = *savedValues; + + RooFit::MultiProcess::JobManager::default_N_workers = NWorkers; + auto likelihood = std::make_shared(pdf, data); + std::unique_ptr m1 = + RooMinimizer::create( + likelihood); + m1->setMinimizerType("Minuit2"); + + m1->setStrategy(0); +// m1->setVerbose(true); + m1->setPrintLevel(-1); + + m1->migrad(); + + RooFitResult *m1result = m1->lastMinuitFit(); + double minNll1 = m1result->minNll(); + double edm1 = m1result->edm(); + double mu1 = mu->getVal(); + double muerr1 = mu->getError(); + + EXPECT_EQ(minNll0, minNll1); + EXPECT_EQ(mu0, mu1); + EXPECT_EQ(muerr0, muerr1); + EXPECT_EQ(edm0, edm1); + + m1->cleanup(); // necessary in tests to clean up global _theFitter +} + +TEST(LikelihoodGradientJobDEBUGGING, DISABLED_Gaussian1DNominal) +{ + // std::size_t NWorkers = 1; + std::size_t seed = 1; + + RooRandom::randomGenerator()->SetSeed(seed); + + RooWorkspace w = RooWorkspace(); + + std::unique_ptr nll; + std::unique_ptr _; + RooAbsPdf *pdf; + RooDataSet *data; + std::tie(nll, pdf, data, _) = generate_1D_gaussian_pdf_nll(w, 10000); + + std::unique_ptr m0 = RooMinimizer::create(*nll); + m0->setMinimizerType("Minuit2"); + + m0->setStrategy(0); + m0->setPrintLevel(2); + + m0->migrad(); + m0->cleanup(); // necessary in tests to clean up global _theFitter +} + +TEST(LikelihoodGradientJobDEBUGGING, DISABLED_Gaussian1DMultiProcess) +{ + std::size_t NWorkers = 1; + std::size_t seed = 1; + + RooRandom::randomGenerator()->SetSeed(seed); + + RooWorkspace w = RooWorkspace(); + + std::unique_ptr nll; + std::unique_ptr values; + RooAbsPdf *pdf; + RooDataSet *data; + std::tie(nll, pdf, data, values) = generate_1D_gaussian_pdf_nll(w, 10000); + + RooFit::MultiProcess::JobManager::default_N_workers = NWorkers; + auto likelihood = std::make_shared(pdf, data); + std::unique_ptr m1 = + RooMinimizer::create( + likelihood); + m1->setMinimizerType("Minuit2"); + + m1->setStrategy(0); + m1->setPrintLevel(2); + + m1->migrad(); + m1->cleanup(); // necessary in tests to clean up global _theFitter +} + +TEST(LikelihoodGradientJob, RepeatMigrad) +{ + // do multiple minimizations using MP::GradMinimizer, testing breakdown and rebuild + + // parameters + std::size_t NWorkers = 2; + std::size_t seed = 5; + + RooRandom::randomGenerator()->SetSeed(seed); + + RooWorkspace w = RooWorkspace(); + + std::unique_ptr nll; + std::unique_ptr values; + RooAbsPdf *pdf; + RooDataSet *data; + std::tie(nll, pdf, data, values) = generate_1D_gaussian_pdf_nll(w, 10000); + // when c++17 support arrives, change to this: + // auto [nll, values] = generate_1D_gaussian_pdf_nll(w, 10000); + + RooArgSet *savedValues = dynamic_cast(values->snapshot()); + if (savedValues == nullptr) { + throw std::runtime_error("params->snapshot() cannot be casted to RooArgSet!"); + } + + // -------- + + RooFit::MultiProcess::JobManager::default_N_workers = NWorkers; + auto likelihood = std::make_shared(pdf, data); + std::unique_ptr m1 = + RooMinimizer::create( + likelihood); + + m1->setMinimizerType("Minuit2"); + + m1->setStrategy(0); + m1->setPrintLevel(-1); + + std::cout << "... running migrad first time ..." << std::endl; + m1->migrad(); + +// std::cout << "... terminating JobManager instance ..." << std::endl; +// RooFit::MultiProcess::JobManager::instance()->terminate(); + + *values = *savedValues; + + std::cout << "... running migrad second time ..." << std::endl; + m1->migrad(); + + std::cout << "... cleaning up minimizer ..." << std::endl; + m1->cleanup(); // necessary in tests to clean up global _theFitter +} + +TEST_P(LikelihoodGradientJob, GaussianND) +{ + // do a minimization, but now using GradMinimizer and its MP version + + // parameters + std::size_t NWorkers = std::get<0>(GetParam()); + // RooFit::MultiProcess::NLLVarTask mp_task_mode = std::get<1>(GetParam()); + std::size_t seed = std::get<1>(GetParam()); + + unsigned int N = 4; + + RooRandom::randomGenerator()->SetSeed(seed); + + RooWorkspace w = RooWorkspace(); + + std::unique_ptr nll; + std::unique_ptr values; + RooAbsPdf *pdf; + RooDataSet *data; + std::tie(nll, pdf, data, values) = generate_ND_gaussian_pdf_nll(w, N, 1000); + // when c++17 support arrives, change to this: + // auto [nll, all_values] = generate_ND_gaussian_pdf_nll(w, N, 1000); + + RooArgSet *savedValues = dynamic_cast(values->snapshot()); + if (savedValues == nullptr) { + throw std::runtime_error("params->snapshot() cannot be casted to RooArgSet!"); + } + + RooWallTimer wtimer; + + // -------- + + std::unique_ptr m0 = RooMinimizer::create(*nll); + m0->setMinimizerType("Minuit2"); + + m0->setStrategy(0); + m0->setPrintLevel(-1); + + wtimer.start(); + m0->migrad(); + wtimer.stop(); + std::cout << "\nwall clock time RooGradMinimizer.migrad (NWorkers = " << NWorkers << ", seed = " << seed + << "): " << wtimer.timing_s() << " s" << std::endl; + + RooFitResult *m0result = m0->lastMinuitFit(); + double minNll0 = m0result->minNll(); + double edm0 = m0result->edm(); + double mean0[N]; + double std0[N]; + for (unsigned ix = 0; ix < N; ++ix) { + { + std::ostringstream os; + os << "m" << ix; + mean0[ix] = dynamic_cast(w.arg(os.str().c_str()))->getVal(); + } + { + std::ostringstream os; + os << "s" << ix; + std0[ix] = dynamic_cast(w.arg(os.str().c_str()))->getVal(); + } + } + + // -------- + + *values = *savedValues; + + // -------- + + RooFit::MultiProcess::JobManager::default_N_workers = NWorkers; + auto likelihood = std::make_shared(pdf, data); + std::unique_ptr m1 = + RooMinimizer::create( + likelihood); + m1->setMinimizerType("Minuit2"); + + m1->setStrategy(0); + m1->setPrintLevel(-1); + + wtimer.start(); + m1->migrad(); + wtimer.stop(); + std::cout << "wall clock time MP::GradMinimizer.migrad (NWorkers = " << NWorkers << ", seed = " << seed + << "): " << wtimer.timing_s() << " s\n" + << std::endl; + + RooFitResult *m1result = m1->lastMinuitFit(); + double minNll1 = m1result->minNll(); + double edm1 = m1result->edm(); + double mean1[N]; + double std1[N]; + for (unsigned ix = 0; ix < N; ++ix) { + { + std::ostringstream os; + os << "m" << ix; + mean1[ix] = dynamic_cast(w.arg(os.str().c_str()))->getVal(); + } + { + std::ostringstream os; + os << "s" << ix; + std1[ix] = dynamic_cast(w.arg(os.str().c_str()))->getVal(); + } + } + + EXPECT_EQ(minNll0, minNll1); + EXPECT_EQ(edm0, edm1); + + for (unsigned ix = 0; ix < N; ++ix) { + EXPECT_EQ(mean0[ix], mean1[ix]); + EXPECT_EQ(std0[ix], std1[ix]); + } + + m1->cleanup(); // necessary in tests to clean up global _theFitter +} + +INSTANTIATE_TEST_SUITE_P(NworkersSeed, LikelihoodGradientJob, + ::testing::Combine(::testing::Values(1, 2, 3), // number of workers + ::testing::Values(2, 3))); // random seed + + +class BasicTest: public ::testing::Test { +protected: + void SetUp() override { + RooRandom::randomGenerator()->SetSeed(seed); + clean_flags = std::make_shared(); + } + + std::size_t seed = 23; + RooWorkspace w; + std::unique_ptr nll; + RooAbsPdf *pdf; + RooAbsData *data; + std::shared_ptr likelihood; + std::shared_ptr clean_flags; +}; + + +class LikelihoodSimBinnedConstrainedTest : public BasicTest { +protected: + void SetUp() override { + BasicTest::SetUp(); + // Unbinned pdfs that define template histograms + + w.factory("Gaussian::gA(x[-10,10],-2,3)") ; + w.factory("Gaussian::gB(x[-10,10],2,1)") ; + w.factory("Uniform::u(x)"); + + // Generate template histograms + + RooDataHist* h_sigA = w.pdf("gA")->generateBinned(*w.var("x"),1000) ; + RooDataHist* h_sigB = w.pdf("gB")->generateBinned(*w.var("x"),1000) ; + RooDataHist *h_bkg = w.pdf("u")->generateBinned(*w.var("x"), 1000); + + w.import(*h_sigA, RooFit::Rename("h_sigA")); + w.import(*h_sigB, RooFit::Rename("h_sigB")); + w.import(*h_bkg, RooFit::Rename("h_bkg")); + + // Construct binned pdf as sum of amplitudes + w.factory("HistFunc::hf_sigA(x,h_sigA)") ; + w.factory("HistFunc::hf_sigB(x,h_sigB)") ; + w.factory("HistFunc::hf_bkg(x,h_bkg)") ; + + w.factory("ASUM::model_phys_A(mu_sig[1,-1,10]*hf_sigA,expr::mu_bkg_A('1+0.02*alpha_bkg_A',alpha_bkg_A[-5,5])*hf_bkg)") ; + w.factory("ASUM::model_phys_B(mu_sig*hf_sigB,expr::mu_bkg_B('1+0.05*alpha_bkg_B',alpha_bkg_B[-5,5])*hf_bkg)") ; + + // Construct L_subs: Gaussian subsidiary measurement that constrains alpha_bkg + w.factory("Gaussian:model_subs_A(alpha_bkg_obs_A[0],alpha_bkg_A,1)") ; + w.factory("Gaussian:model_subs_B(alpha_bkg_obs_B[0],alpha_bkg_B,1)") ; + + // Construct full pdfs for each component (A,B) + w.factory("PROD::model_A(model_phys_A,model_subs_A)") ; + w.factory("PROD::model_B(model_phys_B,model_subs_B)") ; + + // Construct simulatenous pdf + w.factory("SIMUL::model(index[A,B],A=model_A,B=model_B)") ; + + pdf = w.pdf("model"); + // Construct dataset from physics pdf + data = pdf->generate(RooArgSet(*w.var("x"), *w.cat("index")), RooFit::AllBinned()); + } +}; + +TEST_F(LikelihoodSimBinnedConstrainedTest, BasicParameters) +{ + // original test: + nll.reset(pdf->createNLL(*data, RooFit::GlobalObservables(RooArgSet(*w.var("alpha_bkg_obs_A"), *w.var("alpha_bkg_obs_B"))))); + + // -------- + + auto nll0 = nll->getVal(); + + likelihood = RooFit::TestStatistics::build_simultaneous_likelihood( + pdf, data, RooFit::TestStatistics::GlobalObservables({*w.var("alpha_bkg_obs_A"), *w.var("alpha_bkg_obs_B")})); + RooFit::TestStatistics::LikelihoodSerial nll_ts(likelihood, clean_flags/*, nullptr*/); + + nll_ts.evaluate(); + auto nll1 = nll_ts.return_result(); + + EXPECT_DOUBLE_EQ(nll0, nll1); +} + +TEST_F(LikelihoodSimBinnedConstrainedTest, Minimize) +{ + // do a minimization, but now using GradMinimizer and its MP version + nll.reset(pdf->createNLL(*data, RooFit::Constrain(RooArgSet(*w.var("alpha_bkg_obs_A"))), + RooFit::GlobalObservables(RooArgSet(*w.var("alpha_bkg_obs_B"))), RooFit::Offset(kTRUE))); + + // parameters + std::size_t NWorkers = 2; //std::get<0>(GetParam()); + + RooArgSet *values = pdf->getParameters(data); + + values->add(*pdf); + values->add(*nll); + + RooArgSet *savedValues = dynamic_cast(values->snapshot()); + if (savedValues == nullptr) { + throw std::runtime_error("params->snapshot() cannot be casted to RooArgSet!"); + } + + // -------- + + std::unique_ptr m0 = RooMinimizer::create(*nll); + + m0->setMinimizerType("Minuit2"); + m0->setStrategy(0); +// m0->setVerbose(true); + m0->setPrintLevel(1); + + m0->migrad(); + + RooFitResult *m0result = m0->lastMinuitFit(); + double minNll_nominal = m0result->minNll(); + double edm_nominal = m0result->edm(); + double alpha_bkg_A_nominal = w.var("alpha_bkg_A")->getVal(); + double alpha_bkg_A_error_nominal = w.var("alpha_bkg_A")->getError(); + double alpha_bkg_B_nominal = w.var("alpha_bkg_B")->getVal(); + double alpha_bkg_B_error_nominal = w.var("alpha_bkg_B")->getError(); + double mu_sig_nominal = w.var("mu_sig")->getVal(); + double mu_sig_error_nominal = w.var("mu_sig")->getError(); + + *values = *savedValues; + + RooFit::MultiProcess::JobManager::default_N_workers = NWorkers; + + auto likelihood = RooFit::TestStatistics::build_simultaneous_likelihood( + pdf, data, RooFit::TestStatistics::ConstrainedParameters({*w.var("alpha_bkg_obs_A")}), + RooFit::TestStatistics::GlobalObservables({*w.var("alpha_bkg_obs_B")})); + + std::unique_ptr m1 = RooMinimizer::create(likelihood); + m1->enable_likelihood_offsetting(true); + + m1->setMinimizerType("Minuit2"); + m1->setStrategy(0); +// m1->setVerbose(true); + m1->setPrintLevel(1); + m1->optimizeConst(2); + + m1->migrad(); + + RooFitResult *m1result = m1->lastMinuitFit(); + double minNll_GradientJob = m1result->minNll(); + double edm_GradientJob = m1result->edm(); + double alpha_bkg_A_GradientJob = w.var("alpha_bkg_A")->getVal(); + double alpha_bkg_A_error_GradientJob = w.var("alpha_bkg_A")->getError(); + double alpha_bkg_B_GradientJob = w.var("alpha_bkg_B")->getVal(); + double alpha_bkg_B_error_GradientJob = w.var("alpha_bkg_B")->getError(); + double mu_sig_GradientJob = w.var("mu_sig")->getVal(); + double mu_sig_error_GradientJob = w.var("mu_sig")->getError(); + + EXPECT_EQ(minNll_nominal, minNll_GradientJob); + EXPECT_EQ(edm_nominal, edm_GradientJob); + EXPECT_EQ(alpha_bkg_A_nominal, alpha_bkg_A_GradientJob); + EXPECT_EQ(alpha_bkg_A_error_nominal, alpha_bkg_A_error_GradientJob); + EXPECT_EQ(alpha_bkg_B_nominal, alpha_bkg_B_GradientJob); + EXPECT_EQ(alpha_bkg_B_error_nominal, alpha_bkg_B_error_GradientJob); + EXPECT_EQ(mu_sig_nominal, mu_sig_GradientJob); + EXPECT_EQ(mu_sig_error_nominal, mu_sig_error_GradientJob); + + m1->cleanup(); // necessary in tests to clean up global _theFitter +} + +class CarstenGGFWorkspaceTest: public ::testing::Test { +protected: + void SetUp() override { + RooRandom::randomGenerator()->SetSeed(seed); + + TFile *_file0 = TFile::Open("/Users/pbos/projects/apcocsm/carsten/lxplus/ggF/ggF-stxs1-v1.root"); + + w = static_cast(gDirectory->Get("HWWRun2GGF")); + + data = w->data("obsData"); + auto mc = dynamic_cast(w->genobj("ModelConfig")); + global_observables = mc->GetGlobalObservables(); + nuisance_parameters = mc->GetNuisanceParameters(); + pdf = w->pdf(mc->GetPdf()->GetName()); + } + + std::size_t seed = 23; + RooWorkspace* w; + RooAbsPdf *pdf; + RooAbsData *data; + const RooArgSet *global_observables; + const RooArgSet *nuisance_parameters; + std::unique_ptr m; +}; + +TEST_F(CarstenGGFWorkspaceTest, DISABLED_NoMultiProcess) +{ + RooAbsReal *nll = pdf->createNLL(*data, + RooFit::GlobalObservables(*global_observables), + RooFit::Constrain(*nuisance_parameters), + RooFit::Offset(kTRUE)); + + m = RooMinimizer::create(*nll); + + m->setPrintLevel(1); + m->setStrategy(0); + m->setProfile(false); + m->optimizeConst(2); + m->setMinimizerType("Minuit2"); +// m->setVerbose(kTRUE); + m->setEps(1); + + m->migrad(); + + m->cleanup(); // necessary in tests to clean up global _theFitter +} + +TEST_F(CarstenGGFWorkspaceTest, DISABLED_MultiProcess) +{ + RooFit::MultiProcess::JobManager::default_N_workers = 4; + auto likelihood = RooFit::TestStatistics::build_simultaneous_likelihood(pdf, data, RooFit::TestStatistics::ConstrainedParameters(*nuisance_parameters), RooFit::TestStatistics::GlobalObservables(*global_observables)); + m = RooMinimizer::create(likelihood); + m->enable_likelihood_offsetting(true); + + m->setPrintLevel(1); + m->setStrategy(0); + m->setProfile(false); + m->optimizeConst(2); + m->setMinimizerType("Minuit2"); +// m->setVerbose(kTRUE); + m->setEps(1); + + m->migrad(); + + m->cleanup(); // necessary in tests to clean up global _theFitter +} \ No newline at end of file diff --git a/roofit/roofitcore/test/TestStatistics/testLikelihoodSerial.cxx b/roofit/roofitcore/test/TestStatistics/testLikelihoodSerial.cxx new file mode 100644 index 0000000000000..1d604167cc3b6 --- /dev/null +++ b/roofit/roofitcore/test/TestStatistics/testLikelihoodSerial.cxx @@ -0,0 +1,442 @@ +/* + * Project: RooFit + * Authors: + * PB, Patrick Bos, Netherlands eScience Center, p.bos@esciencecenter.nl + * + * Copyright (c) 2016-2020, Netherlands eScience Center + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ + +#include // runtime_error + +#include +#include +#include + +#include +#include +#include + +#include "RooDataHist.h" // complete type in Binned test +#include "RooCategory.h" // complete type in MultiBinnedConstraint test + +#include +#include +#include +#include +#include +#include +#include +#include +#include // need to complete type for debugging +#include +#include +#include + +#include "gtest/gtest.h" +#include "../test_lib.h" // generate_1D_gaussian_pdf_nll + +class Environment : public testing::Environment { +public: + void SetUp() override { + RooMsgService::instance().setGlobalKillBelow(RooFit::ERROR); + } +}; + +testing::Environment* const test_env = + testing::AddGlobalTestEnvironment(new Environment); + +class LikelihoodSerialTest: public ::testing::Test { +protected: + void SetUp() override { + RooRandom::randomGenerator()->SetSeed(seed); + clean_flags = std::make_shared(); + } + + std::size_t seed = 23; + RooWorkspace w; + std::unique_ptr nll; + std::unique_ptr values; + RooAbsPdf *pdf; + RooAbsData *data; + std::shared_ptr likelihood; + std::shared_ptr clean_flags; +}; + +class LikelihoodSerialBinnedDatasetTest : public LikelihoodSerialTest { +protected: + void SetUp() override { + LikelihoodSerialTest::SetUp(); + + // Unbinned pdfs that define template histograms + w.factory("Gaussian::g(x[-10,10],0,2)"); + w.factory("Uniform::u(x)"); + + // Generate template histograms + RooDataHist *h_sig = w.pdf("g")->generateBinned(*w.var("x"), 1000); + RooDataHist *h_bkg = w.pdf("u")->generateBinned(*w.var("x"), 1000); + + w.import(*h_sig, RooFit::Rename("h_sig")); + w.import(*h_bkg, RooFit::Rename("h_bkg")); + + // Construct binned pdf as sum of amplitudes + w.factory("HistFunc::hf_sig(x,h_sig)"); + w.factory("HistFunc::hf_bkg(x,h_bkg)"); + w.factory("ASUM::model(mu_sig[1,-1,10]*hf_sig,mu_bkg[1,-1,10]*hf_bkg)"); + + pdf = w.pdf("model"); + } +}; + +TEST_F(LikelihoodSerialTest, UnbinnedGaussian1D) +{ + std::tie(nll, pdf, data, values) = generate_1D_gaussian_pdf_nll(w, 10000); + likelihood = std::make_shared(pdf, data); + RooFit::TestStatistics::LikelihoodSerial nll_ts(likelihood, clean_flags/*, nullptr*/); + + auto nll0 = nll->getVal(); + + nll_ts.evaluate(); + auto nll1 = nll_ts.return_result(); + + EXPECT_EQ(nll0, nll1); +} + +TEST_F(LikelihoodSerialTest, UnbinnedGaussianND) +{ + unsigned int N = 1; + + std::tie(nll, pdf, data, values) = generate_ND_gaussian_pdf_nll(w, N, 1000); + likelihood = std::make_shared(pdf, data); + RooFit::TestStatistics::LikelihoodSerial nll_ts(likelihood, clean_flags/*, nullptr*/); + + auto nll0 = nll->getVal(); + + nll_ts.evaluate(); + auto nll1 = nll_ts.return_result(); + + EXPECT_EQ(nll0, nll1); +} + +TEST_F(LikelihoodSerialBinnedDatasetTest, UnbinnedPdf) +{ + data = pdf->generateBinned(*w.var("x")); + + nll.reset(pdf->createNLL(*data)); + + likelihood = std::make_shared(pdf, data); + RooFit::TestStatistics::LikelihoodSerial nll_ts(likelihood, clean_flags/*, nullptr*/); + + auto nll0 = nll->getVal(); + + nll_ts.evaluate(); + auto nll1 = nll_ts.return_result(); + + EXPECT_EQ(nll0, nll1); +} + + +TEST_F(LikelihoodSerialBinnedDatasetTest, UnbinnedPdfWithBinnedLikelihoodAttribute) +{ + pdf->setAttribute("BinnedLikelihood"); + data = pdf->generateBinned(*w.var("x")); + + nll.reset(pdf->createNLL(*data)); + + likelihood = std::make_shared(pdf, data); + RooFit::TestStatistics::LikelihoodSerial nll_ts(likelihood, clean_flags/*, nullptr*/); + + auto nll0 = nll->getVal(); + + nll_ts.evaluate(); + auto nll1 = nll_ts.return_result(); + + EXPECT_EQ(nll0, nll1); +} + + + +TEST_F(LikelihoodSerialBinnedDatasetTest, BinnedManualNLL) +{ + data = pdf->generateBinned(*w.var("x")); + + // manually create NLL, ripping all relevant parts from RooAbsPdf::createNLL, except here we also set binnedL = true + RooArgSet projDeps; + RooAbsTestStatistic::Configuration nll_config; + nll_config.verbose = false; + nll_config.cloneInputData = false; + nll_config.binnedL = true; + int extended = 2; + RooNLLVar nll_manual("nlletje", "-log(likelihood)", *pdf, *data, projDeps, nll_config, extended); + + likelihood = std::make_shared(pdf, data); + RooFit::TestStatistics::LikelihoodSerial nll_ts(likelihood, clean_flags/*, nullptr*/); + + auto nll0 = nll_manual.getVal(); + + nll_ts.evaluate(); + auto nll1 = nll_ts.return_result(); + + EXPECT_EQ(nll0, nll1); +} + + +TEST_F(LikelihoodSerialTest, SimBinned) +{ + // Unbinned pdfs that define template histograms + w.factory("Gaussian::gA(x[-10,10],-2,3)"); + w.factory("Gaussian::gB(x[-10,10],2,1)"); + w.factory("Uniform::u(x)"); + + // Generate template histograms + RooDataHist *h_sigA = w.pdf("gA")->generateBinned(*w.var("x"), 1000); + RooDataHist *h_sigB = w.pdf("gB")->generateBinned(*w.var("x"), 1000); + RooDataHist *h_bkg = w.pdf("u")->generateBinned(*w.var("x"), 1000); + + w.import(*h_sigA, RooFit::Rename("h_sigA")); + w.import(*h_sigB, RooFit::Rename("h_sigB")); + w.import(*h_bkg, RooFit::Rename("h_bkg")); + + // Construct L_phys: binned pdf as sum of amplitudes + w.factory("HistFunc::hf_sigA(x,h_sigA)"); + w.factory("HistFunc::hf_sigB(x,h_sigB)"); + w.factory("HistFunc::hf_bkg(x,h_bkg)"); + + w.factory("ASUM::model_A(mu_sig[1,-1,10]*hf_sigA,mu_bkg_A[1,-1,10]*hf_bkg)"); + w.factory("ASUM::model_B(mu_sig*hf_sigB,mu_bkg_B[1,-1,10]*hf_bkg)"); + + w.pdf("model_A")->setAttribute("BinnedLikelihood"); + w.pdf("model_B")->setAttribute("BinnedLikelihood"); + + // Construct simulatenous pdf + w.factory("SIMUL::model(index[A,B],A=model_A,B=model_B)"); + + // Construct dataset + pdf = w.pdf("model"); + data = pdf->generate(RooArgSet(*w.var("x"), *w.cat("index")), RooFit::AllBinned()); + + nll.reset(pdf->createNLL(*data)); + + likelihood = RooFit::TestStatistics::build_simultaneous_likelihood(pdf, data); + RooFit::TestStatistics::LikelihoodSerial nll_ts(likelihood, clean_flags/*, nullptr*/); + + auto nll0 = nll->getVal(); + + nll_ts.evaluate(); + auto nll1 = nll_ts.return_result(); + + EXPECT_EQ(nll0, nll1); +} + +TEST_F(LikelihoodSerialTest, BinnedConstrained) +{ + // Unbinned pdfs that define template histograms + + w.factory("Gaussian::g(x[-10,10],0,2)"); + w.factory("Uniform::u(x)"); + + // Generate template histograms + + RooDataHist *h_sig = w.pdf("g")->generateBinned(*w.var("x"), 1000); + RooDataHist *h_bkg = w.pdf("u")->generateBinned(*w.var("x"), 1000); + + w.import(*h_sig, RooFit::Rename("h_sig")); + w.import(*h_bkg, RooFit::Rename("h_bkg")); + + // Construct binned pdf as sum of amplitudes + w.factory("HistFunc::hf_sig(x,h_sig)"); + w.factory("HistFunc::hf_bkg(x,h_bkg)"); + w.factory("ASUM::model_phys(mu_sig[1,-1,10]*hf_sig,expr::mu_bkg('1+0.02*alpha_bkg',alpha_bkg[-5,5])*hf_bkg)") ; + + // Construct L_subs: Gaussian subsidiary measurement that constrains alpha_bkg + w.factory("Gaussian:model_subs(alpha_bkg_obs[0],alpha_bkg,1)") ; + + // Construct full pdf + w.factory("PROD::model(model_phys,model_subs)") ; + + pdf = w.pdf("model"); + // Construct dataset from physics pdf + data = w.pdf("model_phys")->generateBinned(*w.var("x")); + + nll.reset(pdf->createNLL(*data, RooFit::GlobalObservables(*w.var("alpha_bkg_obs")))); + + // -------- + + auto nll0 = nll->getVal(); + + likelihood = +// std::make_shared(pdf, data, RooFit::GlobalObservables(*w.var("alpha_bkg_obs"))); +// std::make_shared(pdf, data); +// std::make_shared(pdf, data, RooFit::TestStatistics::GlobalObservables({*w.var("alpha_bkg_obs")})); + RooFit::TestStatistics::build_unbinned_constrained_likelihood(pdf, data, RooFit::TestStatistics::GlobalObservables({*w.var("alpha_bkg_obs")})); + RooFit::TestStatistics::LikelihoodSerial nll_ts(likelihood, clean_flags/*, nullptr*/); + + nll_ts.evaluate(); + auto nll1 = nll_ts.return_result(); + + EXPECT_EQ(nll0, nll1); +} + +TEST_F(LikelihoodSerialTest, SimUnbinned) +{ + // SIMULTANEOUS FIT OF 2 UNBINNED DATASETS + + w.factory("ExtendPdf::egA(Gaussian::gA(x[-10,10],mA[2,-10,10],s[3,0.1,10]),nA[1000])") ; + w.factory("ExtendPdf::egB(Gaussian::gB(x,mB[-2,-10,10],s),nB[100])") ; + w.factory("SIMUL::model(index[A,B],A=egA,B=egB)") ; + + pdf = w.pdf("model"); + // Construct dataset from physics pdf + data = pdf->generate(RooArgSet(*w.var("x"),*w.cat("index"))); + + nll.reset(pdf->createNLL(*data)); + + // -------- + + auto nll0 = nll->getVal(); + + likelihood = RooFit::TestStatistics::build_simultaneous_likelihood(pdf, data); + RooFit::TestStatistics::LikelihoodSerial nll_ts(likelihood, clean_flags/*, nullptr*/); + + nll_ts.evaluate(); + auto nll1 = nll_ts.return_result(); + + EXPECT_EQ(nll0, nll1); +} + +TEST_F(LikelihoodSerialTest, SimUnbinnedNonExtended) +{ + // SIMULTANEOUS FIT OF 2 UNBINNED DATASETS + + w.factory("ExtendPdf::egA(Gaussian::gA(x[-10,10],mA[2,-10,10],s[3,0.1,10]),nA[1000])") ; + w.factory("ExtendPdf::egB(Gaussian::gB(x,mB[-2,-10,10],s),nB[100])") ; + w.factory("SIMUL::model(index[A,B],A=gA,B=gB)") ; + + RooDataSet* dA = w.pdf("gA")->generate(*w.var("x"),1) ; + RooDataSet* dB = w.pdf("gB")->generate(*w.var("x"),1) ; + w.cat("index")->setLabel("A") ; + dA->addColumn(*w.cat("index")) ; + w.cat("index")->setLabel("B") ; + dB->addColumn(*w.cat("index")) ; + + data = (RooDataSet*) dA->Clone() ; + static_cast(data)->append(*dB) ; + + pdf = w.pdf("model"); + + nll.reset(pdf->createNLL(*data)); + + likelihood = RooFit::TestStatistics::build_simultaneous_likelihood(pdf, data); + RooFit::TestStatistics::LikelihoodSerial nll_ts(likelihood, clean_flags/*, nullptr*/); + + auto nll0 = nll->getVal(); + + nll_ts.evaluate(); + auto nll1 = nll_ts.return_result(); + + EXPECT_EQ(nll0, nll1); +} + + +class LikelihoodSerialSimBinnedConstrainedTest : public LikelihoodSerialTest { +protected: + void SetUp() override { + LikelihoodSerialTest::SetUp(); + // Unbinned pdfs that define template histograms + + w.factory("Gaussian::gA(x[-10,10],-2,3)") ; + w.factory("Gaussian::gB(x[-10,10],2,1)") ; + w.factory("Uniform::u(x)"); + + // Generate template histograms + + RooDataHist* h_sigA = w.pdf("gA")->generateBinned(*w.var("x"),1000) ; + RooDataHist* h_sigB = w.pdf("gB")->generateBinned(*w.var("x"),1000) ; + RooDataHist *h_bkg = w.pdf("u")->generateBinned(*w.var("x"), 1000); + + w.import(*h_sigA, RooFit::Rename("h_sigA")); + w.import(*h_sigB, RooFit::Rename("h_sigB")); + w.import(*h_bkg, RooFit::Rename("h_bkg")); + + // Construct binned pdf as sum of amplitudes + w.factory("HistFunc::hf_sigA(x,h_sigA)") ; + w.factory("HistFunc::hf_sigB(x,h_sigB)") ; + w.factory("HistFunc::hf_bkg(x,h_bkg)") ; + + w.factory("ASUM::model_phys_A(mu_sig[1,-1,10]*hf_sigA,expr::mu_bkg_A('1+0.02*alpha_bkg_A',alpha_bkg_A[-5,5])*hf_bkg)") ; + w.factory("ASUM::model_phys_B(mu_sig*hf_sigB,expr::mu_bkg_B('1+0.05*alpha_bkg_B',alpha_bkg_B[-5,5])*hf_bkg)") ; + + // Construct L_subs: Gaussian subsidiary measurement that constrains alpha_bkg + w.factory("Gaussian:model_subs_A(alpha_bkg_obs_A[0],alpha_bkg_A,1)") ; + w.factory("Gaussian:model_subs_B(alpha_bkg_obs_B[0],alpha_bkg_B,1)") ; + + // Construct full pdfs for each component (A,B) + w.factory("PROD::model_A(model_phys_A,model_subs_A)") ; + w.factory("PROD::model_B(model_phys_B,model_subs_B)") ; + + // Construct simulatenous pdf + w.factory("SIMUL::model(index[A,B],A=model_A,B=model_B)") ; + + pdf = w.pdf("model"); + // Construct dataset from physics pdf + data = pdf->generate(RooArgSet(*w.var("x"), *w.cat("index")), RooFit::AllBinned()); + } +}; + +TEST_F(LikelihoodSerialSimBinnedConstrainedTest, BasicParameters) +{ + // original test: + nll.reset(pdf->createNLL(*data, RooFit::GlobalObservables(RooArgSet(*w.var("alpha_bkg_obs_A"), *w.var("alpha_bkg_obs_B"))))); + + // -------- + + auto nll0 = nll->getVal(); + + likelihood = RooFit::TestStatistics::build_simultaneous_likelihood( + pdf, data, RooFit::TestStatistics::GlobalObservables({*w.var("alpha_bkg_obs_A"), *w.var("alpha_bkg_obs_B")})); + RooFit::TestStatistics::LikelihoodSerial nll_ts(likelihood, clean_flags/*, nullptr*/); + + nll_ts.evaluate(); + auto nll1 = nll_ts.return_result(); + + EXPECT_DOUBLE_EQ(nll0, nll1); +} + +TEST_F(LikelihoodSerialSimBinnedConstrainedTest, ConstrainedAndOffset) +{ + // a variation to test some additional parameters (ConstrainedParameters and offsetting) + nll.reset(pdf->createNLL(*data, RooFit::Constrain(RooArgSet(*w.var("alpha_bkg_obs_A"))), + RooFit::GlobalObservables(RooArgSet(*w.var("alpha_bkg_obs_B"))), RooFit::Offset(kTRUE))); + + // -------- + + auto nll0 = nll->getVal(); + + likelihood = RooFit::TestStatistics::build_simultaneous_likelihood( + pdf, data, RooFit::TestStatistics::ConstrainedParameters({*w.var("alpha_bkg_obs_A")}), + RooFit::TestStatistics::GlobalObservables({*w.var("alpha_bkg_obs_B")})); + RooFit::TestStatistics::LikelihoodSerial nll_ts(likelihood, clean_flags/*, nullptr*/); + nll_ts.enable_offsetting(true); + + nll_ts.evaluate(); + double nll1, carry1; + std::tie(nll1, carry1) = RooFit::kahan_add(nll_ts.return_result(), nll_ts.offset(), nll_ts.offset_carry() + likelihood->get_carry()); + + EXPECT_DOUBLE_EQ(nll0, nll1); + EXPECT_FALSE(nll_ts.offset() == 0); + + // also check against RooRealL value + RooFit::TestStatistics::RooRealL nll_real("real_nll", "RooRealL version", likelihood); + + auto nll2 = nll_real.getVal(); + + EXPECT_EQ(nll0, nll2); + EXPECT_DOUBLE_EQ(nll1, nll2); + +// printf("nll0: %a\tnll1: %a\tnll2: %a\tnll_ts.return_result(): %a\tnll_ts.offset: %a\tnll_ts.offset_carry: %a\tlikelihood.get_carry: %a\tcarry1: %a\n", nll0, nll1, nll2, nll_ts.return_result(), nll_ts.offset(), nll_ts.offset_carry(), likelihood->get_carry(), carry1); +} diff --git a/roofit/roofitcore/test/ULPdiff.h b/roofit/roofitcore/test/ULPdiff.h new file mode 100644 index 0000000000000..dc8884d25838d --- /dev/null +++ b/roofit/roofitcore/test/ULPdiff.h @@ -0,0 +1,80 @@ +/* + * ULP difference for use in test comparisons of floats/doubles + * Based on https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/ + */ + +#ifndef ROOT_ULPDIFF_H +#define ROOT_ULPDIFF_H + +#include // For int32_t, etc. + +union Floaty_t +{ + Floaty_t(float num = 0.0f) : f(num) {} + // Portable extraction of components. + bool Negative() const { return i < 0; } + + int32_t i; + float f; +}; + +union Doubly_t +{ + Doubly_t(double num = 0.0) : f(num) {} + // Portable extraction of components. + bool Negative() const { return i < 0; } + + int64_t i; + double f; +}; + + +int ulp_diff(float A, float B) { + Floaty_t uA(A); + Floaty_t uB(B); + + // Different signs means they do not match. + if (uA.Negative() != uB.Negative()) + { + // Check for equality to make sure +0==-0 + if (A == B) + return true; + return false; + } + + // Find the difference in ULPs. + int ulpsDiff = abs(uA.i - uB.i); + + return ulpsDiff; +} + +long int ulp_diff(double A, double B) { + Doubly_t uA(A); + Doubly_t uB(B); + + // Different signs means they do not match. + if (uA.Negative() != uB.Negative()) + { + // Check for equality to make sure +0==-0 + if (A == B) + return true; + return false; + } + + // Find the difference in ULPs. + long int ulpsDiff = abs(uA.i - uB.i); + + return ulpsDiff; +} + + +template +bool AlmostEqualUlps(F A, F B, I maxUlpsDiff) { + I ulpsDiff = ulp_diff(A, B); + if (ulpsDiff <= maxUlpsDiff) + return true; + + return false; +} + +#endif //ROOT_ULPDIFF_H diff --git a/roofit/roofitcore/test/testBidirMMapPipe.cxx b/roofit/roofitcore/test/testBidirMMapPipe.cxx new file mode 100644 index 0000000000000..59e40ba7ebbed --- /dev/null +++ b/roofit/roofitcore/test/testBidirMMapPipe.cxx @@ -0,0 +1,793 @@ +/***************************************************************************** + * Project: RooFit * + * Package: RooFitCore * + * @(#)root/roofitcore:$Id$ + * Authors: * + * PB, Patrick Bos, NL eScience Center, p.bos@esciencecenter.nl * + * IP, Inti Pelupessy, NL eScience Center, i.pelupessy@esciencecenter.nl * + * * + * Redistribution and use in source and binary forms, * + * with or without modification, are permitted according to the terms * + * listed in LICENSE (http://roofit.sourceforge.net/license.txt) * + *****************************************************************************/ + +#include + +#include +#include + +#include "gtest/gtest.h" + +using namespace RooFit; + +int simplechild(BidirMMapPipe& pipe) +{ + // child does an echo loop + while (pipe.good() && !pipe.eof()) { + // read a string + std::string str; + pipe >> str; + if (!pipe) return -1; + if (pipe.eof()) break; + if (!str.empty()) { + std::cout << "[CHILD (PID " << getpid() << ")] : read: " << str << std::endl; + str = str + "... early in the morning?"; + } + pipe << str << BidirMMapPipe::flush; + // did our parent tell us to shut down? + if (str.empty()) break; + if (!pipe) return -1; + if (pipe.eof()) break; + std::cout << "[CHILD (PID " << getpid() << ")] : wrote: " << str << std::endl; + } + std::cout << "[CHILD (PID " << getpid() << ")] : shutting down " << pipe.isParent() << std::endl; + std::cout << "[CHILD (PID " << getpid() << ")] : close " << pipe.close() << std::endl; + return 0; +} + + +#include +int randomchild(BidirMMapPipe& pipe, bool grand) +{ + // child sends out something at random intervals + ::srand48(::getpid()); + { + // wait for parent's go ahead signal + std::string s; + pipe >> s; + } + std::string prestring, PRESTRING; + if (grand) { + prestring = "grand"; + PRESTRING = "GRAND"; + } + // no shutdown sequence needed on this side - we're producing the data, + // and the parent can just read until we're done (when it'll get EOF) + for (int i = 0; i < 5; ++i) { + // sleep a random time between 0 and .9 seconds + ::usleep(int(1e6 * ::drand48())); + std::ostringstream buf; + buf << prestring << "child pid " << ::getpid() << " sends message " << i; + std::string str = buf.str(); + std::cout << "[" << PRESTRING << "CHILD (PID " << getpid() << ")] : " << str << std::endl; + pipe << str << BidirMMapPipe::flush; + if (!pipe) return -1; + if (pipe.eof()) break; + } + // tell parent we're shutting down + pipe << "" << BidirMMapPipe::flush; + // wait for parent to acknowledge + std::string s; + pipe >> s; + pipe.close(); + return 0; +} + +int benchchildrtt(BidirMMapPipe& pipe) +{ + // child does the equivalent of listening for pings and sending the + // packet back + char* str = 0; + while (pipe && !pipe.eof()) { + pipe >> str; + if (!pipe) { + std::free(str); + pipe.close(); + return -1; + } + if (pipe.eof()) break; + pipe << str << BidirMMapPipe::flush; + // if we have just completed the shutdown handshake, we break here + if (!std::strlen(str)) break; + } + std::free(str); + pipe.close(); + return 0; +} + +int benchchildsink(BidirMMapPipe& pipe) +{ + // child behaves like a sink + char* str = 0; + while (pipe && !pipe.eof()) { + pipe >> str; + if (!std::strlen(str)) break; + } + pipe << "" << BidirMMapPipe::flush; + std::free(str); + pipe.close(); + return 0; +} + +int benchchildsource(BidirMMapPipe& pipe) +{ + // child behaves like a source + char* str = 0; + for (unsigned i = 0; i <= 24; ++i) { + str = reinterpret_cast(std::realloc(str, (1 << i) + 1)); + std::memset(str, '4', 1 << i); + str[1 << i] = 0; + for (unsigned j = 0; j < 1 << 7; ++j) { + pipe << str; + if (!pipe || pipe.eof()) { + std::free(str); + pipe.close(); + return -1; + } + } + // tell parent we're done with this block size + pipe << "" << BidirMMapPipe::flush; + } + // tell parent to shut down + pipe << "" << BidirMMapPipe::flush; + std::free(str); + pipe.close(); + return 0; +} + +template +BidirMMapPipe* spawnChild(int (*childexec)(BidirMMapPipe&, Args...), Args... args) +{ + // create a pipe with the given child at the remote end + BidirMMapPipe *p = new BidirMMapPipe(); + if (p->isChild()) { + int retVal = childexec(*p, args...); + delete p; + std::_Exit(retVal); + } + return p; +} + +// simple echo loop test +TEST(BidirMMapPipe, simple){ + std::cout << "[PARENT]: simple challenge-response test, " + "one child:" << std::endl; + BidirMMapPipe* pipe = spawnChild(simplechild); + for (int i = 0; i < 5; ++i) { + std::string str("What shall we do with a drunken sailor..."); + *pipe << str << BidirMMapPipe::flush; + ASSERT_TRUE(*pipe); + std::cout << "[PARENT (PID " << getpid() << ")]: wrote: " << str << std::endl; + *pipe >> str; + ASSERT_TRUE(*pipe); + std::cout << "[PARENT (PID " << getpid() << ")]: read: " << str << std::endl; + } + // send shutdown string + *pipe << "" << BidirMMapPipe::flush; + // wait for shutdown handshake + std::string s; + *pipe >> s; + int retVal = pipe->close(); + std::cout << "[PARENT (PID " << getpid() << ")]: exit status of child: " << retVal << + std::endl; + delete pipe; +} + + // simple poll test - children send 5 results in random intervals +TEST(BidirMMapPipe, poll) + { + unsigned nch = 20; + std::cout << std::endl << "[PARENT (PID " << getpid() << ")]: polling test, " << nch << + " children:" << std::endl; + typedef BidirMMapPipe::PollEntry PollEntry; + // poll data structure + BidirMMapPipe::PollVector pipes; + pipes.reserve(nch); + // spawn children + for (unsigned i = 0; i < nch; ++i) { + std::cout << "[PARENT (PID " << getpid() << ")]: spawning child " << i << std::endl; + pipes.push_back(PollEntry(spawnChild(randomchild, false), + BidirMMapPipe::Readable)); + } + // wake children up + std::cout << "[PARENT (PID " << getpid() << ")]: waking up children" << std::endl; + for (unsigned i = 0; i < nch; ++i) + *pipes[i].pipe << "" << BidirMMapPipe::flush; + std::cout << "[PARENT (PID " << getpid() << ")]: waiting for events on children's pipes" << std::endl; + // while at least some children alive + while (!pipes.empty()) { + // poll, wait until status change (infinite timeout) + int npipes = BidirMMapPipe::poll(pipes, -1); + // scan for pipes with changed status + for (std::vector::iterator it = pipes.begin(); + npipes && pipes.end() != it; ) { + if (!it->revents) { + // unchanged, next one + ++it; + continue; + } + --npipes; // maybe we can stop early... + // read from pipes which are readable + if (it->revents & BidirMMapPipe::Readable) { + std::string s; + *(it->pipe) >> s; + if (!s.empty()) { + std::cout << "[PARENT (PID " << getpid() << ")]: Read from pipe " << it->pipe << + ": " << s << std::endl; + ++it; + continue; + } else { + // child is shutting down... + *(it->pipe) << "" << BidirMMapPipe::flush; + goto childcloses; + } + } + // retire pipes with error or end-of-file condition + if (it->revents & (BidirMMapPipe::Error | + BidirMMapPipe::EndOfFile | + BidirMMapPipe::Invalid)) { + std::cerr << "[DEBUG]: Event on pipe " << it->pipe << + " revents" << + ((it->revents & BidirMMapPipe::Readable) ? " Readable" : "") << + ((it->revents & BidirMMapPipe::Writable) ? " Writable" : "") << + ((it->revents & BidirMMapPipe::ReadError) ? " ReadError" : "") << + ((it->revents & BidirMMapPipe::WriteError) ? " WriteError" : "") << + ((it->revents & BidirMMapPipe::ReadEndOfFile) ? " ReadEndOfFile" : "") << + ((it->revents & BidirMMapPipe::WriteEndOfFile) ? " WriteEndOfFile" : "") << + ((it->revents & BidirMMapPipe::ReadInvalid) ? " ReadInvalid" : "") << + ((it->revents & BidirMMapPipe::WriteInvalid) ? " WriteInvalid" : "") << + std::endl; +childcloses: + int retVal = it->pipe->close(); + std::cout << "[PARENT (PID " << getpid() << ")]: child exit status: " << + retVal << ", number of children still alive: " << + (pipes.size() - 1) << std::endl; + delete it->pipe; + it = pipes.erase(it); + continue; + } + } + } + } + //~ // little benchmark - round trip time + //~ { + //~ std::cout << std::endl << "[PARENT]: benchmark: round-trip times vs block size" << std::endl; + //~ for (unsigned i = 0; i <= 24; ++i) { + //~ char *s = new char[1 + (1 << i)]; + //~ std::memset(s, 'A', 1 << i); + //~ s[1 << i] = 0; + //~ const unsigned n = 1 << 7; + //~ double avg = 0., min = 1e42, max = -1e42; + //~ BidirMMapPipe *pipe = spawnChild(benchchildrtt); + //~ for (unsigned j = n; j--; ) { + //~ struct timeval t1; + //~ ::gettimeofday(&t1, 0); + //~ *pipe << s << BidirMMapPipe::flush; + //~ if (!*pipe || pipe->eof()) break; + //~ *pipe >> s; + //~ if (!*pipe || pipe->eof()) break; + //~ struct timeval t2; + //~ ::gettimeofday(&t2, 0); + //~ t2.tv_sec -= t1.tv_sec; + //~ t2.tv_usec -= t1.tv_usec; + //~ double dt = 1e-6 * double(t2.tv_usec) + double(t2.tv_sec); + //~ if (dt < min) min = dt; + //~ if (dt > max) max = dt; + //~ avg += dt; + //~ } + //~ // send a shutdown string + //~ *pipe << "" << BidirMMapPipe::flush; + //~ // get child's shutdown ok + //~ *pipe >> s; + //~ avg /= double(n); + //~ avg *= 1e6; min *= 1e6; max *= 1e6; + //~ int retVal = pipe->close(); + //~ if (retVal) { + //~ std::cout << "[PARENT]: child exited with code " << retVal << std::endl; + //~ return retVal; + //~ } + //~ delete pipe; + //~ // there is a factor 2 in the formula for the transfer rate below, + //~ // because we transfer data of twice the size of the block - once + //~ // to the child, and once for the return trip + //~ std::cout << "block size " << std::setw(9) << (1 << i) << + //~ " avg " << std::setw(7) << avg << " us min " << + //~ std::setw(7) << min << " us max " << std::setw(7) << max << + //~ "us speed " << std::setw(9) << + //~ 2. * (double(1 << i) / double(1 << 20) / (1e-6 * avg)) << + //~ " MB/s" << std::endl; + //~ delete[] s; + //~ } + //~ std::cout << "[PARENT]: all children had exit code 0" << std::endl; + //~ } + //~ // little benchmark - child as sink + //~ { + //~ std::cout << std::endl << "[PARENT]: benchmark: raw transfer rate with child as sink" << std::endl; + //~ for (unsigned i = 0; i <= 24; ++i) { + //~ char *s = new char[1 + (1 << i)]; + //~ std::memset(s, 'A', 1 << i); + //~ s[1 << i] = 0; + //~ const unsigned n = 1 << 7; + //~ double avg = 0., min = 1e42, max = -1e42; + //~ BidirMMapPipe *pipe = spawnChild(benchchildsink); + //~ for (unsigned j = n; j--; ) { + //~ struct timeval t1; + //~ ::gettimeofday(&t1, 0); + //~ // streaming mode - we do not flush here + //~ *pipe << s; + //~ if (!*pipe || pipe->eof()) break; + //~ struct timeval t2; + //~ ::gettimeofday(&t2, 0); + //~ t2.tv_sec -= t1.tv_sec; + //~ t2.tv_usec -= t1.tv_usec; + //~ double dt = 1e-6 * double(t2.tv_usec) + double(t2.tv_sec); + //~ if (dt < min) min = dt; + //~ if (dt > max) max = dt; + //~ avg += dt; + //~ } + //~ // send a shutdown string + //~ *pipe << "" << BidirMMapPipe::flush; + //~ // get child's shutdown ok + //~ *pipe >> s; + //~ avg /= double(n); + //~ avg *= 1e6; min *= 1e6; max *= 1e6; + //~ int retVal = pipe->close(); + //~ if (retVal) { + //~ std::cout << "[PARENT]: child exited with code " << retVal << std::endl; + //~ return retVal; + //~ } + //~ delete pipe; + //~ std::cout << "block size " << std::setw(9) << (1 << i) << + //~ " avg " << std::setw(7) << avg << " us min " << + //~ std::setw(7) << min << " us max " << std::setw(7) << max << + //~ "us speed " << std::setw(9) << + //~ (double(1 << i) / double(1 << 20) / (1e-6 * avg)) << + //~ " MB/s" << std::endl; + //~ delete[] s; + //~ } + //~ std::cout << "[PARENT]: all children had exit code 0" << std::endl; + //~ } + //~ // little benchmark - child as source + //~ { + //~ std::cout << std::endl << "[PARENT]: benchmark: raw transfer rate with child as source" << std::endl; + //~ char *s = 0; + //~ double avg = 0., min = 1e42, max = -1e42; + //~ unsigned n = 0, bsz = 0; + //~ BidirMMapPipe *pipe = spawnChild(benchchildsource); + //~ while (*pipe && !pipe->eof()) { + //~ struct timeval t1; + //~ ::gettimeofday(&t1, 0); + //~ // streaming mode - we do not flush here + //~ *pipe >> s; + //~ if (!*pipe || pipe->eof()) break; + //~ struct timeval t2; + //~ ::gettimeofday(&t2, 0); + //~ t2.tv_sec -= t1.tv_sec; + //~ t2.tv_usec -= t1.tv_usec; + //~ double dt = 1e-6 * double(t2.tv_usec) + double(t2.tv_sec); + //~ if (std::strlen(s)) { + //~ ++n; + //~ if (dt < min) min = dt; + //~ if (dt > max) max = dt; + //~ avg += dt; + //~ bsz = std::strlen(s); + //~ } else { + //~ if (!n) break; + //~ // next block size + //~ avg /= double(n); + //~ avg *= 1e6; min *= 1e6; max *= 1e6; +//~ + //~ std::cout << "block size " << std::setw(9) << bsz << + //~ " avg " << std::setw(7) << avg << " us min " << + //~ std::setw(7) << min << " us max " << std::setw(7) << + //~ max << "us speed " << std::setw(9) << + //~ (double(bsz) / double(1 << 20) / (1e-6 * avg)) << + //~ " MB/s" << std::endl; + //~ n = 0; + //~ avg = 0.; + //~ min = 1e42; + //~ max = -1e42; + //~ } + //~ } + //~ int retVal = pipe->close(); + //~ std::cout << "[PARENT]: child exited with code " << retVal << std::endl; + //~ if (retVal) return retVal; + //~ delete pipe; + //~ std::free(s); + //~ } + //~ return 0; + +// additional tests + + +int simplerelay(BidirMMapPipe& in,BidirMMapPipe& out) +{ + // child does an echo loop + while (in.good() && !in.eof()) { + // read a string + std::string str; + in >> str; + if (!in) return -1; + if (in.eof()) break; + //~ if (!str.empty()) { + std::cout << "[RELAY (PID " << getpid() << ")] : from in: " << str << std::endl; + out << str << BidirMMapPipe::flush; + std::cout << "[RELAY (PID " << getpid() << ")] : to out: " << str << std::endl; + //~ } + // did our parent tell us to shut down? + if (str.empty()) break; + if (!out) return -1; + if (out.eof()) break; + out >> str; + if (!in) return -1; + if (in.eof()) break; + //~ if (!str.empty()) { + std::cout << "[RELAY (PID " << getpid() << ")] : from out: " << str << std::endl; + in << str << BidirMMapPipe::flush; + std::cout << "[RELAY (PID " << getpid() << ")] : to in: " << str << std::endl; + //~ } + } + std::cout << "[RELAY (PID " << getpid() << ")] : shutting down " << std::endl; + return 0; +} + + +#include +#include + +// simple echo loop test +template +int simple_echo(T& out, BidirMMapPipe* pipe, std::string extra_string = "") +{ + { + out << "[PARENT (PID " << getpid() << ")]: simple challenge-response test, " + "one child (extra_string: " << extra_string << "):" << std::endl; + for (int i = 0; i < 2; ++i) { + std::string str("What shall we do with a drunken sailor..."); + str += extra_string; + *pipe << str + " "+ std::to_string(i) << BidirMMapPipe::flush; + if (!*pipe) return -1; + out << "[PARENT (PID " << getpid() << ")]: wrote: " << str << std::endl; + *pipe >> str; + if (!*pipe) return -1; + out << "[PARENT (PID " << getpid() << ")]: read: " << str << std::endl; +// out.flush(); + } + // send shutdown string + *pipe << "" << BidirMMapPipe::flush; + // wait for shutdown handshake + std::string s; + *pipe >> s; + int retVal = pipe->close(); + out << "[PARENT (PID " << getpid() << ")]: status of child: " << std::to_string(retVal) << + std::endl; +// out.flush(); + return retVal; + } +} + +void simple_echo_direct() +{ + BidirMMapPipe* pipe = spawnChild(simplechild); + simple_echo(std::cout, pipe); + delete pipe; +} + +void simple_echo_relay() +{ + BidirMMapPipe* pipe1 = spawnChild(simplechild); + BidirMMapPipe* pipe2 = new BidirMMapPipe(true, false, false); + + if(pipe2->isChild()) { + std :: cout << "child of p2 considers itself pipe parent? " << pipe1->isParent() << std:: endl; + simplerelay(*pipe2, *pipe1); // forward output over pipe2 + pipe2->close(); + pipe1->close(); + } + if(pipe2->isParent()) { + simple_echo(std::cout, pipe2); + } + std::cout << "ending" << std::endl; +} + +TEST(BidirMMapPipe, direct) +{ + simple_echo_direct(); +} + + +TEST(BidirMMapPipe, relay) +{ + simple_echo_relay(); +} + + +TEST(BidirMMapPipe, bothDirectAndRelay) +{ + simple_echo_direct(); + simple_echo_relay(); + simple_echo_direct(); +} + + +TEST(BidirMMapPipe, grandChild) { + BidirMMapPipe* pipe1 = new BidirMMapPipe(); + if (pipe1->isChild()) { + BidirMMapPipe* pipe2 = spawnChild(simplechild); + + if(pipe2->isParent()) { + simplerelay(*pipe1, *pipe2); // forward output over pipe2 + pipe1->close(); + } + } else { + // send an echo over pipe1 from master, which simplerelay should send on over pipe2 + simple_echo(std::cout, pipe1); + } + + std::cout << "ending" << std::endl; +} + +TEST(BidirMMapPipe, greatGrandChild) { + BidirMMapPipe* pipe1 = new BidirMMapPipe(); + if (pipe1->isChild()) { + BidirMMapPipe* pipe2 = new BidirMMapPipe(); + + if(pipe2->isParent()) { + simplerelay(*pipe1, *pipe2); // forward output over pipe2 + } else { + BidirMMapPipe *pipe3 = spawnChild(simplechild); + + if (pipe3->isParent()) { + simplerelay(*pipe2, *pipe3); // forward output over pipe2 + pipe2->close(); + pipe1->close(); + } + } + + } else { + // send an echo over pipe1 from master, which simplerelay should send on over pipe2 and pipe3 + simple_echo(std::cout, pipe1); + } + + std::cout << "ending" << std::endl; +} + + +TEST(BidirMMapPipe, multipleChildrenAndGrandChildren) { + BidirMMapPipe* child1 = new BidirMMapPipe(); + if (child1->isChild()) { + BidirMMapPipe* grandchild1_1 = spawnChild(simplechild); + + if(grandchild1_1->isParent()) { + simplerelay(*child1, *grandchild1_1); // forward output over grandchild1_1 + delete grandchild1_1; + std::_Exit(0); // exit child1 process + } + } else { + BidirMMapPipe* child2 = new BidirMMapPipe(); + + if (child2->isChild()) { + BidirMMapPipe* grandchild2_1 = spawnChild(simplechild); + + if(grandchild2_1->isParent()) { + simplerelay(*child2, *grandchild2_1); // forward output over grandchild2_1 +// child2->close(); + delete grandchild2_1; +// delete child2; + std::_Exit(0); // exit child2 process + } + } else { + // send an echo over child2 from master, which simplerelay should send on over grandchild2_1 + simple_echo(std::cout, child2, " second child"); + } + + delete child2; + + // send an echo over child1 from master, which simplerelay should send on over grandchild1_1 + simple_echo(std::cout, child1, " first child"); + } + + delete child1; + + std::cout << "ending" << std::endl; +} + + +void poll_relay(BidirMMapPipe& parent, BidirMMapPipe::PollVector & grandchildren) { + { + // wait for parent's go ahead signal + std::string s; + parent >> s; + // and relay it to grandchildren + std::cout << "[CHILD (PID " << getpid() << ")]: waking up my grandchildren" << std::endl; + for (unsigned i = 0; i < grandchildren.size(); ++i) { + *grandchildren[i].pipe << "" << BidirMMapPipe::flush; + } + } + + std::cout << "[CHILD (PID " << getpid() << ")]: waiting for events on grandchildren's pipes" << std::endl; + // while at least some children alive + while (!grandchildren.empty()) { + // poll, wait until status change (infinite timeout) + int npipes = BidirMMapPipe::poll(grandchildren, -1); + // scan for pipes with changed status + for (auto it = grandchildren.begin(); npipes && grandchildren.end() != it; ) { + if (!it->revents) { + // unchanged, next one + ++it; + continue; + } + --npipes; // maybe we can stop early... + // read from pipes which are readable + if (it->revents & BidirMMapPipe::Readable) { + std::string s; + pid_t grandchild_pid = it->pipe->pidOtherEnd(); + *(it->pipe) >> s; + if (!s.empty()) { + std::cout << "[CHILD (PID " << getpid() << ")]: Read from pipe " << it->pipe << + ": " << s << ", passing on to parent" << std::endl; + std::stringstream ss; + ss << "from child " << getpid() << s; + parent << ss.str() << grandchild_pid << BidirMMapPipe::flush; + ++it; + continue; + } else { + // grandchild is shutting down... + *(it->pipe) << "" << BidirMMapPipe::flush; + goto grandchildcloses; + } + } + // retire pipes with error or end-of-file condition + if (it->revents & (BidirMMapPipe::Error | + BidirMMapPipe::EndOfFile | + BidirMMapPipe::Invalid)) { + std::cerr << "[DEBUG GRANDCHILD]: Event on pipe " << it->pipe << + " revents" << + ((it->revents & BidirMMapPipe::Readable) ? " Readable" : "") << + ((it->revents & BidirMMapPipe::Writable) ? " Writable" : "") << + ((it->revents & BidirMMapPipe::ReadError) ? " ReadError" : "") << + ((it->revents & BidirMMapPipe::WriteError) ? " WriteError" : "") << + ((it->revents & BidirMMapPipe::ReadEndOfFile) ? " ReadEndOfFile" : "") << + ((it->revents & BidirMMapPipe::WriteEndOfFile) ? " WriteEndOfFile" : "") << + ((it->revents & BidirMMapPipe::ReadInvalid) ? " ReadInvalid" : "") << + ((it->revents & BidirMMapPipe::WriteInvalid) ? " WriteInvalid" : "") << + std::endl; + grandchildcloses: + int retVal = it->pipe->close(); + std::cout << "[CHILD (PID " << getpid() << ")]: grandchild exit status: " << + retVal << ", number of grandchildren still alive: " << + (grandchildren.size() - 1) << std::endl; + delete it->pipe; + it = grandchildren.erase(it); + continue; + } + } + } + +} + + +// hierarchical poll test - grandchildren send 5 results in random intervals +TEST(BidirMMapPipe, pollHierarchy) { + unsigned nch = 5; // spawn 5 children and 5x5 grandchildren, 31 processes in total + std::cout << std::endl << "[PARENT (PID " << getpid() << ")]: polling test, " << nch << + " children, " << nch*nch << " grandchildren:" << std::endl; + + // poll data structures + BidirMMapPipe::PollVector children, grandchildren; + children.reserve(nch); + grandchildren.reserve(nch); + std::map N_received_signals; + + // spawn children + for (unsigned i = 0; i < nch; ++i) { + std::cout << "[PARENT (PID " << getpid() << ")]: spawning child " << i << std::endl; + children.emplace_back(new BidirMMapPipe(), BidirMMapPipe::Readable); + + BidirMMapPipe& child = *children.back().pipe; + // THE BELOW BLOCK TAKES PLACE ON THE CHILDREN + if (child.isChild()) { + for (unsigned j = 0; j < nch; ++j) { + std::cout << "[CHILD " << i << " (PID " << getpid() << ")]: spawning grandchild " << j << std::endl; + grandchildren.emplace_back(spawnChild(randomchild, true), BidirMMapPipe::Readable); + } + poll_relay(child, grandchildren); + std::cout << "[CHILD " << i << " (PID " << getpid() << ")]: finished poll_relay" << std::endl; + + // tell parent we're shutting down + child << "" << BidirMMapPipe::flush; + std::cout << "[CHILD " << i << " (PID " << getpid() << ")]: told parent we're shutting down" << std::endl; + // wait for parent to acknowledge + std::string s; + child >> s; + std::cout << "[CHILD " << i << " (PID " << getpid() << ")]: parent acknowledged, exiting process" << std::endl; +// child.close(); + + std::_Exit(0); + } + // THE ABOVE BLOCK TAKES PLACE ON THE CHILDREN + + } + + // wake grandchildren up via children + std::cout << "[PARENT (PID " << getpid() << ")]: waking up grandchildren via children" << std::endl; + for (unsigned i = 0; i < nch; ++i) { + *children[i].pipe << "" << BidirMMapPipe::flush; + } + + std::cout << "[PARENT (PID " << getpid() << ")]: waiting for events on children's pipes" << std::endl; + // while at least some children alive + while (!children.empty()) { + // poll, wait until status change (infinite timeout) + int npipes = BidirMMapPipe::poll(children, -1); + // scan for pipes with changed status + for (auto it = children.begin(); npipes && children.end() != it; ) { + if (!it->revents) { + // unchanged, next one + ++it; + continue; + } + --npipes; // maybe we can stop early... + // read from pipes which are readable + if (it->revents & BidirMMapPipe::Readable) { + std::string s; + *(it->pipe) >> s; + if (!s.empty()) { + pid_t grandchild_pid; + *(it->pipe) >> grandchild_pid; + std::cout << "[PARENT (PID " << getpid() << ")]: Read from pipe " << it->pipe << + ": " << s << std::endl; + ++it; + ++N_received_signals[grandchild_pid]; + continue; + } else { + // child is shutting down... + std::cout << "[PARENT (PID " << getpid() << ")]: got shutdown message (empty string) from pipe " << it->pipe << ", sending back shutdown handshake" << std::endl; + *(it->pipe) << "" << BidirMMapPipe::flush; + goto childcloses; + } + } + // retire pipes with error or end-of-file condition + if (it->revents & (BidirMMapPipe::Error | + BidirMMapPipe::EndOfFile | + BidirMMapPipe::Invalid)) { + std::cerr << "[DEBUG CHILD]: Event on pipe " << it->pipe << + " revents" << + ((it->revents & BidirMMapPipe::Readable) ? " Readable" : "") << + ((it->revents & BidirMMapPipe::Writable) ? " Writable" : "") << + ((it->revents & BidirMMapPipe::ReadError) ? " ReadError" : "") << + ((it->revents & BidirMMapPipe::WriteError) ? " WriteError" : "") << + ((it->revents & BidirMMapPipe::ReadEndOfFile) ? " ReadEndOfFile" : "") << + ((it->revents & BidirMMapPipe::WriteEndOfFile) ? " WriteEndOfFile" : "") << + ((it->revents & BidirMMapPipe::ReadInvalid) ? " ReadInvalid" : "") << + ((it->revents & BidirMMapPipe::WriteInvalid) ? " WriteInvalid" : "") << + std::endl; + childcloses: + int retVal = it->pipe->close(); + std::cout << "[PARENT (PID " << getpid() << ")]: child exit status: " << + retVal << ", number of children still alive: " << + (children.size() - 1) << std::endl; + delete it->pipe; + it = children.erase(it); + continue; + } + } + } + + std::cout << "\n" << "number of grandchildren that sent signals: " << N_received_signals.size() << "\n"; + for (auto element : N_received_signals) { + std::cout << "counted " << element.second << " signals from grandchild with PID " << element.first << "\n"; + EXPECT_EQ(element.second, 5u); + } + std::cout << std::flush; +} diff --git a/roofit/roofitcore/test/testRooCacheManager.cxx b/roofit/roofitcore/test/testRooCacheManager.cxx new file mode 100644 index 0000000000000..464d109c6bb6d --- /dev/null +++ b/roofit/roofitcore/test/testRooCacheManager.cxx @@ -0,0 +1,62 @@ +// Tests for the RooCacheManager +// Author: Jonas Rembser, CERN, May 2021 + +#include "RooObjCacheManager.h" +#include "RooRealVar.h" + +#include "gtest/gtest.h" + +#include +#include + +TEST(RooCacheManager, TestSelectFromArgSet) +{ + // Test RooCacheManager::selectFromSet1 and RooCacheManager::selectFromSet2. + + // the cached class doesn't matter for this test, it just has to be an object that the cache is going to own + class CacheElem : public RooAbsCacheElement { + public: + virtual ~CacheElem() {} + virtual RooArgList containedArgs(Action) { return {}; } + }; + + // RooObjCacheManager is a wrapper around RooCacheManager + RooObjCacheManager mgr{nullptr, 100}; + + // fill some vector with RooAbsReals for testing + std::vector vars; + for (int i = 0; i < 10; ++i) { + auto name = std::string("v") + std::to_string(i); + vars.emplace_back(name.c_str(), name.c_str(), static_cast(i)); + } + + RooArgSet nset1{vars[0], vars[1], "nset1"}; + RooArgSet iset1{vars[2], vars[3], vars[4], "nset2"}; + + RooArgSet nset2{vars[5], vars[6], vars[7], "nset1"}; + RooArgSet iset2{vars[8], vars[9], "nset2"}; + + int idx1 = mgr.setObj(&nset1, &iset1, new CacheElem); + int idx2 = mgr.setObj(&nset2, &iset2, new CacheElem); + + std::cout << idx1 << std::endl; + std::cout << idx2 << std::endl; + + auto sel11 = mgr.selectFromSet1({vars[0], vars[4]}, idx1); + auto sel12 = mgr.selectFromSet2({vars[2], vars[4]}, idx1); + + auto sel21 = mgr.selectFromSet1({vars[1], vars[6]}, idx2); + auto sel22 = mgr.selectFromSet2({vars[0], vars[1]}, idx2); + + // check if the expected number of args were selected + EXPECT_EQ(sel11.size(), 1); + EXPECT_EQ(sel12.size(), 2); + EXPECT_EQ(sel21.size(), 1); + EXPECT_EQ(sel22.size(), 0); + + // check if the correct args were selected + EXPECT_TRUE(sel11.containsInstance(vars[0])); + EXPECT_TRUE(sel12.containsInstance(vars[2])); + EXPECT_TRUE(sel12.containsInstance(vars[4])); + EXPECT_TRUE(sel21.containsInstance(vars[6])); +} diff --git a/roofit/roofitcore/test/test_lib.h b/roofit/roofitcore/test/test_lib.h new file mode 100644 index 0000000000000..2543eb93a7a63 --- /dev/null +++ b/roofit/roofitcore/test/test_lib.h @@ -0,0 +1,185 @@ +/***************************************************************************** + * Project: RooFit * + * Package: RooFitCore * + * @(#)root/roofitcore:$Id$ + * Authors: * + * PB, Patrick Bos, NL eScience Center, p.bos@esciencecenter.nl * + * * + * Redistribution and use in source and binary forms, * + * with or without modification, are permitted according to the terms * + * listed in LICENSE (http://roofit.sourceforge.net/license.txt) * + *****************************************************************************/ + +#ifndef ROOT_TEST_LIB_H +#define ROOT_TEST_LIB_H + +#include + +#include // make_unique + +#include "RooWorkspace.h" +#include "RooRandom.h" +#include "RooAddPdf.h" +#include "RooDataSet.h" +#include "RooRealVar.h" // for the dynamic cast to have a complete type + + +RooAbsPdf * generate_1D_gaussian_pdf(RooWorkspace &w) +{ + w.factory("Gaussian::g(x[-5,5],mu[0,-3,3],sigma[1])"); + RooAbsPdf *pdf = w.pdf("g"); + return pdf; +} + +RooDataSet * generate_1D_dataset(RooWorkspace &w, RooAbsPdf *pdf, unsigned long N_events) +{ + RooDataSet *data = pdf->generate(RooArgSet(*w.var("x")), N_events); + return data; +} + + +std::tuple, RooAbsPdf *, RooDataSet *, std::unique_ptr> +generate_1D_gaussian_pdf_nll(RooWorkspace &w, unsigned long N_events) +{ + RooAbsPdf *pdf = generate_1D_gaussian_pdf(w); + + RooDataSet *data = generate_1D_dataset(w, pdf, N_events); + + RooRealVar *mu = w.var("mu"); + mu->setVal(-2.9); + + std::unique_ptr nll{pdf->createNLL(*data)}; + + // save initial values for the start of all minimizations + std::unique_ptr values = std::make_unique(*mu, *pdf, *nll, "values"); + + return std::make_tuple(std::move(nll), pdf, data, std::move(values)); +} + +// return two unique_ptrs, the first because nll is a pointer, +// the second because RooArgSet doesn't have a move ctor +std::tuple, RooAbsPdf *, RooDataSet *, std::unique_ptr> +generate_ND_gaussian_pdf_nll(RooWorkspace &w, unsigned int n, unsigned long N_events) { + RooArgSet obs_set; + + // create gaussian parameters + double mean[n], sigma[n]; + for (unsigned ix = 0; ix < n; ++ix) { + mean[ix] = RooRandom::randomGenerator()->Gaus(0, 2); + sigma[ix] = 0.1 + abs(RooRandom::randomGenerator()->Gaus(0, 2)); + } + + // create gaussians and also the observables and parameters they depend on + for (unsigned ix = 0; ix < n; ++ix) { + std::ostringstream os; + os << "Gaussian::g" << ix + << "(x" << ix << "[-10,10]," + << "m" << ix << "[" << mean[ix] << ",-10,10]," + << "s" << ix << "[" << sigma[ix] << ",0.1,10])"; + w.factory(os.str().c_str()); + } + + // create uniform background signals on each observable + for (unsigned ix = 0; ix < n; ++ix) { + { + std::ostringstream os; + os << "Uniform::u" << ix << "(x" << ix << ")"; + w.factory(os.str().c_str()); + } + + // gather the observables in a list for data generation below + { + std::ostringstream os; + os << "x" << ix; + obs_set.add(*w.arg(os.str().c_str())); + } + } + + RooArgSet pdf_set = w.allPdfs(); + + // create event counts for all pdfs + RooArgSet count_set; + + // ... for the gaussians + for (unsigned ix = 0; ix < n; ++ix) { + std::stringstream os, os2; + os << "Nsig" << ix; + os2 << "#signal events comp " << ix; + RooRealVar a(os.str().c_str(), os2.str().c_str(), N_events/10, 0., 10*N_events); + w.import(a); + // gather in count_set + count_set.add(*w.arg(os.str().c_str())); + } + // ... and for the uniform background components + for (unsigned ix = 0; ix < n; ++ix) { + std::stringstream os, os2; + os << "Nbkg" << ix; + os2 << "#background events comp " << ix; + RooRealVar a(os.str().c_str(), os2.str().c_str(), N_events/10, 0., 10*N_events); + w.import(a); + // gather in count_set + count_set.add(*w.arg(os.str().c_str())); + } + + RooAddPdf* sum = new RooAddPdf("sum", "gaussians+uniforms", pdf_set, count_set); + w.import(*sum); // keep sum around after returning + + // --- Generate a toyMC sample from composite PDF --- + RooDataSet *data = sum->generate(obs_set, N_events); + + std::unique_ptr nll {sum->createNLL(*data)}; + + // set values randomly so that they actually need to do some fitting + for (unsigned ix = 0; ix < n; ++ix) { + { + std::ostringstream os; + os << "m" << ix; + dynamic_cast(w.arg(os.str().c_str()))->setVal(RooRandom::randomGenerator()->Gaus(0, 2)); + } + { + std::ostringstream os; + os << "s" << ix; + dynamic_cast(w.arg(os.str().c_str()))->setVal(0.1 + abs(RooRandom::randomGenerator()->Gaus(0, 2))); + } + } + + // gather all values of parameters, pdfs and nll here for easy + // saving and restoring + std::unique_ptr all_values = std::make_unique(pdf_set, count_set, "all_values"); + all_values->add(*nll); + all_values->add(*sum); + for (unsigned ix = 0; ix < n; ++ix) { + { + std::ostringstream os; + os << "m" << ix; + all_values->add(*w.arg(os.str().c_str())); + } + { + std::ostringstream os; + os << "s" << ix; + all_values->add(*w.arg(os.str().c_str())); + } + } + + return std::make_tuple(std::move(nll), sum, data, std::move(all_values)); +} + + +class Hex { +public: + explicit Hex(double n) : number_(n) {} + operator double() const { return number_; } + bool operator==(const Hex& other) { + return double(*this) == double(other); + } + +private: + double number_; +}; + +::std::ostream& operator<<(::std::ostream& os, const Hex& hex) { + return os << std::hexfloat << double(hex) << std::defaultfloat; // whatever needed to print bar to os +} + + +#endif //ROOT_TEST_LIB_H diff --git a/test/Makefile.win32 b/test/Makefile.win32 index 9a0231dd8ef71..f490a6dcff74c 100644 --- a/test/Makefile.win32 +++ b/test/Makefile.win32 @@ -49,27 +49,19 @@ OutPutOpt = -out: # Win32 system with Microsoft Visual C/C++ -## VS2012 (VC11): configure subsystem version -## See: https://blogs.msdn.com/b/vcblog/archive/2012/10/08/10357555.aspx -## (APPVER used in win32.mak to set subsystem version) -!if ([nmake /? 2>&1 | findstr /c:"Version 11\." > nul ] == 0) || \ - ([nmake /? 2>&1 | findstr /c:"Version 12\." > nul ] == 0) -APPVER = 5.01 -cc = cl -link = link -implib = lib -lflags = $(lflags) /INCREMENTAL:NO /NOLOGO -DLLENTRY = @12 -conlflags = $(lflags) -subsystem:console -guilflags = $(lflags) -subsystem:windows -dlllflags = $(lflags) -entry:_DllMainCRTStartup$(DLLENTRY) -dll -!else -!include -!endif +APPVER = 5.01 +cc = cl +link = link +implib = lib +lflags = $(lflags) /INCREMENTAL:NO /NOLOGO +DLLENTRY = @12 +conlflags = $(lflags) -subsystem:console +guilflags = $(lflags) -subsystem:windows +dlllflags = $(lflags) -entry:_DllMainCRTStartup$(DLLENTRY) -dll CC = $(cc) CXX = $(cc) CXXFLAGS = -nologo -EHs -GR -DWIN32 -W3 -D_WIN32 -D_WINDOWS \ - -I$(ROOTSYS)/include -wd4244 \ + -I$(ROOTSYS)/include -wd4244 -D_CRT_SECURE_NO_DEPRECATE \ -FIw32pragma.h LD = $(link) @@ -85,37 +77,15 @@ CXXOPT = -Z7 -MD LDOPT = -debug !endif -# Check if nmake version is 8.xx or 9.xx -!if ([nmake /? 2>&1 | findstr /c:"Version 8\." > nul ] == 0) || \ - ([nmake /? 2>&1 | findstr /c:"Version 9\." > nul ] == 0) -MT_EXE = mt -nologo -manifest $@.manifest -outputresource:$@;1 -MT_DLL = mt -nologo -manifest $@.manifest -outputresource:$@;2 -EXTRAFLAGS = -D_CRT_SECURE_NO_DEPRECATE -!else if ([nmake /? 2>&1 | findstr /c:"Version 10\." > nul ] == 0) || \ - ([nmake /? 2>&1 | findstr /c:"Version 11\." > nul ] == 0) || \ - ([nmake /? 2>&1 | findstr /c:"Version 12\." > nul ] == 0) -EXTRAFLAGS = -D_CRT_SECURE_NO_DEPRECATE -!else -MT_EXE = -MT_DLL = -EXTRAFLAGS = -G5 -!endif - - -LDFLAGS = $(LDOPT) $(conlflags) -nologo -include:_G__cpp_setupG__Hist \ - -include:_G__cpp_setupG__Graf -include:_G__cpp_setupG__G3D \ - -include:_G__cpp_setupG__GPad -include:_G__cpp_setupG__Tree \ - -include:_G__cpp_setupG__Rint -include:_G__cpp_setupG__PostScript \ - -include:_G__cpp_setupG__Matrix -include:_G__cpp_setupG__Physics +LDFLAGS = $(LDOPT) $(conlflags) -nologo SOFLAGS = $(dlllflags:-pdb:none=) -ROOTLIBS = $(ROOTSYS)\lib\libCore.lib \ - $(ROOTSYS)\lib\libCint.lib $(ROOTSYS)\lib\libHist.lib \ +ROOTLIBS = $(ROOTSYS)\lib\libCore.lib $(ROOTSYS)\lib\libHist.lib \ $(ROOTSYS)\lib\libGraf.lib $(ROOTSYS)\lib\libGraf3d.lib \ $(ROOTSYS)\lib\libGpad.lib $(ROOTSYS)\lib\libTree.lib \ $(ROOTSYS)\lib\libRint.lib $(ROOTSYS)\lib\libPostscript.lib \ $(ROOTSYS)\lib\libMatrix.lib $(ROOTSYS)\lib\libPhysics.lib \ $(ROOTSYS)\lib\libNet.lib $(ROOTSYS)\lib\libRIO.lib \ - $(ROOTSYS)\lib\libMathCore.lib + $(ROOTSYS)\lib\libMathCore.lib $(ROOTSYS)\lib\libTreePlayer.lib LIBS = $(ROOTLIBS) GLIBS = $(LIBS) $(ROOTSYS)\lib\libGui.lib $(ROOTSYS)\lib\libGraf.lib \ $(ROOTSYS)\lib\libGpad.lib @@ -192,17 +162,17 @@ STRESSGUILIBS = $(ROOTSYS)\lib\libASImage.lib $(ROOTSYS)\lib\libASImageGui.lib \ $(ROOTSYS)\lib\libRecorder.lib $(ROOTSYS)\lib\libGuiHtml.lib !if exist("$(ROOTSYS)\lib\libGenVector.lib") -STRESSVECO = stressVector.$(ObjSuf) -STRESSVECS = stressVector.$(SrcSuf) -STRESSVEC = stressVector$(ExeSuf) +STRESSVECO = stressVector.$(ObjSuf) +STRESSVECS = stressVector.$(SrcSuf) +STRESSVEC = stressVector$(ExeSuf) STRESSMATHO = stressMathCore.$(ObjSuf) STRESSMATHS = stressMathCore.$(SrcSuf) STRESSMATHLIBS = $(ROOTSYS)\lib\libGenVector.lib -TRACKMATHSRC = TrackMathCoreDict.$(SrcSuf) -TRACKMATHOBJ = TrackMathCoreDict.$(ObjSuf) -TRACKMATHLIB = libTrackMathCoreDict.$(DllSuf) +TRACKMATHSRC = TrackMathCoreDict.$(SrcSuf) +TRACKMATHOBJ = TrackMathCoreDict.$(ObjSuf) +TRACKMATHLIB = libTrackMathCoreDict.$(DllSuf) STRESSMATH = stressMathCore$(ExeSuf) !endif @@ -249,9 +219,9 @@ STRESSO = stress.$(ObjSuf) STRESSS = stress.$(SrcSuf) STRESS = stress$(ExeSuf) -STRESSGEOMETRYO = stressGeometry.$(ObjSuf) -STRESSGEOMETRYS = stressGeometry.$(SrcSuf) -STRESSGEOMETRY = stressGeometry$(ExeSuf) +STRESSGEOMETRYO = stressGeometry.$(ObjSuf) +STRESSGEOMETRYS = stressGeometry.$(SrcSuf) +STRESSGEOMETRY = stressGeometry$(ExeSuf) STRESSSHAPESO = stressShapes.$(ObjSuf) STRESSSHAPESS = stressShapes.$(SrcSuf) @@ -366,193 +336,158 @@ $(EVENTSO): $(EVENTO) BINDEXPLIB $* $(EVENTO) > $*.def lib -nologo -MACHINE:IX86 $(EVENTO) -def:$*.def $(OutPutOpt)$(EVENTLIB) $(LD) $(SOFLAGS) $(LDFLAGS) $(EVENTO) $*.exp $(LIBS) $(OutPutOpt)$(EVENTSO) - $(MT_DLL) @echo "$(EVENTSO) done" $(EVENTMTSO): $(EVENTMTO) BINDEXPLIB $* $(EVENTMTO) > $*.def lib -nologo -MACHINE:IX86 $(EVENTMTO) -def:$*.def $(OutPutOpt)$(EVENTMTLIB) $(LD) $(SOFLAGS) $(LDFLAGS) $(EVENTMTO) $*.exp $(LIBS) $(OutPutOpt)$(EVENTMTSO) - $(MT_DLL) @echo "$(EVENTMTSO) done" $(EVENT): $(EVENTSO) $(MAINEVENTO) $(LD) $(LDFLAGS) $(MAINEVENTO) $(EVENTLIB) $(LIBS) $(OutPutOpt)$(EVENT) - $(MT_EXE) @echo "$(EVENT) done" $(HWORLD): $(HWORLDO) $(LD) $(LDFLAGS) $(HWORLDO) $(GLIBS) $(OutPutOpt)$@ - $(MT_EXE) @echo "$@ done" $(CTORTURE): $(CTORTUREO) $(LD) $(LDFLAGS) $(CTORTUREO) $(LIBS) $(OutPutOpt)$@ - $(MT_EXE) @echo "$@ done" $(HSIMPLE): $(HSIMPLEO) $(LD) $(LDFLAGS) $(HSIMPLEO) $(LIBS) $(OutPutOpt)$@ - $(MT_EXE) @echo "$@ done" $(MINEXAM): $(MINEXAMO) $(LD) $(LDFLAGS) $(MINEXAMO) $(LIBS) $(OutPutOpt)$@ - $(MT_EXE) @echo "$@ done" $(TSTRING): $(TSTRINGO) $(LD) $(LDFLAGS) $(TSTRINGO) $(LIBS) $(OutPutOpt)$@ - $(MT_EXE) @echo "$@ done" $(TCOLLEX): $(TCOLLEXO) $(LD) $(LDFLAGS) $(TCOLLEXO) $(LIBS) $(OutPutOpt)$@ - $(MT_EXE) @echo "$@ done" $(TCOLLBM): $(TCOLLBMO) $(LD) $(LDFLAGS) $(TCOLLBMO) $(LIBS) $(OutPutOpt)$@ - $(MT_EXE) @echo "$@ done" $(VVECTOR): $(VVECTORO) $(LD) $(LDFLAGS) $(VVECTORO) $(LIBS) $(OutPutOpt)$@ - $(MT_EXE) @echo "$@ done" $(VMATRIX): $(VMATRIXO) $(LD) $(LDFLAGS) $(VMATRIXO) $(LIBS) $(OutPutOpt)$@ - $(MT_EXE) @echo "$@ done" $(VLAZY): $(VLAZYO) $(LD) $(LDFLAGS) $(VLAZYO) $(LIBS) $(OutPutOpt)$@ - $(MT_EXE) @echo "$@ done" $(STRESSL): $(STRESSLO) $(LD) $(LDFLAGS) $(STRESSLO) $(LIBS) $(OutPutOpt)$@ - $(MT_EXE) @echo "$@ done" $(STRESSG): $(STRESSGO) $(LD) $(LDFLAGS) $(STRESSGO) $(LIBS) $(OutPutOpt)$@ - $(MT_EXE) @echo "$@ done" $(STRESSGUI): $(STRESSGUIO) $(LD) $(LDFLAGS) $(STRESSGUIO) $(GLIBS) $(STRESSGUILIBS) $(OutPutOpt)$@ - $(MT_EXE) @echo "$@ done" $(STRESSSP): $(STRESSSPO) $(LD) $(LDFLAGS) $(STRESSSPO) $(LIBS) $(ROOTSYS)\lib\libSpectrum.lib $(OutPutOpt)$@ - $(MT_EXE) @echo "$@ done" !if exist("$(ROOTSYS)\lib\libGenVector.lib") $(STRESSVEC): $(STRESSVECO) $(LD) $(LDFLAGS) $(STRESSVECO) $(LIBS) $(ROOTSYS)\lib\libGenVector.lib $(OutPutOpt)$@ - $(MT_EXE) @echo "$@ done" $(TRACKMATHLIB): $(TRACKMATHOBJ) $(LD) $(SOFLAGS) $(LDFLAGS) $(TRACKMATHOBJ) $(LIBS) $(ROOTSYS)\lib\libGenVector.lib $(OutPutOpt)$@ - $(MT_DLL) @echo "$@ done" $(STRESSMATH): $(STRESSMATHO) $(TRACKMATHLIB) $(LD) $(LDFLAGS) $(STRESSMATHO) $(LIBS) $(ROOTSYS)\lib\libGenVector.lib $(OutPutOpt)$@ - $(MT_EXE) @echo "$@ done" !endif !if exist("$(ROOTSYS)\lib\libMathMore.lib") $(STRESSMATHMORE): $(STRESSMATHMOREO) $(LD) $(LDFLAGS) $(STRESSMATHMOREO) $(LIBS) $(ROOTSYS)\lib\libMathMore.lib $(OutPutOpt)$@ - $(MT_EXE) @echo "$@ done" !endif !if exist("$(ROOTSYS)\lib\libTMVA.lib") $(STRESSTMVA): $(STRESSTMVAO) $(LD) $(LDFLAGS) $(STRESSTMVAO) $(LIBS) $(ROOTSYS)\lib\libTMVA.lib $(OutPutOpt)$@ - $(MT_EXE) @echo "$@ done" !endif $(TESTBITS): $(TESTBITSO) $(LD) $(LDFLAGS) $(TESTBITSO) $(LIBS) $(OutPutOpt)$@ - $(MT_EXE) @echo "$@ done" $(THREADS): $(THREADSO) $(LD) $(LDFLAGS) $(THREADSO) $(LIBS) $(ROOTSYS)\lib\libThread.lib $(OutPutOpt)$@ - $(MT_EXE) @echo "$@ done" $(QPRANDOM): $(QPRANDOMO) $(LD) $(LDFLAGS) $(QPRANDOMO) $(LIBS) $(ROOTSYS)\lib\libQuadp.lib $(OutPutOpt)$@ - $(MT_EXE) @echo "$@ done" $(GUITEST): $(GUITESTO) $(LD) $(LDFLAGS) $(GUITESTO) $(GLIBS) $(OutPutOpt)$@ - $(MT_EXE) @echo "$@ done" $(GUIVIEWER): $(GUIVIEWERO) $(LD) $(LDFLAGS) $(GUIVIEWERO) $(GLIBS) $(OutPutOpt)$@ - $(MT_EXE) @echo "$@ done" $(STRESS): $(STRESSO) $(EVENT) $(LD) $(LDFLAGS) $(STRESSO) $(EVENTLIB) $(LIBS) $(OutPutOpt)$@ - $(MT_EXE) @echo "$@ done" $(STRESSGEOMETRY): $(STRESSGEOMETRYO) $(LD) $(LDFLAGS) $(STRESSGEOMETRYO) $(LIBS) $(ROOTSYS)\lib\libGeom.lib $(OutPutOpt)$@ - $(MT_EXE) @echo "$@ done" $(STRESSSHAPES): $(STRESSSHAPESO) $(LD) $(LDFLAGS) $(STRESSSHAPESO) $(LIBS) $(ROOTSYS)\lib\libGeom.lib $(OutPutOpt)$@ - $(MT_EXE) @echo "$@ done" !if exist("$(ROOTSYS)\lib\libRooFit.lib") $(STRESSROOFIT): $(STRESSROOFITO) $(LD) $(LDFLAGS) $(STRESSROOFITO) $(LIBS) $(ROOTSYS)\lib\libRooFit.lib $(ROOTSYS)\lib\libRooFitCore.lib $(ROOTSYS)\lib\libHtml.lib $(ROOTSYS)\lib\libThread.lib $(ROOTSYS)\lib\libMinuit.lib $(OutPutOpt)$@ - $(MT_EXE) @echo "$@ done" !endif !if exist("$(ROOTSYS)\lib\libRooStats.lib") $(STRESSROOSTATS): $(STRESSROOSTATSO) $(LD) $(LDFLAGS) $(STRESSROOSTATSO) $(LIBS) $(ROOTSYS)\lib\libRooStats.lib $(ROOTSYS)\lib\libRooFit.lib $(ROOTSYS)\lib\libRooFitCore.lib $(OutPutOpt)$@ - $(MT_EXE) @echo "$@ done" !endif !if exist("$(ROOTSYS)\lib\libHistFactory.lib") $(STRESSHISTFACTORY): $(STRESSHISTFACTORYO) $(LD) $(LDFLAGS) $(STRESSHISTFACTORYO) $(LIBS) $(ROOTSYS)\lib\libHistFactory.lib $(ROOTSYS)\lib\libRooStats.lib $(ROOTSYS)\lib\libRooFit.lib $(ROOTSYS)\lib\libRooFitCore.lib $(ROOTSYS)\lib\libHtml.lib $(ROOTSYS)\lib\libThread.lib $(ROOTSYS)\lib\libMinuit.lib $(ROOTSYS)\lib\libFoam.lib $(ROOTSYS)\lib\libProof.lib $(EXTRAROOFITLIBS) $(OutPutOpt)$@ - $(MT_EXE) @echo "$@ done" !endif $(STRESSFIT): $(STRESSFITO) $(LD) $(LDFLAGS) $(STRESSFITO) $(LIBS) $(OutPutOpt)$@ - $(MT_EXE) @echo "$@ done" !if exist("$(ROOTSYS)\lib\libUnuran.lib") !if exist("$(ROOTSYS)\lib\libminuit2.lib") $(STRESSHISTOFIT): $(STRESSHISTOFITO) $(LD) $(LDFLAGS) $(STRESSHISTOFITO) $(LIBS) $(ROOTSYS)\lib\libUnuran.lib $(OutPutOpt)$@ - $(MT_EXE) @echo "$@ done" !endif !endif @@ -564,51 +499,41 @@ $(STRESSENTRYLIST): $(STRESSENTRYLISTO) $(STRESSHEPIX): $(STRESSHEPIXO) $(STRESSGEOMETRY) $(STRESSFIT) $(STRESSL) \ $(STRESSSP) $(STRESS) $(LD) $(LDFLAGS) $(STRESSHEPIXO) $(LIBS) $(OutPutOpt)$@ - $(MT_EXE) @echo "$@ done" $(STRESSPROOF): $(STRESSPROOFO) $(LD) $(LDFLAGS) $(STRESSPROOFO) $(LIBS) $(ROOTSYS)\lib\libProof.lib $(ROOTSYS)\lib\libThread.lib $(OutPutOpt)$@ - $(MT_EXE) @echo "$@ done" $(BENCH): $(BENCHO) $(TBENCHSO) $(LD) $(LDFLAGS) $(BENCHO) $(TBENCHO) $(LIBS) $(OutPutOpt)$@ - $(MT_EXE) @echo "$@ done" Hello: $(HELLOSO) $(HELLOSO): $(HELLOO) $(LD) $(SOFLAGS) $(LDFLAGS) $(HELLOO) $(GLIBS) $(OutPutOpt)$@ - $(MT_DLL) Aclock: $(ACLOCKSO) $(ACLOCKSO): $(ACLOCKO) $(LD) $(SOFLAGS) $(LDFLAGS) $(ACLOCKO) $(GLIBS) $(OutPutOpt)$@ - $(MT_DLL) Tetris: $(TETRISSO) $(TETRISSO): $(TETRISO) $(LD) $(SOFLAGS) $(LDFLAGS) $(TETRISO) $(GLIBS) $(OutPutOpt)$@ - $(MT_DLL) $(TBENCHSO): $(TBENCHO) $(LD) $(SOFLAGS) $(LDFLAGS) $(TBENCHO) $(LIBS) $(OutPutOpt)$@ - $(MT_DLL) $(STRESSINTERP): $(STRESSINTERPO) $(LD) $(LDFLAGS) $(STRESSINTERPO) $(LIBS) $(OutPutOpt)$@ - $(MT_EXE) @echo "$@ done" $(STRESSITER): $(STRESSITERO) $(LD) $(LDFLAGS) $(STRESSITERO) $(LIBS) $(OutPutOpt)$@ - $(MT_EXE) @echo "$@ done" $(STRESSHIST): $(STRESSHISTO) $(LD) $(LDFLAGS) $(STRESSHISTO) $(LIBS) $(OutPutOpt)$@ - $(MT_EXE) @echo "$@ done" clean: @@ -660,8 +585,8 @@ TrackMathCoreDict.$(SrcSuf): TrackMathCore.h TrackMathCoreLinkDef.h @rootcint -f $@ -c TrackMathCore.h TrackMathCoreLinkDef.h stressProof.$(ObjSuf): stressProof.$(SrcSuf) - $(CXX) $(CXXFLAGS) $(EXTRAFLAGS) $(CXXOPT) -I$(TUTDIR) -c $** + $(CXX) $(CXXFLAGS) $(CXXOPT) -I$(TUTDIR) -c $** .$(SrcSuf).$(ObjSuf): - $(CXX) $(CXXFLAGS) $(EXTRAFLAGS) $(CXXOPT) -c $< + $(CXX) $(CXXFLAGS) $(CXXOPT) -c $< diff --git a/tree/dataframe/inc/ROOT/RDFHelpers.hxx b/tree/dataframe/inc/ROOT/RDFHelpers.hxx index ed6a276d96427..13e1056adc1c3 100644 --- a/tree/dataframe/inc/ROOT/RDFHelpers.hxx +++ b/tree/dataframe/inc/ROOT/RDFHelpers.hxx @@ -49,7 +49,7 @@ template class PassAsVecHelper, T, F> { template using AlwaysT = T; - F fFunc; + typename std::decay::type fFunc; public: PassAsVecHelper(F &&f) : fFunc(std::forward(f)) {} diff --git a/tree/dataframe/src/RDFUtils.cxx b/tree/dataframe/src/RDFUtils.cxx index cc15f9c60f6de..0f83f900cf4d8 100644 --- a/tree/dataframe/src/RDFUtils.cxx +++ b/tree/dataframe/src/RDFUtils.cxx @@ -137,7 +137,8 @@ std::string ComposeRVecTypeName(const std::string &valueType) std::string GetLeafTypeName(TLeaf *leaf, const std::string &colName) { - std::string colType = leaf->GetTypeName(); + const char *colTypeCStr = leaf->GetTypeName(); + std::string colType = colTypeCStr == nullptr ? "" : colTypeCStr; if (colType.empty()) throw std::runtime_error("Could not deduce type of leaf " + colName); if (leaf->GetLeafCount() != nullptr && leaf->GetLenStatic() == 1) { diff --git a/tree/dataframe/test/dataframe_helpers.cxx b/tree/dataframe/test/dataframe_helpers.cxx index 44fd9f97d8095..33bae46a5c87c 100644 --- a/tree/dataframe/test/dataframe_helpers.cxx +++ b/tree/dataframe/test/dataframe_helpers.cxx @@ -56,6 +56,19 @@ TEST(RDFHelpers, PassAsVec) EXPECT_EQ(1u, *df.Filter(PassAsVec<2, int>(TwoOnesDeque), {"one", "_1"}).Count()); } + +// this tests https://github.com/root-project/root/issues/8276 +TEST(RDFHelpers, ReturnPassAsVec) +{ + auto returnPassAsVecLambda = [] { + double f = 42; + auto fn = [f](std::vector) { return f; }; + return PassAsVec<1, int>(fn); + }; + auto fn = returnPassAsVecLambda(); + EXPECT_EQ(fn(0), 42.); +} + class SaveGraphTestHelper { private: RDataFrame rd1; diff --git a/tree/dataframe/test/dataframe_interface.cxx b/tree/dataframe/test/dataframe_interface.cxx index 9a37e1c0ff499..354b0b4dc7b70 100644 --- a/tree/dataframe/test/dataframe_interface.cxx +++ b/tree/dataframe/test/dataframe_interface.cxx @@ -1,6 +1,7 @@ +#include "ROOT/RCsvDS.hxx" #include "ROOT/RDataFrame.hxx" +#include "ROOT/RStringView.hxx" #include "ROOT/RTrivialDS.hxx" -#include "ROOT/RCsvDS.hxx" #include "TMemFile.h" #include "TSystem.h" #include "TTree.h" @@ -448,7 +449,7 @@ TEST(RDataFrameInterface, ColumnWithSimpleStruct) ROOT::RDataFrame df(t); const std::vector expected({ "c.a", "a", "c.b", "b", "c" }); EXPECT_EQ(df.GetColumnNames(), expected); - for (const std::string &col : {"c.a", "a"}) { + for (std::string_view col : {"c.a", "a"}) { EXPECT_DOUBLE_EQ(df.Mean(col).GetValue(), 42.); // compiled EXPECT_DOUBLE_EQ(df.Mean(col).GetValue(), 42.); // jitted } diff --git a/tree/dataframe/test/datasource_more.cxx b/tree/dataframe/test/datasource_more.cxx index 683c3907f70c3..cd83f6da188a2 100644 --- a/tree/dataframe/test/datasource_more.cxx +++ b/tree/dataframe/test/datasource_more.cxx @@ -60,6 +60,7 @@ TEST(RArraysDS, SnapshotAndShortSyntaxForCollectionSizes) auto *blist = t->GetListOfBranches(); EXPECT_EQ(blist->GetEntries(), 1u); EXPECT_STREQ(blist->At(0)->GetName(), "var"); + f.Close(); // Windows does not allow deletion/recreation of files that are still in use. // Snapshot must throw if #var is passed explicitly EXPECT_THROW(df.Snapshot("t", fname, {"#var"}), std::runtime_error); diff --git a/tree/tree/src/TTree.cxx b/tree/tree/src/TTree.cxx index 29cf5430fab57..93f2dcbba64cf 100644 --- a/tree/tree/src/TTree.cxx +++ b/tree/tree/src/TTree.cxx @@ -3256,8 +3256,8 @@ TTree* TTree::CloneTree(Long64_t nentries /* = -1 */, Option_t* option /* = "" * //////////////////////////////////////////////////////////////////////////////// /// Set branch addresses of passed tree equal to ours. -/// If undo is true, reset the branch address instead of copying them. -/// This insures 'separation' of a cloned tree from its original +/// If undo is true, reset the branch addresses instead of copying them. +/// This ensures 'separation' of a cloned tree from its original. void TTree::CopyAddresses(TTree* tree, Bool_t undo) { diff --git a/tutorials/CMakeLists.txt b/tutorials/CMakeLists.txt index 940cf22e0d589..dd198a798cfaf 100644 --- a/tutorials/CMakeLists.txt +++ b/tutorials/CMakeLists.txt @@ -182,6 +182,7 @@ if(NOT ROOT_mathmore_FOUND) math/LegendreAssoc.C math/Legendre.C math/mathmoreIntegration.C + math/multivarGaus.C math/tStudent.C math/normalDist.C roostats/TestNonCentral.C diff --git a/tutorials/image/hsumanim.C b/tutorials/image/hsumanim.C index 49b1dd5468aa5..ecfaa2a1e90bd 100644 --- a/tutorials/image/hsumanim.C +++ b/tutorials/image/hsumanim.C @@ -72,7 +72,7 @@ void hsumanim() { // make infinite animation by adding "++" to the file name if (gROOT->IsBatch()) c1->Print("hsumanim.gif++"); - //You can view the animated file hsumanim.gif with Netscape/IE or mozilla + // you can view the animated file hsumanim.gif with a web browser gBenchmark->Show("hsum"); } diff --git a/tutorials/math/exampleFunction.py b/tutorials/math/exampleFunction.py index 9ea6cb36c663a..442b8bb83dfef 100644 --- a/tutorials/math/exampleFunction.py +++ b/tutorials/math/exampleFunction.py @@ -68,7 +68,11 @@ def g(x): return 2 * x gradFunc = ROOT.Math.GradFunctor1D(f, g) #check if ROOT has mathmore -if (ROOT.gSystem.Load("libMathMore") < 0) : +prevLevel = ROOT.gErrorIgnoreLevel +ROOT.gErrorIgnoreLevel=ROOT.kFatal +ret = ROOT.gSystem.Load("libMathMore") +ROOT.gErrorIgnoreLevel=prevLevel +if (ret < 0) : print("ROOT has not Mathmore") print("derivative value at x = 1", gradFunc.Derivative(1) ) From 6670a3fac0b00c5f6238456e0df560b47b987f46 Mon Sep 17 00:00:00 2001 From: "E. G. Patrick Bos" Date: Thu, 24 Jun 2021 15:34:57 +0200 Subject: [PATCH 02/10] remove old MultiProcess proof of concept --- roofit/roofitcore/CMakeLists.txt | 6 +- .../inc/{MultiProcess => }/BidirMMapPipe.h | 0 .../inc/MultiProcess/GradMinimizer.h | 71 -- roofit/roofitcore/inc/MultiProcess/Job.h | 98 --- roofit/roofitcore/inc/MultiProcess/NLLVar.h | 112 ---- .../roofitcore/inc/MultiProcess/TaskManager.h | 247 ------- roofit/roofitcore/inc/MultiProcess/Vector.h | 182 ----- roofit/roofitcore/inc/MultiProcess/messages.h | 80 --- roofit/roofitcore/inc/MultiProcess/util.h | 22 - .../src/{MultiProcess => }/BidirMMapPipe.cxx | 2 +- .../src/MultiProcess/GradMinimizerFcn.cxx | 232 ------- roofit/roofitcore/src/MultiProcess/Job.cxx | 176 ----- roofit/roofitcore/src/MultiProcess/NLLVar.cxx | 225 ------- .../src/MultiProcess/TaskManager.cxx | 622 ------------------ .../roofitcore/src/MultiProcess/messages.cxx | 70 -- roofit/roofitcore/src/MultiProcess/util.cxx | 62 -- roofit/roofitcore/src/RooRealMPFE.cxx | 2 +- roofit/roofitcore/test/CMakeLists.txt | 2 +- .../test/{MultiProcess => }/MPFEnll.cpp | 0 .../test/MultiProcess/VectorGradMinimizer.cpp | 306 --------- .../test/MultiProcess/VectorNLL.cpp | 395 ----------- .../roofitcore/test/MultiProcess_Vector.cxx | 186 ------ roofit/roofitcore/test/testBidirMMapPipe.cxx | 2 +- 23 files changed, 6 insertions(+), 3094 deletions(-) rename roofit/roofitcore/inc/{MultiProcess => }/BidirMMapPipe.h (100%) delete mode 100644 roofit/roofitcore/inc/MultiProcess/GradMinimizer.h delete mode 100644 roofit/roofitcore/inc/MultiProcess/Job.h delete mode 100644 roofit/roofitcore/inc/MultiProcess/NLLVar.h delete mode 100644 roofit/roofitcore/inc/MultiProcess/TaskManager.h delete mode 100644 roofit/roofitcore/inc/MultiProcess/Vector.h delete mode 100644 roofit/roofitcore/inc/MultiProcess/messages.h delete mode 100644 roofit/roofitcore/inc/MultiProcess/util.h rename roofit/roofitcore/src/{MultiProcess => }/BidirMMapPipe.cxx (99%) delete mode 100644 roofit/roofitcore/src/MultiProcess/GradMinimizerFcn.cxx delete mode 100644 roofit/roofitcore/src/MultiProcess/Job.cxx delete mode 100644 roofit/roofitcore/src/MultiProcess/NLLVar.cxx delete mode 100644 roofit/roofitcore/src/MultiProcess/TaskManager.cxx delete mode 100644 roofit/roofitcore/src/MultiProcess/messages.cxx delete mode 100644 roofit/roofitcore/src/MultiProcess/util.cxx rename roofit/roofitcore/test/{MultiProcess => }/MPFEnll.cpp (100%) delete mode 100644 roofit/roofitcore/test/MultiProcess/VectorGradMinimizer.cpp delete mode 100644 roofit/roofitcore/test/MultiProcess/VectorNLL.cpp delete mode 100644 roofit/roofitcore/test/MultiProcess_Vector.cxx diff --git a/roofit/roofitcore/CMakeLists.txt b/roofit/roofitcore/CMakeLists.txt index 39d2036ce0ec9..3517d6bce05a5 100644 --- a/roofit/roofitcore/CMakeLists.txt +++ b/roofit/roofitcore/CMakeLists.txt @@ -27,6 +27,7 @@ endif() ROOT_STANDARD_LIBRARY_PACKAGE(RooFitCore HEADERS + BidirMMapPipe.h Floats.h Roo1DTable.h RooAbsAnaConvPdf.h @@ -237,14 +238,12 @@ ROOT_STANDARD_LIBRARY_PACKAGE(RooFitCore RooWorkspace.h RooWorkspaceHandle.h RooXYChi2Var.h - RooTimer.h RooJsonListFile.h RooTaskSpec.h RooGaussMinimizer.h RooGaussMinimizerFcn.h NumericalDerivatorMinuit2.h RooGradMinimizerFcn.h - MultiProcess/BidirMMapPipe.h # TestStatistics/GradMinimizer.h TestStatistics/LikelihoodGradientJob.h TestStatistics/LikelihoodGradientWrapper.h @@ -277,6 +276,7 @@ ROOT_STANDARD_LIBRARY_PACKAGE(RooFitCore RooFitLegacy/RooNameSet.h RooFitLegacy/RooTreeData.h SOURCES + src/BidirMMapPipe.cxx src/Roo1DTable.cxx src/RooAbsAnaConvPdf.cxx src/RooAbsArg.cxx @@ -479,14 +479,12 @@ ROOT_STANDARD_LIBRARY_PACKAGE(RooFitCore src/RooVectorDataStore.cxx src/RooWorkspace.cxx src/RooXYChi2Var.cxx - src/RooTimer.cxx src/RooJsonListFile.cxx src/RooTaskSpec.cxx src/RooGaussMinimizer.cxx src/RooGaussMinimizerFcn.cxx src/NumericalDerivatorMinuit2.cxx src/RooGradMinimizerFcn.cxx - src/MultiProcess/BidirMMapPipe.cxx # src/TestStatistics/GradMinimizerFcn.cxx src/TestStatistics/LikelihoodGradientJob.cxx src/TestStatistics/LikelihoodGradientWrapper.cxx diff --git a/roofit/roofitcore/inc/MultiProcess/BidirMMapPipe.h b/roofit/roofitcore/inc/BidirMMapPipe.h similarity index 100% rename from roofit/roofitcore/inc/MultiProcess/BidirMMapPipe.h rename to roofit/roofitcore/inc/BidirMMapPipe.h diff --git a/roofit/roofitcore/inc/MultiProcess/GradMinimizer.h b/roofit/roofitcore/inc/MultiProcess/GradMinimizer.h deleted file mode 100644 index ba968bd2966b6..0000000000000 --- a/roofit/roofitcore/inc/MultiProcess/GradMinimizer.h +++ /dev/null @@ -1,71 +0,0 @@ -/***************************************************************************** - * Project: RooFit * - * Package: RooFitCore * - * @(#)root/roofitcore:$Id$ - * Authors: * - * PB, Patrick Bos, NL eScience Center, p.bos@esciencecenter.nl * - * * - * Redistribution and use in source and binary forms, * - * with or without modification, are permitted according to the terms * - * listed in LICENSE (http://roofit.sourceforge.net/license.txt) * - *****************************************************************************/ - -#ifndef ROOFIT_MULTIPROCESS_GRADMINIMIZER_H -#define ROOFIT_MULTIPROCESS_GRADMINIMIZER_H - -#include -#include -#include -#include - -namespace RooFit { - namespace MultiProcessV1 { - class GradMinimizerFcn : public RooFit::MultiProcessV1::Vector { - public: - GradMinimizerFcn(RooAbsReal *funct, RooMinimizerGenericPtr context, - std::size_t _N_workers, bool verbose = false); - GradMinimizerFcn(const GradMinimizerFcn& other); - - ROOT::Math::IMultiGradFunction* Clone() const override; - - void update_state(); - void update_real(std::size_t ix, double val, bool is_constant) override; - - // the const is inherited from ...::evaluate. We are not - // actually const though, so we use a horrible hack. -// Double_t evaluate() const override; -// Double_t evaluate_non_const(); - - // --- RESULT LOGISTICS --- - void send_back_task_result_from_worker(std::size_t task) override; - void receive_task_result_on_queue(std::size_t task, std::size_t worker_id) override; - void send_back_results_from_queue_to_master() override; - void clear_results() override; - void receive_results_on_master() override; - - // overrides IGradientFunctionMultiDimTempl::Gradient etc from - // Math/IFunction.h, which are const, but we are not actually const, so - // we use a const cast hack (the mutable versions): - void Gradient(const double *x, double *grad) const override; - void mutable_Gradient(const double *x, double *grad); - void G2ndDerivative(const double *x, double *g2) const override; - void mutable_G2ndDerivative(const double *x, double *g2); - void GStepSize(const double *x, double *gstep) const override; - void mutable_GStepSize(const double *x, double *gstep); - - void CalculateAll(const double *x); - - private: - void evaluate_task(std::size_t task) override; - double get_task_result(std::size_t /*task*/) override; - - // members - std::size_t N_tasks = 0; - std::vector completed_task_ids; - }; - - using GradMinimizer = RooMinimizerTemplate; - } // namespace MultiProcessV1 -} // namespace RooFit - -#endif //ROOFIT_MULTIPROCESS_GRADMINIMIZER_H diff --git a/roofit/roofitcore/inc/MultiProcess/Job.h b/roofit/roofitcore/inc/MultiProcess/Job.h deleted file mode 100644 index 1d2c95ba6032b..0000000000000 --- a/roofit/roofitcore/inc/MultiProcess/Job.h +++ /dev/null @@ -1,98 +0,0 @@ -/***************************************************************************** - * Project: RooFit * - * Package: RooFitCore * - * @(#)root/roofitcore:$Id$ - * Authors: * - * PB, Patrick Bos, NL eScience Center, p.bos@esciencecenter.nl * - * IP, Inti Pelupessy, NL eScience Center, i.pelupessy@esciencecenter.nl * - * * - * Redistribution and use in source and binary forms, * - * with or without modification, are permitted according to the terms * - * listed in LICENSE (http://roofit.sourceforge.net/license.txt) * - *****************************************************************************/ - -#ifndef ROOFIT_MULTIPROCESS_JOB_H -#define ROOFIT_MULTIPROCESS_JOB_H - -#include -#include // shared_ptr - -namespace RooFit { - namespace MultiProcessV1 { - // forward declaration - class TaskManager; - - /* - * @brief interface class for defining the actual work that TaskManager must do - * - * Think of "job" as in "employment", e.g. the job of a baker, which - * involves *tasks* like baking and selling bread. The Job must define the - * tasks through its execution (evaluate_task) and returning its result - * (get_task_result), based on a task index argument. - * - * Classes inheriting from Job must implement the pure virtual methods: - * - void evaluate_task(std::size_t task) - * - double get_task_result(std::size_t task) - * - void update_real(std::size_t ix, double val, bool is_constant) - * - void receive_task_result_on_queue(std::size_t task, std::size_t worker_id) - * - void send_back_results_from_queue_to_master() - * - void clear_results() - * - void receive_results_on_master() - * - * and can optionally override the virtual methods: - * - double call_double_const_method(std::string key) - * - void send_back_task_result_from_worker(std::size_t task) - * - * Note that Job::call_double_const_method throws a logic_error if not - * overridden. - */ - class Job { - public: - explicit Job(std::size_t _N_workers); - Job(const Job & other); - - virtual void evaluate_task(std::size_t task) = 0; - // TODO: replace get_task_result return type (double) with something more flexible - virtual double get_task_result(std::size_t task) = 0; - virtual void update_real(std::size_t ix, double val, bool is_constant) = 0; - - virtual double call_double_const_method(std::string /*key*/); - virtual void send_back_task_result_from_worker(std::size_t task); - - virtual void receive_task_result_on_queue(std::size_t task, std::size_t worker_id) = 0; - // an example implementation that receives only one double and stores it in - // a std::map: - // { - // double result; - // pipe >> job_object_id >> result; - // pipe << Q2W::result_received << BidirMMapPipe::flush; - // JobTask job_task(job_object_id, task); - // results[job_task] = result; - // } - - virtual void send_back_results_from_queue_to_master() = 0; - // after results have been retrieved, they should be cleared to ensure - // they won't be retrieved the next time again - virtual void clear_results() = 0; - - virtual void receive_results_on_master() = 0; - - TaskManager* get_manager(); - - static void worker_loop(); - - protected: - std::size_t N_workers; - std::size_t id; - bool waiting_for_queued_tasks = false; - - private: - // do not use _manager directly, it must first be initialized! use get_manager() - TaskManager* _manager = nullptr; - - static bool worker_loop_running; - }; - - } // namespace MultiProcessV1 -} // namespace RooFit -#endif //ROOFIT_MULTIPROCESS_JOB_H diff --git a/roofit/roofitcore/inc/MultiProcess/NLLVar.h b/roofit/roofitcore/inc/MultiProcess/NLLVar.h deleted file mode 100644 index f456fd17fdcd4..0000000000000 --- a/roofit/roofitcore/inc/MultiProcess/NLLVar.h +++ /dev/null @@ -1,112 +0,0 @@ -/***************************************************************************** - * Project: RooFit * - * Package: RooFitCore * - * @(#)root/roofitcore:$Id$ - * Authors: * - * PB, Patrick Bos, NL eScience Center, p.bos@esciencecenter.nl * - * IP, Inti Pelupessy, NL eScience Center, i.pelupessy@esciencecenter.nl * - * * - * Redistribution and use in source and binary forms, * - * with or without modification, are permitted according to the terms * - * listed in LICENSE (http://roofit.sourceforge.net/license.txt) * - *****************************************************************************/ - -#ifndef ROOFIT_MULTIPROCESS_NLLVAR_H -#define ROOFIT_MULTIPROCESS_NLLVAR_H - -#include -#include -#include - -namespace RooFit { - namespace MultiProcessV1 { - - enum class NLLVarTask { - all_events, - single_event, - bulk_partition, - interleave - }; - - // for debugging: - std::ostream& operator<<(std::ostream& out, const NLLVarTask value); - - - // --- kahan summation templates --- - - template - typename C::value_type sum_kahan(const C& container) { - using ValueType = typename C::value_type; - ValueType sum = 0, carry = 0; - for (auto element : container) { - ValueType y = element - carry; - ValueType t = sum + y; - carry = (t - sum) - y; - sum = t; - } - return sum; - } - - template - ValueType sum_kahan(const std::map& map) { - ValueType sum = 0, carry = 0; - for (auto element : map) { - ValueType y = element.second - carry; - ValueType t = sum + y; - carry = (t - sum) - y; - sum = t; - } - return sum; - } - - template - std::pair sum_of_kahan_sums(const C& sum_values, const C& sum_carrys) { - using ValueType = typename C::value_type; - ValueType sum = 0, carry = 0; - for (std::size_t ix = 0; ix < sum_values.size(); ++ix) { - ValueType y = sum_values[ix]; - carry += sum_carrys[ix]; - y -= carry; - const ValueType t = sum + y; - carry = (t - sum) - y; - sum = t; - } - return std::pair(sum, carry); - } - - - - class NLLVar : public RooFit::MultiProcessV1::Vector { - public: - NLLVar(std::size_t NumCPU, NLLVarTask task_mode, const RooNLLVar& nll); - void init_vars(); - void update_parameters(); - - // the const is inherited from RooAbsTestStatistic::evaluate. We are not - // actually const though, so we use a horrible hack. - Double_t evaluate() const override; - Double_t evaluate_non_const(); - - // --- RESULT LOGISTICS --- - void send_back_task_result_from_worker(std::size_t task) override; - void receive_task_result_on_queue(std::size_t task, std::size_t worker_id) override; - void send_back_results_from_queue_to_master() override; - void clear_results() override; - void receive_results_on_master() override; - - private: - void evaluate_task(std::size_t task) override; - double get_task_result(std::size_t /*task*/) override; - - // members - std::map carrys; - double result = 0; - double carry = 0; - std::size_t N_tasks = 0; - NLLVarTask mp_task_mode; - }; - - } // namespace MultiProcessV1 -} // namespace RooFit - -#endif //ROOFIT_MULTIPROCESS_NLLVAR_H diff --git a/roofit/roofitcore/inc/MultiProcess/TaskManager.h b/roofit/roofitcore/inc/MultiProcess/TaskManager.h deleted file mode 100644 index 74c514b166b85..0000000000000 --- a/roofit/roofitcore/inc/MultiProcess/TaskManager.h +++ /dev/null @@ -1,247 +0,0 @@ -/***************************************************************************** - * Project: RooFit * - * Package: RooFitCore * - * @(#)root/roofitcore:$Id$ - * Authors: * - * PB, Patrick Bos, NL eScience Center, p.bos@esciencecenter.nl * - * IP, Inti Pelupessy, NL eScience Center, i.pelupessy@esciencecenter.nl * - * * - * Redistribution and use in source and binary forms, * - * with or without modification, are permitted according to the terms * - * listed in LICENSE (http://roofit.sourceforge.net/license.txt) * - *****************************************************************************/ - -#ifndef ROOFIT_MULTIPROCESS_TASKMANAGER_H -#define ROOFIT_MULTIPROCESS_TASKMANAGER_H - -#include -#include -#include -//#include -#include -#include -// N.B.: cannot use forward declarations for BidirMMapPipe, because we need -// the nested BidirMMapPipe::PollVector as well. - -#include // getpid - -namespace RooFit { - namespace MultiProcessV1 { - - // forward declarations - class Job; - enum class M2Q: int; - enum class W2Q: int; - - // some helper types - using Task = std::size_t; - using JobTask = std::pair; // combined job_object and task identifier type - - // TaskManager handles message passing and communication with a queue of - // tasks and workers that execute the tasks. The queue is in a separate - // process that can communicate with the master process (from where this - // object is created) and the queue process communicates with the worker - // processes. - // - // The TaskManager class does work defined by subclasses of the Job class. - // - // For message passing, enum class T based on int are used. The implementer - // must make sure that T can be sent over the BidirMMapPipe, i.e. that - // operator<<(BidirMMapPipe&, T) and >>(BidirMMapPipe&, T) are implemented. - // This is currently done in messages.cxx/.h, see examples there. - // - // Make sure that activate() is called soon after instantiation of TaskManager, - // because everything in between construction and activate() gets executed - // on all processes (master, queue and slaves). Activate starts the queue - // loop on the queue process, which means it can start doing its job. - // Worker processes have to be activated separately from the Job objects - // themselves. Activate cannot be called from inside the constructor, since - // the loops would prevent the constructor from returning a constructed - // object (thus defying its purpose). Note that at the end of activate, the - // queue and child processes are killed. This is achieved by sending the - // terminate message, which is done automatically in the destructor of this - // class, but can also be done manually via terminate(). - // - // When using everything as intended, i.e. by only instantiating via the - // instance() method, activate() is called from Job::get_manager() - // immediately after creation, so one need not worry about the above. - class TaskManager { - public: - static TaskManager* instance(std::size_t N_workers); - - static TaskManager* instance(); - - static bool is_instantiated(); - - void identify_processes(); - - explicit TaskManager(std::size_t N_workers); - - ~TaskManager(); - - static std::size_t add_job_object(Job *job_object); - - static Job *get_job_object(std::size_t job_object_id); - - static bool remove_job_object(std::size_t job_object_id); - - void terminate() noexcept; - - void close_worker_connections(); - - void terminate_workers(); - - void activate(); - - bool is_activated(); - - bool process_queue_pipe_message(M2Q message); - - void retrieve(); - - void process_worker_pipe_message(std::size_t this_worker_id, W2Q message); - - void queue_loop(); - - bool from_queue(JobTask &job_task); - - void to_queue(JobTask job_task); - - bool is_master(); - - bool is_queue(); - - bool is_worker(); - - std::size_t get_worker_id(); - -// std::map& get_results(); - double call_double_const_method(const std::string& method_key, std::size_t job_id, std::size_t worker_id_call); - - - // -- WORKER - QUEUE COMMUNICATION -- - - void send_from_worker_to_queue(); - - template - void send_from_worker_to_queue(T item, Ts ... items) { - try { - zmqSvc().send(*this_worker_qw_socket, item); - } catch (zmq::error_t& e) { - std::cerr << e.what() << " -- errnum: " << e.num() << std::endl; - throw; - }; -// if (sizeof...(items) > 0) { // this will only work with if constexpr, c++17 - send_from_worker_to_queue(items...); - } - - template - value_t receive_from_worker_on_queue(std::size_t this_worker_id) { - try { - auto value = zmqSvc().receive(*qw_sockets[this_worker_id]); - return value; - } catch (zmq::error_t& e) { - std::cerr << e.what() << " -- errnum: " << e.num() << std::endl; - throw; - }; - } - - void send_from_queue_to_worker(std::size_t this_worker_id); - - template - void send_from_queue_to_worker(std::size_t this_worker_id, T item, Ts ... items) { - try { - zmqSvc().send(*qw_sockets[this_worker_id], item); - } catch (zmq::error_t& e) { - std::cerr << e.what() << " -- errnum: " << e.num() << std::endl; - throw; - }; -// if (sizeof...(items) > 0) { // this will only work with if constexpr, c++17 - send_from_queue_to_worker(this_worker_id, items...); - } - - template - value_t receive_from_queue_on_worker() { - try { - auto value = zmqSvc().receive(*this_worker_qw_socket); - return value; - } catch (zmq::error_t& e) { - std::cerr << e.what() << " -- errnum: " << e.num() << std::endl; - throw; - }; - } - - - // -- QUEUE - MASTER COMMUNICATION -- - - void send_from_queue_to_master(); - - template - void send_from_queue_to_master(T item, Ts ... items) { - try { - zmqSvc().send(*mq_socket, item); - } catch (zmq::error_t& e) { - std::cerr << e.what() << " -- errnum: " << e.num() << std::endl; - throw; - }; -// if (sizeof...(items) > 0) { // this will only work with if constexpr, c++17 - send_from_queue_to_master(items...); - } - - template - value_t receive_from_queue_on_master() { - try { - auto value = zmqSvc().receive(*mq_socket); - return value; - } catch (zmq::error_t& e) { - std::cerr << e.what() << " -- errnum: " << e.num() << std::endl; - throw; - }; - } - - void send_from_master_to_queue(); - - template - void send_from_master_to_queue(T item, Ts ... items) { - send_from_queue_to_master(item, items...); - } - - template - value_t receive_from_master_on_queue() { - return receive_from_queue_on_master(); - } - - void flush_ostreams(); - - private: - void initialize_processes(bool cpu_pinning = true); - void shutdown_processes(); -// BidirMMapPipe::PollVector get_poll_vector(); - - std::size_t N_workers; - std::vector > qw_sockets; - std::vector worker_pids; // master must wait for workers after completion, for which it needs their PIDs - pid_t queue_pid; - // for convenience on the worker processes, we use this_worker_pipe as an - // alias for worker_pipes.back() - ZmqLingeringSocketPtr<> this_worker_qw_socket; - ZmqLingeringSocketPtr<> mq_socket; - std::size_t worker_id; - bool _is_master = false; - bool _is_queue = false; - std::queue queue; - std::size_t N_tasks = 0; // total number of received tasks - std::size_t N_tasks_completed = 0; - bool queue_activated = false; - bool processes_initialized = false; -// std::unique_ptr zmq_context; - - static std::map job_objects; - static std::size_t job_counter; - static std::unique_ptr _instance; - }; - - } // namespace MultiProcessV1 -} // namespace RooFit - -#endif //ROOFIT_MULTIPROCESS_TASKMANAGER_H diff --git a/roofit/roofitcore/inc/MultiProcess/Vector.h b/roofit/roofitcore/inc/MultiProcess/Vector.h deleted file mode 100644 index 02fe0f7adfd35..0000000000000 --- a/roofit/roofitcore/inc/MultiProcess/Vector.h +++ /dev/null @@ -1,182 +0,0 @@ -/***************************************************************************** - * Project: RooFit * - * Package: RooFitCore * - * @(#)root/roofitcore:$Id$ - * Authors: * - * PB, Patrick Bos, NL eScience Center, p.bos@esciencecenter.nl * - * IP, Inti Pelupessy, NL eScience Center, i.pelupessy@esciencecenter.nl * - * * - * Redistribution and use in source and binary forms, * - * with or without modification, are permitted according to the terms * - * listed in LICENSE (http://roofit.sourceforge.net/license.txt) * - *****************************************************************************/ - -#ifndef ROOFIT_MULTIPROCESS_VECTOR_H -#define ROOFIT_MULTIPROCESS_VECTOR_H - -#include -#include -#include -#include -#include - -namespace RooFit { - namespace MultiProcessV1 { - - // Vector defines an interface and communication machinery to build a - // parallelized subclass of an existing non-concurrent numerical class that - // can be expressed as a vector of independent sub-calculations. - // - // The way to use Vector is to define a new class that inherits from - // Vector. Vector itself inherits from its template - // parameter class, so the inheritance tree will become: - // - // ParallelClass -> Vector -> ConcurrentClass - // - // where -> denotes "inherits from". - // - // While Vector is an abstract class, it provides some default method - // implementations that define the result of each task as a single vector - // element, i.e. a double precision floating point number. By overriding - // these methods, the result of a Vector task can be redefined as, for - // instance, a sum and a carry value for when the actual result of the task - // is given by Kahan summation and the carry value needs to be propagated - // back to the master process for calculating the total sum value. These - // methods are: - // - // - receive_task_result_on_queue - // - send_back_results_from_queue_to_master - // - clear_results - // - receive_results_on_master - // - send_back_task_result_from_worker - // - // Note that the results vector "defines" result type as well. In - // some cases this may not be wanted, but we expect that in such a case the - // Vector class itself is not suitable and a different Job subclass should - // be defined. One clear exception is the case where a vector is wanted, - // but not one of doubles but of another type. This use-case is - // accommodated through the result_t template parameter. - // - // Classes inheriting from Vector must implement the pure virtual methods: - // - void evaluate_task(std::size_t task) - // - double get_task_result(std::size_t task) - // - // and can optionally override the virtual methods (in addition to the - // result logistics methods mentioned above): - // - void update_real(std::size_t ix, double val, bool is_constant) - // - double call_double_const_method(std::string key) - // - template - class Vector : public Base, public Job { - public: - template - Vector(std::size_t _N_workers, Targs ...args) : - Base(args...), - Job(_N_workers) - { - id = TaskManager::add_job_object(this); - } - - Vector(const Vector & other) : - Base(other), - Job(other), - results(other.results), - _vars(other._vars), - _saveVars(other._saveVars), - _forceCalc(other._forceCalc) - { - id = TaskManager::add_job_object(this); - } - - ~Vector() { - TaskManager::remove_job_object(id); - } - - void update_real(std::size_t ix, double val, bool is_constant) override { - if (get_manager()->is_worker()) { - RooRealVar *rvar = (RooRealVar *) _vars.at(ix); - rvar->setVal(static_cast(val)); - if (rvar->isConstant() != is_constant) { - rvar->setConstant(static_cast(is_constant)); - } - } - } - - protected: - void gather_worker_results() { - if (waiting_for_queued_tasks) { - get_manager()->retrieve(); - waiting_for_queued_tasks = false; - } - } - - void receive_task_result_on_queue(std::size_t task, std::size_t worker_id) override { - result_t result = get_manager()->template receive_from_worker_on_queue(worker_id); - results[task] = result; - } - - void send_back_results_from_queue_to_master() override { - get_manager()->send_from_queue_to_master(results.size()); - for (auto const &item : results) { - get_manager()->send_from_queue_to_master(item.first, item.second); - } - } - - void clear_results() override { - // empty results cache - results.clear(); - } - - void receive_results_on_master() override { - std::size_t N_job_tasks = get_manager()->template receive_from_queue_on_master(); - for (std::size_t task_ix = 0ul; task_ix < N_job_tasks; ++task_ix) { - std::size_t task_id = get_manager()->template receive_from_queue_on_master(); - results[task_id] = get_manager()->template receive_from_queue_on_master(); - } - } - - // Here we define maps of functions that a subclass may want to call on - // the worker process and have the result sent back to master. Each - // function type needs custom implementation, so we only allow a selected - // number of function pointer types. Templates could make the Vector - // header slightly more compact, but the implementation would not change - // much, so this explicit approach seems preferable. - using double_const_method_t = double (Vector::*)() const; - std::map double_const_methods; - double call_double_const_method(std::string key) override { - return (this->*double_const_methods[key])(); - } - // Another example would be: - // std::map::*)(double)> double_from_double_methods; - // We leave this out for now, as we don't currently need it. - // - // Every type also needs a corresponding set of: - // - messages from master to queue - // - messages from queue to worker - // - method to return the method pointer from the object to be able to - // call it from the static worker_loop. - // The method could be implemented using templates, but the messages - // cannot, further motivating the use of explicit implementation of - // every specific case. - // - // Note that due to the relatively expensive nature of these calls, - // they should be used only in non-work-mode. - - // -- members -- - protected: - std::map results; - - // the following members are used for syncing, but might be replaced by - // remote "mapped function calls" (see above): - - // used in NLLVar - RooListProxy _vars; // Variables - RooArgList _saveVars; // Copy of variables - bool _forceCalc = false; - - }; // class Vector - - } // namespace MultiProcessV1 -} // namespace RooFit - -#endif //ROOFIT_MULTIPROCESS_VECTOR_H diff --git a/roofit/roofitcore/inc/MultiProcess/messages.h b/roofit/roofitcore/inc/MultiProcess/messages.h deleted file mode 100644 index 2f37c03027153..0000000000000 --- a/roofit/roofitcore/inc/MultiProcess/messages.h +++ /dev/null @@ -1,80 +0,0 @@ -/***************************************************************************** - * Project: RooFit * - * Package: RooFitCore * - * @(#)root/roofitcore:$Id$ - * Authors: * - * PB, Patrick Bos, NL eScience Center, p.bos@esciencecenter.nl * - * IP, Inti Pelupessy, NL eScience Center, i.pelupessy@esciencecenter.nl * - * * - * Redistribution and use in source and binary forms, * - * with or without modification, are permitted according to the terms * - * listed in LICENSE (http://roofit.sourceforge.net/license.txt) * - *****************************************************************************/ - -#ifndef ROOFIT_MULTIPROCESS_MESSAGES_H -#define ROOFIT_MULTIPROCESS_MESSAGES_H - -#include - -namespace RooFit { - namespace MultiProcessV1 { - - // Messages from master to queue - enum class M2Q : int { - terminate = 100, - enqueue = 10, - retrieve = 11, - update_real = 12, -// update_cat = 13, - call_double_const_method = 15, - flush_ostreams = 16 - }; - - // Messages from queue to master - enum class Q2M : int { - retrieve_rejected = 20, - retrieve_accepted = 21 - }; - - // Messages from worker to queue - enum class W2Q : int { - dequeue = 30, - send_result = 31 - }; - - // Messages from queue to worker - enum class Q2W : int { - terminate = 400, - dequeue_rejected = 40, - dequeue_accepted = 41, - result_received = 43, - update_real = 44, -// update_cat = 45 - call_double_const_method = 46, - flush_ostreams = 47 - }; - - // stream output operators for debugging - std::ostream& operator<<(std::ostream& out, const M2Q value); - std::ostream& operator<<(std::ostream& out, const Q2M value); - std::ostream& operator<<(std::ostream& out, const Q2W value); - std::ostream& operator<<(std::ostream& out, const W2Q value); - - } // namespace MultiProcessV1 - -// // forward declaration: -// class BidirMMapPipe; -// -// // bipe stream operators for message enum classes -// BidirMMapPipe& operator<<(BidirMMapPipe& bipe, const MultiProcess::M2Q& sent); -// BidirMMapPipe& operator>>(BidirMMapPipe& bipe, MultiProcess::M2Q& received); -// BidirMMapPipe& operator<<(BidirMMapPipe& bipe, const MultiProcess::Q2M& sent); -// BidirMMapPipe& operator>>(BidirMMapPipe& bipe, MultiProcess::Q2M& received); -// BidirMMapPipe& operator<<(BidirMMapPipe& bipe, const MultiProcess::Q2W& sent); -// BidirMMapPipe& operator>>(BidirMMapPipe& bipe, MultiProcess::Q2W& received); -// BidirMMapPipe& operator<<(BidirMMapPipe& bipe, const MultiProcess::W2Q& sent); -// BidirMMapPipe& operator>>(BidirMMapPipe& bipe, MultiProcess::W2Q& received); - -} // namespace RooFit - -#endif //ROOFIT_MULTIPROCESS_MESSAGES_H diff --git a/roofit/roofitcore/inc/MultiProcess/util.h b/roofit/roofitcore/inc/MultiProcess/util.h deleted file mode 100644 index 2a2d9551bbbe2..0000000000000 --- a/roofit/roofitcore/inc/MultiProcess/util.h +++ /dev/null @@ -1,22 +0,0 @@ -/***************************************************************************** - * Project: RooFit * - * Package: RooFitCore * - * @(#)root/roofitcore:$Id$ - * Authors: * - * PB, Patrick Bos, NL eScience Center, p.bos@esciencecenter.nl * - * * - * Redistribution and use in source and binary forms, * - * with or without modification, are permitted according to the terms * - * listed in LICENSE (http://roofit.sourceforge.net/license.txt) * - *****************************************************************************/ - -#ifndef ROOFIT_MULTIPROCESS_UTIL_H -#define ROOFIT_MULTIPROCESS_UTIL_H - -#include // getpid, pid_t -namespace RooFit { - namespace MultiProcessV1 { - int wait_for_child(pid_t child_pid, bool may_throw, int retries_before_killing); - } -} -#endif //ROOFIT_MULTIPROCESS_UTIL_H diff --git a/roofit/roofitcore/src/MultiProcess/BidirMMapPipe.cxx b/roofit/roofitcore/src/BidirMMapPipe.cxx similarity index 99% rename from roofit/roofitcore/src/MultiProcess/BidirMMapPipe.cxx rename to roofit/roofitcore/src/BidirMMapPipe.cxx index a08f51aef370e..f4728b041ab4d 100644 --- a/roofit/roofitcore/src/MultiProcess/BidirMMapPipe.cxx +++ b/roofit/roofitcore/src/BidirMMapPipe.cxx @@ -30,7 +30,7 @@ #include #include -#include +#include #include #define BEGIN_NAMESPACE_ROOFIT namespace RooFit { diff --git a/roofit/roofitcore/src/MultiProcess/GradMinimizerFcn.cxx b/roofit/roofitcore/src/MultiProcess/GradMinimizerFcn.cxx deleted file mode 100644 index 6c2cabeb0cda3..0000000000000 --- a/roofit/roofitcore/src/MultiProcess/GradMinimizerFcn.cxx +++ /dev/null @@ -1,232 +0,0 @@ -/***************************************************************************** - * Project: RooFit * - * Package: RooFitCore * - * @(#)root/roofitcore:$Id$ - * Authors: * - * PB, Patrick Bos, NL eScience Center, p.bos@esciencecenter.nl * - * * - * Redistribution and use in source and binary forms, * - * with or without modification, are permitted according to the terms * - * listed in LICENSE (http://roofit.sourceforge.net/license.txt) * - *****************************************************************************/ - -#include -#include -#include - -#include - -namespace RooFit { - namespace MultiProcessV1 { - GradMinimizerFcn::GradMinimizerFcn(RooAbsReal *funct, RooMinimizerGenericPtr context, std::size_t _N_workers, - bool verbose) : - RooFit::MultiProcessV1::Vector(_N_workers, funct, context, verbose) { - N_tasks = NDim(); - completed_task_ids.reserve(N_tasks); - // TODO: make sure that the full gradients are sent back so that the - // derivator will depart from correct state next step everywhere! - } - - // copy ctor (necessary for Clone) - GradMinimizerFcn::GradMinimizerFcn(const GradMinimizerFcn& other) : - RooFit::MultiProcessV1::Vector(other), - N_tasks(other.N_tasks), - completed_task_ids(other.completed_task_ids) {} - - ROOT::Math::IMultiGradFunction* GradMinimizerFcn::Clone() const { - return new GradMinimizerFcn(*this) ; - } - - // SYNCHRONIZATION FROM MASTER TO WORKERS - - void GradMinimizerFcn::update_state() { - // TODO optimization: only send changed parameters (now sending all) - RooFit::MultiProcessV1::M2Q msg = RooFit::MultiProcessV1::M2Q::update_real; - for (std::size_t ix = 0; ix < NDim(); ++ix) { - get_manager()->send_from_master_to_queue(msg, id, ix, _grad.Grad()(ix), false); - } - for (std::size_t ix = 0; ix < NDim(); ++ix) { - get_manager()->send_from_master_to_queue(msg, id, ix + 1 * NDim(), _grad.G2()(ix), false); - } - for (std::size_t ix = 0; ix < NDim(); ++ix) { - get_manager()->send_from_master_to_queue(msg, id, ix + 2 * NDim(), _grad.Gstep()(ix), false); - } - - std::size_t ix = 0; - for (auto parameter : _grad_params) { - get_manager()->send_from_master_to_queue(msg, id, ix + 3 * NDim(), parameter, false); - ++ix; - } - } - - void GradMinimizerFcn::update_real(std::size_t ix, double val, bool /*is_constant*/) - { - if (get_manager()->is_worker()) { - // ix is defined in "flat" FunctionGradient space ix_dim * size + ix_component - switch (ix / NDim()) { - case 0: { - _grad[ix % NDim()].derivative = val; - break; - } - case 1: { - _grad[ix % NDim()].second_derivative = val; - break; - } - case 2: { - _grad[ix % NDim()].step_size = val; - break; - } - case 3: { - sync_parameter(val, ix % NDim()); - break; - } - default: throw std::runtime_error("ix out of range in GradMinimizerFcn::update_real!"); - } - } - } - - // END SYNCHRONIZATION FROM MASTER TO WORKERS - - - // SYNCHRONIZATION FROM WORKERS TO MASTER - - void GradMinimizerFcn::send_back_task_result_from_worker(std::size_t task) { - get_manager()->send_from_worker_to_queue(id, task, _grad[task].derivative, _grad[task].second_derivative, _grad[task].step_size); - } - - void GradMinimizerFcn::receive_task_result_on_queue(std::size_t task, std::size_t worker_id) - { - completed_task_ids.push_back(task); - _grad[task].derivative = get_manager()->receive_from_worker_on_queue(worker_id); - _grad[task].second_derivative = get_manager()->receive_from_worker_on_queue(worker_id); - _grad[task].step_size = get_manager()->receive_from_worker_on_queue(worker_id); - } - - void GradMinimizerFcn::send_back_results_from_queue_to_master() { - get_manager()->send_from_queue_to_master(completed_task_ids.size()); - for (auto task : completed_task_ids) { - get_manager()->send_from_queue_to_master(task, _grad[task].derivative, _grad[task].second_derivative, _grad[task].step_size); - } - } - - void GradMinimizerFcn::clear_results() { - completed_task_ids.clear(); - } - - void GradMinimizerFcn::receive_results_on_master() - { - std::size_t N_completed_tasks = get_manager()->receive_from_queue_on_master(); - for (unsigned int sync_ix = 0u; sync_ix < N_completed_tasks; ++sync_ix) { - std::size_t task = get_manager()->receive_from_queue_on_master(); - _grad[task].derivative = get_manager()->receive_from_queue_on_master(); - _grad[task].second_derivative = get_manager()->receive_from_queue_on_master(); - _grad[task].step_size = get_manager()->receive_from_queue_on_master(); - } - } - - // END SYNCHRONIZATION FROM WORKERS TO MASTER - - - // ACTUAL WORK - - void GradMinimizerFcn::evaluate_task(std::size_t task) { - RooWallTimer timer; - RooCPUTimer ctimer; - run_derivator(task); - ctimer.stop(); - timer.stop(); - oocxcoutD((TObject*)nullptr,Benchmarking1) << "worker_id: " << get_manager()->get_worker_id() << ", task: " << task << ", partial derivative time: " << timer.timing_s() << "s -- cputime: " << ctimer.timing_s() << "s" << std::endl; - } - - double GradMinimizerFcn::get_task_result(std::size_t task) { - // this is useless here - return _grad.Grad()(task); - } - - - void GradMinimizerFcn::CalculateAll(const double *x) { - auto get_time = [](){return std::chrono::duration_cast(std::chrono::high_resolution_clock::now().time_since_epoch()).count();}; - decltype(get_time()) t1, t2; - - if (get_manager()->is_master()) { - // do Grad, G2 and Gstep here and then just return results from the - // separate functions below - bool was_not_synced = sync_parameters(x); - if (was_not_synced) { - // update parameters and object states that changed since last calculation (or creation if first time) - t1 = get_time(); - update_state(); - t2 = get_time(); - - RooWallTimer timer; - - // master fills queue with tasks - for (std::size_t ix = 0; ix < N_tasks; ++ix) { - JobTask job_task(id, ix); - get_manager()->to_queue(job_task); - } - waiting_for_queued_tasks = true; - - // wait for task results back from workers to master (put into _grad) - gather_worker_results(); - - timer.stop(); - - oocxcoutD((TObject*)nullptr,Benchmarking1) << "update_state: " << (t2 - t1)/1.e9 << "s (from " << t1 << " to " << t2 << "ns), gradient work: " << timer.timing_s() << "s" << std::endl; - } - } - } - - void GradMinimizerFcn::Gradient(const double *x, double *grad) const { - const_cast(this)->mutable_Gradient(x, grad); - } - - void GradMinimizerFcn::mutable_Gradient(const double *x, double *grad) { - if (get_manager()->is_master()) { - CalculateAll(x); - - // TODO: maybe make a flag to avoid this copy operation, but maybe not worth the effort - // put the results from _grad into *grad - for (std::size_t ix = 0; ix < NDim(); ++ix) { - grad[ix] = _grad.Grad()(ix); - } - } - } - - void GradMinimizerFcn::G2ndDerivative(const double *x, double *g2) const { - const_cast(this)->mutable_G2ndDerivative(x, g2); - } - - void GradMinimizerFcn::mutable_G2ndDerivative(const double *x, double *g2) { - if (get_manager()->is_master()) { - CalculateAll(x); - - // TODO: maybe make a flag to avoid this copy operation, but maybe not worth the effort - // put the results from _grad into *grad - for (std::size_t ix = 0; ix < NDim(); ++ix) { - g2[ix] = _grad.G2()(ix); - } - } - } - - void GradMinimizerFcn::GStepSize(const double *x, double *gstep) const { - const_cast(this)->mutable_GStepSize(x, gstep); - } - - void GradMinimizerFcn::mutable_GStepSize(const double *x, double *gstep) { - if (get_manager()->is_master()) { - CalculateAll(x); - - // TODO: maybe make a flag to avoid this copy operation, but maybe not worth the effort - // put the results from _grad into *grad - for (std::size_t ix = 0; ix < NDim(); ++ix) { - gstep[ix] = _grad.Gstep()(ix); - } - } - } - - // END ACTUAL WORK - -// RooGradMinimizerFcn(funct, context, verbose), N_workers() {} - } // namespace MultiProcess -} // namespace RooFit diff --git a/roofit/roofitcore/src/MultiProcess/Job.cxx b/roofit/roofitcore/src/MultiProcess/Job.cxx deleted file mode 100644 index 0913e008f7944..0000000000000 --- a/roofit/roofitcore/src/MultiProcess/Job.cxx +++ /dev/null @@ -1,176 +0,0 @@ -/***************************************************************************** - * Project: RooFit * - * Package: RooFitCore * - * @(#)root/roofitcore:$Id$ - * Authors: * - * PB, Patrick Bos, NL eScience Center, p.bos@esciencecenter.nl * - * IP, Inti Pelupessy, NL eScience Center, i.pelupessy@esciencecenter.nl * - * * - * Redistribution and use in source and binary forms, * - * with or without modification, are permitted according to the terms * - * listed in LICENSE (http://roofit.sourceforge.net/license.txt) * - *****************************************************************************/ - -#include // getpid - -#include - -#include -#include -#include - -namespace RooFit { - namespace MultiProcessV1 { - Job::Job(std::size_t _N_workers) : N_workers(_N_workers) {} - Job::Job(const Job & other) : - N_workers(other.N_workers), - waiting_for_queued_tasks(other.waiting_for_queued_tasks), - _manager(other._manager) - {} - - double Job::call_double_const_method(std::string /*key*/) { - throw std::logic_error("call_double_const_method not implemented for this Job"); - } - - // This default sends back only one double as a result; can be overloaded - // e.g. for RooAbsCategorys, for tuples, etc. The queue_loop and master - // process must implement corresponding result receivers. - void Job::send_back_task_result_from_worker(std::size_t task) { - double result = get_task_result(task); - get_manager()->send_from_worker_to_queue(id, task, result); - } - - // static function - void Job::worker_loop() { - assert(TaskManager::instance()->is_worker()); - worker_loop_running = true; - bool carry_on = true; - Task task; - std::size_t job_id; - Q2W message_q2w; - - // use a flag to not ask twice - bool dequeue_acknowledged = true; - - auto get_time = [](){return std::chrono::duration_cast(std::chrono::high_resolution_clock::now().time_since_epoch()).count();}; - - while (carry_on) { - decltype(get_time()) t1 = 0, t2 = 0, t3 = 0; - - // try to dequeue a task - if (dequeue_acknowledged) { // don't ask twice - t1 = get_time(); - TaskManager::instance()->send_from_worker_to_queue(W2Q::dequeue); - dequeue_acknowledged = false; - } - - // receive handshake - message_q2w = TaskManager::instance()->receive_from_queue_on_worker(); - - switch (message_q2w) { - case Q2W::terminate: { - carry_on = false; - break; - } - - case Q2W::dequeue_rejected: { - t2 = get_time(); - oocxcoutD((TObject*)nullptr,Benchmarking2) << "no work: worker " << TaskManager::instance()->get_worker_id() << " asked at " << t1 << " and got rejected at " << t2 << std::endl; - - dequeue_acknowledged = true; - break; - } - case Q2W::dequeue_accepted: { - dequeue_acknowledged = true; - job_id = TaskManager::instance()->receive_from_queue_on_worker(); - task = TaskManager::instance()->receive_from_queue_on_worker(); - - t2 = get_time(); - TaskManager::get_job_object(job_id)->evaluate_task(task); - - t3 = get_time(); - oocxcoutD((TObject*)nullptr,Benchmarking2) << "job done: worker " << TaskManager::instance()->get_worker_id() << " asked at " << t1 << ", started at " << t2 << " and finished at " << t3 << std::endl; - - TaskManager::instance()->send_from_worker_to_queue(W2Q::send_result); - TaskManager::get_job_object(job_id)->send_back_task_result_from_worker(task); - - message_q2w = TaskManager::instance()->receive_from_queue_on_worker(); - if (message_q2w != Q2W::result_received) { - std::cerr << "worker " << getpid() << " sent result, but did not receive Q2W::result_received handshake! Got " << message_q2w << " instead." << std::endl; - throw std::runtime_error(""); - } - break; - } - - // previously this was non-work mode - - case Q2W::update_real: { - auto t1 = get_time(); - - job_id = TaskManager::instance()->receive_from_queue_on_worker(); - std::size_t ix = TaskManager::instance()->receive_from_queue_on_worker(); - double val = TaskManager::instance()->receive_from_queue_on_worker(); - bool is_constant = TaskManager::instance()->receive_from_queue_on_worker(); - TaskManager::get_job_object(job_id)->update_real(ix, val, is_constant); - - auto t2 = get_time(); - oocxcoutD((TObject*)nullptr,Benchmarking1) << "update_real on worker " << TaskManager::instance()->get_worker_id() << ": " << (t2 - t1)/1.e9 << "s (from " << t1 << " to " << t2 << "ns)" << std::endl; - - break; - } - - case Q2W::call_double_const_method: { - job_id = TaskManager::instance()->receive_from_queue_on_worker(); - std::string key = TaskManager::instance()->receive_from_queue_on_worker(); - Job * job = TaskManager::get_job_object(job_id); -// double (* method)() = job->get_double_const_method(key); - double result = job->call_double_const_method(key); - TaskManager::instance()->send_from_worker_to_queue(result); - break; - } - - case Q2W::flush_ostreams: { - TaskManager::instance()->flush_ostreams(); - break; - } - - case Q2W::result_received: { - std::cerr << "In worker_loop: " << message_q2w << " message received, but should only be received as handshake!" << std::endl; - break; - } - - } - } - } - - TaskManager* Job::get_manager() { - if (!_manager) { - _manager = TaskManager::instance(N_workers); - -// _manager->identify_processes(); -// sleep(10); - } - - // N.B.: must check for activation here, otherwise get_manager is not callable - // from queue loop! - if (!_manager->is_activated()) { - _manager->activate(); - } - - if (!worker_loop_running && _manager->is_worker()) { - Job::worker_loop(); - _manager->close_worker_connections(); - // flush remaining output - _manager->flush_ostreams(); -// std::cout << "exiting worker process " << getpid() << std::endl; - std::_Exit(0); - } - - return _manager; - } - - // initialize static members - bool Job::worker_loop_running = false; - - } // namespace MultiProcess -} // namespace RooFit diff --git a/roofit/roofitcore/src/MultiProcess/NLLVar.cxx b/roofit/roofitcore/src/MultiProcess/NLLVar.cxx deleted file mode 100644 index ddd46b05704f5..0000000000000 --- a/roofit/roofitcore/src/MultiProcess/NLLVar.cxx +++ /dev/null @@ -1,225 +0,0 @@ -/***************************************************************************** - * Project: RooFit * - * Package: RooFitCore * - * @(#)root/roofitcore:$Id$ - * Authors: * - * PB, Patrick Bos, NL eScience Center, p.bos@esciencecenter.nl * - * IP, Inti Pelupessy, NL eScience Center, i.pelupessy@esciencecenter.nl * - * * - * Redistribution and use in source and binary forms, * - * with or without modification, are permitted according to the terms * - * listed in LICENSE (http://roofit.sourceforge.net/license.txt) * - *****************************************************************************/ - -#include -#include -#include -#include // make_unique -#include // std::tie - -namespace RooFit { - namespace MultiProcessV1 { - - std::ostream& operator<<(std::ostream& out, const NLLVarTask value) { - const char* s = 0; -#define PROCESS_VAL(p) case(p): s = #p; break; - switch(value){ - PROCESS_VAL(NLLVarTask::all_events); - PROCESS_VAL(NLLVarTask::single_event); - PROCESS_VAL(NLLVarTask::bulk_partition); - PROCESS_VAL(NLLVarTask::interleave); - } -#undef PROCESS_VAL - return out << s; - } - - - NLLVar::NLLVar(std::size_t NumCPU, NLLVarTask task_mode, const RooNLLVar& nll) : - RooFit::MultiProcessV1::Vector(NumCPU, nll), // uses copy constructor for the RooNLLVar part - mp_task_mode(task_mode) - { - if (_gofOpMode == RooAbsTestStatistic::GOFOpMode::MPMaster) { - TaskManager::remove_job_object(id); - throw std::logic_error("Cannot create MPRooNLLVar based on a multi-CPU enabled RooNLLVar! The use of the BidirMMapPipe by MPFE in RooNLLVar conflicts with the use of BidirMMapPipe by MultiProcess classes."); - } - - _vars = RooListProxy("vars", "vars", this); - init_vars(); - switch (mp_task_mode) { - case NLLVarTask::all_events: { - N_tasks = 1; - break; - } - case NLLVarTask::single_event: { - N_tasks = static_cast(_data->numEntries()); - break; - } - case NLLVarTask::bulk_partition: - case NLLVarTask::interleave: { - N_tasks = NumCPU; - break; - } - } - - double_const_methods["getCarry"] = &NLLVar::getCarry; - } - - void NLLVar::init_vars() { - // Empty current lists - _vars.removeAll() ; - _saveVars.removeAll() ; - - // Retrieve non-constant parameters - auto vars = std::make_unique(*getParameters(RooArgSet())); - RooArgList varList(*vars); - - // Save in lists - _vars.add(varList); - _saveVars.addClone(varList); - } - - void NLLVar::update_parameters() { - if (get_manager()->is_master()) { - for (std::size_t ix = 0u; ix < static_cast(_vars.getSize()); ++ix) { - bool valChanged = !_vars[ix].isIdentical(_saveVars[ix], kTRUE); - bool constChanged = (_vars[ix].isConstant() != _saveVars[ix].isConstant()); - - if (valChanged || constChanged) { - if (constChanged) { - ((RooRealVar *) &_saveVars[ix])->setConstant(_vars[ix].isConstant()); - } - // TODO: Check with Wouter why he uses copyCache in MPFE; makes it very difficult to extend, because copyCache is protected (so must be friend). Moved setting value to if-block below. - // _saveVars[ix].copyCache(&_vars[ix]); - - // send message to queue (which will relay to workers) - RooAbsReal * rar_val = dynamic_cast(&_vars[ix]); - if (rar_val) { - Double_t val = rar_val->getVal(); - dynamic_cast(&_saveVars[ix])->setVal(val); - RooFit::MultiProcessV1::M2Q msg = RooFit::MultiProcessV1::M2Q::update_real; - Bool_t isC = _vars[ix].isConstant(); - get_manager()->send_from_master_to_queue(msg, id, ix, val, isC); - } - // TODO: implement category handling - // } else if (dynamic_cast(var)) { - // M2Q msg = M2Q::update_cat ; - // UInt_t cat_ix = ((RooAbsCategory*)var)->getIndex(); - // *_pipe << msg << ix << cat_ix; - // } - } - } - } - } - - Double_t NLLVar::evaluate() const { - return const_cast(this)->evaluate_non_const(); - } - - Double_t NLLVar::evaluate_non_const() { - if (get_manager()->is_master()) { - // update parameters that changed since last calculation (or creation if first time) - update_parameters(); - - // master fills queue with tasks - for (std::size_t ix = 0; ix < N_tasks; ++ix) { - JobTask job_task(id, ix); - get_manager()->to_queue(job_task); - } - waiting_for_queued_tasks = true; - - // wait for task results back from workers to master - gather_worker_results(); - - // put the results in vectors for calling sum_of_kahan_sums (TODO: make a map-friendly sum_of_kahan_sums) - std::vector results_vec, carrys_vec; - for (auto const &item : results) { - results_vec.emplace_back(item.second); - carrys_vec.emplace_back(carrys[item.first]); - } - - // sum task results - std::tie(result, carry) = sum_of_kahan_sums(results_vec, carrys_vec); - } - return result; - } - - // --- RESULT LOGISTICS --- - - void NLLVar::send_back_task_result_from_worker(std::size_t task) { - result = get_task_result(task); - carry = getCarry(); - get_manager()->send_from_worker_to_queue(id, task, result, carry); - } - - void NLLVar::receive_task_result_on_queue(std::size_t task, std::size_t worker_id) { - result = get_manager()->receive_from_worker_on_queue(worker_id); - carry = get_manager()->receive_from_worker_on_queue(worker_id); - results[task] = result; - carrys[task] = carry; - } - - void NLLVar::send_back_results_from_queue_to_master() { - get_manager()->send_from_queue_to_master(results.size()); - for (auto const &item : results) { - get_manager()->send_from_queue_to_master(item.first, item.second, carrys[item.first]); - } - } - - void NLLVar::clear_results() { - // empty results caches - results.clear(); - carrys.clear(); - } - - void NLLVar::receive_results_on_master() { - std::size_t N_job_tasks = get_manager()->receive_from_queue_on_master(); - for (std::size_t task_ix = 0ul; task_ix < N_job_tasks; ++task_ix) { - std::size_t task_id = get_manager()->receive_from_queue_on_master(); - results[task_id] = get_manager()->receive_from_queue_on_master(); - carrys[task_id] = get_manager()->receive_from_queue_on_master(); - } - } - - // --- END OF RESULT LOGISTICS --- - - void NLLVar::evaluate_task(std::size_t task) { - assert(get_manager()->is_worker()); - std::size_t N_events = static_cast(_data->numEntries()); - // "default" values (all events in one task) - std::size_t first = task; - std::size_t last = N_events; - std::size_t step = 1; - switch (mp_task_mode) { - case NLLVarTask::all_events: { - // default values apply - break; - } - case NLLVarTask::single_event: { - last = task + 1; - break; - } - case NLLVarTask::bulk_partition: { - first = N_events * task / N_tasks; - last = N_events * (task + 1) / N_tasks; - break; - } - case NLLVarTask::interleave: { - step = N_tasks; - break; - } - } - - result = evaluatePartition(first, last, step); - } - - double NLLVar::get_task_result(std::size_t /*task*/) { - // TODO: this is quite ridiculous, having a get_task_result without task - // argument. We should have a cache, e.g. a map, that gives the result for - // a given task. The caller (usually send_back_task_result_from_worker) can - // then decide whether to erase the value from the cache to keep it clean. - assert(get_manager()->is_worker()); - return result; - } - - } // namespace MultiProcess -} // namespace RooFit diff --git a/roofit/roofitcore/src/MultiProcess/TaskManager.cxx b/roofit/roofitcore/src/MultiProcess/TaskManager.cxx deleted file mode 100644 index 59abd5ea74678..0000000000000 --- a/roofit/roofitcore/src/MultiProcess/TaskManager.cxx +++ /dev/null @@ -1,622 +0,0 @@ -/***************************************************************************** - * Project: RooFit * - * Package: RooFitCore * - * @(#)root/roofitcore:$Id$ - * Authors: * - * PB, Patrick Bos, NL eScience Center, p.bos@esciencecenter.nl * - * IP, Inti Pelupessy, NL eScience Center, i.pelupessy@esciencecenter.nl * - * * - * Redistribution and use in source and binary forms, * - * with or without modification, are permitted according to the terms * - * listed in LICENSE (http://roofit.sourceforge.net/license.txt) * - *****************************************************************************/ - -#include // logic_error -#include -#include // getpid -// for cpu affinity -#if !defined(__APPLE__) && !defined(_WIN32) -#include -#endif - -#include - -#include // make_unique in C++11 -#include -#include -#include -#include -#include -#include // for_each - -namespace RooFit { - namespace MultiProcessV1 { - - void set_socket_immediate(ZmqLingeringSocketPtr<> & socket) { - int optval = 1; - socket->setsockopt(ZMQ_IMMEDIATE, &optval, sizeof(optval)); - } - - // static function - TaskManager* TaskManager::instance(std::size_t N_workers) { - if (!TaskManager::is_instantiated()) { - assert(N_workers != 0); - _instance = std::make_unique(N_workers); - } - // these sanity checks no longer make sense with the worker_pipes only being maintained on the queue process -// } else { -// // some sanity checks -// if(_instance->is_master() && N_workers != _instance->worker_pipes.size()) { -// std::cerr << "On PID " << getpid() << ": N_workers != tmp->worker_pipes.size())! N_workers = " << N_workers << ", tmp->worker_pipes.size() = " << _instance->worker_pipes.size() << std::endl; -// throw std::logic_error(""); -// } else if (_instance->is_worker()) { -// if (_instance->get_worker_id() + 1 != _instance->worker_pipes.size()) { -// std::cerr << "On PID " << getpid() << ": tmp->get_worker_id() + 1 != tmp->worker_pipes.size())! tmp->get_worker_id() = " << _instance->get_worker_id() << ", tmp->worker_pipes.size() = " << _instance->worker_pipes.size() << std::endl; -// throw std::logic_error(""); -// } -// } -// } - return _instance.get(); - } - - // static function - TaskManager* TaskManager::instance() { - if (!TaskManager::is_instantiated()) { - throw std::runtime_error("in TaskManager::instance(): no instance was created yet! Call TaskManager::instance(std::size_t N_workers) first."); - } - return _instance.get(); - } - - // static function - bool TaskManager::is_instantiated() { - return static_cast(_instance); - } - - - void TaskManager::identify_processes() { - // identify yourselves (for debugging) - if (!(_is_master || _is_queue)) { - std::cout << "I'm a worker, PID " << getpid() << '\n'; - } else if (_is_master) { - std::cout << "I'm master, PID " << getpid() << '\n'; - } else if (_is_queue) { - std::cout << "I'm queue, PID " << getpid() << '\n'; - } - } - - // constructor - // Don't construct IPQM objects manually, use the static instance if - // you need to run multiple jobs. - TaskManager::TaskManager(std::size_t N_workers) : N_workers(N_workers) { - // This class defines three types of processes: - // 1. master: the initial main process. It defines and enqueues tasks - // and processes results. - // 2. workers: a pool of processes that will try to take tasks from the - // queue. These are first forked from master. - // 3. queue: communication between the other types (necessarily) goes - // through this process. This process runs the queue_loop and - // maintains the queue of tasks. It is forked last and initialized - // with third BidirMMapPipe parameter false, which makes it the - // process that manages all pipes, though the pool of pages remains - // on the master process. - // The reason for using this layout is that we use BidirMMapPipe for - // forking and communicating between processes, and BidirMMapPipe only - // supports forking from one process, not from an already forked - // process (if forked using BidirMMapPipe). The latter layout would - // allow us to fork first the queue from the main process and then fork - // the workers from the queue, which may feel more natural. - - initialize_processes(); - } - - void TaskManager::initialize_processes(bool cpu_pinning) { - // Initialize processes; - // ... first workers: - worker_pids.resize(N_workers); - pid_t child_pid {}; - for (std::size_t ix = 0; ix < N_workers; ++ix) { - child_pid = fork(); - if (!child_pid) { // we're on the worker - worker_id = ix; - break; - } else { // we're on master - worker_pids[ix] = child_pid; - } - } - - // ... then queue: - if (child_pid) { // we're on master - queue_pid = fork(); - if (!queue_pid) { // we're now on queue - _is_queue = true; - } else { - _is_master = true; - } - } - - // after all forks, create zmq connections (zmq context is automatically created in the ZeroMQSvc class and maintained as singleton) - try { - if (is_master()) { - mq_socket.reset(zmqSvc().socket_ptr(zmq::PAIR)); //REQ)); - set_socket_immediate(mq_socket); - mq_socket->bind("ipc:///tmp/roofitMP_master_queue"); - // mq_socket->bind("tcp://*:55555"); - } else if (is_queue()) { - // first the queue-worker sockets - qw_sockets.resize(N_workers); // do resize instead of reserve so that the unique_ptrs are initialized (to nullptr) so that we can do reset below, alternatively you can do push/emplace_back with move or something - for (std::size_t ix = 0; ix < N_workers; ++ix) { - qw_sockets[ix].reset(zmqSvc().socket_ptr(zmq::PAIR)); //REP)); - set_socket_immediate(qw_sockets[ix]); - std::stringstream socket_name; - socket_name << "ipc:///tmp/roofitMP_queue_worker_" << ix; - // socket_name << "tcp://*:" << 55556 + ix; - qw_sockets[ix]->bind(socket_name.str()); - } - // then the master-queue socket - mq_socket.reset(zmqSvc().socket_ptr(zmq::PAIR)); //REP)); - set_socket_immediate(mq_socket); - mq_socket->connect("ipc:///tmp/roofitMP_master_queue"); - // mq_socket->connect("tcp://127.0.0.1:55555"); - } else if (is_worker()) { - this_worker_qw_socket.reset(zmqSvc().socket_ptr(zmq::PAIR)); //REQ)); - set_socket_immediate(this_worker_qw_socket); - std::stringstream socket_name; - socket_name << "ipc:///tmp/roofitMP_queue_worker_" << worker_id; - // socket_name << "tcp://127.0.0.1:" << 55556 + worker_id; - this_worker_qw_socket->connect(socket_name.str()); - } else { - // should never get here - throw std::runtime_error("TaskManager::initialize_processes: I'm neither master, nor queue, nor a worker"); - } - } catch (zmq::error_t& e) { - std::cerr << e.what() << " -- errnum: " << e.num() << std::endl; - throw; - }; - - if (cpu_pinning) { - #if defined(__APPLE__) - static bool affinity_warned = false; - if (is_master() & !affinity_warned) { - ooccoutD(static_cast(nullptr),Eval) << "CPU affinity cannot be set on macOS"; - } - #elif defined(_WIN32) - if (is_master()) std::cerr << "WARNING: CPU affinity setting not implemented on Windows, continuing...\n"; - #else - cpu_set_t mask; - // zero all bits in mask - CPU_ZERO(&mask); - // set correct bit - std::size_t set_cpu; - if (is_master()) { - set_cpu = N_workers + 1; - } else if (is_queue()) { - set_cpu = N_workers; - } else { - set_cpu = worker_id; - } - CPU_SET(set_cpu, &mask); - /* sched_setaffinity returns 0 in success */ - - if (sched_setaffinity(0, sizeof(mask), &mask) == -1) { - std::cerr << "WARNING: Could not set CPU affinity, continuing...\n"; - } else { - std::cerr << "CPU affinity set to cpu " << set_cpu << " in process " << getpid() << '\n'; - } - #endif - } - -// identify_processes(); - - processes_initialized = true; - } - - - TaskManager::~TaskManager() { - ooccoutD(static_cast(nullptr),Eval) << "destroying TaskManager on PID " << getpid() << (is_worker() ? " worker" : (is_queue()? " queue" : " master")); - // The TM instance gets created by some Job. Once all Jobs are gone, the - // TM will get destroyed. In this case, the job_objects map should have - // been emptied. This check makes sure: - assert(TaskManager::job_objects.empty()); - terminate(); - } - - - // static function - // returns job_id for added job_object - std::size_t TaskManager::add_job_object(Job *job_object) { - if (TaskManager::is_instantiated()) { - if (_instance->is_activated()) { - std::stringstream ss; - ss << "Cannot add Job to activated TaskManager instantiation (forking has already taken place)! Instance object at raw ptr " << _instance.get(); - throw std::logic_error("Cannot add Job to activated TaskManager instantiation (forking has already taken place)! Call terminate() on the instance before adding new Jobs."); - } - } - std::size_t job_id = job_counter++; - job_objects[job_id] = job_object; - return job_id; - } - - // static function - Job* TaskManager::get_job_object(std::size_t job_object_id) { - return job_objects[job_object_id]; - } - - // static function - bool TaskManager::remove_job_object(std::size_t job_object_id) { - bool removed_succesfully = job_objects.erase(job_object_id) == 1; - if (job_objects.empty()) { - _instance.reset(nullptr); - } - return removed_succesfully; - } - - - void TaskManager::terminate() noexcept { - try { - if (is_master()) { - send_from_master_to_queue(M2Q::terminate); -// int period = 0; -// mq_socket->setsockopt(ZMQ_LINGER, &period, sizeof(period)); - mq_socket.reset(nullptr); - zmqSvc().close_context(); - queue_activated = false; - shutdown_processes(); - } -// } catch (const BidirMMapPipe::Exception& e) { -// std::cerr << "WARNING: in TaskManager::terminate, something in BidirMMapPipe threw an exception! Message:\n\t" << e.what() << std::endl; - } catch (const std::exception& e) { - std::cerr << "WARNING: something in TaskManager::terminate threw an exception! Original exception message:\n" << e.what() << '\n'; - } - } - - - void TaskManager::shutdown_processes() { - if (is_master()) { -// send_from_master_to_queue(M2Q::terminate); -// *queue_pipe << M2Q::terminate << BidirMMapPipe::flush; - - // first wait for the workers that will be terminated by the queue - for (auto pid : worker_pids) { - wait_for_child(pid, true, 5); - } - // then wait for the queue - wait_for_child(queue_pid, true, 5); - -// // delete queue_pipe (not worker_pipes, only on queue process!) -// // CAUTION: the following invalidates a possibly created PollVector -// queue_pipe.reset(); // sets to nullptr - -// for (auto it = worker_pids.begin(); it != worker_pids.end(); ++it) { -// BidirMMapPipe::wait_for_child(*it, true); -// } - } - - processes_initialized = false; - _is_master = false; - } - - void TaskManager::close_worker_connections() { -// int period = 0; - if (is_worker()) { -// this_worker_qw_socket->setsockopt(ZMQ_LINGER, &period, sizeof(period)); - this_worker_qw_socket.reset(nullptr); - zmqSvc().close_context(); - } else if (is_queue()) { - for (std::size_t worker_ix = 0ul; worker_ix < N_workers; ++worker_ix) { -// qw_sockets[worker_ix]->setsockopt(ZMQ_LINGER, &period, sizeof(period)); - qw_sockets[worker_ix].reset(nullptr); - } - } - } - - void TaskManager::terminate_workers() { - if (is_queue()) { -// for (std::unique_ptr &worker_pipe : worker_pipes) { -// *worker_pipe << Q2W::terminate << BidirMMapPipe::flush; -// for(auto& worker_socket : qw_sockets) { -// zmqSvc().send(*worker_socket, Q2W::terminate); - for(std::size_t worker_ix = 0; worker_ix < N_workers; ++worker_ix) { - send_from_queue_to_worker(worker_ix, Q2W::terminate); -// int retval = worker_pipe->close(); -// if (0 != retval) { -// std::cerr << "error terminating worker_pipe for worker with PID " << worker_pipe->pidOtherEnd() << "; child return value is " << retval << std::endl; -// } - } - close_worker_connections(); - } - } - - - // start message loops on child processes and quit processes afterwards - void TaskManager::activate() { -// std::cout << "activating" << std::endl; - // should be called soon after creation of this object, because everything in - // between construction and activate gets executed both on the master process - // and on the slaves - if (!processes_initialized) { -// std::cout << "intializing" << std::endl; - initialize_processes(); - } - - queue_activated = true; // set on all processes, master, queue and slaves - - if (is_queue()) { - queue_loop(); - terminate_workers(); -// int period = 0; -// mq_socket->setsockopt(ZMQ_LINGER, &period, sizeof(period)); - mq_socket.reset(nullptr); // delete/close master-queue socket from queue side - zmqSvc().close_context(); - std::_Exit(0); - } - } - - - bool TaskManager::is_activated() { - return queue_activated; - } - - - // CAUTION: - // this function returns a vector of pointers that may get invalidated by - // the terminate function! -// BidirMMapPipe::PollVector TaskManager::get_poll_vector() { -// BidirMMapPipe::PollVector poll_vector; -// poll_vector.reserve(1 + worker_pipes.size()); -// poll_vector.emplace_back(queue_pipe.get(), BidirMMapPipe::Readable); -// for (std::unique_ptr& pipe : worker_pipes) { -// poll_vector.emplace_back(pipe.get(), BidirMMapPipe::Readable); -// } -// return poll_vector; -// } - - - bool TaskManager::process_queue_pipe_message(M2Q message) { - bool carry_on = true; - - switch (message) { - case M2Q::terminate: { - carry_on = false; - } - break; - - case M2Q::enqueue: { - // enqueue task - auto job_object_id = receive_from_master_on_queue(); - auto task = receive_from_master_on_queue(); - JobTask job_task(job_object_id, task); - to_queue(job_task); - N_tasks++; - } - break; - - case M2Q::retrieve: { - // retrieve task results after queue is empty and all - // tasks have been completed - if (queue.empty() && N_tasks_completed == N_tasks) { - send_from_queue_to_master(Q2M::retrieve_accepted, job_objects.size()); - for (auto job_tuple : job_objects) { - send_from_queue_to_master(job_tuple.first); // job id - job_tuple.second->send_back_results_from_queue_to_master(); // N_job_tasks, task_ids and results - job_tuple.second->clear_results(); - } - // reset number of received and completed tasks - N_tasks = 0; - N_tasks_completed = 0; - } else { - send_from_queue_to_master(Q2M::retrieve_rejected); // handshake message: tasks not done yet, try again - } - } - break; - - case M2Q::update_real: { - auto get_time = [](){return std::chrono::duration_cast(std::chrono::high_resolution_clock::now().time_since_epoch()).count();}; - auto t1 = get_time(); - auto job_id = receive_from_master_on_queue(); - auto ix = receive_from_master_on_queue(); - auto val = receive_from_master_on_queue(); - auto is_constant = receive_from_master_on_queue(); - for (std::size_t worker_ix = 0; worker_ix < N_workers; ++worker_ix) { - send_from_queue_to_worker(worker_ix, Q2W::update_real, job_id, ix, val, is_constant); - } - auto t2 = get_time(); - oocxcoutD((TObject*)nullptr,Benchmarking1) << "update_real on queue: " << (t2 - t1)/1.e9 << "s (from " << t1 << " to " << t2 << "ns)" << std::endl; - } - break; - - case M2Q::call_double_const_method: { - auto job_id = receive_from_master_on_queue(); - auto worker_id_call = receive_from_master_on_queue(); - auto key = receive_from_master_on_queue(); - send_from_queue_to_worker(worker_id_call, Q2W::call_double_const_method, job_id, key); - auto result = receive_from_worker_on_queue(worker_id_call); - send_from_queue_to_master(result); - } - break; - - case M2Q::flush_ostreams: { - flush_ostreams(); - } - break; - } - - return carry_on; - } - - - void TaskManager::retrieve() { - if (_is_master) { - bool carry_on = true; - while (carry_on) { - send_from_master_to_queue(M2Q::retrieve); - auto handshake = receive_from_queue_on_master(); - if (handshake == Q2M::retrieve_accepted) { - carry_on = false; - auto N_jobs = receive_from_queue_on_master(); - for (std::size_t job_ix = 0; job_ix < N_jobs; ++job_ix) { - auto job_object_id = receive_from_queue_on_master(); - TaskManager::get_job_object(job_object_id)->receive_results_on_master(); - } - } - } - } - } - - - double TaskManager::call_double_const_method(const std::string& method_key, std::size_t job_id, std::size_t worker_id_call) { - send_from_master_to_queue(M2Q::call_double_const_method, job_id, worker_id_call, method_key); - auto result = receive_from_queue_on_master(); - return result; - } - - // -- WORKER - QUEUE COMMUNICATION -- - - void TaskManager::send_from_worker_to_queue() { -// *this_worker_pipe << BidirMMapPipe::flush; - } - - void TaskManager::send_from_queue_to_worker(std::size_t /*this_worker_id*/) { -// *worker_pipes[this_worker_id] << BidirMMapPipe::flush; - } - - // -- QUEUE - MASTER COMMUNICATION -- - - void TaskManager::send_from_queue_to_master() { -// *queue_pipe << BidirMMapPipe::flush; - } - - void TaskManager::send_from_master_to_queue() { - send_from_queue_to_master(); - } - - - void TaskManager::process_worker_pipe_message(std::size_t this_worker_id, W2Q message) { - switch (message) { - case W2Q::dequeue: { - // dequeue task - JobTask job_task; - if (from_queue(job_task)) { - send_from_queue_to_worker(this_worker_id, Q2W::dequeue_accepted, job_task.first, job_task.second); - } else { - send_from_queue_to_worker(this_worker_id, Q2W::dequeue_rejected); - } - break; - } - - case W2Q::send_result: { - // receive back task result - auto job_object_id = receive_from_worker_on_queue(this_worker_id); - auto task = receive_from_worker_on_queue(this_worker_id); - TaskManager::get_job_object(job_object_id)->receive_task_result_on_queue(task, this_worker_id); - send_from_queue_to_worker(this_worker_id, Q2W::result_received); - N_tasks_completed++; - break; - } - } - } - - - void TaskManager::queue_loop() { - if (_is_queue) { - bool carry_on = true; - ZeroMQPoller poller; - auto mq_index = poller.register_socket(*mq_socket, zmq::POLLIN); - for(auto& s : qw_sockets) { - poller.register_socket(*s, zmq::POLLIN); - } - - while (carry_on) { - // poll: wait until status change (-1: infinite timeout) - auto poll_result = poller.poll(-1); - // then process incoming messages from sockets - for (auto readable_socket : poll_result) { - // message comes from the master/queue socket (first element): - if (readable_socket.first == mq_index) { - auto message = receive_from_master_on_queue(); - carry_on = process_queue_pipe_message(message); - // on terminate, also stop for-loop, no need to check other - // sockets anymore: - if (!carry_on) { - break; - } - } else { // from a worker socket - auto this_worker_id = readable_socket.first - 1; - auto message = receive_from_worker_on_queue(this_worker_id); - process_worker_pipe_message(this_worker_id, message); - } - } - } - } - } - - - // Have a worker ask for a task-message from the queue - bool TaskManager::from_queue(JobTask &job_task) { - if (queue.empty()) { - return false; - } else { - job_task = queue.front(); - queue.pop(); - return true; - } - } - - - // Enqueue a task - void TaskManager::to_queue(JobTask job_task) { - if (is_master()) { - if (!queue_activated) { - activate(); - } - send_from_master_to_queue(M2Q::enqueue, job_task.first, job_task.second); - } else if (is_queue()) { - queue.push(job_task); - } else { - throw std::logic_error("calling Communicator::to_master_queue from slave process"); - } - } - - - bool TaskManager::is_master() { - return _is_master; - } - - bool TaskManager::is_queue() { - return _is_queue; - } - - bool TaskManager::is_worker() { - return !(_is_master || _is_queue); - } - - std::size_t TaskManager::get_worker_id() { - return worker_id; - } - - void TaskManager::flush_ostreams() { - if (is_master()) { - send_from_master_to_queue(M2Q::flush_ostreams); - } else if (is_queue()) { - // flush output - std::cout << std::flush; - std::clog << std::flush; - for (std::size_t worker_ix = 0; worker_ix < N_workers; ++worker_ix) { - send_from_queue_to_worker(worker_ix, Q2W::flush_ostreams); - } - } else if (is_worker()) { - // flush output - std::cout << std::flush; - std::clog << std::flush; - } else { - throw std::logic_error("calling TaskManager::flush_ostreams from unknown type of process (not master, queue or worker)"); - } - } - - - // initialize static members - std::map TaskManager::job_objects; - std::size_t TaskManager::job_counter = 0; - std::unique_ptr TaskManager::_instance {nullptr}; - - } // namespace MultiProcess -} // namespace RooFit \ No newline at end of file diff --git a/roofit/roofitcore/src/MultiProcess/messages.cxx b/roofit/roofitcore/src/MultiProcess/messages.cxx deleted file mode 100644 index ac9595760dff8..0000000000000 --- a/roofit/roofitcore/src/MultiProcess/messages.cxx +++ /dev/null @@ -1,70 +0,0 @@ -/***************************************************************************** - * Project: RooFit * - * Package: RooFitCore * - * @(#)root/roofitcore:$Id$ - * Authors: * - * PB, Patrick Bos, NL eScience Center, p.bos@esciencecenter.nl * - * IP, Inti Pelupessy, NL eScience Center, i.pelupessy@esciencecenter.nl * - * * - * Redistribution and use in source and binary forms, * - * with or without modification, are permitted according to the terms * - * listed in LICENSE (http://roofit.sourceforge.net/license.txt) * - *****************************************************************************/ - -#include - -namespace RooFit { - namespace MultiProcessV1 { - - // for debugging -#define PROCESS_VAL(p) case(p): s = #p; break; - - std::ostream& operator<<(std::ostream& out, const M2Q value){ - const char* s = 0; - switch(value){ - PROCESS_VAL(M2Q::terminate); - PROCESS_VAL(M2Q::enqueue); - PROCESS_VAL(M2Q::retrieve); - PROCESS_VAL(M2Q::update_real); - PROCESS_VAL(M2Q::call_double_const_method); - PROCESS_VAL(M2Q::flush_ostreams); - } - return out << s; - } - - std::ostream& operator<<(std::ostream& out, const Q2M value){ - const char* s = 0; - switch(value){ - PROCESS_VAL(Q2M::retrieve_rejected); - PROCESS_VAL(Q2M::retrieve_accepted); - } - return out << s; - } - - std::ostream& operator<<(std::ostream& out, const W2Q value){ - const char* s = 0; - switch(value){ - PROCESS_VAL(W2Q::dequeue); - PROCESS_VAL(W2Q::send_result); - } - return out << s; - } - - std::ostream& operator<<(std::ostream& out, const Q2W value){ - const char* s = 0; - switch(value){ - PROCESS_VAL(Q2W::terminate); - PROCESS_VAL(Q2W::dequeue_rejected); - PROCESS_VAL(Q2W::dequeue_accepted); - PROCESS_VAL(Q2W::update_real); - PROCESS_VAL(Q2W::result_received); - PROCESS_VAL(Q2W::call_double_const_method); - PROCESS_VAL(Q2W::flush_ostreams); - } - return out << s; - } - -#undef PROCESS_VAL - - } // namespace MultiProcess -} // namespace RooFit diff --git a/roofit/roofitcore/src/MultiProcess/util.cxx b/roofit/roofitcore/src/MultiProcess/util.cxx deleted file mode 100644 index 91c0a99c26bb6..0000000000000 --- a/roofit/roofitcore/src/MultiProcess/util.cxx +++ /dev/null @@ -1,62 +0,0 @@ -/***************************************************************************** - * Project: RooFit * - * Package: RooFitCore * - * @(#)root/roofitcore:$Id$ - * Authors: * - * PB, Patrick Bos, NL eScience Center, p.bos@esciencecenter.nl * - * * - * Redistribution and use in source and binary forms, * - * with or without modification, are permitted according to the terms * - * listed in LICENSE (http://roofit.sourceforge.net/license.txt) * - *****************************************************************************/ - -#include // kill, SIGKILL -#include // cerr, and indirectly WNOHANG, EINTR, W* macros -#include // runtime_error -#include // waitpid -#include - -#include - -#include - -namespace RooFit { - namespace MultiProcessV1 { - - int wait_for_child(pid_t child_pid, bool may_throw, int retries_before_killing) { - int status = 0; - int patience = retries_before_killing; - pid_t tmp; - do { - if (patience-- < 1) { - ::kill(child_pid, SIGKILL); - } - tmp = waitpid(child_pid, &status, WNOHANG); - } while ( - tmp == 0 // child has not yet changed state, try again - || (-1 == tmp && EINTR == errno) // retry on interrupted system call - ); - - if (patience < 1) { - ooccoutD(static_cast(nullptr),Eval) << "Had to send PID " << child_pid << " " << (-patience+1) << " SIGKILLs"; - } - - if (0 != status) { - if (WIFEXITED(status)) { - printf("exited, status=%d\n", WEXITSTATUS(status)); - } else if (WIFSIGNALED(status)) { - printf("killed by signal %d\n", WTERMSIG(status)); - } else if (WIFSTOPPED(status)) { - printf("stopped by signal %d\n", WSTOPSIG(status)); - } else if (WIFCONTINUED(status)) { - printf("continued\n"); - } - } - - if (-1 == tmp && may_throw) throw std::runtime_error(std::string("waitpid, errno ") + std::to_string(errno)); - - return status; - } - - } -} \ No newline at end of file diff --git a/roofit/roofitcore/src/RooRealMPFE.cxx b/roofit/roofitcore/src/RooRealMPFE.cxx index c30088ff7853b..881a59cf24cca 100644 --- a/roofit/roofitcore/src/RooRealMPFE.cxx +++ b/roofit/roofitcore/src/RooRealMPFE.cxx @@ -48,7 +48,7 @@ For general multiprocessing in ROOT, please refer to the TProcessExecutor class. #include "RooFit.h" #ifndef _WIN32 -#include +#include #endif #include diff --git a/roofit/roofitcore/test/CMakeLists.txt b/roofit/roofitcore/test/CMakeLists.txt index fad3c966739ae..879c532e8b62c 100644 --- a/roofit/roofitcore/test/CMakeLists.txt +++ b/roofit/roofitcore/test/CMakeLists.txt @@ -41,7 +41,7 @@ endif() ROOT_ADD_GTEST(testRooProductPdf testRooProductPdf.cxx LIBRARIES RooFitCore) ROOT_ADD_GTEST(testNaNPacker testNaNPacker.cxx LIBRARIES RooFitCore) -ROOT_ADD_GTEST(testMPFEnll MultiProcess/MPFEnll.cpp LIBRARIES RooFitCore) +ROOT_ADD_GTEST(testMPFEnll MPFEnll.cpp LIBRARIES RooFitCore) #find_package(ZeroMQ REQUIRED) diff --git a/roofit/roofitcore/test/MultiProcess/MPFEnll.cpp b/roofit/roofitcore/test/MPFEnll.cpp similarity index 100% rename from roofit/roofitcore/test/MultiProcess/MPFEnll.cpp rename to roofit/roofitcore/test/MPFEnll.cpp diff --git a/roofit/roofitcore/test/MultiProcess/VectorGradMinimizer.cpp b/roofit/roofitcore/test/MultiProcess/VectorGradMinimizer.cpp deleted file mode 100644 index 5078620f5858c..0000000000000 --- a/roofit/roofitcore/test/MultiProcess/VectorGradMinimizer.cpp +++ /dev/null @@ -1,306 +0,0 @@ -/***************************************************************************** - * Project: RooFit * - * Package: RooFitCore * - * @(#)root/roofitcore:$Id$ - * Authors: * - * PB, Patrick Bos, NL eScience Center, p.bos@esciencecenter.nl * - * IP, Inti Pelupessy, NL eScience Center, i.pelupessy@esciencecenter.nl * - * VC, Vince Croft, DIANA / NYU, vincent.croft@cern.ch * - * * - * Redistribution and use in source and binary forms, * - * with or without modification, are permitted according to the terms * - * listed in LICENSE (http://roofit.sourceforge.net/license.txt) * - *****************************************************************************/ - -#include // runtime_error - -#include -#include -#include - -#include - -#include - -#include "gtest/gtest.h" -#include "../test_lib.h" // generate_1D_gaussian_pdf_nll - -class MPGradMinimizer : public ::testing::TestWithParam> {}; - -TEST_P(MPGradMinimizer, Gaussian1D) { - // do a minimization, but now using GradMinimizer and its MP version - - RooMsgService::instance().setGlobalKillBelow(RooFit::ERROR); - - // parameters - std::size_t NWorkers = std::get<0>(GetParam()); -// RooFit::MultiProcess::NLLVarTask mp_task_mode = std::get<1>(GetParam()); - std::size_t seed = std::get<1>(GetParam()); - - RooRandom::randomGenerator()->SetSeed(seed); - - RooWorkspace w = RooWorkspace(); - - std::unique_ptr nll; - std::unique_ptr values; - std::tie(nll, values) = generate_1D_gaussian_pdf_nll(w, 10000); - // when c++17 support arrives, change to this: -// auto [nll, values] = generate_1D_gaussian_pdf_nll(w, 10000); - RooRealVar *mu = w.var("mu"); - - RooArgSet *savedValues = dynamic_cast(values->snapshot()); - if (savedValues == nullptr) { - throw std::runtime_error("params->snapshot() cannot be casted to RooArgSet!"); - } - - // -------- - - RooMinimizer m0(*nll); - m0.setMinimizerType("Minuit2"); - - m0.setStrategy(0); - m0.setPrintLevel(-1); - - m0.migrad(); - - RooFitResult *m0result = m0.lastMinuitFit(); - double minNll0 = m0result->minNll(); - double edm0 = m0result->edm(); - double mu0 = mu->getVal(); - double muerr0 = mu->getError(); - - *values = *savedValues; - - RooFit::MultiProcess::GradMinimizer m1(*nll, NWorkers); - m1.setMinimizerType("Minuit2"); - - m1.setStrategy(0); - m1.setPrintLevel(-1); - - m1.migrad(); - - RooFitResult *m1result = m1.lastMinuitFit(); - double minNll1 = m1result->minNll(); - double edm1 = m1result->edm(); - double mu1 = mu->getVal(); - double muerr1 = mu->getError(); - - EXPECT_EQ(minNll0, minNll1); - EXPECT_EQ(mu0, mu1); - EXPECT_EQ(muerr0, muerr1); - EXPECT_EQ(edm0, edm1); - - m1.cleanup(); // necessary in tests to clean up global _theFitter -} - - -TEST(MPGradMinimizerDEBUGGING, DISABLED_Gaussian1DNominal) { -// std::size_t NWorkers = 1; - std::size_t seed = 1; - - RooRandom::randomGenerator()->SetSeed(seed); - - RooWorkspace w = RooWorkspace(); - - std::unique_ptr nll; - std::unique_ptr _; - std::tie(nll, _) = generate_1D_gaussian_pdf_nll(w, 10000); - - RooMinimizer m0(*nll); - m0.setMinimizerType("Minuit2"); - - m0.setStrategy(0); - m0.setPrintLevel(2); - - m0.migrad(); - m0.cleanup(); // necessary in tests to clean up global _theFitter -} - -TEST(MPGradMinimizerDEBUGGING, DISABLED_Gaussian1DMultiProcess) { - std::size_t NWorkers = 1; - std::size_t seed = 1; - - RooRandom::randomGenerator()->SetSeed(seed); - - RooWorkspace w = RooWorkspace(); - - std::unique_ptr nll; - std::unique_ptr values; - std::tie(nll, values) = generate_1D_gaussian_pdf_nll(w, 10000); - - RooFit::MultiProcess::GradMinimizer m1(*nll, NWorkers); - m1.setMinimizerType("Minuit2"); - - m1.setStrategy(0); - m1.setPrintLevel(2); - - m1.migrad(); - m1.cleanup(); // necessary in tests to clean up global _theFitter -} - - -TEST(MPGradMinimizer, RepeatMigrad) { - // do multiple minimizations using MP::GradMinimizer, testing breakdown and rebuild - - RooMsgService::instance().setGlobalKillBelow(RooFit::ERROR); - - // parameters - std::size_t NWorkers = 2; - std::size_t seed = 5; - - RooRandom::randomGenerator()->SetSeed(seed); - - RooWorkspace w = RooWorkspace(); - - std::unique_ptr nll; - std::unique_ptr values; - std::tie(nll, values) = generate_1D_gaussian_pdf_nll(w, 10000); - // when c++17 support arrives, change to this: -// auto [nll, values] = generate_1D_gaussian_pdf_nll(w, 10000); - - RooArgSet *savedValues = dynamic_cast(values->snapshot()); - if (savedValues == nullptr) { - throw std::runtime_error("params->snapshot() cannot be casted to RooArgSet!"); - } - - // -------- - - RooFit::MultiProcess::GradMinimizer m1(*nll, NWorkers); - - m1.setMinimizerType("Minuit2"); - - m1.setStrategy(0); - m1.setPrintLevel(-1); - - std::cout << "... running migrad first time ..." << std::endl; - m1.migrad(); - - std::cout << "... terminating TaskManager instance ..." << std::endl; - RooFit::MultiProcess::TaskManager::instance()->terminate(); - - *values = *savedValues; - - std::cout << "... running migrad second time ..." << std::endl; - m1.migrad(); - - std::cout << "... cleaning up minimizer ..." << std::endl; - m1.cleanup(); // necessary in tests to clean up global _theFitter -} - - -TEST_P(MPGradMinimizer, GaussianND) { - // do a minimization, but now using GradMinimizer and its MP version - - RooMsgService::instance().setGlobalKillBelow(RooFit::ERROR); - - // parameters - std::size_t NWorkers = std::get<0>(GetParam()); -// RooFit::MultiProcess::NLLVarTask mp_task_mode = std::get<1>(GetParam()); - std::size_t seed = std::get<1>(GetParam()); - - unsigned int N = 4; - - RooRandom::randomGenerator()->SetSeed(seed); - - RooWorkspace w = RooWorkspace(); - - std::unique_ptr nll; - std::unique_ptr values; - std::tie(nll, values) = generate_ND_gaussian_pdf_nll(w, N, 1000); - // when c++17 support arrives, change to this: -// auto [nll, all_values] = generate_ND_gaussian_pdf_nll(w, N, 1000); - - RooArgSet *savedValues = dynamic_cast(values->snapshot()); - if (savedValues == nullptr) { - throw std::runtime_error("params->snapshot() cannot be casted to RooArgSet!"); - } - - RooWallTimer wtimer; - - // -------- - - RooMinimizer m0(*nll); - m0.setMinimizerType("Minuit2"); - - m0.setStrategy(0); - m0.setPrintLevel(-1); - - wtimer.start(); - m0.migrad(); - wtimer.stop(); - std::cout << "\nwall clock time RooGradMinimizer.migrad (NWorkers = " - << NWorkers << ", seed = " << seed << "): " - << wtimer.timing_s() << " s" << std::endl; - - RooFitResult *m0result = m0.lastMinuitFit(); - double minNll0 = m0result->minNll(); - double edm0 = m0result->edm(); - double mean0[N]; - double std0[N]; - for (unsigned ix = 0; ix < N; ++ix) { - { - std::ostringstream os; - os << "m" << ix; - mean0[ix] = dynamic_cast(w.arg(os.str().c_str()))->getVal(); - } - { - std::ostringstream os; - os << "s" << ix; - std0[ix] = dynamic_cast(w.arg(os.str().c_str()))->getVal(); - } - } - - // -------- - - *values = *savedValues; - - // -------- - - RooFit::MultiProcess::GradMinimizer m1(*nll, NWorkers); - m1.setMinimizerType("Minuit2"); - - m1.setStrategy(0); - m1.setPrintLevel(-1); - - wtimer.start(); - m1.migrad(); - wtimer.stop(); - std::cout << "wall clock time MP::GradMinimizer.migrad (NWorkers = " - << NWorkers << ", seed = " << seed << "): " - << wtimer.timing_s() << " s\n" << std::endl; - - RooFitResult *m1result = m1.lastMinuitFit(); - double minNll1 = m1result->minNll(); - double edm1 = m1result->edm(); - double mean1[N]; - double std1[N]; - for (unsigned ix = 0; ix < N; ++ix) { - { - std::ostringstream os; - os << "m" << ix; - mean1[ix] = dynamic_cast(w.arg(os.str().c_str()))->getVal(); - } - { - std::ostringstream os; - os << "s" << ix; - std1[ix] = dynamic_cast(w.arg(os.str().c_str()))->getVal(); - } - } - - EXPECT_EQ(minNll0, minNll1); - EXPECT_EQ(edm0, edm1); - - for (unsigned ix = 0; ix < N; ++ix) { - EXPECT_EQ(mean0[ix], mean1[ix]); - EXPECT_EQ(std0[ix], std1[ix]); - } - - m1.cleanup(); // necessary in tests to clean up global _theFitter -} - - - -INSTANTIATE_TEST_SUITE_P(NworkersSeed, - MPGradMinimizer, - ::testing::Combine(::testing::Values(1,2,3), // number of workers - ::testing::Values(2,3))); // random seed diff --git a/roofit/roofitcore/test/MultiProcess/VectorNLL.cpp b/roofit/roofitcore/test/MultiProcess/VectorNLL.cpp deleted file mode 100644 index c3f9f4ad99441..0000000000000 --- a/roofit/roofitcore/test/MultiProcess/VectorNLL.cpp +++ /dev/null @@ -1,395 +0,0 @@ -/***************************************************************************** - * Project: RooFit * - * Package: RooFitCore * - * @(#)root/roofitcore:$Id$ - * Authors: * - * PB, Patrick Bos, NL eScience Center, p.bos@esciencecenter.nl * - * IP, Inti Pelupessy, NL eScience Center, i.pelupessy@esciencecenter.nl * - * VC, Vince Croft, DIANA / NYU, vincent.croft@cern.ch * - * * - * Redistribution and use in source and binary forms, * - * with or without modification, are permitted according to the terms * - * listed in LICENSE (http://roofit.sourceforge.net/license.txt) * - *****************************************************************************/ - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "gtest/gtest.h" -#include "../test_lib.h" // Hex - - -class MultiProcessVectorNLL : public ::testing::TestWithParam> {}; - - -TEST_P(MultiProcessVectorNLL, getVal) { - // Real-life test: calculate a NLL using event-based parallelization. This - // should replicate RooRealMPFE results. - RooRandom::randomGenerator()->SetSeed(std::get<2>(GetParam())); - RooWorkspace w; - w.factory("Gaussian::g(x[-5,5],mu[0,-3,3],sigma[1])"); - auto x = w.var("x"); - RooAbsPdf *pdf = w.pdf("g"); - RooDataSet *data = pdf->generate(RooArgSet(*x), 10000); - auto nll = pdf->createNLL(*data); - - auto nominal_result = nll->getVal(); - - std::size_t NumCPU = std::get<0>(GetParam()); - RooFit::MultiProcess::NLLVarTask mp_task_mode = std::get<1>(GetParam()); - - RooFit::MultiProcess::NLLVar nll_mp(NumCPU, mp_task_mode, *dynamic_cast(nll)); - - auto mp_result = nll_mp.getVal(); - - EXPECT_DOUBLE_EQ(Hex(nominal_result), Hex(mp_result)); - if (HasFailure()) { - std::cout << "failed test had parameters NumCPU = " << NumCPU << ", task_mode = " << mp_task_mode << ", seed = " << std::get<2>(GetParam()) << std::endl; - } -} - -void check_NLL_type(RooAbsReal *nll) { - if (dynamic_cast(nll) != nullptr) { - std::cout << "the NLL object is a RooAddition*..." << std::endl; - bool has_rooconstraintsum = false; - RooFIter nll_component_iter = nll->getComponents()->fwdIterator(); - RooAbsArg *nll_component; - while ((nll_component = nll_component_iter.next())) { - if (nll_component->IsA() == RooConstraintSum::Class()) { - has_rooconstraintsum = true; - break; - } else if (nll_component->IsA() != RooNLLVar::Class() && nll_component->IsA() != RooAddition::Class()) { - std::cerr << "... containing an unexpected component class: " << nll_component->ClassName() << std::endl; - throw std::runtime_error("RooAddition* type NLL object contains unexpected component class!"); - } - } - if (has_rooconstraintsum) { - std::cout << "...containing a RooConstraintSum component: " << nll_component->GetName() << std::endl; - } else { - std::cout << "...containing only RooNLLVar components." << std::endl; - } - } else if (dynamic_cast(nll) != nullptr) { - std::cout << "the NLL object is a RooNLLVar*" << std::endl; - } -} - - -void count_NLL_components(RooAbsReal *nll) { - if (dynamic_cast(nll) != nullptr) { - std::cout << "the NLL object is a RooAddition*..." << std::endl; - unsigned nll_component_count = 0; - RooFIter nll_component_iter = nll->getComponents()->fwdIterator(); - RooAbsArg *nll_component; - while ((nll_component = nll_component_iter.next())) { - if (nll_component->IsA() != RooNLLVar::Class()) { - ++nll_component_count; - } - } - std::cout << "...containing " << nll_component_count << " RooNLLVar components." << std::endl; - } else if (dynamic_cast(nll) != nullptr) { - std::cout << "the NLL object is a RooNLLVar*" << std::endl; - } -} - - -TEST_P(MultiProcessVectorNLL, getValRooAddition) { - RooMsgService::instance().setGlobalKillBelow(RooFit::ERROR); - - std::size_t NumCPU = std::get<0>(GetParam()); - - RooRandom::randomGenerator()->SetSeed(std::get<2>(GetParam())); - - RooWorkspace w; - w.factory("Gaussian::g(x[-10,10],mu[0,-3,3],sigma[1])"); - - RooRealVar *x = w.var("x"); - x->setRange("x_range",-3,0); - x->setRange("another_range",1,7); - - RooAbsPdf *pdf = w.pdf("g"); - RooDataSet *data = pdf->generate(*x, 10000); - - RooAbsReal *nll = pdf->createNLL(*data, RooFit::NumCPU(NumCPU), - RooFit::Range("x_range"), RooFit::Range("another_range")); - - check_NLL_type(nll); - count_NLL_components(nll); - - delete nll; - delete data; -} - - -TEST_P(MultiProcessVectorNLL, getValRooConstraintSumAddition) { - // modified from https://github.com/roofit-dev/rootbench/blob/43d12f33e8dac7af7d587b53a2804ddf6717e92f/root/roofit/roofit/RooFitASUM.cxx#L417 - - RooMsgService::instance().setGlobalKillBelow(RooFit::ERROR); - - int cpu = 1; - int bins = 10000; - - RooRealVar x("x","x",0,bins); - x.setBins(bins); -// Parameters - RooRealVar a0("a0","a0",0); - RooRealVar a1("a1","a1",1,0,2); - RooRealVar a2("a2","a2",0); - - RooPolynomial p0("p0","p0",x); - RooPolynomial p1("p1","p1",x,RooArgList(a0,a1,a2),0); - - RooDataHist *dh_bkg = p0.generateBinned(x, 1000000000); - RooDataHist *dh_sig = p1.generateBinned(x, 100000000); - dh_bkg->SetName("dh_bkg"); - dh_sig->SetName("dh_sig"); - - a1.setVal(2); - RooDataHist *dh_sig_up = p1.generateBinned(x, 1100000000); - dh_sig_up->SetName("dh_sig_up"); - a1.setVal(.5); - RooDataHist *dh_sig_down = p1.generateBinned(x, 900000000); - dh_sig_down->SetName("dh_sig_down"); - - RooWorkspace w = RooWorkspace("w"); - w.import(x); - w.import(*dh_sig); - w.import(*dh_bkg); - w.import(*dh_sig_up); - w.import(*dh_sig_down); - w.factory("HistFunc::hf_sig(x,dh_sig)"); - w.factory("HistFunc::hf_bkg(x,dh_bkg)"); - w.factory("HistFunc::hf_sig_up(x,dh_sig_up)"); - w.factory("HistFunc::hf_sig_down(x,dh_sig_down)"); - w.factory("PiecewiseInterpolation::pi_sig(hf_sig,hf_sig_down,hf_sig_up,alpha[-5,5])"); - - w.factory("ASUM::model(mu[1,0,5]*pi_sig,nu[1]*hf_bkg)"); - w.factory("Gaussian::constraint(alpha,0,1)"); - w.factory("PROD::model2(model,constraint)"); - - RooAbsPdf *pdf = w.pdf("model2"); - - RooDataHist *data = pdf->generateBinned(x, 1100000); - RooAbsReal *nll = pdf->createNLL(*data, RooFit::NumCPU(cpu, 0)); - - check_NLL_type(nll); - count_NLL_components(nll); - - delete nll; -} - -TEST_P(MultiProcessVectorNLL, setVal) { - // calculate the NLL twice with different parameters - - RooRandom::randomGenerator()->SetSeed(std::get<2>(GetParam())); - RooWorkspace w; - w.factory("Gaussian::g(x[-5,5],mu[0,-3,3],sigma[1])"); - auto x = w.var("x"); - RooAbsPdf *pdf = w.pdf("g"); - RooDataSet *data = pdf->generate(RooArgSet(*x), 10000); - auto nll = pdf->createNLL(*data); - - std::size_t NumCPU = std::get<0>(GetParam()); - RooFit::MultiProcess::NLLVarTask mp_task_mode = std::get<1>(GetParam()); - - RooFit::MultiProcess::NLLVar nll_mp(NumCPU, mp_task_mode, *dynamic_cast(nll)); - - // calculate first results - nll->getVal(); - nll_mp.getVal(); - - w.var("mu")->setVal(2); - - // calculate second results after parameter change - auto nominal_result2 = nll->getVal(); - auto mp_result2 = nll_mp.getVal(); - - EXPECT_DOUBLE_EQ(Hex(nominal_result2), Hex(mp_result2)); - if (HasFailure()) { - std::cout << "failed test had parameters NumCPU = " << NumCPU << ", task_mode = " << mp_task_mode << ", seed = " << std::get<2>(GetParam()) << std::endl; - } -} - - -INSTANTIATE_TEST_SUITE_P(NworkersModeSeed, - MultiProcessVectorNLL, - ::testing::Combine(::testing::Values(1,2,3), // number of workers - ::testing::Values(RooFit::MultiProcess::NLLVarTask::all_events, - RooFit::MultiProcess::NLLVarTask::single_event, - RooFit::MultiProcess::NLLVarTask::bulk_partition, - RooFit::MultiProcess::NLLVarTask::interleave), - ::testing::Values(2,3))); // random seed - - - -class NLLMultiProcessVsMPFE : public ::testing::TestWithParam> {}; - -TEST_P(NLLMultiProcessVsMPFE, getVal) { - // Compare our MP NLL to actual RooRealMPFE results using the same strategies. - - RooMsgService::instance().setGlobalKillBelow(RooFit::ERROR); - - // parameters - std::size_t NumCPU = std::get<0>(GetParam()); - RooFit::MultiProcess::NLLVarTask mp_task_mode = std::get<1>(GetParam()); - std::size_t seed = std::get<2>(GetParam()); - - RooRandom::randomGenerator()->SetSeed(seed); - - RooWorkspace w; - w.factory("Gaussian::g(x[-5,5],mu[0,-3,3],sigma[1])"); - auto x = w.var("x"); - RooAbsPdf *pdf = w.pdf("g"); - RooDataSet *data = pdf->generate(RooArgSet(*x), 10000); - - int mpfe_task_mode = 0; - if (mp_task_mode == RooFit::MultiProcess::NLLVarTask::interleave) { - mpfe_task_mode = 1; - } - - auto nll_mpfe = pdf->createNLL(*data, RooFit::NumCPU(NumCPU, mpfe_task_mode)); - - auto mpfe_result = nll_mpfe->getVal(); - - // create new nll without MPFE for creating nll_mp (an MPFE-enabled RooNLLVar interferes with MP::Vector's bipe use) - auto nll = pdf->createNLL(*data); - RooFit::MultiProcess::NLLVar nll_mp(NumCPU, mp_task_mode, *dynamic_cast(nll)); - - auto mp_result = nll_mp.getVal(); - - EXPECT_EQ(Hex(mpfe_result), Hex(mp_result)); - if (HasFailure()) { - std::cout << "failed test had parameters NumCPU = " << NumCPU << ", task_mode = " << mp_task_mode << ", seed = " << seed << std::endl; - } -} - - -TEST_P(NLLMultiProcessVsMPFE, minimize) { - // do a minimization (e.g. like in GradMinimizer_Gaussian1D test) - - RooMsgService::instance().setGlobalKillBelow(RooFit::ERROR); - - // TODO: see whether it performs adequately - - // parameters - std::size_t NumCPU = std::get<0>(GetParam()); - RooFit::MultiProcess::NLLVarTask mp_task_mode = std::get<1>(GetParam()); - std::size_t seed = std::get<2>(GetParam()); - - RooRandom::randomGenerator()->SetSeed(seed); - - RooWorkspace w = RooWorkspace(); - - w.factory("Gaussian::g(x[-5,5],mu[0,-3,3],sigma[1])"); - auto x = w.var("x"); - RooAbsPdf *pdf = w.pdf("g"); - RooRealVar *mu = w.var("mu"); - - RooDataSet *data = pdf->generate(RooArgSet(*x), 10000); - mu->setVal(-2.9); - - int mpfe_task_mode; - switch (mp_task_mode) { - case RooFit::MultiProcess::NLLVarTask::bulk_partition: { - mpfe_task_mode = 0; - break; - } - case RooFit::MultiProcess::NLLVarTask::interleave: { - mpfe_task_mode = 1; - break; - } - default: { - throw std::logic_error("can only compare bulk_partition and interleave strategies to MPFE NLL"); - } - } - - auto nll_mpfe = pdf->createNLL(*data, RooFit::NumCPU(NumCPU, mpfe_task_mode)); - auto nll_nominal = pdf->createNLL(*data); - RooFit::MultiProcess::NLLVar nll_mp(NumCPU, mp_task_mode, *dynamic_cast(nll_nominal)); - - // save initial values for the start of all minimizations - RooArgSet values = RooArgSet(*mu, *pdf); - - RooArgSet *savedValues = dynamic_cast(values.snapshot()); - if (savedValues == nullptr) { - throw std::runtime_error("params->snapshot() cannot be casted to RooArgSet!"); - } - - // -------- - - RooMinimizer m0(*nll_mpfe); - m0.setMinimizerType("Minuit2"); - - m0.setStrategy(0); - m0.setPrintLevel(-1); - - m0.migrad(); - - RooFitResult *m0result = m0.lastMinuitFit(); - double minNll0 = m0result->minNll(); - double edm0 = m0result->edm(); - double mu0 = mu->getVal(); - double muerr0 = mu->getError(); - - values = *savedValues; - - RooMinimizer m1(nll_mp); - m1.setMinimizerType("Minuit2"); - - m1.setStrategy(0); - m1.setPrintLevel(-1); - - m1.migrad(); - - RooFitResult *m1result = m1.lastMinuitFit(); - double minNll1 = m1result->minNll(); - double edm1 = m1result->edm(); - double mu1 = mu->getVal(); - double muerr1 = mu->getError(); - - EXPECT_EQ(minNll0, minNll1); - EXPECT_EQ(mu0, mu1); - EXPECT_EQ(muerr0, muerr1); - EXPECT_EQ(edm0, edm1); - - m1.cleanup(); // necessary in tests to clean up global _theFitter -} - - -INSTANTIATE_TEST_SUITE_P(NworkersModeSeed, - NLLMultiProcessVsMPFE, - ::testing::Combine(::testing::Values(2,3), // number of workers - ::testing::Values(RooFit::MultiProcess::NLLVarTask::bulk_partition, - RooFit::MultiProcess::NLLVarTask::interleave), - ::testing::Values(2,3))); // random seed - - -TEST(NLLMultiProcessVsMPFE, throwOnCreatingMPwithMPFE) { - // Using an MPFE-enabled NLL should throw when creating an MP NLL. - RooWorkspace w; - w.factory("Gaussian::g(x[-5,5],mu[0,-3,3],sigma[1])"); - auto x = w.var("x"); - RooAbsPdf *pdf = w.pdf("g"); - RooDataSet *data = pdf->generate(RooArgSet(*x), 10); - - RooAbsReal* nll_mpfe = pdf->createNLL(*data, RooFit::NumCPU(2)); - - EXPECT_THROW({ - RooFit::MultiProcess::NLLVar nll_mp(2, RooFit::MultiProcess::NLLVarTask::bulk_partition, *dynamic_cast(nll_mpfe)); - }, std::logic_error); - - delete nll_mpfe; -} diff --git a/roofit/roofitcore/test/MultiProcess_Vector.cxx b/roofit/roofitcore/test/MultiProcess_Vector.cxx deleted file mode 100644 index b9891b9f87643..0000000000000 --- a/roofit/roofitcore/test/MultiProcess_Vector.cxx +++ /dev/null @@ -1,186 +0,0 @@ -/***************************************************************************** - * Project: RooFit * - * Package: RooFitCore * - * @(#)root/roofitcore:$Id$ - * Authors: * - * PB, Patrick Bos, NL eScience Center, p.bos@esciencecenter.nl * - * IP, Inti Pelupessy, NL eScience Center, i.pelupessy@esciencecenter.nl * - * VC, Vince Croft, DIANA / NYU, vincent.croft@cern.ch * - * * - * Redistribution and use in source and binary forms, * - * with or without modification, are permitted according to the terms * - * listed in LICENSE (http://roofit.sourceforge.net/license.txt) * - *****************************************************************************/ - -// MultiProcess back-end -//#include -#include -#include -#include - -#include // std::_Exit -#include -#include -#include -//#include -#include // accumulate -#include // for google test Combine in parameterized test - -#include -#include - -#include "gtest/gtest.h" -#include "test_lib.h" - -class xSquaredPlusBVectorSerial { - public: - xSquaredPlusBVectorSerial(double b, std::vector x_init) : - _b("b", "b", b), - x(std::move(x_init)), - result(x.size()) {} - - virtual void evaluate() { - // call evaluate_task for each task - for (std::size_t ix = 0; ix < x.size(); ++ix) { - result[ix] = std::pow(x[ix], 2) + _b.getVal(); - } - } - - std::vector get_result() { - evaluate(); - return result; - } - - protected: - RooRealVar _b; - std::vector x; - std::vector result; -}; - - -using RooFit::MultiProcess::JobTask; - -class xSquaredPlusBVectorParallel : public RooFit::MultiProcess::Vector { - public: - xSquaredPlusBVectorParallel(std::size_t NumCPU, double b_init, std::vector x_init) : - RooFit::MultiProcess::Vector(NumCPU, b_init, - x_init) // NumCPU stands for everything that defines the parallelization behaviour (number of cpu, strategy, affinity etc) - {} - - void evaluate() override { - if (get_manager()->is_master()) { - // master fills queue with tasks - for (std::size_t task_id = 0; task_id < x.size(); ++task_id) { - JobTask job_task(id, task_id); - get_manager()->to_queue(job_task); - } - waiting_for_queued_tasks = true; - - // wait for task results back from workers to master - gather_worker_results(); - - // put task results in desired container (same as used in serial class) - for (std::size_t task_id = 0; task_id < x.size(); ++task_id) { - result[task_id] = results[task_id]; - } - } - } - - - private: - void evaluate_task(std::size_t task) override { - assert(get_manager()->is_worker()); - result[task] = std::pow(x[task], 2) + _b.getVal(); - } - - double get_task_result(std::size_t task) override { - assert(get_manager()->is_worker()); - return result[task]; - } - -}; - -class MultiProcessVectorSingleJob : public ::testing::TestWithParam { - // You can implement all the usual fixture class members here. - // To access the test parameter, call GetParam() from class - // TestWithParam. -}; - - - - -TEST_P(MultiProcessVectorSingleJob, getResult) { - // Simple test case: calculate x^2 + b, where x is a vector. This case does - // both a simple calculation (squaring the input vector x) and represents - // handling of state updates in b. - std::vector x{0, 1, 2, 3}; - double b_initial = 3.; - - // start serial test - - xSquaredPlusBVectorSerial x_sq_plus_b(b_initial, x); - - auto y = x_sq_plus_b.get_result(); - std::vector y_expected{3, 4, 7, 12}; - - EXPECT_EQ(Hex(y[0]), Hex(y_expected[0])); - EXPECT_EQ(Hex(y[1]), Hex(y_expected[1])); - EXPECT_EQ(Hex(y[2]), Hex(y_expected[2])); - EXPECT_EQ(Hex(y[3]), Hex(y_expected[3])); - - std::size_t NumCPU = GetParam(); - - // start parallel test - - xSquaredPlusBVectorParallel x_sq_plus_b_parallel(NumCPU, b_initial, x); - - auto y_parallel = x_sq_plus_b_parallel.get_result(); - - EXPECT_EQ(Hex(y_parallel[0]), Hex(y_expected[0])); - EXPECT_EQ(Hex(y_parallel[1]), Hex(y_expected[1])); - EXPECT_EQ(Hex(y_parallel[2]), Hex(y_expected[2])); - EXPECT_EQ(Hex(y_parallel[3]), Hex(y_expected[3])); -} - - -INSTANTIATE_TEST_SUITE_P(NumberOfWorkerProcesses, - MultiProcessVectorSingleJob, - ::testing::Values(1,2,3)); - - -class MultiProcessVectorMultiJob : public ::testing::TestWithParam {}; - -TEST_P(MultiProcessVectorMultiJob, getResult) { - // Simple test case: calculate x^2 + b, where x is a vector. This case does - // both a simple calculation (squaring the input vector x) and represents - // handling of state updates in b. - std::vector x{0, 1, 2, 3}; - double b_initial = 3.; - - std::vector y_expected{3, 4, 7, 12}; - - std::size_t NumCPU = GetParam(); - - // define jobs - xSquaredPlusBVectorParallel x_sq_plus_b_parallel(NumCPU, b_initial, x); - xSquaredPlusBVectorParallel x_sq_plus_b_parallel2(NumCPU, b_initial + 1, x); - - // do stuff - auto y_parallel = x_sq_plus_b_parallel.get_result(); - auto y_parallel2 = x_sq_plus_b_parallel2.get_result(); - - EXPECT_EQ(Hex(y_parallel[0]), Hex(y_expected[0])); - EXPECT_EQ(Hex(y_parallel[1]), Hex(y_expected[1])); - EXPECT_EQ(Hex(y_parallel[2]), Hex(y_expected[2])); - EXPECT_EQ(Hex(y_parallel[3]), Hex(y_expected[3])); - - EXPECT_EQ(Hex(y_parallel2[0]), Hex(y_expected[0] + 1)); - EXPECT_EQ(Hex(y_parallel2[1]), Hex(y_expected[1] + 1)); - EXPECT_EQ(Hex(y_parallel2[2]), Hex(y_expected[2] + 1)); - EXPECT_EQ(Hex(y_parallel2[3]), Hex(y_expected[3] + 1)); -} - - -INSTANTIATE_TEST_SUITE_P(NumberOfWorkerProcesses, - MultiProcessVectorMultiJob, - ::testing::Values(2,1,3)); diff --git a/roofit/roofitcore/test/testBidirMMapPipe.cxx b/roofit/roofitcore/test/testBidirMMapPipe.cxx index 59e40ba7ebbed..e0a3f56815e59 100644 --- a/roofit/roofitcore/test/testBidirMMapPipe.cxx +++ b/roofit/roofitcore/test/testBidirMMapPipe.cxx @@ -11,7 +11,7 @@ * listed in LICENSE (http://roofit.sourceforge.net/license.txt) * *****************************************************************************/ -#include +#include #include #include From 47a68e670bb31eb266c5333c783bcafcfb62f6ac Mon Sep 17 00:00:00 2001 From: "E. G. Patrick Bos" Date: Thu, 24 Jun 2021 16:07:46 +0200 Subject: [PATCH 03/10] remove RooTimer class --- roofit/roofitcore/inc/RooAbsTestStatistic.h | 5 - roofit/roofitcore/inc/RooRealMPFE.h | 9 - roofit/roofitcore/inc/RooTimer.h | 49 --- roofit/roofitcore/inc/RooTrace.h | 12 +- .../src/NumericalDerivatorMinuit2.cxx | 5 +- roofit/roofitcore/src/RooAbsTestStatistic.cxx | 35 -- roofit/roofitcore/src/RooRealIntegral.cxx | 33 -- roofit/roofitcore/src/RooRealMPFE.cxx | 351 +----------------- roofit/roofitcore/src/RooTimer.cxx | 48 --- roofit/roofitcore/src/RooTrace.cxx | 31 +- .../TestStatistics/LikelihoodGradientJob.cxx | 8 - .../src/TestStatistics/RooBinnedL.cxx | 4 - .../src/TestStatistics/RooUnbinnedL.cxx | 4 - roofit/roofitcore/test/RooGradMinimizer.cxx | 65 ---- .../testLikelihoodGradientJob.cpp | 12 - .../TestStatistics/testLikelihoodSerial.cxx | 1 - 16 files changed, 4 insertions(+), 668 deletions(-) delete mode 100644 roofit/roofitcore/inc/RooTimer.h delete mode 100644 roofit/roofitcore/src/RooTimer.cxx diff --git a/roofit/roofitcore/inc/RooAbsTestStatistic.h b/roofit/roofitcore/inc/RooAbsTestStatistic.h index 1e4c0866efc3d..3cc4cdae5405f 100644 --- a/roofit/roofitcore/inc/RooAbsTestStatistic.h +++ b/roofit/roofitcore/inc/RooAbsTestStatistic.h @@ -171,11 +171,6 @@ class RooAbsTestStatistic : public RooAbsReal { mutable Double_t _evalCarry = 0.0; //! carry of Kahan sum in evaluatePartition private: - void _collectNumIntTimings(Bool_t clear_timings = kTRUE) const; - - void _setNumIntTimingInPdfs(Bool_t flag = kTRUE); - - void _initTiming(); ClassDef(RooAbsTestStatistic,3) // Abstract base class for real-valued test statistics }; diff --git a/roofit/roofitcore/inc/RooRealMPFE.h b/roofit/roofitcore/inc/RooRealMPFE.h index 35f8493878b92..8585cc0d5cb8b 100644 --- a/roofit/roofitcore/inc/RooRealMPFE.h +++ b/roofit/roofitcore/inc/RooRealMPFE.h @@ -67,9 +67,6 @@ class RooRealMPFE : public RooAbsReal { enum Message { SendReal=0, SendCat, Calculate, Retrieve, ReturnValue, Terminate, ConstOpt, Verbose, LogEvalError, ApplyNLLW2, EnableOffset, CalculateNoOffset, SetCpuAffinity, TaskSpec, - EnableTimingNumInts, DisableTimingNumInts, - MeasureCommunicationTime, - RetrieveTimings, GetPID }; @@ -112,12 +109,6 @@ class RooRealMPFE : public RooAbsReal { // RooArgSet* _components = 0; // RooAbsArg* _findComponent(std::string name); - void _time_communication_overhead() const; - - void setTimingNumInts(Bool_t flag = kTRUE); - std::map collectTimingsFromServer(Bool_t clear_timings = kTRUE) const; - - void _initTiming(); // RooTaskSpec _taskspecification; Int_t _setNum ; //! Partition number of this instance in parallel calculation mode Int_t _numSets ; //! Total number of partitions in parallel calculation mode diff --git a/roofit/roofitcore/inc/RooTimer.h b/roofit/roofitcore/inc/RooTimer.h deleted file mode 100644 index 7fad6b7a3489b..0000000000000 --- a/roofit/roofitcore/inc/RooTimer.h +++ /dev/null @@ -1,49 +0,0 @@ -#ifndef ROO_TIMER -#define ROO_TIMER - -#include -#include -#include -#include -#include "RooJsonListFile.h" - -class RooTimer { -public: - virtual void start() = 0; - virtual void stop() = 0; - double timing_s(); - void set_timing_s(double timing_s); - void store_timing_in_RooTrace(const std::string &name); - - static std::vector timing_outfiles; - static std::vector timings; - -private: - double _timing_s; -}; - -class RooWallTimer: public RooTimer { -public: - RooWallTimer(); - virtual void start(); - virtual void stop(); - -private: - std::chrono::time_point _timing_begin, _timing_end; -}; - -/// @class RooCPUTimer -/// Measures the CPU time on the local process. Note that for multi-process runs, -/// e.g. when using RooRealMPFE, the child process CPU times are not included! -/// Use a separate timer in child processes to measure their CPU timing. -class RooCPUTimer: public RooTimer { -public: - RooCPUTimer(); - virtual void start(); - virtual void stop(); - -private: - struct timespec _timing_begin, _timing_end; -}; - -#endif diff --git a/roofit/roofitcore/inc/RooTrace.h b/roofit/roofitcore/inc/RooTrace.h index daea848b721c2..451528f080879 100644 --- a/roofit/roofitcore/inc/RooTrace.h +++ b/roofit/roofitcore/inc/RooTrace.h @@ -51,16 +51,6 @@ class RooTrace { static void printObjectCounts() ; - static std::map objectTiming; - - // TODO: for windows version, change to private _timing_flag and use public method that returns its value - static int timing_flag; - - static Bool_t time_numInts(); - static void set_time_numInts(Bool_t flag); - -private: - static Bool_t _time_numInts; protected: @@ -91,7 +81,7 @@ class RooTrace { std::map _specialCount ; std::map _specialSize ; - ClassDef(RooTrace,1) // Memory tracer utility for RooFit objects + ClassDef(RooTrace,0) // Memory tracer utility for RooFit objects }; diff --git a/roofit/roofitcore/src/NumericalDerivatorMinuit2.cxx b/roofit/roofitcore/src/NumericalDerivatorMinuit2.cxx index 18348470517c5..d4218810acbd3 100644 --- a/roofit/roofitcore/src/NumericalDerivatorMinuit2.cxx +++ b/roofit/roofitcore/src/NumericalDerivatorMinuit2.cxx @@ -34,7 +34,6 @@ #include // needed here because in Fitter is only a forward declaration -#include #include //#include @@ -341,7 +340,6 @@ void NumericalDerivatorMinuit2::SetInitialGradient(const ROOT::Math::IBaseFuncti }; decltype(get_time()) t1, t2; - RooWallTimer timer; t1 = get_time(); assert(function != nullptr && "function is a nullptr"); @@ -412,9 +410,8 @@ void NumericalDerivatorMinuit2::SetInitialGradient(const ROOT::Math::IBaseFuncti } t2 = get_time(); - timer.stop(); // oocxcoutD((TObject *)nullptr, Benchmarking1) -// << "SetInitialGradient time: " << timer.timing_s() << "s (from " << t1 << " to " << t2 << "ns)" << std::endl; +// << "SetInitialGradient time: (from " << t1 << " to " << t2 << "ns)" << std::endl; } bool NumericalDerivatorMinuit2::always_exactly_mimic_minuit2() const diff --git a/roofit/roofitcore/src/RooAbsTestStatistic.cxx b/roofit/roofitcore/src/RooAbsTestStatistic.cxx index 01d1d9c50cf44..6b26f63564304 100644 --- a/roofit/roofitcore/src/RooAbsTestStatistic.cxx +++ b/roofit/roofitcore/src/RooAbsTestStatistic.cxx @@ -58,8 +58,6 @@ combined in the main thread. #include #include -// timing -#include "RooTimer.h" // getpid and getppid: #include "unistd.h" #include @@ -455,39 +453,6 @@ void RooAbsTestStatistic::initMPMode(RooAbsReal* real, RooAbsData* data, const R } -//////////////////////////////////////////////////////////////////////////////// -/// Activate timing of numerical integral normalization terms in the pdf. -/// This function should be called from the process that evaluates the pdf. -/// Using RooRealMPFE this means it should be called from the serverLoop(). - -void RooAbsTestStatistic::_setNumIntTimingInPdfs(Bool_t flag) { - // find all pdf nodes with a normalization integral and set the activate timing flag on them - // Get list of branch nodes in expression - RooArgSet blist; - RooAbsArg* node; - - _func->branchNodeServerList(&blist); - - // Iterator over branch nodes - RooFIter iter = blist.fwdIterator(); - while((node = iter.next())) { - RooAbsPdf *pdfNode = dynamic_cast(node); - if (!pdfNode) continue; - // Skip self-normalized nodes - if (pdfNode->selfNormalized()) continue; - - // TODO: move everything below to RooAbsPdf::setNumIntTiming(RooArgSet& obsSet, Bool_t flag) and only call that here? Or do we not want to pass around the observables? - // set attribute on or off - pdfNode->set_num_int_timing_flag(flag); - - // Retrieve normalization integral object for branch nodes that are pdfs - RooRealIntegral *normint = const_cast(dynamic_cast(pdfNode->getNormIntegral(*(_data->get())))); - if (!normint) continue; - - normint->activateTimingNumInts(); - } -} - //////////////////////////////////////////////////////////////////////////////// /// Initialize simultaneous p.d.f processing mode. Strip simultaneous /// p.d.f into individual components, split dataset in subset diff --git a/roofit/roofitcore/src/RooRealIntegral.cxx b/roofit/roofitcore/src/RooRealIntegral.cxx index 5dffedd0c2449..d07cfa1be1cba 100644 --- a/roofit/roofitcore/src/RooRealIntegral.cxx +++ b/roofit/roofitcore/src/RooRealIntegral.cxx @@ -50,7 +50,6 @@ integration is performed in the various implementations of the RooAbsIntegrator #include "RooHelpers.h" #include "ROOT/RMakeUnique.hxx" -#include "RooTimer.h" // getpid and getppid: #include "unistd.h" @@ -571,42 +570,10 @@ RooRealIntegral::RooRealIntegral(const char *name, const char *title, _sumCat.addOwned(*sumCat) ; } - // activate timing on numerical integrals - if (RooTrace::time_numInts()) { - activateTimingNumInts(); - } - TRACE_CREATE } -void RooRealIntegral::activateTimingNumInts() { - // activate timing on numerical integrals -// const RooAbsArg& pdfNode = _function.arg(); - const RooAbsPdf& pdfNode = dynamic_cast(_function.arg()); - // TODO: check whether indeed only RooAbsPdf _functions have integral components - Bool_t timing_flag = pdfNode.num_int_timing_flag(); - - RooFIter ni_iter = _intList.fwdIterator(); - while (RooAbsArg *normint = ni_iter.next()) { - // Integral expressions can be composite objects (in case of disjoint normalization ranges) - // Therefore: retrieve list of branch nodes of integral expression - RooArgList bi; - normint->branchNodeServerList(&bi); - RooFIter ib_iter = bi.fwdIterator(); - RooAbsArg *inode; - while ((inode = ib_iter.next())) { - // If a RooRealIntegal component is found... - if (inode->IsA() == RooRealIntegral::Class()) { - // Retrieve the number of real dimensions that is integrated numerically, - RooRealIntegral *rri = (RooRealIntegral *) inode; - rri->setNumIntTiming(timing_flag); - } - } - } -} - - //////////////////////////////////////////////////////////////////////////////// /// Set appropriate cache operation mode for integral depending on cache operation diff --git a/roofit/roofitcore/src/RooRealMPFE.cxx b/roofit/roofitcore/src/RooRealMPFE.cxx index 881a59cf24cca..7e296b213018f 100644 --- a/roofit/roofitcore/src/RooRealMPFE.cxx +++ b/roofit/roofitcore/src/RooRealMPFE.cxx @@ -77,8 +77,6 @@ For general multiprocessing in ROOT, please refer to the TProcessExecutor class. RooMPSentinel RooRealMPFE::_sentinel ; -// timing -#include "RooTimer.h" // getpid and getppid: #include "unistd.h" @@ -224,9 +222,6 @@ void RooRealMPFE::initialize() clearEvalErrorLog() ; // Fork server process and setup IPC _pipe = new BidirMMapPipe(); -// if (RooTrace::timing_flag > 0) { -// _initTiming(); -// } if (_pipe->isChild()) { // Start server loop // RooTrace::callgrind_zero() ; @@ -259,115 +254,6 @@ void RooRealMPFE::initialize() } -//////////////////////////////////////////////////////////////////////////////// -/// Open the timing file and initialize its field names -void RooRealMPFE::_initTiming() { - std::stringstream proc_id_ss; - - if (_setNum < 0 || _setNum >= _numSets) { - // _setNum not a valid number, setting subprocess output file ID to PID - proc_id_ss << getpid(); - } else { - // otherwise use the ppid combined with the setnum - proc_id_ss << getppid() << "_sub" << _setNum; - } - - std::string proc_id = proc_id_ss.str(); - - if (_pipe->isChild()) { - if (RooTimer::timing_outfiles.size() < 1) { - RooTimer::timing_outfiles.emplace_back(); - } - // on server (slave process) - switch (RooTrace::timing_flag) { - case 9: { - stringstream filename_ss; - filename_ss << "timing_RRMPFE_serverloop_while_p" << proc_id << ".json"; - RooTimer::timing_outfiles[0].open(filename_ss.str().c_str()); - std::string names[3] = {"RRMPFE_serverloop_while_wall_s", "pid", "ppid"}; - RooTimer::timing_outfiles[0].set_member_names(names, names + 3); - break; - } - default: { - // no server-side timing, do nothing - break; - } - } - } else { - // on client (master process) - while (static_cast(RooTimer::timing_outfiles.size()) < _numSets) { - RooTimer::timing_outfiles.emplace_back(); - } - - switch (RooTrace::timing_flag) { - case 4: { - stringstream filename_ss; - filename_ss << "timing_RRMPFE_evaluate_full_p" << proc_id << ".json"; - RooTimer::timing_outfiles[_setNum].open(filename_ss.str().c_str()); - std::string names[2] = {"RRMPFE_evaluate_wall_s", "pid"}; - RooTimer::timing_outfiles[_setNum].set_member_names(names, names + 2); - break; - } - - case 5: { - stringstream filename_ss; - filename_ss << "timing_wall_RRMPFE_evaluate_client_p" << proc_id << ".json"; - RooTimer::timing_outfiles[_setNum].open(filename_ss.str().c_str()); - std::string names[4] = {"time s", "cpu/wall", "segment", "pid"}; - RooTimer::timing_outfiles[_setNum].set_member_names(names, names + 4); - break; - } - - case 6: { - stringstream filename_ss; - filename_ss << "timing_cpu_RRMPFE_evaluate_client_p" << proc_id << ".json"; - RooTimer::timing_outfiles[_setNum].open(filename_ss.str().c_str()); - std::string names[4] = {"time s", "cpu/wall", "segment", "pid"}; - RooTimer::timing_outfiles[_setNum].set_member_names(names, names + 4); - break; - } - - case 7: { - stringstream filename_ss; - - if (_state==Initialize) { - filename_ss << "timing_RRMPFE_calculate_initialize_p" << proc_id << ".json"; - RooTimer::timing_outfiles[_setNum].open(filename_ss.str().c_str()); - std::string names[2] = {"RRMPFE_calculate_initialize_wall_s", "pid"}; - RooTimer::timing_outfiles[_setNum].set_member_names(names, names + 2); - } - - if (_state==Inline) { - filename_ss << "timing_RRMPFE_calculate_inline_p" << proc_id << ".json"; - RooTimer::timing_outfiles[_setNum].open(filename_ss.str().c_str()); - std::string names[2] = {"RRMPFE_calculate_inline_wall_s", "pid"}; - RooTimer::timing_outfiles[_setNum].set_member_names(names, names + 2); - } - - if (_state==Client) { - filename_ss << "timing_RRMPFE_calculate_client_p" << proc_id << ".json"; - RooTimer::timing_outfiles[_setNum].open(filename_ss.str().c_str()); - std::string names[2] = {"RRMPFE_calculate_client_wall_s", "pid"}; - RooTimer::timing_outfiles[_setNum].set_member_names(names, names + 2); - } - break; - } - - case 10: { - // no writing to file, do nothing - break; - } - - default: { - // no client-side timing, do nothing - break; - } - } - } - -} - - //////////////////////////////////////////////////////////////////////////////// /// Set the cpu affinity of the server process to a specific cpu. @@ -378,18 +264,8 @@ void RooRealMPFE::setCpuAffinity(int cpu) { //////////////////////////////////////////////////////////////////////////////// -/// Pipe stream operators for timing type and TaskSpec. +/// Pipe stream operators for TaskSpec. namespace RooFit { - using WallClock = std::chrono::system_clock; - using TimePoint = WallClock::time_point; - using Duration = WallClock::duration; - - BidirMMapPipe& operator<<(BidirMMapPipe& bipe, const TimePoint& wall) { - Duration::rep const ns = wall.time_since_epoch().count(); - bipe.write(&ns, sizeof(ns)); - return bipe; - } - BidirMMapPipe& operator<<(BidirMMapPipe& bipe, const RooTaskSpec::Task& Task) { // cout<<"passing Task out"<>(BidirMMapPipe& bipe, TimePoint& wall) { - Duration::rep ns; - bipe.read(&ns, sizeof(ns)); - Duration const d(ns); - wall = TimePoint(d); - - return bipe; - } - // BidirMMapPipe& operator>>(BidirMMapPipe& bipe, const RooTaskSpec::Task& Task) { // const char *name = Task.name; // bipe.read(&name, sizeof(name)); @@ -472,8 +339,6 @@ void RooRealMPFE::setTaskSpec() { void RooRealMPFE::serverLoop() { #ifndef _WIN32 - RooWallTimer timer; - int msg; Int_t idx, index, numErrors; @@ -483,10 +348,6 @@ void RooRealMPFE::serverLoop() { clearEvalErrorLog(); while (*_pipe && !_pipe->eof()) { - if (RooTrace::timing_flag == 9) { - timer.start(); - } - *_pipe >> msg; if (Terminate == msg) { @@ -681,61 +542,6 @@ void RooRealMPFE::serverLoop() { break; } - - case EnableTimingNumInts: { - // This must be done server-side, otherwise you have to copy all timing flags to server manually anyway - // FIXME: make this more general than just RooAbsTestStatistic (when needed) - dynamic_cast(_arg.absArg())->_setNumIntTimingInPdfs(); - break; - } - - - case DisableTimingNumInts: { - dynamic_cast(_arg.absArg())->_setNumIntTimingInPdfs(kFALSE); - break; - } - - - case MeasureCommunicationTime: { - // Measure end time asap, since time of arrival at this case block is what we need to measure - // communication overhead, i.e. time between sending message and corresponding action taken. - // Defining the end time variable can reasonably be called overhead too, since many other - // messages also define a piped-message-receiving variable. - TimePoint comm_wallclock_begin, comm_wallclock_end; - comm_wallclock_end = WallClock::now(); - - *_pipe >> comm_wallclock_begin; - - std::cout << "client to server communication overhead timing:" << std::endl; - std::cout << "comm_wallclock_begin: " << std::chrono::duration_cast(comm_wallclock_begin.time_since_epoch()).count() << std::endl; - std::cout << "comm_wallclock_end: " << std::chrono::duration_cast(comm_wallclock_end.time_since_epoch()).count() << std::endl; - - double comm_wallclock_s = std::chrono::duration_cast(comm_wallclock_end - comm_wallclock_begin).count() / 1.e9; - - std::cout << "comm_wallclock (seconds): " << comm_wallclock_s << std::endl; - - comm_wallclock_begin = WallClock::now(); - *_pipe << comm_wallclock_begin << BidirMMapPipe::flush; - - break; - } - - case RetrieveTimings: { - Bool_t clear_timings; - *_pipe >> clear_timings; - *_pipe << static_cast(RooTrace::objectTiming.size()) << BidirMMapPipe::flush; - for (auto it = RooTrace::objectTiming.begin(); it != RooTrace::objectTiming.end(); ++it) { - std::string name = it->first; - double timing_s = it->second; - *_pipe << name << timing_s << BidirMMapPipe::flush; - } - if (clear_timings == kTRUE) { - RooTrace::objectTiming.clear(); - } - - break; - } - case GetPID: { *_pipe << getpid() << BidirMMapPipe::flush; break; @@ -749,12 +555,6 @@ void RooRealMPFE::serverLoop() { break; } - // end timing - if (RooTrace::timing_flag == 9) { - timer.stop(); - RooTimer::timing_outfiles[0] << timer.timing_s() << getpid() << getppid(); - } - if (Terminate == msg) { if (_verboseServer) cout << "RooRealMPFE::serverLoop(" << GetName() @@ -768,17 +568,6 @@ void RooRealMPFE::serverLoop() { } -//////////////////////////////////////////////////////////////////////////////// - -void RooRealMPFE::setTimingNumInts(Bool_t flag) { - if (flag == kTRUE) { - *_pipe << EnableTimingNumInts; - } else { - *_pipe << DisableTimingNumInts; - } -} - - //////////////////////////////////////////////////////////////////////////////// /// Client-side function that instructs server process to start /// asynchronuous (re)calculation of function value. This function @@ -787,51 +576,22 @@ void RooRealMPFE::setTimingNumInts(Bool_t flag) { void RooRealMPFE::calculate() const { - RooWallTimer timer; - // Start asynchronous calculation of arg value if (_state==Initialize) { // cout << "RooRealMPFE::calculate(" << GetName() << ") initializing" << endl ; - if (RooTrace::timing_flag == 7) { - timer.start(); - } - const_cast(this)->initialize() ; - - if (RooTrace::timing_flag == 7) { - timer.stop(); - RooTimer::timing_outfiles[_setNum] << timer.timing_s() << getpid(); - } } // Inline mode -- Calculate value now if (_state==Inline) { // cout << "RooRealMPFE::calculate(" << GetName() << ") performing Inline calculation NOW" << endl ; - if (RooTrace::timing_flag == 7) { - timer.start(); - } - _value = _arg ; clearValueDirty() ; - - if (RooTrace::timing_flag == 7) { - timer.stop(); - RooTimer::timing_outfiles[_setNum] << timer.timing_s() << getpid(); - } } #ifndef _WIN32 // Compare current value of variables with saved values and send changes to server if (_state==Client) { - // timing stuff - if (RooTrace::timing_flag == 7) { - timer.start(); - } - - if (RooTrace::timing_flag == 10) { - _time_communication_overhead(); - } - // cout << "RooRealMPFE::calculate(" << GetName() << ") state is Client trigger remote calculation" << endl ; Int_t i(0) ; RooFIter viter = _vars.fwdIterator() ; @@ -899,12 +659,6 @@ void RooRealMPFE::calculate() const << ") IPC toServer> Retrieve " << endl ; _retrieveDispatched = kTRUE ; - // end timing - if (RooTrace::timing_flag == 7) { - timer.stop(); - RooTimer::timing_outfiles[_setNum] << timer.timing_s() << getpid(); - } - } else if (_state!=Inline) { cout << "RooRealMPFE::calculate(" << GetName() << ") ERROR not in Client or Inline mode" << endl ; @@ -953,28 +707,12 @@ Double_t RooRealMPFE::getValV(const RooArgSet* /*nset*/) const Double_t RooRealMPFE::evaluate() const { - RooWallTimer wtimer, wtimer_before, wtimer_retrieve, wtimer_after; - RooCPUTimer ctimer, ctimer_before, ctimer_retrieve, ctimer_after; - - if (RooTrace::timing_flag == 4) { - wtimer.start(); - } - // Retrieve value of arg Double_t return_value = 0; if (_state==Inline) { return_value = _arg ; } else if (_state==Client) { #ifndef _WIN32 - if (RooTrace::timing_flag == 5) { - wtimer.start(); - wtimer_before.start(); - } - if (RooTrace::timing_flag == 6) { - ctimer.start(); - ctimer_before.start(); - } - bool needflush = false; int msg_i; Message msg; @@ -1003,27 +741,9 @@ Double_t RooRealMPFE::evaluate() const Int_t numError; - if (RooTrace::timing_flag == 5) { - wtimer_before.stop(); - wtimer_retrieve.start(); - } - if (RooTrace::timing_flag == 6) { - ctimer_before.stop(); - ctimer_retrieve.start(); - } - *_pipe >> msg_i >> value >> _evalCarry >> numError; msg = static_cast(msg_i); - if (RooTrace::timing_flag == 5) { - wtimer_retrieve.stop(); - wtimer_after.start(); - } - if (RooTrace::timing_flag == 6) { - ctimer_retrieve.stop(); - ctimer_after.start(); - } - if (msg!=ReturnValue) { cout << "RooRealMPFE::evaluate(" << GetName() << ") ERROR: unexpected message from server process: " << msg << endl ; @@ -1056,36 +776,9 @@ Double_t RooRealMPFE::evaluate() const _calcInProgress = kFALSE ; return_value = value ; - - if (RooTrace::timing_flag == 5) { - wtimer_after.stop(); - wtimer.stop(); - - RooTimer::timing_outfiles[_setNum] << wtimer.timing_s() << "wall" << "all" << getpid(); - RooTimer::timing_outfiles[_setNum] << wtimer_before.timing_s() << "wall" << "before_retrieve" << getpid(); - RooTimer::timing_outfiles[_setNum] << wtimer_retrieve.timing_s() << "wall" << "retrieve" << getpid(); - RooTimer::timing_outfiles[_setNum] << wtimer_after.timing_s() << "wall" << "after_retrieve" << getpid(); - } - - if (RooTrace::timing_flag == 6) { - ctimer_after.stop(); - ctimer.stop(); - - RooTimer::timing_outfiles[_setNum] << ctimer.timing_s() << "cpu" << "all" << getpid(); - RooTimer::timing_outfiles[_setNum] << ctimer_before.timing_s() << "cpu" << "before_retrieve" << getpid(); - RooTimer::timing_outfiles[_setNum] << ctimer_retrieve.timing_s() << "cpu" << "retrieve" << getpid(); - RooTimer::timing_outfiles[_setNum] << ctimer_after.timing_s() << "cpu" << "after_retrieve" << getpid(); - } - - #endif // _WIN32 } - if (RooTrace::timing_flag == 4) { - wtimer.stop(); - RooTimer::timing_outfiles[_setNum] << wtimer.timing_s() << getpid(); - } - return return_value; } @@ -1222,24 +915,6 @@ void RooRealMPFE::enableOffsetting(Bool_t flag) ((RooAbsReal&)_arg.arg()).enableOffsetting(flag) ; } -std::map RooRealMPFE::collectTimingsFromServer(Bool_t clear_timings) const { - std::map server_timings; - - *_pipe << RetrieveTimings << clear_timings << BidirMMapPipe::flush; - - unsigned long numTimings; - *_pipe >> numTimings; - - for (unsigned long i = 0; i < numTimings; ++i) { - std::string name; - double timing_s; - *_pipe >> name >> timing_s; - server_timings.insert({name, timing_s}); - } - - return server_timings; -} - pid_t RooRealMPFE::getPIDFromServer() const { *_pipe << GetPID << BidirMMapPipe::flush; pid_t pid; @@ -1247,26 +922,6 @@ pid_t RooRealMPFE::getPIDFromServer() const { return pid; } -void RooRealMPFE::_time_communication_overhead() const { - // test communication overhead timing - TimePoint comm_wallclock_begin_c2s, comm_wallclock_begin_s2c, comm_wallclock_end_s2c; - // ... from client to server - comm_wallclock_begin_c2s = WallClock::now(); - *_pipe << MeasureCommunicationTime << comm_wallclock_begin_c2s << BidirMMapPipe::flush; - // ... and from server to client - *_pipe >> comm_wallclock_begin_s2c; - comm_wallclock_end_s2c = WallClock::now(); - - std::cout << "server to client communication overhead timing:" << std::endl; - std::cout << "comm_wallclock_begin: " << std::chrono::duration_cast(comm_wallclock_begin_s2c.time_since_epoch()).count() << std::endl; - std::cout << "comm_wallclock_end: " << std::chrono::duration_cast(comm_wallclock_end_s2c.time_since_epoch()).count() << std::endl; - - double comm_wallclock_s = std::chrono::duration_cast(comm_wallclock_end_s2c - comm_wallclock_begin_s2c).count() / 1.e9; - - std::cout << "comm_wallclock (seconds): " << comm_wallclock_s << std::endl; -} - - std::ostream& operator<<(std::ostream& out, const RooRealMPFE::Message value){ const char* s = 0; #define PROCESS_VAL(p) case(p): s = #p; break; @@ -1285,10 +940,6 @@ std::ostream& operator<<(std::ostream& out, const RooRealMPFE::Message value){ PROCESS_VAL(RooRealMPFE::CalculateNoOffset); PROCESS_VAL(RooRealMPFE::SetCpuAffinity); PROCESS_VAL(RooRealMPFE::TaskSpec); - PROCESS_VAL(RooRealMPFE::MeasureCommunicationTime); - PROCESS_VAL(RooRealMPFE::RetrieveTimings); - PROCESS_VAL(RooRealMPFE::EnableTimingNumInts); - PROCESS_VAL(RooRealMPFE::DisableTimingNumInts); PROCESS_VAL(RooRealMPFE::GetPID); default: { s = "unknown Message!"; diff --git a/roofit/roofitcore/src/RooTimer.cxx b/roofit/roofitcore/src/RooTimer.cxx deleted file mode 100644 index 54a2615605c83..0000000000000 --- a/roofit/roofitcore/src/RooTimer.cxx +++ /dev/null @@ -1,48 +0,0 @@ -#include "RooTimer.h" -#include "RooTrace.h" - - -double RooTimer::timing_s() { - return _timing_s; -} - -void RooTimer::set_timing_s(double timing_s) { - _timing_s = timing_s; -} - -void RooTimer::store_timing_in_RooTrace(const std::string &name) { - RooTrace::objectTiming[name] = _timing_s; // subscript operator overwrites existing values, insert does not -} - -std::vector RooTimer::timing_outfiles; -std::vector RooTimer::timings; - - -RooWallTimer::RooWallTimer() { - start(); -} - -void RooWallTimer::start() { - _timing_begin = std::chrono::high_resolution_clock::now(); -} - -void RooWallTimer::stop() { - _timing_end = std::chrono::high_resolution_clock::now(); - set_timing_s(std::chrono::duration_cast(_timing_end - _timing_begin).count() / 1.e9); -} - - -RooCPUTimer::RooCPUTimer() { - start(); -} - -void RooCPUTimer::start() { - clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &_timing_begin); -} - -void RooCPUTimer::stop() { - clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &_timing_end); - long long timing_end_nsec = _timing_end.tv_nsec + 1000000000 * _timing_end.tv_sec; // 1'000'000'000 in c++14 - long long timing_begin_nsec = _timing_begin.tv_nsec + 1000000000 * _timing_begin.tv_sec; // 1'000'000'000 in c++14 - set_timing_s((timing_end_nsec - timing_begin_nsec) / 1.e9); -} \ No newline at end of file diff --git a/roofit/roofitcore/src/RooTrace.cxx b/roofit/roofitcore/src/RooTrace.cxx index 92512d0505f22..c1c63e54c2923 100644 --- a/roofit/roofitcore/src/RooTrace.cxx +++ b/roofit/roofitcore/src/RooTrace.cxx @@ -310,33 +310,4 @@ void RooTrace::callgrind_zero() void RooTrace::callgrind_dump() { ooccoutD((TObject*)0,Tracing) << "RooTrace::callgrind_dump()" << endl ; -} - -//////////////////////////////////////////////////////////////////////////////// -/// static map used to store timings of named objects -std::map RooTrace::objectTiming; - -//////////////////////////////////////////////////////////////////////////////// -/// Flag to switch on timers used for performance benchmarks. Use at your own peril! -/// -/// 1: not used in code, used in scripts to time the entire minimization -/// 2: timing_RATS_evaluate_full -/// 3: timing_RATS_evaluate_mpmaster_perCPU -/// 4: timing_RRMPFE_evaluate_full -/// 5: timing_wall_RRMPFE_evaluate_client -/// 6: timing_cpu_RRMPFE_evaluate_client -/// 7: timing_RRMPFE_calculate_initialize -/// 8: timing_RRMPFE_serverloop_p -/// 9: timing_RRMPFE_serverloop_while_p -/// 10: time communication overhead -int RooTrace::timing_flag = 0; - -Bool_t RooTrace::time_numInts() { - return _time_numInts; -} - -void RooTrace::set_time_numInts(Bool_t flag) { - _time_numInts = flag; -} - -Bool_t RooTrace::_time_numInts = kFALSE; \ No newline at end of file +} \ No newline at end of file diff --git a/roofit/roofitcore/src/TestStatistics/LikelihoodGradientJob.cxx b/roofit/roofitcore/src/TestStatistics/LikelihoodGradientJob.cxx index fbcb40750645f..6a12e0f271ad9 100644 --- a/roofit/roofitcore/src/TestStatistics/LikelihoodGradientJob.cxx +++ b/roofit/roofitcore/src/TestStatistics/LikelihoodGradientJob.cxx @@ -14,7 +14,6 @@ #include #include -#include #include "RooMsgService.h" #include "RooFit/MultiProcess/JobManager.h" #include "RooFit/MultiProcess/Messenger.h" @@ -97,14 +96,7 @@ void LikelihoodGradientJob::set_error_level(double error_level) const void LikelihoodGradientJob::evaluate_task(std::size_t task) { -// RooWallTimer timer; -// RooCPUTimer ctimer; run_derivator(task); -// ctimer.stop(); -// timer.stop(); -// oocxcoutD((TObject *)nullptr, Benchmarking1) -// << "worker_id: " << get_manager()->process_manager().worker_id() << ", task: " << task -// << ", partial derivative time: " << timer.timing_s() << "s -- cputime: " << ctimer.timing_s() << "s" << std::endl; } void LikelihoodGradientJob::update_real(std::size_t ix, double val, bool /*is_constant*/) diff --git a/roofit/roofitcore/src/TestStatistics/RooBinnedL.cxx b/roofit/roofitcore/src/TestStatistics/RooBinnedL.cxx index 178fd33106ac6..d3af874b62157 100644 --- a/roofit/roofitcore/src/TestStatistics/RooBinnedL.cxx +++ b/roofit/roofitcore/src/TestStatistics/RooBinnedL.cxx @@ -153,10 +153,6 @@ double RooBinnedL::evaluate_partition(Section bins, std::size_t /*components_beg result = t; } - // timer.Stop() ; - // cout << "RooNLLVar::evalPart(" << GetName() << ") SET=" << _setNum << " first=" << firstEvent << ", last=" << - // lastEvent << ", step=" << stepSize << ") result = " << result << " CPU = " << timer.CpuTime() << endl ; - // At the end of the first full calculation, wire the caches if (_first) { _first = false; diff --git a/roofit/roofitcore/src/TestStatistics/RooUnbinnedL.cxx b/roofit/roofitcore/src/TestStatistics/RooUnbinnedL.cxx index 692a9393e30b4..efbcaf80c2941 100644 --- a/roofit/roofitcore/src/TestStatistics/RooUnbinnedL.cxx +++ b/roofit/roofitcore/src/TestStatistics/RooUnbinnedL.cxx @@ -175,10 +175,6 @@ double RooUnbinnedL::evaluate_partition(Section events, result = t; } - // timer.Stop() ; - // cout << "RooNLLVar::evalPart(" << GetName() << ") SET=" << _setNum << " first=" << firstEvent << ", last=" << - // lastEvent << ", step=" << stepSize << ") result = " << result << " CPU = " << timer.CpuTime() << endl ; - // At the end of the first full calculation, wire the caches if (_first) { _first = false; diff --git a/roofit/roofitcore/test/RooGradMinimizer.cxx b/roofit/roofitcore/test/RooGradMinimizer.cxx index dca02b3d7f1b0..37cd0a951cb49 100644 --- a/roofit/roofitcore/test/RooGradMinimizer.cxx +++ b/roofit/roofitcore/test/RooGradMinimizer.cxx @@ -16,7 +16,6 @@ #include #include #include -#include #include #include #include @@ -58,19 +57,13 @@ TEST(GradMinimizer, Gaussian1D) // -------- - RooWallTimer wtimer; - - // -------- - RooMinimizer m0(*nll); m0.setMinimizerType("Minuit2"); m0.setStrategy(0); m0.setPrintLevel(-1); - wtimer.start(); m0.migrad(); - wtimer.stop(); RooFitResult *m0result = m0.lastMinuitFit(); double minNll0 = m0result->minNll(); @@ -86,9 +79,7 @@ TEST(GradMinimizer, Gaussian1D) m1->setStrategy(0); m1->setPrintLevel(-1); - wtimer.start(); m1->migrad(); - wtimer.stop(); RooFitResult *m1result = m1->lastMinuitFit(); double minNll1 = m1result->minNll(); @@ -200,26 +191,18 @@ TEST(GradMinimizer, Gaussian2DVarToConst) { // -------- - RooWallTimer wtimer; - - // -------- - RooMinimizer m0(*nll); m0.setMinimizerType("Minuit2"); m0.setStrategy(0); m0.setPrintLevel(-1); - wtimer.start(); m0.migrad(); - wtimer.stop(); values = *savedValues; mu1->setConstant(kTRUE); - wtimer.start(); m0.migrad(); - wtimer.stop(); RooFitResult *m0result = m0.lastMinuitFit(); double minNll0 = m0result->minNll(); @@ -238,16 +221,12 @@ TEST(GradMinimizer, Gaussian2DVarToConst) { m1.setStrategy(0); m1.setPrintLevel(-1); - wtimer.start(); m1.migrad(); - wtimer.stop(); values = *savedValues; mu1->setConstant(kTRUE); - wtimer.start(); m1.migrad(); - wtimer.stop(); RooFitResult *m1result = m1.lastMinuitFit(); double minNll1 = m1result->minNll(); @@ -311,10 +290,6 @@ TEST(GradMinimizer, Gaussian2DConstToVar) { // -------- - RooWallTimer wtimer; - - // -------- - RooMinimizer m0(*nll); m0.setMinimizerType("Minuit2"); @@ -323,16 +298,12 @@ TEST(GradMinimizer, Gaussian2DConstToVar) { mu1->setConstant(kTRUE); - wtimer.start(); m0.migrad(); - wtimer.stop(); values = *savedValues; mu1->setConstant(kFALSE); - wtimer.start(); m0.migrad(); - wtimer.stop(); RooFitResult *m0result = m0.lastMinuitFit(); double minNll0 = m0result->minNll(); @@ -351,16 +322,12 @@ TEST(GradMinimizer, Gaussian2DConstToVar) { m1.setStrategy(0); m1.setPrintLevel(-1); - wtimer.start(); m1.migrad(); - wtimer.stop(); values = *savedValues; mu1->setConstant(kFALSE); - wtimer.start(); m1.migrad(); - wtimer.stop(); RooFitResult *m1result = m1.lastMinuitFit(); double minNll1 = m1result->minNll(); @@ -408,19 +375,13 @@ TEST(GradMinimizer, GaussianND) // -------- - RooWallTimer wtimer; - - // -------- - RooMinimizer m0(*(nll.get())); m0.setMinimizerType("Minuit2"); m0.setStrategy(0); m0.setPrintLevel(-1); - wtimer.start(); m0.migrad(); - wtimer.stop(); RooFitResult *m0result = m0.lastMinuitFit(); double minNll0 = m0result->minNll(); @@ -451,9 +412,7 @@ TEST(GradMinimizer, GaussianND) m1->setStrategy(0); m1->setPrintLevel(-1); - wtimer.start(); m1->migrad(); - wtimer.stop(); RooFitResult *m1result = m1->lastMinuitFit(); double minNll1 = m1result->minNll(); @@ -511,10 +470,6 @@ TEST(GradMinimizerReverse, GaussianND) // -------- - RooWallTimer wtimer; - - // -------- - std::unique_ptr m0 = RooMinimizer::create(*nll); m0->setMinimizerType("Minuit2"); @@ -522,9 +477,7 @@ TEST(GradMinimizerReverse, GaussianND) m0->setStrategy(0); m0->setPrintLevel(-1); - wtimer.start(); m0->migrad(); - wtimer.stop(); RooFitResult *m0result = m0->lastMinuitFit(); double minNll0 = m0result->minNll(); @@ -556,9 +509,7 @@ TEST(GradMinimizerReverse, GaussianND) m1.setStrategy(0); m1.setPrintLevel(-1); - wtimer.start(); m1.migrad(); - wtimer.stop(); RooFitResult *m1result = m1.lastMinuitFit(); double minNll1 = m1result->minNll(); @@ -676,19 +627,13 @@ TEST(GradMinimizer, BranchingPDF) // -------- - RooWallTimer wtimer; - - // -------- - RooMinimizer m0(*nll); m0.setMinimizerType("Minuit2"); m0.setStrategy(0); m0.setPrintLevel(-1); - wtimer.start(); m0.migrad(); - wtimer.stop(); RooFitResult *m0result = m0.lastMinuitFit(); double minNll0 = m0result->minNll(); @@ -714,9 +659,7 @@ TEST(GradMinimizer, BranchingPDF) m1->setStrategy(0); m1->setPrintLevel(-1); - wtimer.start(); m1->migrad(); - wtimer.stop(); RooFitResult *m1result = m1->lastMinuitFit(); double minNll1 = m1result->minNll(); @@ -796,10 +739,6 @@ TEST(GradMinimizerDebugging, DISABLED_BranchingPDFLoadFromWorkspace) // -------- - RooWallTimer wtimer; - - // -------- - all_values.Print("v"); RooMinimizer m0(*nll); m0.setMinimizerType("Minuit2"); @@ -807,9 +746,7 @@ TEST(GradMinimizerDebugging, DISABLED_BranchingPDFLoadFromWorkspace) m0.setStrategy(0); m0.setPrintLevel(-1); - wtimer.start(); m0.migrad(); - wtimer.stop(); RooFitResult *m0result = m0.lastMinuitFit(); double minNll0 = m0result->minNll(); @@ -840,9 +777,7 @@ TEST(GradMinimizerDebugging, DISABLED_BranchingPDFLoadFromWorkspace) m1->setStrategy(0); m1->setPrintLevel(-1); - wtimer.start(); m1->migrad(); - wtimer.stop(); RooFitResult *m1result = m1->lastMinuitFit(); double minNll1 = m1result->minNll(); diff --git a/roofit/roofitcore/test/TestStatistics/testLikelihoodGradientJob.cpp b/roofit/roofitcore/test/TestStatistics/testLikelihoodGradientJob.cpp index bec26ed99318f..0e05ac5095958 100644 --- a/roofit/roofitcore/test/TestStatistics/testLikelihoodGradientJob.cpp +++ b/roofit/roofitcore/test/TestStatistics/testLikelihoodGradientJob.cpp @@ -18,7 +18,6 @@ #include #include -#include #include "RooDataHist.h" // complete type in Binned test #include "RooCategory.h" // complete type in MultiBinnedConstraint test @@ -262,8 +261,6 @@ TEST_P(LikelihoodGradientJob, GaussianND) throw std::runtime_error("params->snapshot() cannot be casted to RooArgSet!"); } - RooWallTimer wtimer; - // -------- std::unique_ptr m0 = RooMinimizer::create(*nll); @@ -272,11 +269,7 @@ TEST_P(LikelihoodGradientJob, GaussianND) m0->setStrategy(0); m0->setPrintLevel(-1); - wtimer.start(); m0->migrad(); - wtimer.stop(); - std::cout << "\nwall clock time RooGradMinimizer.migrad (NWorkers = " << NWorkers << ", seed = " << seed - << "): " << wtimer.timing_s() << " s" << std::endl; RooFitResult *m0result = m0->lastMinuitFit(); double minNll0 = m0result->minNll(); @@ -312,12 +305,7 @@ TEST_P(LikelihoodGradientJob, GaussianND) m1->setStrategy(0); m1->setPrintLevel(-1); - wtimer.start(); m1->migrad(); - wtimer.stop(); - std::cout << "wall clock time MP::GradMinimizer.migrad (NWorkers = " << NWorkers << ", seed = " << seed - << "): " << wtimer.timing_s() << " s\n" - << std::endl; RooFitResult *m1result = m1->lastMinuitFit(); double minNll1 = m1result->minNll(); diff --git a/roofit/roofitcore/test/TestStatistics/testLikelihoodSerial.cxx b/roofit/roofitcore/test/TestStatistics/testLikelihoodSerial.cxx index 1d604167cc3b6..b6c86874cd1a0 100644 --- a/roofit/roofitcore/test/TestStatistics/testLikelihoodSerial.cxx +++ b/roofit/roofitcore/test/TestStatistics/testLikelihoodSerial.cxx @@ -16,7 +16,6 @@ #include #include -#include #include #include From e69c82b86a7da09bce1850e219f20bd87d789dfc Mon Sep 17 00:00:00 2001 From: "E. G. Patrick Bos" Date: Thu, 24 Jun 2021 16:09:42 +0200 Subject: [PATCH 04/10] remove RooJsonListFile class --- roofit/roofitcore/CMakeLists.txt | 2 - roofit/roofitcore/inc/RooJsonListFile.h | 74 ----------------------- roofit/roofitcore/src/RooJsonListFile.cxx | 40 ------------ 3 files changed, 116 deletions(-) delete mode 100644 roofit/roofitcore/inc/RooJsonListFile.h delete mode 100644 roofit/roofitcore/src/RooJsonListFile.cxx diff --git a/roofit/roofitcore/CMakeLists.txt b/roofit/roofitcore/CMakeLists.txt index 3517d6bce05a5..5db2cfef28a11 100644 --- a/roofit/roofitcore/CMakeLists.txt +++ b/roofit/roofitcore/CMakeLists.txt @@ -238,7 +238,6 @@ ROOT_STANDARD_LIBRARY_PACKAGE(RooFitCore RooWorkspace.h RooWorkspaceHandle.h RooXYChi2Var.h - RooJsonListFile.h RooTaskSpec.h RooGaussMinimizer.h RooGaussMinimizerFcn.h @@ -479,7 +478,6 @@ ROOT_STANDARD_LIBRARY_PACKAGE(RooFitCore src/RooVectorDataStore.cxx src/RooWorkspace.cxx src/RooXYChi2Var.cxx - src/RooJsonListFile.cxx src/RooTaskSpec.cxx src/RooGaussMinimizer.cxx src/RooGaussMinimizerFcn.cxx diff --git a/roofit/roofitcore/inc/RooJsonListFile.h b/roofit/roofitcore/inc/RooJsonListFile.h deleted file mode 100644 index 281051684d81c..0000000000000 --- a/roofit/roofitcore/inc/RooJsonListFile.h +++ /dev/null @@ -1,74 +0,0 @@ -#ifndef ROO_JSON_LIST_FILE -#define ROO_JSON_LIST_FILE - -#include -#include -#include - - -class RooJsonListFile { -public: - // ctors - RooJsonListFile(): _member_index(0) {} - RooJsonListFile(const std::string & filename); - // default move ctors - RooJsonListFile(RooJsonListFile&& other) = default; - RooJsonListFile& operator=(RooJsonListFile&& other) = default; - // dtor - ~RooJsonListFile(); - - void open(const std::string & filename); - - template - void set_member_names(Iter begin, Iter end, bool reset_index = true); - - RooJsonListFile& add_member_name(const std::string &name); - - template - RooJsonListFile& operator<< (const T& obj); - -private: - std::ofstream _out; - std::vector _member_names; - unsigned long _next_member_index(); - unsigned long _member_index; -}; - - -template -void RooJsonListFile::set_member_names(Iter begin, Iter end, bool reset_index) { - _member_names.clear(); - for(Iter it = begin; it != end; ++it) { - _member_names.push_back(*it); - } - if (reset_index) { - _member_index = 0; - } -} - -//////////////////////////////////////////////////////////////////////////////// -/// This method assumes that std::ofstream::operator<<(T) exists. - -template -RooJsonListFile& RooJsonListFile::operator<< (const T& obj) -{ - auto ix = _next_member_index(); - if (ix == 0) { - _out << "{"; - } - - // `"member name": ` - _out << "\"" << _member_names[ix] << "\": "; - // `"value"` (comma added below, if not last value in list element) - _out << "\"" << obj << "\""; - - if (ix == _member_names.size() - 1) { - _out << "},\n"; - } else { - _out << ", "; - } - - return *this; -} - -#endif \ No newline at end of file diff --git a/roofit/roofitcore/src/RooJsonListFile.cxx b/roofit/roofitcore/src/RooJsonListFile.cxx deleted file mode 100644 index f8df77a9dfe0f..0000000000000 --- a/roofit/roofitcore/src/RooJsonListFile.cxx +++ /dev/null @@ -1,40 +0,0 @@ -#include "RooJsonListFile.h" - -RooJsonListFile::RooJsonListFile(const std::string & filename) : - _member_index(0) { - open(filename); -} - -void RooJsonListFile::open(const std::string & filename) { - // do not use ios::app for opening out! - // app moves put pointer to end of file before each write, which makes seekp useless. - // See http://en.cppreference.com/w/cpp/io/basic_filebuf/open - _out.open(filename, std::ios_base::in | std::ios_base::out); // "mode r+" - if (!_out.is_open()) { - _out.clear(); - // new file - _out.open(filename, std::ios_base::out); // "mode w" - _out << "[\n"; - } else { - // existing file that, presumably, has been closed with close_json_list() and thus ends with "\n]". - _out.seekp(-2, std::ios_base::end); - _out << ",\n"; - } -} - -RooJsonListFile::~RooJsonListFile() { - _out.seekp(-2, std::ios_base::end); - _out << "\n]"; -} - -unsigned long RooJsonListFile::_next_member_index() { - auto current_index = _member_index; - _member_index = (_member_index + 1) % _member_names.size(); - return current_index; -} - -RooJsonListFile& RooJsonListFile::add_member_name(const std::string &name) { - _member_names.push_back(name); - - return *this; -} \ No newline at end of file From de7cd9d8970a284b4e6dcb9a47837782063ca2c1 Mon Sep 17 00:00:00 2001 From: "E. G. Patrick Bos" Date: Thu, 24 Jun 2021 16:16:03 +0200 Subject: [PATCH 05/10] remove RooTaskSpec class --- roofit/roofitcore/CMakeLists.txt | 2 - roofit/roofitcore/inc/BidirMMapPipe.h | 11 --- roofit/roofitcore/inc/RooAddition.h | 2 - roofit/roofitcore/inc/RooRealMPFE.h | 7 +- roofit/roofitcore/inc/RooTaskSpec.h | 47 ---------- roofit/roofitcore/src/RooRealMPFE.cxx | 66 -------------- roofit/roofitcore/src/RooTaskSpec.cxx | 122 -------------------------- 7 files changed, 1 insertion(+), 256 deletions(-) delete mode 100644 roofit/roofitcore/inc/RooTaskSpec.h delete mode 100644 roofit/roofitcore/src/RooTaskSpec.cxx diff --git a/roofit/roofitcore/CMakeLists.txt b/roofit/roofitcore/CMakeLists.txt index 5db2cfef28a11..2f9d3635fe113 100644 --- a/roofit/roofitcore/CMakeLists.txt +++ b/roofit/roofitcore/CMakeLists.txt @@ -238,7 +238,6 @@ ROOT_STANDARD_LIBRARY_PACKAGE(RooFitCore RooWorkspace.h RooWorkspaceHandle.h RooXYChi2Var.h - RooTaskSpec.h RooGaussMinimizer.h RooGaussMinimizerFcn.h NumericalDerivatorMinuit2.h @@ -478,7 +477,6 @@ ROOT_STANDARD_LIBRARY_PACKAGE(RooFitCore src/RooVectorDataStore.cxx src/RooWorkspace.cxx src/RooXYChi2Var.cxx - src/RooTaskSpec.cxx src/RooGaussMinimizer.cxx src/RooGaussMinimizerFcn.cxx src/NumericalDerivatorMinuit2.cxx diff --git a/roofit/roofitcore/inc/BidirMMapPipe.h b/roofit/roofitcore/inc/BidirMMapPipe.h index 32f1174cad336..20a848b9d2767 100644 --- a/roofit/roofitcore/inc/BidirMMapPipe.h +++ b/roofit/roofitcore/inc/BidirMMapPipe.h @@ -17,17 +17,6 @@ #include #include #include -#include "RooTaskSpec.h" - -// forward declarations -namespace RooFit { - namespace MultiProcessV1 { - enum class M2Q; - enum class Q2M; - enum class W2Q; - enum class Q2W; - } -} #define BEGIN_NAMESPACE_ROOFIT namespace RooFit { #define END_NAMESPACE_ROOFIT } diff --git a/roofit/roofitcore/inc/RooAddition.h b/roofit/roofitcore/inc/RooAddition.h index 499abb108167a..ff7a13070858f 100644 --- a/roofit/roofitcore/inc/RooAddition.h +++ b/roofit/roofitcore/inc/RooAddition.h @@ -19,7 +19,6 @@ #include "RooAbsReal.h" #include "RooListProxy.h" #include "RooObjCacheManager.h" -#include "RooTaskSpec.h" #include class RooRealVar; @@ -27,7 +26,6 @@ class RooArgList; class RooAddition : public RooAbsReal { - friend class RooTaskSpec; public: RooAddition() ; diff --git a/roofit/roofitcore/inc/RooRealMPFE.h b/roofit/roofitcore/inc/RooRealMPFE.h index 8585cc0d5cb8b..05082f231ff13 100644 --- a/roofit/roofitcore/inc/RooRealMPFE.h +++ b/roofit/roofitcore/inc/RooRealMPFE.h @@ -22,7 +22,6 @@ #include "RooArgList.h" #include "RooMPSentinel.h" #include "TStopwatch.h" -#include "RooTaskSpec.h" #include #include #include @@ -66,7 +65,7 @@ class RooRealMPFE : public RooAbsReal { enum Message { SendReal=0, SendCat, Calculate, Retrieve, ReturnValue, Terminate, ConstOpt, Verbose, LogEvalError, ApplyNLLW2, EnableOffset, CalculateNoOffset, - SetCpuAffinity, TaskSpec, + SetCpuAffinity, GetPID }; @@ -82,8 +81,6 @@ class RooRealMPFE : public RooAbsReal { RooListProxy _vars ; // Variables RooArgList _saveVars ; // Copy of variables mutable Bool_t _calcInProgress ; - // RooTaskSpec _taskspecification; - Bool_t _useTaskSpec ; Bool_t _verboseClient ; Bool_t _verboseServer ; Bool_t _inlineMode ; @@ -101,7 +98,6 @@ class RooRealMPFE : public RooAbsReal { static RooMPSentinel _sentinel ; void setCpuAffinity(int cpu); - void setTaskSpec(); pid_t getPIDFromServer() const; void setMPSet(Int_t inSetNum, Int_t inNumSets); @@ -109,7 +105,6 @@ class RooRealMPFE : public RooAbsReal { // RooArgSet* _components = 0; // RooAbsArg* _findComponent(std::string name); - // RooTaskSpec _taskspecification; Int_t _setNum ; //! Partition number of this instance in parallel calculation mode Int_t _numSets ; //! Total number of partitions in parallel calculation mode diff --git a/roofit/roofitcore/inc/RooTaskSpec.h b/roofit/roofitcore/inc/RooTaskSpec.h deleted file mode 100644 index 2e6e92d2b52e3..0000000000000 --- a/roofit/roofitcore/inc/RooTaskSpec.h +++ /dev/null @@ -1,47 +0,0 @@ -/***************************************************************************** - * Project: RooFit * - * Package: RooFitCore * - * File: $Id: RooRealMPFE.h,v 1.7 2007/05/11 09:11:30 verkerke Exp $ - * Authors: * - * WV, Wouter Verkerke, UC Santa Barbara, verkerke@slac.stanford.edu * - * DK, David Kirkby, UC Irvine, dkirkby@uci.edu * - * * - * Copyright (c) 2000-2005, Regents of the University of California * - * and Stanford University. All rights reserved. * - * * - * Redistribution and use in source and binary forms, * - * with or without modification, are permitted according to the terms * - * listed in LICENSE (http://roofit.sourceforge.net/license.txt) * - *****************************************************************************/ -#ifndef ROO_TASK_SPEC -#define ROO_TASK_SPEC - -#include "RooFit.h" -#include -#include -#include "RooAbsTestStatistic.h" - -class RooArgList; - -class RooTaskSpec { - public: - RooTaskSpec(); - RooTaskSpec(RooAbsTestStatistic* rats_nll); - RooTaskSpec(RooAbsReal* nll); - struct Task { - Int_t id; - Bool_t binned; - std::string name; - Int_t entries; - Bool_t is_done; - }; - std::list tasks; - private: - void _initialise(RooAbsTestStatistic* rats); - Task _fill_task(const Int_t n, RooAbsTestStatistic* rats); - Int_t _fit_case; - Int_t n_tasks = tasks.size(); - - -}; -#endif diff --git a/roofit/roofitcore/src/RooRealMPFE.cxx b/roofit/roofitcore/src/RooRealMPFE.cxx index 7e296b213018f..2a6b36875dfc0 100644 --- a/roofit/roofitcore/src/RooRealMPFE.cxx +++ b/roofit/roofitcore/src/RooRealMPFE.cxx @@ -64,7 +64,6 @@ For general multiprocessing in ROOT, please refer to the TProcessExecutor class. #include "RooTrace.h" #include "RooConstVar.h" #include "RooRealIntegral.h" -#include "RooTaskSpec.h" #include "TSystem.h" @@ -99,7 +98,6 @@ RooRealMPFE::RooRealMPFE(const char *name, const char *title, RooAbsReal& arg, I _arg("arg","arg",this,arg), _vars("vars","vars",this), _calcInProgress(kFALSE), - _useTaskSpec(kTRUE), _verboseClient(kFALSE), _verboseServer(kFALSE), _inlineMode(calcInline), @@ -131,7 +129,6 @@ RooRealMPFE::RooRealMPFE(const RooRealMPFE& other, const char* name) : _arg("arg",this,other._arg), _vars("vars",this,other._vars), _calcInProgress(kFALSE), - _useTaskSpec(kTRUE), _verboseClient(other._verboseClient), _verboseServer(other._verboseServer), _inlineMode(other._inlineMode), @@ -236,13 +233,6 @@ void RooRealMPFE::initialize() delete _pipe; _exit(0) ; } else { - if (_useTaskSpec){ -// cout<<"sending arg"<::const_iterator task = TaskSpec.tasks.begin(), end = TaskSpec.tasks.end(); task != end; ++task){ - bipe << *task; - } - return bipe; - } - - // BidirMMapPipe& operator>>(BidirMMapPipe& bipe, const RooTaskSpec::Task& Task) { - // const char *name = Task.name; - // bipe.read(&name, sizeof(name)); - // return bipe; - // } - BidirMMapPipe& operator>>(BidirMMapPipe& bipe, RooTaskSpec::Task& Task) { - bipe >> Task.name; - return bipe; - } -} - - -//////////////////////////////////////////////////////////////////////////////// -/// Set use of RooTaskSpec. - -void RooRealMPFE::setTaskSpec() { -// cout<<"Setting TaskSpec!"<(_arg.absArg()); - RooTaskSpec taskspecification = RooTaskSpec(tmp); - Message msg = TaskSpec; -// cout<<"Got task spec "<< endl; -// tmp->Print(); // WARNING: don't print MPFE values before they're fully initialized! Or make them dirty again afterwards. - *_pipe << msg << *taskspecification.tasks.begin(); - //for (std::list::const_iterator task = taskspecification.tasks.begin(), end = taskspecification.tasks.end(); task != end; ++task){ - // cout << "This task is " << task->name <> taskspecification; -// std::cout << "EEEEEE TaskSpec'd "<< taskspecification.name < this class returns the tools to -analyse the components of this likelihood. -Case 2: Multiple return values. The first of which is always a RooAbsTestStatistic -followed by either more RooAbsTestStatistics or a RooConstraintSum which also -signifies the last term. Here we only consider the first term since the others -represent odd and rare test cases. -ToDo: Simple use demonstration. -**/ - - -#include "RooTaskSpec.h" -// #include "Riostream.h" -// #include "RooFit.h" -// #include -#include "RooAbsTestStatistic.h" -#include "RooAddition.h" -#include "RooAbsData.h" - -using namespace std; -using namespace RooFit; - -//ClassImp(RooTaskSpec); -RooTaskSpec::RooTaskSpec(){}; - -RooTaskSpec::RooTaskSpec(RooAbsTestStatistic* rats_nll){ -// cout << " NLL is a RooAbsTestStatistic (Case 1)" << endl ; -// rats_nll->Print(); // WARNING: don't print MPFE values before they're fully initialized! Or make them dirty again afterwards. - _fit_case = 1; - _initialise(rats_nll); -} - -RooTaskSpec::RooTaskSpec(RooAbsReal* nll){ -// cout <<"starting case2"<(nll) ; - if (ra) { -// cout <<"yes ra, printing RooAddition"<Print(); -// cout <<"printed"<(ra->list().at(0)) ; - if (!rats) { -// cout << "ERROR: NLL is a RooAddition, but first element of addition is not a RooAbsTestStatistic!" << endl ; - _fit_case = 0; -// cout << "It is a "<list().at(0)<Print(); - } -} - - -void RooTaskSpec::_initialise (RooAbsTestStatistic* rats){ - // Check if nll is a AbsTestStatistic - if (rats->numSimultaneous()==0){ - // _set.add(_fill_task(0, rats)); - tasks.push_back(_fill_task(0, rats)); - } else { - for (Int_t i=0 ; i < rats->numSimultaneous() ; i++) { -// cout << "SimComponent #" << i << " = " ; - rats->simComponents()[i]->Print() ; - RooAbsTestStatistic* comp = (RooAbsTestStatistic*) rats->simComponents()[i] ; - tasks.push_back(_fill_task(i, comp)); - } - } -} - -RooTaskSpec::Task RooTaskSpec::_fill_task(Int_t n, RooAbsTestStatistic* rats){ - Bool_t b = rats->function().getAttribute("BinnedLikelihood") ; - Task t; - t.id = n; - t.binned = b; - if (b) { - t.name = rats->function().GetName(); - t.entries = rats->data().numEntries(); - // cout << "Binned Likelihood has probability model named " << rats->function().GetName() - // << " and a binned dataset with " << rats->data().numEntries() - // << " bins with a total event count of " << rats->data().sumEntries() << endl ; - } else { - t.name = rats->function().GetName(); - t.entries = rats->data().numEntries(); - - // cout << "Unbinned likelihood has probability density model named " << rats->function().GetName() - // << " and an dataset with " << rats->data().numEntries() - // << " events with a weight of " << rats->data().sumEntries() << endl ; - } return t; -} - From 292683dac9a9e3540ad88f0853c8c5a1a58161bc Mon Sep 17 00:00:00 2001 From: "E. G. Patrick Bos" Date: Thu, 24 Jun 2021 16:17:36 +0200 Subject: [PATCH 06/10] remove RooGaussMinimizer(Fcn) classes --- roofit/roofitcore/CMakeLists.txt | 4 - roofit/roofitcore/inc/RooGaussMinimizer.h | 104 --- roofit/roofitcore/inc/RooGaussMinimizerFcn.h | 112 ---- roofit/roofitcore/src/RooGaussMinimizer.cxx | 275 -------- .../roofitcore/src/RooGaussMinimizerFcn.cxx | 627 ------------------ roofit/roofitcore/src/RooMinimizerFcn.cxx | 1 - 6 files changed, 1123 deletions(-) delete mode 100644 roofit/roofitcore/inc/RooGaussMinimizer.h delete mode 100644 roofit/roofitcore/inc/RooGaussMinimizerFcn.h delete mode 100644 roofit/roofitcore/src/RooGaussMinimizer.cxx delete mode 100644 roofit/roofitcore/src/RooGaussMinimizerFcn.cxx diff --git a/roofit/roofitcore/CMakeLists.txt b/roofit/roofitcore/CMakeLists.txt index 2f9d3635fe113..f01ee31383724 100644 --- a/roofit/roofitcore/CMakeLists.txt +++ b/roofit/roofitcore/CMakeLists.txt @@ -238,8 +238,6 @@ ROOT_STANDARD_LIBRARY_PACKAGE(RooFitCore RooWorkspace.h RooWorkspaceHandle.h RooXYChi2Var.h - RooGaussMinimizer.h - RooGaussMinimizerFcn.h NumericalDerivatorMinuit2.h RooGradMinimizerFcn.h # TestStatistics/GradMinimizer.h @@ -477,8 +475,6 @@ ROOT_STANDARD_LIBRARY_PACKAGE(RooFitCore src/RooVectorDataStore.cxx src/RooWorkspace.cxx src/RooXYChi2Var.cxx - src/RooGaussMinimizer.cxx - src/RooGaussMinimizerFcn.cxx src/NumericalDerivatorMinuit2.cxx src/RooGradMinimizerFcn.cxx # src/TestStatistics/GradMinimizerFcn.cxx diff --git a/roofit/roofitcore/inc/RooGaussMinimizer.h b/roofit/roofitcore/inc/RooGaussMinimizer.h deleted file mode 100644 index 3b88bdabe5256..0000000000000 --- a/roofit/roofitcore/inc/RooGaussMinimizer.h +++ /dev/null @@ -1,104 +0,0 @@ -/***************************************************************************** - * Project: RooFit * - * Package: RooFitCore * - * File: $Id$ - * Authors: * - * WV, Wouter Verkerke, UC Santa Barbara, verkerke@slac.stanford.edu * - * DK, David Kirkby, UC Irvine, dkirkby@uci.edu * - * AL, Alfio Lazzaro, INFN Milan, alfio.lazzaro@mi.infn.it * - * PB, Patrick Bos, NL eScience Center, p.bos@esciencecenter.nl * - * VC, Vince Croft, DIANA / NYU, vincent.croft@cern.ch * - * * - * * - * Redistribution and use in source and binary forms, * - * with or without modification, are permitted according to the terms * - * listed in LICENSE (http://roofit.sourceforge.net/license.txt) * - *****************************************************************************/ -#ifndef ROO_GAUSS_MINIMIZER -#define ROO_GAUSS_MINIMIZER - -#include "TObject.h" -#include "TStopwatch.h" -#include -#include "TMatrixDSymfwd.h" - - -#include "Fit/Fitter.h" -#include "RooMinimizerFcn.h" -#include "RooGaussMinimizerFcn.h" - -class RooAbsReal ; -class RooFitResult ; -class RooArgList ; -class RooRealVar ; -class RooArgSet ; -class TH2F ; -class RooPlot ; - -class RooGaussMinimizer : public TObject { -public: - - RooGaussMinimizer(RooAbsReal& function) ; - virtual ~RooGaussMinimizer() ; - - enum Strategy { Speed=0, Balance=1, Robustness=2 } ; - enum PrintLevel { None=-1, Reduced=0, Normal=1, ExtraForProblem=2, Maximum=3 } ; - void optimizeConst(Int_t flag) ; - - RooFitResult* fit(const char* options) ; - - Int_t migrad() ; - Int_t hesse() ; - Int_t minos() ; - Int_t minos(const RooArgSet& minosParamList) ; - - Int_t minimize(const char* type, const char* alg=0) ; - - static void cleanup() ; - - void saveStatus(const char* label, Int_t status) { _statusHistory.push_back(std::pair(label,status)) ; } - - Int_t evalCounter() const { return fitterFcn()->evalCounter() ; } - void zeroEvalCount() { fitterFcn()->zeroEvalCount() ; } - - ROOT::Fit::Fitter* fitter() ; - const ROOT::Fit::Fitter* fitter() const ; - -protected: - - friend class RooAbsPdf ; - - inline Int_t getNPar() const { return fitterFcn()->NDim() ; } - inline std::ofstream* logfile() { return fitterFcn()->GetLogFile(); } - inline Double_t& maxFCN() { return fitterFcn()->GetMaxFCN() ; } - - const RooGaussMinimizerFcn* fitterFcn() const { return ( fitter()->GetFCN() ? (dynamic_cast(fitter()->GetFCN())) : _fcn ) ; } - RooGaussMinimizerFcn* fitterFcn() { return ( fitter()->GetFCN() ? (dynamic_cast(fitter()->GetFCN())) : _fcn ) ; } - -private: - - Int_t _printLevel ; - Int_t _status ; - Bool_t _optConst ; - Bool_t _profile ; - RooAbsReal* _func ; - - Bool_t _verbose ; - TStopwatch _timer ; - TStopwatch _cumulTimer ; - Bool_t _profileStart ; - - TMatrixDSym* _extV ; - - RooGaussMinimizerFcn *_fcn; - std::string _minimizerType; - - static ROOT::Fit::Fitter *_theFitter ; - - std::vector > _statusHistory ; - - RooGaussMinimizer(const RooGaussMinimizer&) ; - -} ; - -#endif diff --git a/roofit/roofitcore/inc/RooGaussMinimizerFcn.h b/roofit/roofitcore/inc/RooGaussMinimizerFcn.h deleted file mode 100644 index 37b3aa9243f42..0000000000000 --- a/roofit/roofitcore/inc/RooGaussMinimizerFcn.h +++ /dev/null @@ -1,112 +0,0 @@ -/***************************************************************************** - * Project: RooFit * - * Package: RooFitCore * - * @(#)root/roofitcore:$Id$ - * Authors: * - * AL, Alfio Lazzaro, INFN Milan, alfio.lazzaro@mi.infn.it * - * PB, Patrick Bos, NL eScience Center, p.bos@esciencecenter.nl * - * VC, Vince Croft, DIANA / NYU, vincent.croft@cern.ch * - * * - * * - * Redistribution and use in source and binary forms, * - * with or without modification, are permitted according to the terms * - * listed in LICENSE (http://roofit.sourceforge.net/license.txt) * - *****************************************************************************/ - -#ifndef __ROOFIT_NOROOGAUSSMINIMIZER - -#ifndef ROO_GAUSS_MINIMIZER_FCN -#define ROO_GAUSS_MINIMIZER_FCN - -#include "Math/IFunction.h" -#include "Fit/ParameterSettings.h" -#include "Fit/FitResult.h" - -#include "TMatrixDSym.h" - -#include "RooAbsReal.h" -#include "RooArgList.h" - -#include -#include - -class RooGaussMinimizer; - - -class RooGaussMinimizerFcn : public ROOT::Math::IMultiGradFunction { - - public: - - RooGaussMinimizerFcn(RooAbsReal *funct, RooGaussMinimizer *context, - bool verbose = false); - RooGaussMinimizerFcn(const RooGaussMinimizerFcn& other); - virtual ~RooGaussMinimizerFcn(); - - virtual ROOT::Math::IMultiGradFunction* Clone() const; - virtual unsigned int NDim() const { return _nDim; } - - RooArgList* GetFloatParamList() { return _floatParamList; } - RooArgList* GetConstParamList() { return _constParamList; } - RooArgList* GetInitFloatParamList() { return _initFloatParamList; } - RooArgList* GetInitConstParamList() { return _initConstParamList; } - - void SetEvalErrorWall(Bool_t flag) { _doEvalErrorWall = flag ; } - void SetPrintEvalErrors(Int_t numEvalErrors) { _printEvalErrors = numEvalErrors ; } - Bool_t SetLogFile(const char* inLogfile); - std::ofstream* GetLogFile() { return _logfile; } - void SetVerbose(Bool_t flag=kTRUE) { _verbose = flag ; } - - Double_t& GetMaxFCN() { return _maxFCN; } - Int_t GetNumInvalidNLL() { return _numBadNLL; } - - Bool_t Synchronize(std::vector& parameters, - Bool_t optConst, Bool_t verbose); - void BackProp(const ROOT::Fit::FitResult &results); - void ApplyCovarianceMatrix(TMatrixDSym& V); - - Int_t evalCounter() const { return _evalCounter ; } - void zeroEvalCount() { _evalCounter = 0 ; } - - - private: - - Double_t GetPdfParamVal(Int_t index); - Double_t GetPdfParamErr(Int_t index); - void SetPdfParamErr(Int_t index, Double_t value); - void ClearPdfParamAsymErr(Int_t index); - void SetPdfParamErr(Int_t index, Double_t loVal, Double_t hiVal); - - inline Bool_t SetPdfParamVal(const Int_t &index, const Double_t &value) const; - - - virtual double DoEval(const double * x) const; - void updateFloatVec() ; - - virtual double DoDerivative(const double *x, unsigned int icoord) const; - -private: - - mutable Int_t _evalCounter ; - - RooAbsReal *_funct; - RooGaussMinimizer *_context; - - mutable double _maxFCN; - mutable int _numBadNLL; - mutable int _printEvalErrors; - Bool_t _doEvalErrorWall; - - int _nDim; - std::ofstream *_logfile; - bool _verbose; - - RooArgList* _floatParamList; - std::vector _floatParamVec ; - RooArgList* _constParamList; - RooArgList* _initFloatParamList; - RooArgList* _initConstParamList; - -}; - -#endif -#endif diff --git a/roofit/roofitcore/src/RooGaussMinimizer.cxx b/roofit/roofitcore/src/RooGaussMinimizer.cxx deleted file mode 100644 index 00a6c01abe094..0000000000000 --- a/roofit/roofitcore/src/RooGaussMinimizer.cxx +++ /dev/null @@ -1,275 +0,0 @@ -/***************************************************************************** - * Project: RooFit * - * Package: RooFitCore * - * @(#)root/roofitcore:$Id$ - * Authors: * - * WV, Wouter Verkerke, UC Santa Barbara, verkerke@slac.stanford.edu * - * DK, David Kirkby, UC Irvine, dkirkby@uci.edu * - * AL, Alfio Lazzaro, INFN Milan, alfio.lazzaro@mi.infn.it * - * PB, Patrick Bos, NL eScience Center, p.bos@esciencecenter.nl * - * VC, Vince Croft, DIANA / NYU, vincent.croft@cern.ch * - * * - * * - * Redistribution and use in source and binary forms, * - * with or without modification, are permitted according to the terms * - * listed in LICENSE (http://roofit.sourceforge.net/license.txt) * - *****************************************************************************/ - -/** -\file RooGaussMinimizer.cxx -\class RooGaussMinimizer -\ingroup Roofitcore - -RooGaussMinimizer is a test class for extracting the gradient -functionality from Minuit2 using only a Gaussian function for -easy testing. This approach will be generalized in RooGradMinimizer. -This class is based on RooMinimizer. -**/ - -#include "RooFit.h" -#include "Riostream.h" - -#include "TClass.h" - -#include -#include - -#include "TH1.h" -#include "TH2.h" -#include "TMarker.h" -#include "TGraph.h" -#include "Fit/FitConfig.h" -#include "TStopwatch.h" -#include "TDirectory.h" -#include "TMatrixDSym.h" - -#include "RooArgSet.h" -#include "RooArgList.h" -#include "RooAbsReal.h" -#include "RooAbsRealLValue.h" -#include "RooRealVar.h" -#include "RooAbsPdf.h" -#include "RooSentinel.h" -#include "RooMsgService.h" -#include "RooPlot.h" - -#include "RooMinimizer.h" -#include "RooGaussMinimizer.h" -#include "RooGaussMinimizerFcn.h" -#include "RooFitResult.h" - -#include "Math/Minimizer.h" - -#if (__GNUC__==3&&__GNUC_MINOR__==2&&__GNUC_PATCHLEVEL__==3) -char* operator+( streampos&, char* ); -#endif - -using namespace std; - -ROOT::Fit::Fitter *RooGaussMinimizer::_theFitter = 0 ; - - - -//////////////////////////////////////////////////////////////////////////////// -/// Cleanup method called by atexit handler installed by RooSentinel -/// to delete all global heap objects when the program is terminated - -void RooGaussMinimizer::cleanup() -{ - if (_theFitter) { - delete _theFitter ; - _theFitter =0 ; - } -} - - - -//////////////////////////////////////////////////////////////////////////////// -/// Construct MINUIT interface to given function. Function can be anything, -/// but is typically a -log(likelihood) implemented by RooNLLVar or a chi^2 -/// (implemented by RooChi2Var). Other frequent use cases are a RooAddition -/// of a RooNLLVar plus a penalty or constraint term. This class propagates -/// all RooFit information (floating parameters, their values and errors) -/// to MINUIT before each MINUIT call and propagates all MINUIT information -/// back to the RooFit object at the end of each call (updated parameter -/// values, their (asymmetric errors) etc. The default MINUIT error level -/// for HESSE and MINOS error analysis is taken from the defaultErrorLevel() -/// value of the input function. - -RooGaussMinimizer::RooGaussMinimizer(RooAbsReal& function) -{ - RooSentinel::activate() ; - - // Store function reference - _extV = 0 ; - _func = &function ; - _optConst = kFALSE ; - _verbose = kFALSE ; - _profile = kFALSE ; - _profileStart = kFALSE ; - _printLevel = 1 ; - _minimizerType = "Minuit"; // default minimizer - - if (_theFitter) delete _theFitter ; - _theFitter = new ROOT::Fit::Fitter; - // RooMinimizer *theRooMinimizer = dynamic_cast(this); - _fcn = new RooGaussMinimizerFcn(_func,this,_verbose); - _theFitter->Config().SetMinimizer(_minimizerType.c_str()); - // default max number of calls - _theFitter->Config().MinimizerOptions().SetMaxIterations(500*_fcn->NDim()); - _theFitter->Config().MinimizerOptions().SetMaxFunctionCalls(500*_fcn->NDim()); - - // Declare our parameters to MINUIT - _fcn->Synchronize(_theFitter->Config().ParamsSettings(), - _optConst,_verbose) ; -} - - - -//////////////////////////////////////////////////////////////////////////////// -/// Destructor - -RooGaussMinimizer::~RooGaussMinimizer() -{ - if (_extV) { - delete _extV ; - } - - if (_fcn) { - delete _fcn; - } - -} - - - -//////////////////////////////////////////////////////////////////////////////// -/// Change MINUIT strategy to istrat. Accepted codes -/// are 0,1,2 and represent MINUIT strategies for dealing -/// most efficiently with fast FCNs (0), expensive FCNs (2) -/// and 'intermediate' FCNs (1) -Int_t RooGaussMinimizer::migrad() -{ - _fcn->Synchronize(_theFitter->Config().ParamsSettings(), - _optConst,_verbose) ; - // profileStart() ; - RooAbsReal::setEvalErrorLoggingMode(RooAbsReal::CollectErrors) ; - RooAbsReal::clearEvalErrorLog() ; - - _theFitter->Config().SetMinimizer(_minimizerType.c_str(),"migrad"); - bool ret = _theFitter->FitFCN(*_fcn); - _status = ((ret) ? _theFitter->Result().Status() : -1); - - RooAbsReal::setEvalErrorLoggingMode(RooAbsReal::PrintErrors) ; - // profileStop() ; - _fcn->BackProp(_theFitter->Result()); - - saveStatus("MIGRAD",_status) ; - - return _status ; -} - - -//////////////////////////////////////////////////////////////////////////////// -/// Execute HESSE. Changes in parameter values -/// and calculated errors are automatically -/// propagated back the RooRealVars representing -/// the floating parameters in the MINUIT operation - -Int_t RooGaussMinimizer::hesse() -{ - if (_theFitter->GetMinimizer()==0) { - coutW(Minimization) << "RooGaussMinimizer::hesse: Error, run Migrad before Hesse!" - << endl ; - _status = -1; - } - else { - - _fcn->Synchronize(_theFitter->Config().ParamsSettings(), - _optConst,_verbose) ; - // profileStart() ; - RooAbsReal::setEvalErrorLoggingMode(RooAbsReal::CollectErrors) ; - RooAbsReal::clearEvalErrorLog() ; - - _theFitter->Config().SetMinimizer(_minimizerType.c_str()); - bool ret = _theFitter->CalculateHessErrors(); - _status = ((ret) ? _theFitter->Result().Status() : -1); - - RooAbsReal::setEvalErrorLoggingMode(RooAbsReal::PrintErrors) ; - // profileStop() ; - _fcn->BackProp(_theFitter->Result()); - - saveStatus("HESSE",_status) ; - - } - - return _status ; - -} - -//////////////////////////////////////////////////////////////////////////////// -/// Execute MINOS. Changes in parameter values -/// and calculated errors are automatically -/// propagated back the RooRealVars representing -/// the floating parameters in the MINUIT operation - -Int_t RooGaussMinimizer::minos() -{ - if (_theFitter->GetMinimizer()==0) { - coutW(Minimization) << "RooGaussMinimizer::minos: Error, run Migrad before Minos!" - << endl ; - _status = -1; - } - else { - - _fcn->Synchronize(_theFitter->Config().ParamsSettings(), - _optConst,_verbose) ; - // profileStart() ; - RooAbsReal::setEvalErrorLoggingMode(RooAbsReal::CollectErrors) ; - RooAbsReal::clearEvalErrorLog() ; - - _theFitter->Config().SetMinimizer(_minimizerType.c_str()); - bool ret = _theFitter->CalculateMinosErrors(); - _status = ((ret) ? _theFitter->Result().Status() : -1); - - RooAbsReal::setEvalErrorLoggingMode(RooAbsReal::PrintErrors) ; - // profileStop() ; - _fcn->BackProp(_theFitter->Result()); - - saveStatus("MINOS",_status) ; - - } - - return _status ; - -} - - -//////////////////////////////////////////////////////////////////////////////// -/// If flag is true, perform constant term optimization on -/// function being minimized. - -void RooGaussMinimizer::optimizeConst(Int_t flag) -{ - RooAbsReal::setEvalErrorLoggingMode(RooAbsReal::CollectErrors) ; - - if (_optConst && !flag){ - if (_printLevel>-1) coutI(Minimization) << "RooGaussMinimizer::optimizeConst: deactivating const optimization" << endl ; - _func->constOptimizeTestStatistic(RooAbsArg::DeActivate) ; - _optConst = flag ; - } else if (!_optConst && flag) { - if (_printLevel>-1) coutI(Minimization) << "RooGaussMinimizer::optimizeConst: activating const optimization" << endl ; - _func->constOptimizeTestStatistic(RooAbsArg::Activate,flag>1) ; - _optConst = flag ; - } else if (_optConst && flag) { - if (_printLevel>-1) coutI(Minimization) << "RooGaussMinimizer::optimizeConst: const optimization already active" << endl ; - } else { - if (_printLevel>-1) coutI(Minimization) << "RooGaussMinimizer::optimizeConst: const optimization wasn't active" << endl ; - } - - RooAbsReal::setEvalErrorLoggingMode(RooAbsReal::PrintErrors) ; - -} - - - diff --git a/roofit/roofitcore/src/RooGaussMinimizerFcn.cxx b/roofit/roofitcore/src/RooGaussMinimizerFcn.cxx deleted file mode 100644 index 09a48fd81d077..0000000000000 --- a/roofit/roofitcore/src/RooGaussMinimizerFcn.cxx +++ /dev/null @@ -1,627 +0,0 @@ -/***************************************************************************** - * Project: RooFit * - * Package: RooFitCore * - * @(#)root/roofitcore:$Id$ - * Authors: * - * AL, Alfio Lazzaro, INFN Milan, alfio.lazzaro@mi.infn.it * - * PB, Patrick Bos, NL eScience Center, p.bos@esciencecenter.nl * - * VC, Vince Croft, DIANA / NYU, vincent.croft@cern.ch * - * * - * * - * Redistribution and use in source and binary forms, * - * with or without modification, are permitted according to the terms * - * listed in LICENSE (http://roofit.sourceforge.net/license.txt) * - *****************************************************************************/ - -#ifndef __ROOFIT_NOROOMINIMIZER - -////////////////////////////////////////////////////////////////////////////// -// -// RooGaussMinimizerFcn is am interface class to the ROOT::Math function -// for minization. See RooGaussMinimizer.cxx for more information. -// - -#include - -#include "RooFit.h" -#include "RooMinimizerFcn.h" - -#include "Riostream.h" - -#include "TIterator.h" -#include "TClass.h" - -#include "RooAbsArg.h" -#include "RooAbsPdf.h" -#include "RooArgSet.h" -#include "RooRealVar.h" -#include "RooAbsRealLValue.h" -#include "RooMsgService.h" - -#include "RooMinimizer.h" -#include "RooGaussMinimizer.h" - -using namespace std; - -RooGaussMinimizerFcn::RooGaussMinimizerFcn(RooAbsReal *funct, RooGaussMinimizer* context, - bool verbose) : - _funct(funct), _context(context), - // Reset the *largest* negative log-likelihood value we have seen so far - _maxFCN(-1e30), _numBadNLL(0), - _printEvalErrors(10), _doEvalErrorWall(kTRUE), - _nDim(0), _logfile(0), - _verbose(verbose) -{ - - _evalCounter = 0 ; - - // Examine parameter list - RooArgSet* paramSet = _funct->getParameters(RooArgSet()); - RooArgList paramList(*paramSet); - delete paramSet; - - _floatParamList = (RooArgList*) paramList.selectByAttrib("Constant",kFALSE); - if (_floatParamList->getSize()>1) { - _floatParamList->sort(); - } - _floatParamList->setName("floatParamList"); - - _constParamList = (RooArgList*) paramList.selectByAttrib("Constant",kTRUE); - if (_constParamList->getSize()>1) { - _constParamList->sort(); - } - _constParamList->setName("constParamList"); - - // Remove all non-RooRealVar parameters from list (MINUIT cannot handle them) - TIterator* pIter = _floatParamList->createIterator(); - RooAbsArg* arg; - while ((arg=(RooAbsArg*)pIter->Next())) { - if (!arg->IsA()->InheritsFrom(RooAbsRealLValue::Class())) { - oocoutW(_context,Minimization) << "RooGaussMinimizerFcn::RooGaussMinimizerFcn: removing parameter " - << arg->GetName() - << " from list because it is not of type RooRealVar" << endl; - _floatParamList->remove(*arg); - } - } - delete pIter; - - _nDim = _floatParamList->getSize(); - - updateFloatVec() ; - - // Save snapshot of initial lists - _initFloatParamList = (RooArgList*) _floatParamList->snapshot(kFALSE) ; - _initConstParamList = (RooArgList*) _constParamList->snapshot(kFALSE) ; - -} - - - -RooGaussMinimizerFcn::RooGaussMinimizerFcn(const RooGaussMinimizerFcn& other) : ROOT::Math::IMultiGradFunction(other), - _evalCounter(other._evalCounter), - _funct(other._funct), - _context(other._context), - _maxFCN(other._maxFCN), - _numBadNLL(other._numBadNLL), - _printEvalErrors(other._printEvalErrors), - _doEvalErrorWall(other._doEvalErrorWall), - _nDim(other._nDim), - _logfile(other._logfile), - _verbose(other._verbose), - _floatParamVec(other._floatParamVec) -{ - _floatParamList = new RooArgList(*other._floatParamList) ; - _constParamList = new RooArgList(*other._constParamList) ; - _initFloatParamList = (RooArgList*) other._initFloatParamList->snapshot(kFALSE) ; - _initConstParamList = (RooArgList*) other._initConstParamList->snapshot(kFALSE) ; -} - - -RooGaussMinimizerFcn::~RooGaussMinimizerFcn() -{ - delete _floatParamList; - delete _initFloatParamList; - delete _constParamList; - delete _initConstParamList; -} - - -ROOT::Math::IMultiGradFunction* RooGaussMinimizerFcn::Clone() const -{ - return new RooGaussMinimizerFcn(*this) ; -} - - -Bool_t RooGaussMinimizerFcn::Synchronize(std::vector& parameters, - Bool_t optConst, Bool_t verbose) -{ - - // Internal function to synchronize TMinimizer with current - // information in RooAbsReal function parameters - - Bool_t constValChange(kFALSE) ; - Bool_t constStatChange(kFALSE) ; - - Int_t index(0) ; - - // Handle eventual migrations from constParamList -> floatParamList - for(index= 0; index < _constParamList->getSize() ; index++) { - - RooRealVar *par= dynamic_cast(_constParamList->at(index)) ; - if (!par) continue ; - - RooRealVar *oldpar= dynamic_cast(_initConstParamList->at(index)) ; - if (!oldpar) continue ; - - // Test if constness changed - if (!par->isConstant()) { - - // Remove from constList, add to floatList - _constParamList->remove(*par) ; - _floatParamList->add(*par) ; - _initFloatParamList->addClone(*oldpar) ; - _initConstParamList->remove(*oldpar) ; - constStatChange=kTRUE ; - _nDim++ ; - - if (verbose) { - oocoutI(_context,Minimization) << "RooGaussMinimizerFcn::synchronize: parameter " - << par->GetName() << " is now floating." << endl ; - } - } - - // Test if value changed - if (par->getVal()!= oldpar->getVal()) { - constValChange=kTRUE ; - if (verbose) { - oocoutI(_context,Minimization) << "RooGaussMinimizerFcn::synchronize: value of constant parameter " - << par->GetName() - << " changed from " << oldpar->getVal() << " to " - << par->getVal() << endl ; - } - } - - } - - // Update reference list - *_initConstParamList = *_constParamList ; - - // Synchronize MINUIT with function state - // Handle floatParamList - for(index= 0; index < _floatParamList->getSize(); index++) { - RooRealVar *par= dynamic_cast(_floatParamList->at(index)) ; - - if (!par) continue ; - - Double_t pstep(0) ; - Double_t pmin(0) ; - Double_t pmax(0) ; - - if(!par->isConstant()) { - - // Verify that floating parameter is indeed of type RooRealVar - if (!par->IsA()->InheritsFrom(RooRealVar::Class())) { - oocoutW(_context,Minimization) << "RooGaussMinimizerFcn::fit: Error, non-constant parameter " - << par->GetName() - << " is not of type RooRealVar, skipping" << endl ; - _floatParamList->remove(*par); - index--; - _nDim--; - continue ; - } - - // Set the limits, if not infinite - if (par->hasMin() ) - pmin = par->getMin(); - if (par->hasMax() ) - pmax = par->getMax(); - - // Calculate step size - pstep = par->getError(); - if(pstep <= 0) { - // Floating parameter without error estitimate - if (par->hasMin() && par->hasMax()) { - pstep= 0.1*(pmax-pmin); - - // Trim default choice of error if within 2 sigma of limit - if (pmax - par->getVal() < 2*pstep) { - pstep = (pmax - par->getVal())/2 ; - } else if (par->getVal() - pmin < 2*pstep) { - pstep = (par->getVal() - pmin )/2 ; - } - - // If trimming results in zero error, restore default - if (pstep==0) { - pstep= 0.1*(pmax-pmin); - } - - } else { - pstep=1 ; - } - if(verbose) { - oocoutW(_context,Minimization) << "RooGaussMinimizerFcn::synchronize: WARNING: no initial error estimate available for " - << par->GetName() << ": using " << pstep << endl; - } - } - } else { - pmin = par->getVal() ; - pmax = par->getVal() ; - } - - // new parameter - if (index>=Int_t(parameters.size())) { - - if (par->hasMin() && par->hasMax()) { - parameters.push_back(ROOT::Fit::ParameterSettings(par->GetName(), - par->getVal(), - pstep, - pmin,pmax)); - } - else { - parameters.push_back(ROOT::Fit::ParameterSettings(par->GetName(), - par->getVal(), - pstep)); - if (par->hasMin() ) - parameters.back().SetLowerLimit(pmin); - else if (par->hasMax() ) - parameters.back().SetUpperLimit(pmax); - } - - continue; - - } - - Bool_t oldFixed = parameters[index].IsFixed(); - Double_t oldVar = parameters[index].Value(); - Double_t oldVerr = parameters[index].StepSize(); - Double_t oldVlo = parameters[index].LowerLimit(); - Double_t oldVhi = parameters[index].UpperLimit(); - - if (par->isConstant() && !oldFixed) { - - // Parameter changes floating -> constant : update only value if necessary - if (oldVar!=par->getVal()) { - parameters[index].SetValue(par->getVal()); - if (verbose) { - oocoutI(_context,Minimization) << "RooGaussMinimizerFcn::synchronize: value of parameter " - << par->GetName() << " changed from " << oldVar - << " to " << par->getVal() << endl ; - } - } - parameters[index].Fix(); - constStatChange=kTRUE ; - if (verbose) { - oocoutI(_context,Minimization) << "RooGaussMinimizerFcn::synchronize: parameter " - << par->GetName() << " is now fixed." << endl ; - } - - } else if (par->isConstant() && oldFixed) { - - // Parameter changes constant -> constant : update only value if necessary - if (oldVar!=par->getVal()) { - parameters[index].SetValue(par->getVal()); - constValChange=kTRUE ; - - if (verbose) { - oocoutI(_context,Minimization) << "RooGaussMinimizerFcn::synchronize: value of fixed parameter " - << par->GetName() << " changed from " << oldVar - << " to " << par->getVal() << endl ; - } - } - - } else { - // Parameter changes constant -> floating - if (!par->isConstant() && oldFixed) { - parameters[index].Release(); - constStatChange=kTRUE ; - - if (verbose) { - oocoutI(_context,Minimization) << "RooGaussMinimizerFcn::synchronize: parameter " - << par->GetName() << " is now floating." << endl ; - } - } - - // Parameter changes constant -> floating : update all if necessary - if (oldVar!=par->getVal() || oldVlo!=pmin || oldVhi != pmax || oldVerr!=pstep) { - parameters[index].SetValue(par->getVal()); - parameters[index].SetStepSize(pstep); - if (par->hasMin() && par->hasMax() ) - parameters[index].SetLimits(pmin,pmax); - else if (par->hasMin() ) - parameters[index].SetLowerLimit(pmin); - else if (par->hasMax() ) - parameters[index].SetUpperLimit(pmax); - } - - // Inform user about changes in verbose mode - if (verbose) { - // if ierr<0, par was moved from the const list and a message was already printed - - if (oldVar!=par->getVal()) { - oocoutI(_context,Minimization) << "RooGaussMinimizerFcn::synchronize: value of parameter " - << par->GetName() << " changed from " << oldVar << " to " - << par->getVal() << endl ; - } - if (oldVlo!=pmin || oldVhi!=pmax) { - oocoutI(_context,Minimization) << "RooGaussMinimizerFcn::synchronize: limits of parameter " - << par->GetName() << " changed from [" << oldVlo << "," << oldVhi - << "] to [" << pmin << "," << pmax << "]" << endl ; - } - - // If oldVerr=0, then parameter was previously fixed - if (oldVerr!=pstep && oldVerr!=0) { - oocoutI(_context,Minimization) << "RooGaussMinimizerFcn::synchronize: error/step size of parameter " - << par->GetName() << " changed from " << oldVerr << " to " << pstep << endl ; - } - } - } - } - - if (optConst) { - if (constStatChange) { - - RooAbsReal::setEvalErrorLoggingMode(RooAbsReal::CollectErrors) ; - - oocoutI(_context,Minimization) << "RooGaussMinimizerFcn::synchronize: set of constant parameters changed, rerunning const optimizer" << endl ; - _funct->constOptimizeTestStatistic(RooAbsArg::ConfigChange) ; - } else if (constValChange) { - oocoutI(_context,Minimization) << "RooGaussMinimizerFcn::synchronize: constant parameter values changed, rerunning const optimizer" << endl ; - _funct->constOptimizeTestStatistic(RooAbsArg::ValueChange) ; - } - - RooAbsReal::setEvalErrorLoggingMode(RooAbsReal::PrintErrors) ; - - } - - updateFloatVec() ; - - return 0 ; - -} - -Double_t RooGaussMinimizerFcn::GetPdfParamVal(Int_t index) -{ - // Access PDF parameter value by ordinal index (needed by MINUIT) - - return ((RooRealVar*)_floatParamList->at(index))->getVal() ; -} - -Double_t RooGaussMinimizerFcn::GetPdfParamErr(Int_t index) -{ - // Access PDF parameter error by ordinal index (needed by MINUIT) - return ((RooRealVar*)_floatParamList->at(index))->getError() ; -} - - -void RooGaussMinimizerFcn::SetPdfParamErr(Int_t index, Double_t value) -{ - // Modify PDF parameter error by ordinal index (needed by MINUIT) - - ((RooRealVar*)_floatParamList->at(index))->setError(value) ; -} - - - -void RooGaussMinimizerFcn::ClearPdfParamAsymErr(Int_t index) -{ - // Modify PDF parameter error by ordinal index (needed by MINUIT) - - ((RooRealVar*)_floatParamList->at(index))->removeAsymError() ; -} - - -void RooGaussMinimizerFcn::SetPdfParamErr(Int_t index, Double_t loVal, Double_t hiVal) -{ - // Modify PDF parameter error by ordinal index (needed by MINUIT) - - ((RooRealVar*)_floatParamList->at(index))->setAsymError(loVal,hiVal) ; -} - - -void RooGaussMinimizerFcn::BackProp(const ROOT::Fit::FitResult &results) -{ - // Transfer MINUIT fit results back into RooFit objects - - for (Int_t index= 0; index < _nDim; index++) { - Double_t value = results.Value(index); - SetPdfParamVal(index, value); - - // Set the parabolic error - Double_t err = results.Error(index); - SetPdfParamErr(index, err); - - Double_t eminus = results.LowerError(index); - Double_t eplus = results.UpperError(index); - - if(eplus > 0 || eminus < 0) { - // Store the asymmetric error, if it is available - SetPdfParamErr(index, eminus,eplus); - } else { - // Clear the asymmetric error - ClearPdfParamAsymErr(index) ; - } - } - -} - -Bool_t RooGaussMinimizerFcn::SetLogFile(const char* inLogfile) -{ - // Change the file name for logging of a RooGaussMinimizer of all MINUIT steppings - // through the parameter space. If inLogfile is null, the current log file - // is closed and logging is stopped. - - if (_logfile) { - oocoutI(_context,Minimization) << "RooGaussMinimizerFcn::setLogFile: closing previous log file" << endl ; - _logfile->close() ; - delete _logfile ; - _logfile = 0 ; - } - _logfile = new ofstream(inLogfile) ; - if (!_logfile->good()) { - oocoutI(_context,Minimization) << "RooGaussMinimizerFcn::setLogFile: cannot open file " << inLogfile << endl ; - _logfile->close() ; - delete _logfile ; - _logfile= 0; - } - - return kFALSE ; - -} - - -void RooGaussMinimizerFcn::ApplyCovarianceMatrix(TMatrixDSym& V) -{ - // Apply results of given external covariance matrix. i.e. propagate its errors - // to all RRV parameter representations and give this matrix instead of the - // HESSE matrix at the next save() call - - for (Int_t i=0 ; i<_nDim ; i++) { - // Skip fixed parameters - if (_floatParamList->at(i)->isConstant()) { - continue ; - } - SetPdfParamErr(i, sqrt(V(i,i))) ; - } - -} - - -Bool_t RooGaussMinimizerFcn::SetPdfParamVal(const Int_t &index, const Double_t &value) const -{ - //RooRealVar* par = (RooRealVar*)_floatParamList->at(index); - RooRealVar* par = (RooRealVar*)_floatParamVec[index] ; - - if (par->getVal()!=value) { - if (_verbose) cout << par->GetName() << "=" << value << ", " ; - - par->setVal(value); - return kTRUE; - } - - return kFALSE; -} - - - -//////////////////////////////////////////////////////////////////////////////// - -void RooGaussMinimizerFcn::updateFloatVec() -{ - _floatParamVec.clear() ; - RooFIter iter = _floatParamList->fwdIterator() ; - RooAbsArg* arg ; - _floatParamVec = std::vector(_floatParamList->getSize()) ; - Int_t i(0) ; - while((arg=iter.next())) { - _floatParamVec[i++] = arg ; - } -} - - - -double RooGaussMinimizerFcn::DoEval(const double *x) const -{ - - // Set the parameter values for this iteration - for (int index = 0; index < _nDim; index++) { - if (_logfile && _logfile->is_open()) (*_logfile) << x[index] << " " ; - SetPdfParamVal(index,x[index]); - } - - // Calculate the function for these parameters - RooAbsReal::setHideOffset(kFALSE) ; - double fvalue = _funct->getVal(); - RooAbsReal::setHideOffset(kTRUE) ; - - if (!std::isfinite(fvalue) || RooAbsReal::numEvalErrors() > 0 || fvalue > 1e30) { - - if (_printEvalErrors>=0) { - - if (_doEvalErrorWall) { - oocoutW(_context,Minimization) << "RooGaussMinimizerFcn: Minimized function has error status." << endl - << "Returning maximum FCN so far (" << _maxFCN - << ") to force MIGRAD to back out of this region. Error log follows" << endl ; - } else { - oocoutW(_context,Minimization) << "RooGaussMinimizerFcn: Minimized function has error status but is ignored" << endl ; - } - - TIterator* iter = _floatParamList->createIterator() ; - RooRealVar* var ; - Bool_t first(kTRUE) ; - ooccoutW(_context,Minimization) << "Parameter values: " ; - while((var=(RooRealVar*)iter->Next())) { - if (first) { first = kFALSE ; } else ooccoutW(_context,Minimization) << ", " ; - ooccoutW(_context,Minimization) << var->GetName() << "=" << var->getVal() ; - } - delete iter ; - ooccoutW(_context,Minimization) << endl ; - - RooAbsReal::printEvalErrors(ooccoutW(_context,Minimization),_printEvalErrors) ; - ooccoutW(_context,Minimization) << endl ; - } - - if (_doEvalErrorWall) { - fvalue = _maxFCN+1 ; - } - - RooAbsReal::clearEvalErrorLog() ; - _numBadNLL++ ; - } else if (fvalue>_maxFCN) { - _maxFCN = fvalue ; - } - - // Optional logging - if (_logfile && _logfile->is_open()) - (*_logfile) << setprecision(15) << fvalue << setprecision(4) << endl; - if (_verbose) { - cout << "\nprevFCN" << (_funct->isOffsetting()?"-offset":"") << " = " << setprecision(10) - << fvalue << setprecision(4) << " " ; - cout.flush() ; - } - - _evalCounter++ ; - cout<< "func eval "<is_open()) (*_logfile) << x[index] << " " ; - SetPdfParamVal(index,x[index]); - } - - // Calculate the function for these parameters - RooAbsReal::setHideOffset(kFALSE) ; // EGP TODO: check whether this is necessary - - ///// EGP TODO: REPLACE BELOW DERIVATIVE CALCULATION WITH THE FANCY MINUIT TYPE STUFF - double dx = max(1e-5 * x[icoord], 1e-8); - double fvalue_0 = _funct->getVal(); - - if (_logfile) (*_logfile) << x[icoord] << " " ; - SetPdfParamVal(icoord,x[icoord] + dx); - - double fvalue_dx = _funct->getVal(); - - double derivative_i_value = (fvalue_dx - fvalue_0) / dx; //######## OI THIS IS WHERE WE COMPUTE THE GRADIENT#### - ///// EGP TODO: REPLACE ABOVE DERIVATIVE CALCULATION WITH THE FANCY MINUIT TYPE STUFF - - RooAbsReal::setHideOffset(kTRUE) ; // EGP TODO: check whether this is necessary - - // EGP TODO: decide whether to do error handling and logging, like in DoEval - - // EGP TOOO: update this when changing the derivative algorithm - // Count the function calls necessary for this derivative and use that. - // Except when the derivative itself calls DoEval where the counter is already updated! - _evalCounter += 2; - - cout << "grad value " << derivative_i_value << endl; - return derivative_i_value; -} - - -#endif - diff --git a/roofit/roofitcore/src/RooMinimizerFcn.cxx b/roofit/roofitcore/src/RooMinimizerFcn.cxx index 072d1618dd5b0..f7f73abbab7d6 100644 --- a/roofit/roofitcore/src/RooMinimizerFcn.cxx +++ b/roofit/roofitcore/src/RooMinimizerFcn.cxx @@ -28,7 +28,6 @@ #include "RooAbsRealLValue.h" #include "RooMsgService.h" #include "RooMinimizer.h" -#include "RooGaussMinimizer.h" #include "RooNaNPacker.h" #include "TClass.h" From 6b34faaa44a5aa8f34c7fcd0122575ad53d44780 Mon Sep 17 00:00:00 2001 From: "E. G. Patrick Bos" Date: Thu, 24 Jun 2021 17:33:39 +0200 Subject: [PATCH 07/10] clean RooAbs(Opt)TestStatistic RooTaskSpec dependencies --- .../roofitcore/inc/RooAbsOptTestStatistic.h | 8 ++++---- roofit/roofitcore/inc/RooAbsTestStatistic.h | 14 +------------ .../roofitcore/src/RooAbsOptTestStatistic.cxx | 20 +++++++++---------- 3 files changed, 15 insertions(+), 27 deletions(-) diff --git a/roofit/roofitcore/inc/RooAbsOptTestStatistic.h b/roofit/roofitcore/inc/RooAbsOptTestStatistic.h index 7950d16c90dad..852dc3f17339e 100644 --- a/roofit/roofitcore/inc/RooAbsOptTestStatistic.h +++ b/roofit/roofitcore/inc/RooAbsOptTestStatistic.h @@ -38,11 +38,11 @@ class RooAbsOptTestStatistic : public RooAbsTestStatistic { virtual Double_t combinedValue(RooAbsReal** gofArray, Int_t nVal) const ; - virtual RooAbsReal& function() { return *_funcClone ; } - virtual const RooAbsReal& function() const { return *_funcClone ; } + RooAbsReal& function() { return *_funcClone ; } + const RooAbsReal& function() const { return *_funcClone ; } - virtual RooAbsData& data() ; - virtual const RooAbsData& data() const ; + RooAbsData& data() ; + const RooAbsData& data() const ; virtual const char* cacheUniqueSuffix() const { return Form("_%lx", (ULong_t)_dataClone) ; } diff --git a/roofit/roofitcore/inc/RooAbsTestStatistic.h b/roofit/roofitcore/inc/RooAbsTestStatistic.h index 3cc4cdae5405f..b2537ed6e583b 100644 --- a/roofit/roofitcore/inc/RooAbsTestStatistic.h +++ b/roofit/roofitcore/inc/RooAbsTestStatistic.h @@ -37,8 +37,7 @@ typedef RooAbsData* pRooAbsData ; typedef RooRealMPFE* pRooRealMPFE ; class RooAbsTestStatistic : public RooAbsReal { - friend class RooRealMPFE; - friend class RooWrapperL; + friend class RooRealMPFE; public: struct Configuration { @@ -73,21 +72,12 @@ class RooAbsTestStatistic : public RooAbsReal { } Bool_t setData(RooAbsData& data, Bool_t cloneData=kTRUE) ; - //vinces accessors - Int_t numSimultaneous() const { return _nGof ; } - RooAbsTestStatistic** simComponents() { return _gofArray ; } void enableOffsetting(Bool_t flag) ; Bool_t isOffsetting() const { return _doOffset ; } virtual Double_t offset() const { return _offset.Sum() ; } virtual Double_t offsetCarry() const { return _offset.Carry(); } - virtual RooAbsReal& function() { return *_func ; } - virtual const RooAbsReal& function() const { return *_func ; } - - virtual RooAbsData& data() { return *_data ; } - virtual const RooAbsData& data() const { return *_data ; } - protected: virtual void printCompactTreeHook(std::ostream& os, const char* indent="") ; @@ -170,8 +160,6 @@ class RooAbsTestStatistic : public RooAbsReal { mutable ROOT::Math::KahanSum _offset = 0.0; //! Offset as KahanSum to avoid loss of precision mutable Double_t _evalCarry = 0.0; //! carry of Kahan sum in evaluatePartition -private: - ClassDef(RooAbsTestStatistic,3) // Abstract base class for real-valued test statistics }; diff --git a/roofit/roofitcore/src/RooAbsOptTestStatistic.cxx b/roofit/roofitcore/src/RooAbsOptTestStatistic.cxx index 7a2cb43e060e6..42ae9f78af5c2 100644 --- a/roofit/roofitcore/src/RooAbsOptTestStatistic.cxx +++ b/roofit/roofitcore/src/RooAbsOptTestStatistic.cxx @@ -770,33 +770,33 @@ Bool_t RooAbsOptTestStatistic::setDataSlave(RooAbsData& indata, Bool_t cloneData //////////////////////////////////////////////////////////////////////////////// -RooAbsData& RooAbsOptTestStatistic::data() -{ +RooAbsData& RooAbsOptTestStatistic::data() +{ if (_sealed) { Bool_t notice = (sealNotice() && strlen(sealNotice())) ; - coutW(ObjectHandling) << "RooAbsOptTestStatistic::data(" << GetName() - << ") WARNING: object sealed by creator - access to data is not permitted: " + coutW(ObjectHandling) << "RooAbsOptTestStatistic::data(" << GetName() + << ") WARNING: object sealed by creator - access to data is not permitted: " << (notice?sealNotice():"") << endl ; static RooDataSet dummy ("dummy","dummy",RooArgSet()) ; return dummy ; } - return *_dataClone ; + return *_dataClone ; } //////////////////////////////////////////////////////////////////////////////// -const RooAbsData& RooAbsOptTestStatistic::data() const -{ +const RooAbsData& RooAbsOptTestStatistic::data() const +{ if (_sealed) { Bool_t notice = (sealNotice() && strlen(sealNotice())) ; - coutW(ObjectHandling) << "RooAbsOptTestStatistic::data(" << GetName() - << ") WARNING: object sealed by creator - access to data is not permitted: " + coutW(ObjectHandling) << "RooAbsOptTestStatistic::data(" << GetName() + << ") WARNING: object sealed by creator - access to data is not permitted: " << (notice?sealNotice():"") << endl ; static RooDataSet dummy ("dummy","dummy",RooArgSet()) ; return dummy ; } - return *_dataClone ; + return *_dataClone ; } From 2e3d43b0f1cd3f8fecb92248963a714a366ee6d1 Mon Sep 17 00:00:00 2001 From: "E. G. Patrick Bos" Date: Thu, 24 Jun 2021 20:20:48 +0200 Subject: [PATCH 08/10] clean RooAbsPdf timing left-overs --- roofit/roofitcore/inc/RooAbsPdf.h | 6 +----- roofit/roofitcore/src/RooAbsPdf.cxx | 10 ---------- 2 files changed, 1 insertion(+), 15 deletions(-) diff --git a/roofit/roofitcore/inc/RooAbsPdf.h b/roofit/roofitcore/inc/RooAbsPdf.h index d7fa97e9a68ac..c7655eb6bf00a 100644 --- a/roofit/roofitcore/inc/RooAbsPdf.h +++ b/roofit/roofitcore/inc/RooAbsPdf.h @@ -363,11 +363,7 @@ class RooAbsPdf : public RooAbsReal { template int calculateSumW2CorrectedCovMatrix(Minimizer& minimizer, RooAbsReal const& nll) const; - -public: - Bool_t num_int_timing_flag() const; - void set_num_int_timing_flag(Bool_t flag); - + ClassDef(RooAbsPdf,4) // Abstract PDF with normalization support }; diff --git a/roofit/roofitcore/src/RooAbsPdf.cxx b/roofit/roofitcore/src/RooAbsPdf.cxx index 90674bc08c925..825b4d5a17b69 100644 --- a/roofit/roofitcore/src/RooAbsPdf.cxx +++ b/roofit/roofitcore/src/RooAbsPdf.cxx @@ -3718,14 +3718,4 @@ void RooAbsPdf::setNormRangeOverride(const char* rangeName) _normMgr.sterilize() ; _norm = 0 ; } -} - -//////////////////////////////////////////////////////////////////////////////// - -Bool_t RooAbsPdf::num_int_timing_flag() const { - return getAttribute("num_int_timing_on"); -} - -void RooAbsPdf::set_num_int_timing_flag(Bool_t flag) { - setAttribute("num_int_timing_on", flag); } \ No newline at end of file From d805a2cd6adb7ffeee0ea6842411995545f29f21 Mon Sep 17 00:00:00 2001 From: "E. G. Patrick Bos" Date: Mon, 28 Jun 2021 11:14:44 +0200 Subject: [PATCH 09/10] clean up more left-over timing functionality --- roofit/roofitcore/inc/RooRealIntegral.h | 6 ------ roofit/roofitcore/src/RooRealIntegral.cxx | 19 ++----------------- 2 files changed, 2 insertions(+), 23 deletions(-) diff --git a/roofit/roofitcore/inc/RooRealIntegral.h b/roofit/roofitcore/inc/RooRealIntegral.h index d1b8890bfd6f2..f26769dcb4dff 100644 --- a/roofit/roofitcore/inc/RooRealIntegral.h +++ b/roofit/roofitcore/inc/RooRealIntegral.h @@ -139,12 +139,6 @@ class RooRealIntegral : public RooAbsReal { Bool_t _cacheNum ; // Cache integral if numeric static Int_t _cacheAllNDim ; //! Cache all integrals with given numeric dimension -public: - void setNumIntTiming(Bool_t flag); - void activateTimingNumInts(); -private: - mutable Bool_t _timeNumInt ; //! do not persist - ClassDef(RooRealIntegral,3) // Real-valued function representing an integral over a RooAbsReal object }; diff --git a/roofit/roofitcore/src/RooRealIntegral.cxx b/roofit/roofitcore/src/RooRealIntegral.cxx index d07cfa1be1cba..58dec5a0fc192 100644 --- a/roofit/roofitcore/src/RooRealIntegral.cxx +++ b/roofit/roofitcore/src/RooRealIntegral.cxx @@ -50,8 +50,6 @@ integration is performed in the various implementations of the RooAbsIntegrator #include "RooHelpers.h" #include "ROOT/RMakeUnique.hxx" -// getpid and getppid: -#include "unistd.h" #include "TClass.h" @@ -79,8 +77,7 @@ RooRealIntegral::RooRealIntegral() : _numIntegrand(0), _rangeName(0), _params(0), - _cacheNum(kFALSE), - _timeNumInt(kFALSE) + _cacheNum(kFALSE) { TRACE_CREATE } @@ -721,8 +718,7 @@ RooRealIntegral::RooRealIntegral(const RooRealIntegral& other, const char* name) _numIntegrand(0), _rangeName(other._rangeName), _params(0), - _cacheNum(kFALSE), - _timeNumInt(other._timeNumInt) + _cacheNum(kFALSE) { _funcNormSet = other._funcNormSet ? (RooArgSet*)other._funcNormSet->snapshot(kFALSE) : 0 ; @@ -736,9 +732,6 @@ RooRealIntegral::RooRealIntegral(const RooRealIntegral& other, const char* name) other._intList.snapshot(_saveInt) ; other._sumList.snapshot(_saveSum) ; - // activate timing on numerical integrals - activateTimingNumInts(); - TRACE_CREATE } @@ -1153,11 +1146,3 @@ Int_t RooRealIntegral::getCacheAllNumeric() { return _cacheAllNDim ; } - -void RooRealIntegral::setNumIntTiming(Bool_t flag) { - Int_t numIntDim = this->numIntRealVars().getSize(); - // .. and activate timing if numeric integration occurs - if (numIntDim > 0) { - _timeNumInt = flag; - } -} \ No newline at end of file From eee74bc90c14bb42a78a91bd95c1f368f495bf8b Mon Sep 17 00:00:00 2001 From: "E. G. Patrick Bos" Date: Mon, 28 Jun 2021 12:45:01 +0200 Subject: [PATCH 10/10] Remove unused parameter transformation functions from NumericalDerivatorMinuit2 --- .../inc/NumericalDerivatorMinuit2.h | 2 -- .../src/NumericalDerivatorMinuit2.cxx | 32 ------------------- 2 files changed, 34 deletions(-) diff --git a/roofit/roofitcore/inc/NumericalDerivatorMinuit2.h b/roofit/roofitcore/inc/NumericalDerivatorMinuit2.h index b9e28256510b1..495e9b76cbf4e 100644 --- a/roofit/roofitcore/inc/NumericalDerivatorMinuit2.h +++ b/roofit/roofitcore/inc/NumericalDerivatorMinuit2.h @@ -72,8 +72,6 @@ class NumericalDerivatorMinuit2 { double Int2ext(const ROOT::Fit::ParameterSettings ¶meter, double val) const; double Ext2int(const ROOT::Fit::ParameterSettings ¶meter, double val) const; double DInt2Ext(const ROOT::Fit::ParameterSettings ¶meter, double val) const; - double D2Int2Ext(const ROOT::Fit::ParameterSettings ¶meter, double val) const; - double GStepInt2Ext(const ROOT::Fit::ParameterSettings ¶meter, double val) const; void SetInitialGradient(const ROOT::Math::IBaseFunctionMultiDim *function, const std::vector ¶meters, diff --git a/roofit/roofitcore/src/NumericalDerivatorMinuit2.cxx b/roofit/roofitcore/src/NumericalDerivatorMinuit2.cxx index d4218810acbd3..ed2f67d921074 100644 --- a/roofit/roofitcore/src/NumericalDerivatorMinuit2.cxx +++ b/roofit/roofitcore/src/NumericalDerivatorMinuit2.cxx @@ -292,38 +292,6 @@ double NumericalDerivatorMinuit2::DInt2Ext(const ROOT::Fit::ParameterSettings &p return dd; } -double NumericalDerivatorMinuit2::D2Int2Ext(const ROOT::Fit::ParameterSettings ¶meter, double val) const -{ - double dd = 1.; - if (parameter.IsBound()) { - if (parameter.IsDoubleBound()) { - dd = fDoubleLimTrafo.D2Int2Ext(val, parameter.UpperLimit(), parameter.LowerLimit()); - } else if (parameter.HasUpperLimit() && !parameter.HasLowerLimit()) { - dd = fUpperLimTrafo.D2Int2Ext(val, parameter.UpperLimit()); - } else { - dd = fLowerLimTrafo.D2Int2Ext(val, parameter.LowerLimit()); - } - } - - return dd; -} - -double NumericalDerivatorMinuit2::GStepInt2Ext(const ROOT::Fit::ParameterSettings ¶meter, double val) const -{ - double dd = 1.; - if (parameter.IsBound()) { - if (parameter.IsDoubleBound()) { - dd = fDoubleLimTrafo.GStepInt2Ext(val, parameter.UpperLimit(), parameter.LowerLimit()); - } else if (parameter.HasUpperLimit() && !parameter.HasLowerLimit()) { - dd = fUpperLimTrafo.GStepInt2Ext(val, parameter.UpperLimit()); - } else { - dd = fLowerLimTrafo.GStepInt2Ext(val, parameter.LowerLimit()); - } - } - - return dd; -} - // MODIFIED: // This function was not implemented as in Minuit2. Now it copies the behavior // of InitialGradientCalculator. See https://github.com/roofit-dev/root/issues/10